feat: comprehensive documentation and demo updates
- Update READMEs and docs across multiple languages - Enhance interactive demos for Agent, LLM, VLM, Audio, Image Gen, Terminal, and Web Basics - Add new appendix sections for Database and IDE intros - Update VitePress config, theme, and utility scripts - Clean up unused assets and components
This commit is contained in:
@@ -2,70 +2,70 @@
|
||||
|
||||
### 1. 创建 `docs/zh-cn/appendix/vlm-intro.md` (多模态大模型:给 AI 装上眼睛)
|
||||
|
||||
* **0. 引言**: 从“读万卷书”到“行万里路”。VLM 的核心任务:把图像信号翻译成大模型能懂的语言信号。
|
||||
- **0. 引言**: 从“读万卷书”到“行万里路”。VLM 的核心任务:把图像信号翻译成大模型能懂的语言信号。
|
||||
|
||||
* **1. 第一步:视觉翻译 (Visual Tokenization)**:
|
||||
* **ViT (Vision Transformer)**: 计算机怎么“看”图?将图片切成 16x16 的小方块 (Patches),就像把句子切成词 (Tokens)。
|
||||
* **Qwen-VL 的创新**: 提到 **Naive Dynamic Resolution**(动态分辨率),不强制压缩图片,而是根据图片比例动态切分 Patch,像人眼一样看清细节(分辨率自适应),解决了传统模型“看不清长图”的问题。
|
||||
- **1. 第一步:视觉翻译 (Visual Tokenization)**:
|
||||
- **ViT (Vision Transformer)**: 计算机怎么“看”图?将图片切成 16x16 的小方块 (Patches),就像把句子切成词 (Tokens)。
|
||||
- **Qwen-VL 的创新**: 提到 **Naive Dynamic Resolution**(动态分辨率),不强制压缩图片,而是根据图片比例动态切分 Patch,像人眼一样看清细节(分辨率自适应),解决了传统模型“看不清长图”的问题。
|
||||
|
||||
* **2. 核心难题:跨界沟通 (Projection)**:
|
||||
* 视觉向量 vs 语言向量。我们需要一个“适配器” (Projector),把视觉特征映射到文本空间。
|
||||
* **架构对比**:
|
||||
* **Linear (LLaVA)**: 简单粗暴的线性投影,训练快,保留信息多。
|
||||
* **Q-Former (BLIP-2)**: 使用查询向量 (Query) 提取关键视觉信息,更轻量。
|
||||
* **C-Abstractor (Qwen-VL)**: 结合注意力机制,更高效地压缩视觉信息。
|
||||
- **2. 核心难题:跨界沟通 (Projection)**:
|
||||
- 视觉向量 vs 语言向量。我们需要一个“适配器” (Projector),把视觉特征映射到文本空间。
|
||||
- **架构对比**:
|
||||
- **Linear (LLaVA)**: 简单粗暴的线性投影,训练快,保留信息多。
|
||||
- **Q-Former (BLIP-2)**: 使用查询向量 (Query) 提取关键视觉信息,更轻量。
|
||||
- **C-Abstractor (Qwen-VL)**: 结合注意力机制,更高效地压缩视觉信息。
|
||||
|
||||
* **3. 进化之路:ViT + LLM**:
|
||||
* Vision Transformer (ViT) 负责“看”,LLM 负责“想”和“说”。
|
||||
* **M-LLM**: 像 GPT-4V 或 Qwen2-VL,已经不仅是“拼接”,而是深度的多模态融合,甚至能处理视频(视为连续的图片帧)。
|
||||
- **3. 进化之路:ViT + LLM**:
|
||||
- Vision Transformer (ViT) 负责“看”,LLM 负责“想”和“说”。
|
||||
- **M-LLM**: 像 GPT-4V 或 Qwen2-VL,已经不仅是“拼接”,而是深度的多模态融合,甚至能处理视频(视为连续的图片帧)。
|
||||
|
||||
* **4. 训练揭秘:从对齐到对话**:
|
||||
* **阶段一 (Pre-training)**: 像 CLIP 一样,在大规模图文对上预训练,学会“这张图是猫”。
|
||||
* **阶段二 (Instruction Tuning)**: 学会“看图说话”,使用 `<image>` 标签和对话数据,让模型能回答“这只猫在干什么?”。
|
||||
- **4. 训练揭秘:从对齐到对话**:
|
||||
- **阶段一 (Pre-training)**: 像 CLIP 一样,在大规模图文对上预训练,学会“这张图是猫”。
|
||||
- **阶段二 (Instruction Tuning)**: 学会“看图说话”,使用 `<image>` 标签和对话数据,让模型能回答“这只猫在干什么?”。
|
||||
|
||||
* **5. 总结**: 视觉与语言的统一。
|
||||
- **5. 总结**: 视觉与语言的统一。
|
||||
|
||||
### 2. 创建 `docs/zh-cn/appendix/image-gen-intro.md` (AI 绘画:从噪声中重构世界)
|
||||
|
||||
* **0. 引言**: 生成式 AI 的魔法——从混沌 (Noise) 到秩序 (Data)。
|
||||
- **0. 引言**: 生成式 AI 的魔法——从混沌 (Noise) 到秩序 (Data)。
|
||||
|
||||
* **1. 第一步:降维打击 (VAE & Latent Space)**:
|
||||
* 直接画像素太累了(1024x1024 有百万像素)。我们先用 **VAE (变分自编码器)** 把图片压缩成“潜变量” (Latent),在小黑屋里作画(效率提升)。
|
||||
- **1. 第一步:降维打击 (VAE & Latent Space)**:
|
||||
- 直接画像素太累了(1024x1024 有百万像素)。我们先用 **VAE (变分自编码器)** 把图片压缩成“潜变量” (Latent),在小黑屋里作画(效率提升)。
|
||||
|
||||
* **2. 核心机制:扩散 (Diffusion)**:
|
||||
* **破坏 (Forward)**: 像滴入墨水一样,一步步把图片变成纯高斯噪声。
|
||||
* **重构 (Reverse)**: 训练神经网络预测噪声 $\epsilon$,一步步把墨水“吸”出来。
|
||||
* **SDE 视角**: 理解为在概率分布上进行随机游走。
|
||||
- **2. 核心机制:扩散 (Diffusion)**:
|
||||
- **破坏 (Forward)**: 像滴入墨水一样,一步步把图片变成纯高斯噪声。
|
||||
- **重构 (Reverse)**: 训练神经网络预测噪声 $\epsilon$,一步步把墨水“吸”出来。
|
||||
- **SDE 视角**: 理解为在概率分布上进行随机游走。
|
||||
|
||||
* **3. 进化之路:流匹配 (Flow Matching)**:
|
||||
* **为什么 Diffusion 慢?**: 它的去噪路径是弯弯曲曲的随机路径。
|
||||
* **Flow Matching (Flux/SD3)**: 寻找从噪声分布到图像分布的 **“直线”路径 (Optimal Transport)**。
|
||||
* **向量场 (Vector Field)**: 训练模型预测“速度”而非“噪声”,采样时直接沿着直线飞奔,几步就能画出好图。
|
||||
- **3. 进化之路:流匹配 (Flow Matching)**:
|
||||
- **为什么 Diffusion 慢?**: 它的去噪路径是弯弯曲曲的随机路径。
|
||||
- **Flow Matching (Flux/SD3)**: 寻找从噪声分布到图像分布的 **“直线”路径 (Optimal Transport)**。
|
||||
- **向量场 (Vector Field)**: 训练模型预测“速度”而非“噪声”,采样时直接沿着直线飞奔,几步就能画出好图。
|
||||
|
||||
* **4. 操控:文本如何指挥绘画**:
|
||||
* **CLIP / T5 Encoder**: 充当“甲方”,把 Prompt 变成向量。
|
||||
* **DiT (Diffusion Transformer)**: 像 Sora 和 SD3 一样,用 Transformer 替换掉老的 U-Net,处理能力更强,画质上限更高。
|
||||
- **4. 操控:文本如何指挥绘画**:
|
||||
- **CLIP / T5 Encoder**: 充当“甲方”,把 Prompt 变成向量。
|
||||
- **DiT (Diffusion Transformer)**: 像 Sora 和 SD3 一样,用 Transformer 替换掉老的 U-Net,处理能力更强,画质上限更高。
|
||||
|
||||
* **5. 总结**: 概率分布的搬运工。
|
||||
- **5. 总结**: 概率分布的搬运工。
|
||||
|
||||
### 3. 创建 `docs/zh-cn/appendix/audio-intro.md` (AI 音频:声音的数字化身)
|
||||
|
||||
* **0. 引言**: 听与说的艺术。声音本质上是空气的振动,计算机如何处理?
|
||||
- **0. 引言**: 听与说的艺术。声音本质上是空气的振动,计算机如何处理?
|
||||
|
||||
* **1. 第一步:声音的“文字” (Audio Tokenization)**:
|
||||
* 声音是连续的波形,语言是离散的 Token。
|
||||
* **Neural Codec (VQ-VAE / EnCodec)**: 把连续波形切碎,通过 **量化 (Quantization)** 变成一个个数字 Token (Codebook)。
|
||||
* **RVQ (Residual VQ)**: 像洋葱一样一层层剥开声音细节,保证高保真音质。
|
||||
- **1. 第一步:声音的“文字” (Audio Tokenization)**:
|
||||
- 声音是连续的波形,语言是离散的 Token。
|
||||
- **Neural Codec (VQ-VAE / EnCodec)**: 把连续波形切碎,通过 **量化 (Quantization)** 变成一个个数字 Token (Codebook)。
|
||||
- **RVQ (Residual VQ)**: 像洋葱一样一层层剥开声音细节,保证高保真音质。
|
||||
|
||||
* **2. 核心表示:梅尔频谱 (Mel-Spectrogram)**:
|
||||
* 把“听觉问题”转化成“视觉问题”。在频域上处理声音往往比时域更高效。
|
||||
- **2. 核心表示:梅尔频谱 (Mel-Spectrogram)**:
|
||||
- 把“听觉问题”转化成“视觉问题”。在频域上处理声音往往比时域更高效。
|
||||
|
||||
* **3. 架构演进:从 GPT 到 Flow**:
|
||||
* **AudioLM / VALL-E**: 把声音 Token 当作文字,用 GPT **自回归 (Autoregressive)** 地狂猜下一个音。优点是能学到很好的韵律,缺点是容易“胡言乱语”或无限循环。
|
||||
* **F5-TTS / CosyVoice**: 使用 **Flow Matching** 直接生成频谱。不需要复杂的 Token 预测,而是从噪声中“流”出声音频谱,速度更快,控制更稳,支持零样本克隆。
|
||||
- **3. 架构演进:从 GPT 到 Flow**:
|
||||
- **AudioLM / VALL-E**: 把声音 Token 当作文字,用 GPT **自回归 (Autoregressive)** 地狂猜下一个音。优点是能学到很好的韵律,缺点是容易“胡言乱语”或无限循环。
|
||||
- **F5-TTS / CosyVoice**: 使用 **Flow Matching** 直接生成频谱。不需要复杂的 Token 预测,而是从噪声中“流”出声音频谱,速度更快,控制更稳,支持零样本克隆。
|
||||
|
||||
* **4. 总结**: 统一多模态的未来。
|
||||
- **4. 总结**: 统一多模态的未来。
|
||||
|
||||
### 4. 更新配置
|
||||
|
||||
* 修改 `docs/.vitepress/config.mjs`,在 `zh-cn` 侧边栏添加这三个章节。
|
||||
- 修改 `docs/.vitepress/config.mjs`,在 `zh-cn` 侧边栏添加这三个章节。
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
---
|
||||
name: 教程美化方案
|
||||
description: 使用 VitePress 和 Element Plus 组件美化教程,提升可读性和交互性
|
||||
---
|
||||
|
||||
# 教程美化最佳实践
|
||||
|
||||
你是一个专注于美化 VitePress 教程文档的专家。你的任务是根据用户的需求,使用 VitePress 原生功能和 Element Plus 组件来增强教程的视觉效果和交互性。
|
||||
|
||||
## 可用组件和样式
|
||||
|
||||
### 1. 醒目的提示块
|
||||
|
||||
**VitePress 原生样式** (简单场景)
|
||||
|
||||
```markdown
|
||||
::: tip 💡 提示
|
||||
这是一个提示块,适合放补充信息。
|
||||
:::
|
||||
|
||||
::: warning ⚠️ 注意
|
||||
这是一个警告块,提醒用户注意潜在问题。
|
||||
:::
|
||||
|
||||
::: danger 🚫 危险
|
||||
这是一个危险块,用于警告严重错误。
|
||||
:::
|
||||
|
||||
::: info ℹ️ 信息
|
||||
这是一个信息块,用于一般性说明。
|
||||
:::
|
||||
```
|
||||
|
||||
**Element Plus 样式** (更现代化)
|
||||
|
||||
```html
|
||||
<el-alert title="成功提示的文案" type="success" show-icon />
|
||||
<el-alert title="消息提示的文案" type="info" show-icon />
|
||||
<el-alert title="警告提示的文案" type="warning" show-icon />
|
||||
<el-alert title="错误提示的文案" type="error" show-icon />
|
||||
```
|
||||
|
||||
### 2. 步骤条
|
||||
|
||||
**⚠️ 重要:必须使用 `<ClientOnly>` 包裹 `<StepBar>` 组件**
|
||||
|
||||
```html
|
||||
<ClientOnly>
|
||||
<StepBar
|
||||
:active="1"
|
||||
:items="[
|
||||
{ title: '环境准备', description: '安装 Node.js 和编辑器' },
|
||||
{ title: '代码编写', description: '跟着 AI 写第一行代码' },
|
||||
{ title: '部署上线', description: '发布你的作品' },
|
||||
{ title: '完成', description: '享受成果' }
|
||||
]"
|
||||
/>
|
||||
</ClientOnly>
|
||||
```
|
||||
|
||||
### 3. 折叠内容
|
||||
|
||||
**VitePress 原生**
|
||||
|
||||
````markdown
|
||||
::: details 点击查看详细代码
|
||||
|
||||
```js
|
||||
console.log('Hello World')
|
||||
```
|
||||
````
|
||||
|
||||
:::
|
||||
|
||||
````
|
||||
|
||||
**Element Plus 手风琴**
|
||||
```html
|
||||
<el-collapse accordion>
|
||||
<el-collapse-item title="为什么选择 Easy-Vibe?" name="1">
|
||||
<div>因为它可以让你在 AI 时代零基础学会编程。</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="我需要什么基础?" name="2">
|
||||
<div>只需要会打字,会说话即可。</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
````
|
||||
|
||||
### 4. 代码分组
|
||||
|
||||
````markdown
|
||||
::: code-group
|
||||
|
||||
```bash [npm]
|
||||
npm install easy-vibe
|
||||
```
|
||||
````
|
||||
|
||||
```bash [yarn]
|
||||
yarn add easy-vibe
|
||||
```
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add easy-vibe
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
````
|
||||
|
||||
### 5. 交互式标签页
|
||||
```html
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="场景 A">
|
||||
这里是场景 A 的详细说明...
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="场景 B">
|
||||
这里是场景 B 的详细说明...
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="场景 C">
|
||||
这里是场景 C 的详细说明...
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
````
|
||||
|
||||
### 6. 徽章与标签
|
||||
|
||||
```html
|
||||
这是一段普通文本,但是包含 <el-tag>核心概念</el-tag> 和
|
||||
<el-tag type="danger">重要提醒</el-tag>。
|
||||
|
||||
<el-badge :value="12" class="item">
|
||||
<el-button>评论</el-button>
|
||||
</el-badge>
|
||||
```
|
||||
|
||||
### 7. 进度条
|
||||
|
||||
```html
|
||||
<el-progress
|
||||
:percentage="50"
|
||||
:stroke-width="15"
|
||||
status="exception"
|
||||
striped
|
||||
striped-flow
|
||||
/>
|
||||
<el-progress
|
||||
:percentage="100"
|
||||
:stroke-width="15"
|
||||
status="success"
|
||||
striped
|
||||
striped-flow
|
||||
/>
|
||||
```
|
||||
|
||||
### 8. 数据可视化卡片
|
||||
|
||||
```html
|
||||
<el-card shadow="hover" style="margin: 20px 0; border-radius: 12px;">
|
||||
<template #header>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 20px;">🚀</span>
|
||||
<span style="font-weight: bold; font-size: 16px;">效率的飞跃</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-row :gutter="20" style="margin-bottom: 24px;">
|
||||
<el-col :span="6" :xs="12">
|
||||
<div style="text-align: center; padding: 10px;">
|
||||
<div style="color: #409EFF; font-size: 24px; font-weight: bold;">
|
||||
55%
|
||||
</div>
|
||||
<div style="color: #909399; font-size: 12px; margin-top: 4px;">
|
||||
提升率
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" :xs="12">
|
||||
<div style="text-align: center; padding: 10px;">
|
||||
<div style="color: #67C23A; font-size: 24px; font-weight: bold;">
|
||||
2.4 <span style="font-size: 14px;">天</span>
|
||||
</div>
|
||||
<div style="color: #909399; font-size: 12px; margin-top: 4px;">
|
||||
耗时
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div style="line-height: 1.8; color: #606266;">
|
||||
这里是详细的文字描述,可以配合 <b>加粗</b> 强调关键信息。
|
||||
</div>
|
||||
</el-card>
|
||||
```
|
||||
|
||||
### 9. 核心理念卡片
|
||||
|
||||
```html
|
||||
<el-card
|
||||
shadow="hover"
|
||||
style="border-radius: 16px; border: 2px dashed #FFB6C1; background-color: #FFF0F5; margin: 20px 0;"
|
||||
>
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 24px; font-weight: 600; color: #595959;">
|
||||
✨ 完成比完美更重要 🐣
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
```
|
||||
|
||||
## 使用指南
|
||||
|
||||
根据用户的教程内容需求,选择合适的组件:
|
||||
|
||||
- **内容强调**:使用 `::: tip` 或 `el-alert`,或核心理念卡片
|
||||
- **流程引导**:使用 `<StepBar>`(必须包裹在 `<ClientOnly>` 中)
|
||||
- **信息分层**:使用 `::: details` 或 `el-tabs`
|
||||
- **数据展示**:使用数据可视化卡片
|
||||
- **视觉点缀**:使用 `el-tag` 和 Icons
|
||||
|
||||
## 执行步骤
|
||||
|
||||
1. 理解用户想要美化的教程内容
|
||||
2. 分析内容结构,确定需要哪些组件
|
||||
3. 根据上述组件库,选择最合适的组件
|
||||
4. 将组件代码插入到合适的位置
|
||||
5. 确保所有自定义组件(如 StepBar)都包裹在 `<ClientOnly>` 中
|
||||
@@ -0,0 +1,832 @@
|
||||
---
|
||||
name: 编写交互式教程指南
|
||||
description: 基于 llm-intro.md 的编写模式,总结如何创建高质量的交互式技术教程。涵盖内容展开的循序渐进原则、文章结构规范、交互式组件开发规范、最佳实践及完整开发工作流。
|
||||
---
|
||||
|
||||
# 编写交互式教程指南 (Interactive Tutorial Guide)
|
||||
|
||||
## 概述
|
||||
|
||||
本指南基于 `docs/zh-cn/appendix/llm-intro.md` 的编写模式,总结如何创建高质量的交互式技术教程。
|
||||
|
||||
## 一、内容展开的循序渐进原则
|
||||
|
||||
### 1.1 核心叙事模式
|
||||
|
||||
`llm-intro.md` 采用的是 **"螺旋上升"** 的叙事结构,每个知识点都遵循以下认知路径:
|
||||
|
||||
```
|
||||
具体体验 → 抽象概念 → 历史对比 → 本质揭示 → 前沿拓展
|
||||
```
|
||||
|
||||
#### 原则 1:从具体体验到抽象原理
|
||||
|
||||
**不要一上来就讲定义**,先让读者看到效果。
|
||||
|
||||
**示例(llm-intro.md 第 0 章)**:
|
||||
|
||||
```markdown
|
||||
# 大语言模型入门
|
||||
|
||||
> 💡 **学习指南**:本章节无需编程基础,通过交互式演示带你深入了解大语言模型...
|
||||
|
||||
<LlmQuickStartDemo /> <!-- 先让读者玩起来 -->
|
||||
|
||||
## 0. 引言:从人类语言到机器计算
|
||||
|
||||
人类用语言交流,计算机用数字计算。
|
||||
**大语言模型 (LLM)** 的本质,就是一座连接这两个世界的桥梁。
|
||||
```
|
||||
|
||||
**要点**:
|
||||
|
||||
- ✅ 第一屏就是交互式演示
|
||||
- ✅ 读者"玩过"后再讲原理
|
||||
- ❌ 避免开篇就是"LLM 是基于 Transformer 的..."
|
||||
|
||||
#### 原则 2:从简单到复杂(知识依赖链)
|
||||
|
||||
**每个新概念都建立在前一个概念基础上**,形成知识链条。
|
||||
|
||||
**示例(llm-intro.md 第 1-3 章)**:
|
||||
|
||||
```markdown
|
||||
## 1. 第一步:翻译(Tokenization)
|
||||
|
||||
核心任务:把文字变成数字 ID
|
||||
<TokenizationDemo />
|
||||
|
||||
## 2. 核心难题:如何让计算机"计算"语言?
|
||||
|
||||
问题:只用 ID 太浪费、没内涵
|
||||
方案:Embedding(稠密向量)
|
||||
<EmbeddingDemo />
|
||||
|
||||
## 3. 从 单词 到 矩阵
|
||||
|
||||
回顾:Embedding 把每个词变成向量 → 把向量拼成矩阵 → GPU 高效计算
|
||||
<TokenizerToMatrix />
|
||||
```
|
||||
|
||||
**依赖关系**:
|
||||
|
||||
```
|
||||
Tokenization(基础)
|
||||
↓
|
||||
Embedding(优化 Tokenization)
|
||||
↓
|
||||
矩阵化(优化 Embedding 的计算)
|
||||
↓
|
||||
Transformer(利用矩阵并行)
|
||||
```
|
||||
|
||||
**要点**:
|
||||
|
||||
- 每章开头"回顾"上一章内容
|
||||
- 明确说明"为什么要学这个"
|
||||
- 用"因为...所以..."连接知识点
|
||||
|
||||
#### 原则 3:从问题到方案(对比式讲解)
|
||||
|
||||
**先提出"直白方案"的缺陷,再引出"聪明方案"**。
|
||||
|
||||
**示例(llm-intro.md 第 2.1-2.2 章)**:
|
||||
|
||||
```markdown
|
||||
### 2.1 为什么不用简单的 ID?
|
||||
|
||||
如果只用 ID,计算机会认为"10"和"20"只是两个毫无关系的数字。
|
||||
|
||||
- **缺点1**:太浪费(稀疏,One-Hot 数组太大)。
|
||||
- **缺点2**:没内涵(无法表示"苹果"和"香蕉"都是水果)。
|
||||
|
||||
### 2.2 解决方案:Embedding(稠密向量)
|
||||
|
||||
为了**高效**且**有内涵**地表达一个词,我们发明了 **Embedding**。
|
||||
它不再用一个长长的 0/1 数组,而是用一个短一点的、填满小数的数组...
|
||||
```
|
||||
|
||||
**模板**:
|
||||
|
||||
```markdown
|
||||
### N.1 为什么不用 [朴素方案]?
|
||||
|
||||
[说明朴素方案的问题]
|
||||
|
||||
- **缺点1**:[具体问题]
|
||||
- **缺点2**:[具体问题]
|
||||
|
||||
### N.2 解决方案:[优化方案]
|
||||
|
||||
为了解决上述问题,我们引入了 [优化方案]。
|
||||
[说明优化方案的核心思想]
|
||||
```
|
||||
|
||||
**要点**:
|
||||
|
||||
- 用"为什么不用..."作为章节标题
|
||||
- 用列表清晰列出缺陷
|
||||
- 用"为了...我们引入..."过渡到新方案
|
||||
|
||||
#### 原则 4:从历史到现代(进化式讲解)
|
||||
|
||||
**通过对比"旧技术"和"新技术",说明技术演进的动机**。
|
||||
|
||||
**示例(llm-intro.md 第 4 章)**:
|
||||
|
||||
```markdown
|
||||
### 4.1 为什么要淘汰 RNN?
|
||||
|
||||
以前的模型(RNN)像人读书一样,**从左到右**一个字一个字读。
|
||||
|
||||
- **缺点1**:慢。必须读完第1个字才能读第2个,没法并行。
|
||||
- **缺点2**:忘。读到文章最后,可能已经忘了开头讲什么了。
|
||||
|
||||
### 4.2 Transformer 强在哪?
|
||||
|
||||
现在的 LLM 都基于 Transformer 架构,它完美契合了矩阵并行的特性:
|
||||
|
||||
1. **并行阅读**:它可以**一眼看完**整句话...
|
||||
2. **注意力机制**:让每个词都**直接关注**到其他所有词...
|
||||
```
|
||||
|
||||
**对比表格化**:
|
||||
|
||||
```markdown
|
||||
| 特性 | RNN | Transformer |
|
||||
| :------------- | :----------------- | :--------------------- |
|
||||
| **阅读方式** | 从左到右,逐字处理 | 并行处理,一眼看完整句 |
|
||||
| **计算效率** | 慢(无法并行) | 快(GPU 矩阵运算) |
|
||||
| **长距离记忆** | 容易遗忘 | 注意力机制保持连接 |
|
||||
```
|
||||
|
||||
**要点**:
|
||||
|
||||
- 明确说明"为什么要淘汰"
|
||||
- 用具体场景对比(如"读书方式")
|
||||
- 配合 `<RNNvsTransformer />` 可视化
|
||||
|
||||
#### 原则 5:从现象到本质(揭秘式讲解)
|
||||
|
||||
**先描述用户能观察到的现象,再揭示背后的本质机制**。
|
||||
|
||||
**示例(llm-intro.md 第 5 章)**:
|
||||
|
||||
```markdown
|
||||
## 5. 揭秘:从"续写"到"对话"
|
||||
|
||||
很多人会误以为 ChatGPT 真的懂我们在说什么,但其实它的本能只有一个:**猜下一个词**。
|
||||
|
||||
### 5.1 本能:疯狂续写
|
||||
|
||||
如果你给基础模型输入:"今天天气不错",它可能会续写:"去公园玩吧。"
|
||||
但如果你输入:"美国的首都是哪里?",它可能会续写:"中国首都是哪里?..."
|
||||
|
||||
### 5.2 技巧:用"剧本"来对话
|
||||
|
||||
为了让它变成对话助手,工程师们想出了一个绝妙的办法:**角色扮演**。
|
||||
我们在输入给模型的内容里,悄悄加了一些特殊的**标签**...
|
||||
```
|
||||
|
||||
**叙事结构**:
|
||||
|
||||
```
|
||||
现象(ChatGPT 会对话)
|
||||
↓
|
||||
打破误解(它只是在续写)
|
||||
↓
|
||||
揭示本质(Next Token Prediction)
|
||||
↓
|
||||
技巧揭秘(Template 角色扮演)
|
||||
↓
|
||||
深度交互(TrainingInferenceDemo)
|
||||
```
|
||||
|
||||
**要点**:
|
||||
|
||||
- 用"揭秘"作为章节标题
|
||||
- 用"很多人会误以为..."制造悬念
|
||||
- 用"但其实..."转折到真相
|
||||
- 用"本质只有一个"强调核心
|
||||
|
||||
#### 原则 6:从基础到前沿(拓展式讲解)
|
||||
|
||||
**在讲完核心原理后,介绍最新进展,保持内容的先进性**。
|
||||
|
||||
**示例(llm-intro.md 第 7 章)**:
|
||||
|
||||
```markdown
|
||||
## 7. 前沿探索:会思考的模型、MoE 架构与线性注意力机制
|
||||
|
||||
随着技术的发展,我们发现仅仅靠"预测下一个词"有时候会犯蠢...
|
||||
于是,新一代的 **Thinking Models**(如 OpenAI o1, DeepSeek-R1)诞生了。
|
||||
|
||||
### 7.1 什么是"思考"?
|
||||
|
||||
- **快思考 (System 1)**:凭直觉,脱口而出。容易犯错。
|
||||
- **慢思考 (System 2)**:通过产生"思维链",一步步推理。
|
||||
|
||||
### 7.5 架构进化:从"全能"到"专家团"(Dense vs MoE)
|
||||
|
||||
- **Dense(稠密模型)**:比喻为一个**全能天才**...
|
||||
- **MoE(混合专家模型)**:比喻为一个**专家团队**...
|
||||
```
|
||||
|
||||
**拓展维度**:
|
||||
|
||||
1. **算法演进**:传统 → 现代
|
||||
2. **架构优化**:Dense → MoE
|
||||
3. **效率提升**:标准 Attention → Linear Attention
|
||||
|
||||
**要点**:
|
||||
|
||||
- 用"前沿探索"作为章节标题
|
||||
- 说明"为什么需要新东西"
|
||||
- 用比喻降低理解难度(如"专家团队")
|
||||
- 提供实战指南(Prompt 风格变化)
|
||||
|
||||
### 1.2 章节编排的六个层次
|
||||
|
||||
一个完整的交互式教程应该包含以下层次(从浅到深):
|
||||
|
||||
```
|
||||
层次 0:引子 (类比 + 核心挑战)
|
||||
层次 1:基础概念 (是什么 + 怎么做)
|
||||
层次 2:设计动机 (为什么不用朴素方案)
|
||||
层次 3:核心机制 (本质原理 + 可视化)
|
||||
层次 4:实践应用 (数据格式 + 使用场景)
|
||||
层次 5:前沿拓展 (最新技术 + 未来趋势)
|
||||
```
|
||||
|
||||
**对照检查**:
|
||||
|
||||
- ✅ 每个层次是否有对应章节?
|
||||
- ✅ 层次之间是否有逻辑连接?
|
||||
- ✅ 是否提供了交互式演示?
|
||||
|
||||
### 1.3 段落内的微观展开
|
||||
|
||||
每个段落内部也遵循 **"提出问题 → 分析问题 → 解决问题"** 的三段式结构:
|
||||
|
||||
```markdown
|
||||
[提出问题]
|
||||
计算机看不懂"汉堡"这两个字,它只认识数字。
|
||||
所以,我们的第一个任务是:**把文本切分成计算机能理解的最小单位**。
|
||||
|
||||
[分析问题]
|
||||
|
||||
- **英文**:自带空格,天然容易分词(如 `I love AI`)。
|
||||
- **中文**:没有空格,需要算法来切分(如 `我爱人工智能`)。
|
||||
|
||||
[解决问题]
|
||||
现代 LLM(如 GPT-4)通常使用 **Subword Tokenization(子词分词)** 技术(如 BPE 算法)。
|
||||
它的聪明之处在于:
|
||||
|
||||
- **常用词**(如 "apple")保持完整,作为一个 Token。
|
||||
- **生僻词**(如 "applepie")拆分成常见片段("apple" + "pie")。
|
||||
```
|
||||
|
||||
**要点**:
|
||||
|
||||
- 第一段:提出问题(用粗体强调任务)
|
||||
- 第二段:分析问题(用列表对比)
|
||||
- 第三段:解决问题(说明方案和优势)
|
||||
|
||||
## 二、文章结构规范
|
||||
|
||||
### 2.1 标题与学习指南
|
||||
|
||||
```markdown
|
||||
# 主题名称 (English Title)
|
||||
|
||||
> 💡 **学习指南**:本章节[前置要求],通过[教学方式]带你深入了解[核心主题]。我们将从[起点]开始,一直到[终点]。
|
||||
```
|
||||
|
||||
**要点**:
|
||||
|
||||
- 标题使用中英双语,括号标注
|
||||
- 学习指南块说明:前置要求、教学方式、学习路径
|
||||
- 降低读者焦虑,强调"无需XX基础"
|
||||
|
||||
### 2.2 引子:从问题到动机
|
||||
|
||||
**结构**:
|
||||
|
||||
1. **类比/比喻**:用日常生活中的例子引入
|
||||
2. **核心挑战**:列出 3 个左右要解决的问题
|
||||
3. **路线图**:预告章节结构
|
||||
|
||||
```markdown
|
||||
## 0. 引言:从[熟悉概念]到[新概念]
|
||||
|
||||
[用类比连接两个概念]
|
||||
|
||||
它的核心任务只有一个:**[一句话总结核心目标]**。
|
||||
|
||||
为了实现这个目标,我们需要解决三个核心挑战:
|
||||
|
||||
1. **[挑战1]**:[为什么需要]
|
||||
2. **[挑战2]**:[为什么需要]
|
||||
3. **[挑战3]**:[为什么需要]
|
||||
|
||||
本教程将带你从零开始,一步步拆解[核心主题]的构建过程。
|
||||
```
|
||||
|
||||
### 2.3 主体章节结构
|
||||
|
||||
每个知识点遵循 **"问题 → 方案 → 对比 → 演示"** 的结构:
|
||||
|
||||
```markdown
|
||||
## N. [章节标题]
|
||||
|
||||
[段落1:提出问题/当前困境]
|
||||
|
||||
### N.1 [子问题标题]
|
||||
|
||||
[解释问题产生的背景/原因]
|
||||
|
||||
### N.2 [解决方案标题]
|
||||
|
||||
[提出解决方案,解释其核心思想]
|
||||
|
||||
**关键点**:[一句话总结核心概念]
|
||||
|
||||
<[InteractiveDemo />
|
||||
```
|
||||
|
||||
**要点**:
|
||||
|
||||
- 用问答式标题("什么是XX?" "为什么不用XX?")
|
||||
- 用列表和粗体强调重点
|
||||
- 每个关键概念后紧跟交互式演示
|
||||
|
||||
### 2.4 对比分析表格
|
||||
|
||||
当需要对比多个选项时,使用表格:
|
||||
|
||||
```markdown
|
||||
| 特性 | [选项A] | [选项B] |
|
||||
| :----------- | :----------------- | :----------------- |
|
||||
| **核心逻辑** | **[描述]** | **[描述]** |
|
||||
| **优点** | [优点1]<br>[优点2] | [优点1]<br>[优点2] |
|
||||
| **缺点** | [缺点1]<br>[缺点2] | [缺点1]<br>[缺点2] |
|
||||
| **适用场景** | [场景1] | [场景2] |
|
||||
|
||||
> ⚠️ **注意**:[使用建议或注意事项]
|
||||
```
|
||||
|
||||
### 2.5 数据示例
|
||||
|
||||
当涉及数据格式时,提供 JSON 示例:
|
||||
|
||||
````markdown
|
||||
- **数据示例 (JSON 格式)**:
|
||||
```json
|
||||
// [注释说明这是什么数据]
|
||||
{
|
||||
"field1": "值1",
|
||||
"field2": "值2"
|
||||
}
|
||||
// 模型学会了:[解释这个数据的作用]
|
||||
```
|
||||
````
|
||||
|
||||
### 2.6 名词速查表
|
||||
|
||||
文章末尾提供 Glossary:
|
||||
|
||||
```markdown
|
||||
## N. 名词速查表 (Glossary)
|
||||
|
||||
| 名词 | 全称 | 解释 |
|
||||
| :-------- | :---------- | :------------------------------------- |
|
||||
| **TERM1** | Full Name 1 | **简短中文说明**。详细解释为什么重要。 |
|
||||
| **TERM2** | Full Name 2 | **简短中文说明**。详细解释。 |
|
||||
```
|
||||
|
||||
## 三、交互式组件规范
|
||||
|
||||
### 3.1 组件文件组织
|
||||
|
||||
**目录结构**:
|
||||
|
||||
```
|
||||
docs/.vitepress/theme/components/appendix/{topic-name}/
|
||||
├── {Concept}Demo.vue # 主演示组件
|
||||
├── {Concept}Demo.vue # 其他相关组件
|
||||
└── ...
|
||||
```
|
||||
|
||||
**命名规范**:
|
||||
|
||||
- 使用 PascalCase
|
||||
- 描述性强,如 `TokenizationDemo.vue`、`EmbeddingDemo.vue`
|
||||
- 与 Markdown 中引用的组件名一致
|
||||
|
||||
### 3.2 组件开发规范
|
||||
|
||||
#### 3.2.1 文件头注释
|
||||
|
||||
```vue
|
||||
<!--
|
||||
ComponentName.vue
|
||||
组件用途简述
|
||||
|
||||
用途:
|
||||
[详细说明这个组件要展示什么概念,解决什么教学问题]
|
||||
|
||||
交互功能:
|
||||
- [功能1]:[说明]
|
||||
- [功能2]:[说明]
|
||||
- [功能3]:[说明]
|
||||
-->
|
||||
```
|
||||
|
||||
#### 3.2.2 模板结构
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="[component-name]">
|
||||
<!-- 控制面板(如果有) -->
|
||||
<div class="control-panel">[输入控件、模式切换等]</div>
|
||||
|
||||
<!-- 可视化区域 -->
|
||||
<div class="visualization-area">[核心可视化内容]</div>
|
||||
|
||||
<!-- 说明文字 -->
|
||||
<div class="info-box">
|
||||
<p>
|
||||
<span class="icon">💡</span>
|
||||
<strong>Note:</strong>
|
||||
[教育性说明文字]
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
**层级结构**:
|
||||
|
||||
- `.control-panel`:控制区(输入、切换按钮)
|
||||
- `.visualization-area`:核心可视化区
|
||||
- `.info-box`:说明提示框
|
||||
|
||||
#### 3.2.3 脚本编写
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
// 响应式状态
|
||||
const [stateName] = ref([initialValue])
|
||||
|
||||
// 计算属性
|
||||
const [computedName] = computed(() => {
|
||||
// 逻辑处理
|
||||
return result
|
||||
})
|
||||
|
||||
// 方法
|
||||
const [methodName] = ([params]) => {
|
||||
// 方法实现
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
**要点**:
|
||||
|
||||
- 使用 Vue 3 Composition API (`<script setup>`)
|
||||
- 优先使用 `ref` 和 `computed`
|
||||
- 保持逻辑简洁,复杂计算应有注释
|
||||
|
||||
#### 3.2.4 样式规范
|
||||
|
||||
```vue
|
||||
<style scoped>
|
||||
.[component-name] {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
/* 控制面板样式 */
|
||||
}
|
||||
|
||||
.visualization-area {
|
||||
/* 可视化区域样式 */
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
**CSS 变量使用**:
|
||||
|
||||
- `var(--vp-c-bg)`:主背景色
|
||||
- `var(--vp-c-bg-soft)`:次背景色
|
||||
- `var(--vp-c-bg-alt)`:交替背景色
|
||||
- `var(--vp-c-divider)`:分隔线颜色
|
||||
- `var(--vp-c-brand)`:主题色
|
||||
- `var(--vp-c-text-1/2/3)`:文本颜色(1最深,3最浅)
|
||||
|
||||
**布局工具**:
|
||||
|
||||
- 使用 Flexbox:`display: flex; gap: 1rem;`
|
||||
- 响应式:`@media (max-width: 640px) { ... }`
|
||||
- 圆角:`border-radius: 6px/8px/12px`
|
||||
- 过渡:`transition: all 0.2s`
|
||||
|
||||
### 3.3 交互设计原则
|
||||
|
||||
#### 3.3.1 即时反馈
|
||||
|
||||
```javascript
|
||||
// 用户操作立即触发视觉反馈
|
||||
const handleInput = (value) => {
|
||||
result.value = processInput(value)
|
||||
// 不需要点击"提交"按钮
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3.2 颜色编码
|
||||
|
||||
```javascript
|
||||
// 使用颜色区分不同状态/类型
|
||||
const colorClasses = [
|
||||
'color-0', // rgba(255, 99, 132, 0.2) - 红色
|
||||
'color-1', // rgba(54, 162, 235, 0.2) - 蓝色
|
||||
'color-2', // rgba(255, 206, 86, 0.2) - 黄色
|
||||
'color-3', // rgba(75, 192, 192, 0.2) - 青色
|
||||
'color-4' // rgba(153, 102, 255, 0.2) - 紫色
|
||||
]
|
||||
```
|
||||
|
||||
#### 3.3.3 多模式支持
|
||||
|
||||
```javascript
|
||||
// 提供不同视角/模式
|
||||
const modes = [
|
||||
{ id: 'mode1', label: '模式1', desc: '说明' },
|
||||
{ id: 'mode2', label: '模式2', desc: '说明' }
|
||||
]
|
||||
|
||||
const currentMode = ref('mode1')
|
||||
```
|
||||
|
||||
#### 3.3.4 动画效果
|
||||
|
||||
```css
|
||||
/* 淡入动画 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 脉冲动画 */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.4;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
/* 弹性过渡 */
|
||||
transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
```
|
||||
|
||||
### 3.4 常见组件模式
|
||||
|
||||
#### 3.4.1 输入演示型
|
||||
|
||||
**示例**:`TokenizationDemo.vue`
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="demo">
|
||||
<div class="input-group">
|
||||
<label>Input</label>
|
||||
<textarea v-model="inputText"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="output">
|
||||
<div v-for="item in processedItems" :key="item.id">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const inputText = ref('默认示例文本')
|
||||
|
||||
const processedItems = computed(() => {
|
||||
return process(inputText.value)
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 3.4.2 模式切换型
|
||||
|
||||
**示例**:`EmbeddingDemo.vue`
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="demo">
|
||||
<div class="mode-selector">
|
||||
<button
|
||||
v-for="mode in modes"
|
||||
:key="mode.id"
|
||||
:class="{ active: currentMode === mode.id }"
|
||||
@click="currentMode = mode.id"
|
||||
>
|
||||
{{ mode.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
{{ modes.find((m) => m.id === currentMode)?.content }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### 3.4.3 步骤引导型
|
||||
|
||||
**示例**:`TrainingInferenceDemo.vue`(假设)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="demo">
|
||||
<div class="steps">
|
||||
<button
|
||||
v-for="(step, index) in steps"
|
||||
:key="index"
|
||||
:class="{ active: currentStep === index }"
|
||||
@click="currentStep = index"
|
||||
>
|
||||
{{ step.title }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="step-content">
|
||||
{{ steps[currentStep].content }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 四、Markdown 使用技巧
|
||||
|
||||
### 4.1 组件引用
|
||||
|
||||
```markdown
|
||||
<ConceptDemo />
|
||||
|
||||
<!-- 带间距 -->
|
||||
<ConceptDemo />
|
||||
|
||||
<!-- 多个组件 -->
|
||||
<Demo1 />
|
||||
<Demo2 />
|
||||
```
|
||||
|
||||
### 4.2 代码块
|
||||
|
||||
````markdown
|
||||
```javascript
|
||||
// 代码示例
|
||||
const example = 'highlighted'
|
||||
```
|
||||
````
|
||||
|
||||
### 4.3 强调与标注
|
||||
|
||||
```markdown
|
||||
**粗体强调**
|
||||
_斜体_
|
||||
`代码片段`
|
||||
|
||||
> 引用块
|
||||
```
|
||||
|
||||
### 4.4 分隔线
|
||||
|
||||
```markdown
|
||||
---
|
||||
|
||||
<!-- 使用分隔线区分不同主题 -->
|
||||
```
|
||||
|
||||
## 五、最佳实践
|
||||
|
||||
### 5.1 内容层面
|
||||
|
||||
1. **从具体到抽象**:先展示效果,再解释原理
|
||||
2. **用类比降低认知负担**:如"Tokenizer 就像翻译官"
|
||||
3. **多问"为什么"**:解释设计动机,不只讲是什么
|
||||
4. **保持简洁**:每个知识点控制在 200-300 字
|
||||
|
||||
### 5.2 交互层面
|
||||
|
||||
1. **提供默认值**:让用户打开即见效果
|
||||
2. **即时反馈**:不需要点击"提交"才看结果
|
||||
3. **容错性**:输入验证,友好提示错误
|
||||
4. **移动端适配**:使用 `@media` 查询
|
||||
|
||||
### 5.3 可视化层面
|
||||
|
||||
1. **颜色有意义**:不仅美观,更要传递信息
|
||||
2. **动画适度**:增强理解,不干扰阅读
|
||||
3. **层次清晰**:用阴影、边框、间距区分层级
|
||||
4. **统一风格**:项目内组件使用相似的设计语言
|
||||
|
||||
### 5.4 性能优化
|
||||
|
||||
1. **计算属性缓存**:使用 `computed` 而非方法
|
||||
2. **避免不必要的响应式**:静态数据用 `const`
|
||||
3. **懒加载**:复杂组件考虑按需加载
|
||||
|
||||
## 六、开发工作流
|
||||
|
||||
### 6.1 创建新教程步骤
|
||||
|
||||
1. **规划内容**:
|
||||
- 列出要讲解的核心概念
|
||||
- 为每个概念设计交互演示
|
||||
- 画出组件草图
|
||||
|
||||
2. **创建组件**:
|
||||
|
||||
```bash
|
||||
mkdir -p docs/.vitepress/theme/components/appendix/{topic-name}
|
||||
touch docs/.vitepress/theme/components/appendix/{topic-name}/{Concept}Demo.vue
|
||||
```
|
||||
|
||||
3. **编写组件**:
|
||||
- 添加文件头注释
|
||||
- 实现 `<template>`、`<script setup>`、`<style scoped>`
|
||||
- 本地测试交互效果
|
||||
|
||||
4. **编写文档**:
|
||||
- 创建 Markdown 文件
|
||||
- 按照文章结构规范编写
|
||||
- 在适当位置嵌入组件
|
||||
|
||||
5. **测试发布**:
|
||||
|
||||
```bash
|
||||
npm run dev # 本地预览
|
||||
npm run build # 构建检查
|
||||
```
|
||||
|
||||
### 6.2 调试技巧
|
||||
|
||||
1. **Vue DevTools**:检查组件状态
|
||||
2. **Console.log**:输出中间结果
|
||||
3. **简化测试**:先用静态数据测试布局
|
||||
|
||||
## 七、示例对照
|
||||
|
||||
参考以下文件学习完整实现:
|
||||
|
||||
| 文件 | 说明 |
|
||||
| --------------------------------------------------------------------------- | -------------- |
|
||||
| `docs/zh-cn/appendix/llm-intro.md` | 完整的文章结构 |
|
||||
| `docs/.vitepress/theme/components/appendix/llm-intro/LlmQuickStartDemo.vue` | 聊天交互型组件 |
|
||||
| `docs/.vitepress/theme/components/appendix/llm-intro/TokenizationDemo.vue` | 输入处理型组件 |
|
||||
| `docs/.vitepress/theme/components/appendix/llm-intro/EmbeddingDemo.vue` | 模式切换型组件 |
|
||||
|
||||
## 八、检查清单
|
||||
|
||||
发布前确认:
|
||||
|
||||
- [ ] 文章结构完整(标题、引言、主体、总结)
|
||||
- [ ] 每个核心概念都有交互演示
|
||||
- [ ] 组件有文件头注释
|
||||
- [ ] 使用 VitePress CSS 变量
|
||||
- [ ] 响应式设计(移动端测试)
|
||||
- [ ] 默认值示例让组件"打开即用"
|
||||
- [ ] 动画流畅不卡顿
|
||||
- [ ] 文字无错别字
|
||||
- [ ] 代码示例可运行
|
||||
- [ ] `npm run build` 成功
|
||||
|
||||
---
|
||||
|
||||
**总结**:优秀的交互式教程 = 清晰的逻辑 + 即时的反馈 + 精致的可视化。遵循本规范,可以创建出让读者"秒懂"复杂概念的高质量内容。
|
||||
@@ -0,0 +1,41 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
- `docs/`: VitePress site source (Markdown content, sidebar/nav, assets referenced by docs).
|
||||
- `docs/.vitepress/theme/`: custom theme, global component registration in `index.js`, shared styles in `style.css`, layout in `Layout.vue`.
|
||||
- `docs/.vitepress/theme/components/appendix/*/`: interactive Vue demos used inside appendix pages (e.g. `web-basics/`, `deployment/`).
|
||||
- `assets/`: repo-level images/media (if referenced, prefer linking/copying into `docs/public/` or a doc-local folder when appropriate).
|
||||
- `scripts/`, `tools/`, `update_readmes.cjs`: utility scripts for maintaining docs.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
This repo is a VitePress (Vue 3) documentation project. Requires Node.js **>= 18**.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev # start local docs server (hot reload)
|
||||
npm run build # production build (use as CI-style check)
|
||||
npm run preview # preview the built site locally
|
||||
npm run format # run Prettier on the whole repo
|
||||
```
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
- Formatting: Prettier (`npm run format`). Keep diffs small and avoid reformatting unrelated files.
|
||||
- Vue components: Vue 3 SFCs with `<script setup>`, PascalCase filenames (e.g. `SemanticTagsDemo.vue`).
|
||||
- CSS: prefer VitePress theme variables (`var(--vp-c-*)`) and keep components responsive (`@media (max-width: 720px)` when needed).
|
||||
- Docs: use clear headings and short paragraphs; components are referenced in Markdown as `<ComponentName />`.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
There is no dedicated test framework in this repo. Use `npm run build` as the primary correctness check, and manually verify interactive components in `npm run dev`.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
- Commits follow a Conventional Commits style seen in history: `feat: ...`, `fix: ...`, `docs: ...` (optionally scoped like `feat(docs): ...`).
|
||||
- PRs should include: a short description, screenshots/GIFs for UI or component changes, and any relevant paths touched (e.g. `docs/zh-cn/appendix/...`, `docs/.vitepress/theme/...`).
|
||||
|
||||
## Configuration & Deployment Notes
|
||||
|
||||
- `vercel.json` is present; keep builds reproducible and avoid relying on local-only assets.
|
||||
@@ -43,11 +43,9 @@
|
||||
<a href="docs-readme/de-DE/README.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-d9d9d9"></a>
|
||||
</p>
|
||||
|
||||
|
||||
**Easy-Vibe 是一个开源的、基于项目的 AI 编程课程,教你如何从零开始构建真正的 AI 产品。**
|
||||
**Easy-Vibe is an open-source, project-based AI coding course that teaches you how to build real AI products from scratch.**
|
||||
|
||||
|
||||
AI 编程、全栈 Web 应用开发、AI Agent、工作流和 RAG 系统
|
||||
|
||||
👉 专为初学者、产品经理和开发者设计。将 AI Demo 转化为可部署的产品。
|
||||
@@ -73,79 +71,79 @@ AI 编程、全栈 Web 应用开发、AI Agent、工作流和 RAG 系统
|
||||
|
||||
### 零、幼儿园
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [前言:学习地图](docs/zh-cn/stage-0/0.1-learning-map/index.md) | 整体学习路径导览 | ✅ |
|
||||
| [初级一:AI 时代,会说话就会编程](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 通过贪吃蛇等案例初步感受 AI 编程的能力 | ✅ |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :----------------------------------------------------------------------------------------------- | :------------------------------------- | :--- |
|
||||
| [前言:学习地图](docs/zh-cn/stage-0/0.1-learning-map/index.md) | 整体学习路径导览 | ✅ |
|
||||
| [初级一:AI 时代,会说话就会编程](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 通过贪吃蛇等案例初步感受 AI 编程的能力 | ✅ |
|
||||
|
||||
### 一、AI 产品经理
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [初级二:认识 AI IDE 工具](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | 学会使用 IDE,在本地制作小游戏 | ✅ |
|
||||
| [初级三:动手做出原型](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | 从需求分析、AI 生成单页面,再到生成多页面产品原型 | ✅ |
|
||||
| [初级四:给原型加上 AI 能力](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | 学会接入常见 AI 能力(文本、图片、视频) | ✅ |
|
||||
| [初级五:完整项目实战](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 模拟真实场景、接受用户反馈迭代,完整化项目 | ✅ |
|
||||
| [大作业:做一个完整的 Web 应用原型并展示](docs/zh-cn/stage-1/1.5-final-project/index.md) | 完整实现应用,展示应用效果 | ✅ |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :---------------------------------------------------------------------------------------- | :------------------------------------------------ | :--- |
|
||||
| [初级二:认识 AI IDE 工具](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | 学会使用 IDE,在本地制作小游戏 | ✅ |
|
||||
| [初级三:动手做出原型](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | 从需求分析、AI 生成单页面,再到生成多页面产品原型 | ✅ |
|
||||
| [初级四:给原型加上 AI 能力](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | 学会接入常见 AI 能力(文本、图片、视频) | ✅ |
|
||||
| [初级五:完整项目实战](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 模拟真实场景、接受用户反馈迭代,完整化项目 | ✅ |
|
||||
| [大作业:做一个完整的 Web 应用原型并展示](docs/zh-cn/stage-1/1.5-final-project/index.md) | 完整实现应用,展示应用效果 | ✅ |
|
||||
|
||||
#### 附录
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [附录A:产品思维补充](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | 从零到一做产品需要考虑的思维框架 | ✅ |
|
||||
| [附录B:常见报错及解决方案](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | vibe coding 中的常见错误及排查方法 | ✅ |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :-------------------------------------------------------------------------------- | :--------------------------------- | :--- |
|
||||
| [附录A:产品思维补充](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | 从零到一做产品需要考虑的思维框架 | ✅ |
|
||||
| [附录B:常见报错及解决方案](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | vibe coding 中的常见错误及排查方法 | ✅ |
|
||||
|
||||
### 二、初中级开发工程师
|
||||
|
||||
#### 前端部分
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [前端零:使用 lovart 生产素材](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 |
|
||||
| [前端一:Figma 与 MasterGo 入门](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 |
|
||||
| [前端二:构建第一个现代应用程序-UI 设计](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 |
|
||||
| [前端三:参考 UI 设计规范与多产品 UI 设计](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 围绕统一主视觉扩展多产品界面,练习系统化设计能力 | 🚧 |
|
||||
| [前端四:一起做霍格沃茨画像](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | 从 0 到 1 做出接入 AI 能力的前端应用,串联设计与开发 | ✅ |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :--- |
|
||||
| [前端零:使用 lovart 生产素材](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 |
|
||||
| [前端一:Figma 与 MasterGo 入门](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 |
|
||||
| [前端二:构建第一个现代应用程序-UI 设计](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 |
|
||||
| [前端三:参考 UI 设计规范与多产品 UI 设计](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 围绕统一主视觉扩展多产品界面,练习系统化设计能力 | 🚧 |
|
||||
| [前端四:一起做霍格沃茨画像](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | 从 0 到 1 做出接入 AI 能力的前端应用,串联设计与开发 | ✅ |
|
||||
|
||||
#### 后端与全栈部分
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [后端一:什么是 API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | 理解 HTTP 接口与请求响应模型,为后端集成与联调做准备 | ✅ |
|
||||
| [后端二:从数据库到 Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | 在 Supabase 上落地数据库和 API,打通数据模型与前端页面 | ✅ |
|
||||
| [后端三:大模型辅助编写接口代码与接口文档](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | 用大模型协助生成接口与数据库文档及代码,实现可读可测的后端 | 🚧 |
|
||||
| [后端四:Git 工作流](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | 在 Git 工作流中管理代码,进行版本控制和协作 | ✅ |
|
||||
| [后端五:Zeabur 部署](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | 将应用部署到 Zeabur 上线 | ✅ |
|
||||
| [后端六:现代 CLI 开发工具](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | 使用 CLI 类 AI 编程工具加速开发与调试,形成个人工程化工作流 | ✅ |
|
||||
| [后端七:如何集成 Stripe 等收费系统](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 接入支付系统,完成收费链路与基础结算流程 | 🚧 |
|
||||
| [大作业 1:构建第一个现代应用程序-全栈应用](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 综合前端、后端与支付模块,完成可上线的全栈 Web 应用 | 🚧 |
|
||||
| [大作业 2:现代前端组件库 + Trae 实战](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 使用现代前端组件库与 Trae,独立完成可登录注册并支持收费的产品 | 🚧 |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------ | :--- |
|
||||
| [后端一:什么是 API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | 理解 HTTP 接口与请求响应模型,为后端集成与联调做准备 | ✅ |
|
||||
| [后端二:从数据库到 Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | 在 Supabase 上落地数据库和 API,打通数据模型与前端页面 | ✅ |
|
||||
| [后端三:大模型辅助编写接口代码与接口文档](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | 用大模型协助生成接口与数据库文档及代码,实现可读可测的后端 | 🚧 |
|
||||
| [后端四:Git 工作流](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | 在 Git 工作流中管理代码,进行版本控制和协作 | ✅ |
|
||||
| [后端五:Zeabur 部署](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | 将应用部署到 Zeabur 上线 | ✅ |
|
||||
| [后端六:现代 CLI 开发工具](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | 使用 CLI 类 AI 编程工具加速开发与调试,形成个人工程化工作流 | ✅ |
|
||||
| [后端七:如何集成 Stripe 等收费系统](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 接入支付系统,完成收费链路与基础结算流程 | 🚧 |
|
||||
| [大作业 1:构建第一个现代应用程序-全栈应用](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 综合前端、后端与支付模块,完成可上线的全栈 Web 应用 | 🚧 |
|
||||
| [大作业 2:现代前端组件库 + Trae 实战](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 使用现代前端组件库与 Trae,独立完成可登录注册并支持收费的产品 | 🚧 |
|
||||
|
||||
#### AI 能力附录
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [AI 一:Dify 入门与知识库集成](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | 用 Dify Workflow 与基础 RAG 搭建工具类产品,为后续应用升级打样 | ✅ |
|
||||
| [AI 二:学会查询 AI 词典与集成多模态 API](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 学会查找合适的模型与 API,并把文本、图像等多模态能力接入产品 | 🚧 |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------- | :--- |
|
||||
| [AI 一:Dify 入门与知识库集成](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | 用 Dify Workflow 与基础 RAG 搭建工具类产品,为后续应用升级打样 | ✅ |
|
||||
| [AI 二:学会查询 AI 词典与集成多模态 API](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 学会查找合适的模型与 API,并把文本、图像等多模态能力接入产品 | 🚧 |
|
||||
|
||||
### 三、高级开发工程师
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [高级一:MCP 与 ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | 通过 MCP 与 Skills 扩展 IDE 能力,把外部服务接成工具 | 🚧 |
|
||||
| [高级二:如何让 Coding Tools 长时间工作](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 设计和配置长时间运行的任务,让 Coding Tools 更稳定可靠 | 🚧 |
|
||||
| [高级三:多平台开发:如何构建微信小程序](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | 了解微信小程序生态,从官方模板到上线完成一个前端小程序 | ✅ |
|
||||
| [高级四:多平台开发:如何构建微信小程序-包含后端](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 在小程序中接入数据库与后端逻辑,打通完整业务闭环 | 🚧 |
|
||||
| [高级五:多平台开发:如何构建安卓程序](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | 使用 Expo 等工具,完成 Web/原生一体化的安卓应用开发 | 🚧 |
|
||||
| [高级六:多平台开发:如何构建 iOS 程序](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | 使用 Expo 等工具,完成 Web/原生一体化的 iOS 应用开发 | 🚧 |
|
||||
| [高级七:如何构建属于自己的个人网页与学术博客](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 从选型、搭建到部署,构建展示个人项目与学术成果的长久在线主页 | 🚧 |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :--- |
|
||||
| [高级一:MCP 与 ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | 通过 MCP 与 Skills 扩展 IDE 能力,把外部服务接成工具 | 🚧 |
|
||||
| [高级二:如何让 Coding Tools 长时间工作](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 设计和配置长时间运行的任务,让 Coding Tools 更稳定可靠 | 🚧 |
|
||||
| [高级三:多平台开发:如何构建微信小程序](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | 了解微信小程序生态,从官方模板到上线完成一个前端小程序 | ✅ |
|
||||
| [高级四:多平台开发:如何构建微信小程序-包含后端](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 在小程序中接入数据库与后端逻辑,打通完整业务闭环 | 🚧 |
|
||||
| [高级五:多平台开发:如何构建安卓程序](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | 使用 Expo 等工具,完成 Web/原生一体化的安卓应用开发 | 🚧 |
|
||||
| [高级六:多平台开发:如何构建 iOS 程序](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | 使用 Expo 等工具,完成 Web/原生一体化的 iOS 应用开发 | 🚧 |
|
||||
| [高级七:如何构建属于自己的个人网页与学术博客](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 从选型、搭建到部署,构建展示个人项目与学术成果的长久在线主页 | 🚧 |
|
||||
|
||||
#### AI 能力附录
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [高级 AI 一:什么是 RAG 以及它如何工作](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | 系统理解 RAG 原理与常见架构,为复杂应用提供知识检索基础 | ✅ |
|
||||
| [高级 AI 二:中高级 RAG 与工作流编排:以 LangGraph 为例](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | 使用 LangGraph 等工具设计多步工作流与中高级 RAG 系统 | 🚧 |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :--- |
|
||||
| [高级 AI 一:什么是 RAG 以及它如何工作](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | 系统理解 RAG 原理与常见架构,为复杂应用提供知识检索基础 | ✅ |
|
||||
| [高级 AI 二:中高级 RAG 与工作流编排:以 LangGraph 为例](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | 使用 LangGraph 等工具设计多步工作流与中高级 RAG 系统 | 🚧 |
|
||||
|
||||
## 🛠️ 如何学习
|
||||
|
||||
|
||||
+52
-51
@@ -11,7 +11,7 @@
|
||||
|
||||
# Easy-Vibe: Learn Vibe Coding from 0 to 1
|
||||
|
||||
### *من الصفر، تعلم قائم على المشاريع، بناء أول منتج ذكاء اصطناعي لك*
|
||||
### _من الصفر، تعلم قائم على المشاريع، بناء أول منتج ذكاء اصطناعي لك_
|
||||
|
||||
<p align="center">
|
||||
📌 <a href="https://datawhalechina.github.io/easy-vibe/">ابدأ القراءة أونلاين (Start Reading Online)</a>
|
||||
@@ -78,79 +78,79 @@
|
||||
|
||||
### 0. الروضة
|
||||
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :--- | :--- | :--- |
|
||||
| [المقدمة: خريطة التعلم](docs/stage-0/0.1-learning-map/index.md) | دليل مسار التعلم الشامل | ✅ |
|
||||
| [المبتدئ 1: عصر الذكاء الاصطناعي، إذا كنت تستطيع التحدث يمكنك البرمجة](docs/stage-0/0.2-ai-capabilities-through-games/index.md) | تجربة قدرات البرمجة بالذكاء الاصطناعي لأول مرة من خلال حالات مثل Snake | ✅ |
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------- | :----- |
|
||||
| [المقدمة: خريطة التعلم](docs/stage-0/0.1-learning-map/index.md) | دليل مسار التعلم الشامل | ✅ |
|
||||
| [المبتدئ 1: عصر الذكاء الاصطناعي، إذا كنت تستطيع التحدث يمكنك البرمجة](docs/stage-0/0.2-ai-capabilities-through-games/index.md) | تجربة قدرات البرمجة بالذكاء الاصطناعي لأول مرة من خلال حالات مثل Snake | ✅ |
|
||||
|
||||
### 1. مدير منتج الذكاء الاصطناعي
|
||||
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :--- | :--- | :--- |
|
||||
| [المبتدئ 2: معرفة أدوات IDE الذكاء الاصطناعي](docs/stage-1/1.1-introduction-to-ai-ide/index.md) | تعلم استخدام IDE، إنشاء ألعاب مصغرة محليًا | ✅ |
|
||||
| [المبتدئ 3: بناء نموذج أولي بنفسك](docs/stage-1/1.2-building-prototype/index.md) | تحليل المتطلبات، توليد صفحة واحدة بالذكاء الاصطناعي، حتى توليد نماذج منتجات متعددة الصفحات | ✅ |
|
||||
| [المبتدئ 4: إضافة قدرات الذكاء الاصطناعي إلى النموذج الأولي](docs/stage-1/1.3-integrating-ai-capabilities/index.md) | تعلم توصيل القدرات الشائعة للذكاء الاصطناعي (النص، الصورة، الفيديو) | ✅ |
|
||||
| [المبتدئ 5: مشروع كامل عملي](docs/stage-1/1.4-complete-project-practice/index.md) | محاكاة سيناريوهات حقيقية، قبول ملاحظات المستخدمين للتحسين، إكمال المشروع | ✅ |
|
||||
| [المشروع الكبير: إنشاء نموذج أولي كامل لتطبيق الويب وعرضه](docs/stage-1/1.5-final-project/index.md) | تنفيذ التطبيق بالكامل، عرض آثار التطبيق | ✅ |
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------- | :----- |
|
||||
| [المبتدئ 2: معرفة أدوات IDE الذكاء الاصطناعي](docs/stage-1/1.1-introduction-to-ai-ide/index.md) | تعلم استخدام IDE، إنشاء ألعاب مصغرة محليًا | ✅ |
|
||||
| [المبتدئ 3: بناء نموذج أولي بنفسك](docs/stage-1/1.2-building-prototype/index.md) | تحليل المتطلبات، توليد صفحة واحدة بالذكاء الاصطناعي، حتى توليد نماذج منتجات متعددة الصفحات | ✅ |
|
||||
| [المبتدئ 4: إضافة قدرات الذكاء الاصطناعي إلى النموذج الأولي](docs/stage-1/1.3-integrating-ai-capabilities/index.md) | تعلم توصيل القدرات الشائعة للذكاء الاصطناعي (النص، الصورة، الفيديو) | ✅ |
|
||||
| [المبتدئ 5: مشروع كامل عملي](docs/stage-1/1.4-complete-project-practice/index.md) | محاكاة سيناريوهات حقيقية، قبول ملاحظات المستخدمين للتحسين، إكمال المشروع | ✅ |
|
||||
| [المشروع الكبير: إنشاء نموذج أولي كامل لتطبيق الويب وعرضه](docs/stage-1/1.5-final-project/index.md) | تنفيذ التطبيق بالكامل، عرض آثار التطبيق | ✅ |
|
||||
|
||||
#### الملاحق
|
||||
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :--- | :--- | :--- |
|
||||
| [الملحق A: مكمل تفكير المنتج](docs/stage-1/appendix-a-product-thinking/index.md) | أطر التفكير اللازمة لبناء منتج من الصفر إلى واحد | ✅ |
|
||||
| [الملحق B: الأخطاء الشائعة والحلول](docs/stage-1/appendix-b-common-errors/index.md) | الأخطاء الشائعة في Vibe Coding وطرق استكشاف الأخطاء | ✅ |
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :---------------------------------------------------------------------------------- | :-------------------------------------------------- | :----- |
|
||||
| [الملحق A: مكمل تفكير المنتج](docs/stage-1/appendix-a-product-thinking/index.md) | أطر التفكير اللازمة لبناء منتج من الصفر إلى واحد | ✅ |
|
||||
| [الملحق B: الأخطاء الشائعة والحلول](docs/stage-1/appendix-b-common-errors/index.md) | الأخطاء الشائعة في Vibe Coding وطرق استكشاف الأخطاء | ✅ |
|
||||
|
||||
### 2. مهندس تطوير مستوى المبتدئ-المتوسط
|
||||
|
||||
#### قسم الواجهة الأمامية
|
||||
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :--- | :--- | :--- |
|
||||
| [الواجهة الأمامية 0: استخدام lovart لتوليد الأصول](docs/stage-2/frontend/2.0-lovart-assets/) | استخدام lovart لتوليد الأصول المرئية مثل الشخصيات والمشاهد دفعة واحدة، توفير أساس الأصول لتصميم الواجهة وتطوير الواجهة الأمامية | 🚧 |
|
||||
| [الواجهة الأمامية 1: مقدمة إلى Figma و MasterGo](docs/stage-2/frontend/2.1-figma-mastergo/) | استخدام أدوات التصميم لتنظيم هيكل المعلومات وهيكل الصفحة، وإعداد الأساس لتنفيذ الواجهة الأمامية | 🚧 |
|
||||
| [الواجهة الأمامية 2: بناء أول تطبيق حديث - تصميم الواجهة](docs/stage-2/frontend/2.2-ui-design/) | إكمال واجهة قائمة على المكونات بناءً على التصاميم، تحقيق المسار الأول من التصميم إلى الكود | 🚧 |
|
||||
| [الواجهة الأمامية 3: مرجع مواصفات تصميم الواجهة وتصميم واجهة متعدد المنتجات](docs/stage-2/frontend/2.3-multi-product-ui/) | توسيع واجهات متعددة المنتجات حول بصري رئيسي موحد، ممارسة قدرات التصميم المنهجي | 🚧 |
|
||||
| [الواجهة الأمامية 4: لنصنع صور هوجورتس معًا](docs/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | إنشاء تطبيق واجهة أمامية بقدرات ذكاء اصطناعي مدمجة من 0 إلى 1، ربط التصميم والتطوير | ✅ |
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | :----- |
|
||||
| [الواجهة الأمامية 0: استخدام lovart لتوليد الأصول](docs/stage-2/frontend/2.0-lovart-assets/) | استخدام lovart لتوليد الأصول المرئية مثل الشخصيات والمشاهد دفعة واحدة، توفير أساس الأصول لتصميم الواجهة وتطوير الواجهة الأمامية | 🚧 |
|
||||
| [الواجهة الأمامية 1: مقدمة إلى Figma و MasterGo](docs/stage-2/frontend/2.1-figma-mastergo/) | استخدام أدوات التصميم لتنظيم هيكل المعلومات وهيكل الصفحة، وإعداد الأساس لتنفيذ الواجهة الأمامية | 🚧 |
|
||||
| [الواجهة الأمامية 2: بناء أول تطبيق حديث - تصميم الواجهة](docs/stage-2/frontend/2.2-ui-design/) | إكمال واجهة قائمة على المكونات بناءً على التصاميم، تحقيق المسار الأول من التصميم إلى الكود | 🚧 |
|
||||
| [الواجهة الأمامية 3: مرجع مواصفات تصميم الواجهة وتصميم واجهة متعدد المنتجات](docs/stage-2/frontend/2.3-multi-product-ui/) | توسيع واجهات متعددة المنتجات حول بصري رئيسي موحد، ممارسة قدرات التصميم المنهجي | 🚧 |
|
||||
| [الواجهة الأمامية 4: لنصنع صور هوجورتس معًا](docs/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | إنشاء تطبيق واجهة أمامية بقدرات ذكاء اصطناعي مدمجة من 0 إلى 1، ربط التصميم والتطوير | ✅ |
|
||||
|
||||
#### قسم الواجهة الخلفية والكامل
|
||||
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :--- | :--- | :--- |
|
||||
| [الواجهة الخلفية 1: ما هي API](docs/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | فهم واجهات HTTP ونماذج الطلب-الاستجابة، الاستعداد للتكامل الخلفي والتنسيق | ✅ |
|
||||
| [الواجهة الخلفية 2: من قواعد البيانات إلى Supabase](docs/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) : تنفيذ قواعد البيانات وواجهات API على Supabase، ربط نماذج البيانات بصفحات الواجهة الأمامية | ✅ |
|
||||
| [الواجهة الخلفية 3: LLM المساعدة في كتابة كود الواجهة والتوثيق](docs/stage-2/backend/2.3-ai-interface-code/) : استخدام LLM للمساعدة في توليد التوثيق والكود للواجهات وقواعد البيانات، تحقيق خلفي قابل للقراءة والاختبار | 🚧 |
|
||||
| [الواجهة الخلفية 4: سير عمل Git](docs/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) : إدارة الكود في سير عمل Git، إجراء التحكم في الإصدار والتعاون | ✅ |
|
||||
| [الواجهة الخلفية 5: نشر Zeabur](docs/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) : نشر التطبيق على Zeabur لجعله أونلاين | ✅ |
|
||||
| [الواجهة الخلفية 6: أدوات تطوير CLI حديثة](docs/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) : استخدام أدوات برمجة الذكاء الاصطناعي من نوع CLI لتسريع التطوير وتصحيح الأخطاء، تشكيل سير عمل هندسي شخصي | ✅ |
|
||||
| [الواجهة الخلفية 7: كيفية دمج أنظمة الدفع مثل Stripe](docs/stage-2/backend/2.7-stripe-payment/) : توصيل أنظمة الدفع، إكمال تدفق الدفع وعملية التسوية الأساسية | 🚧 |
|
||||
| [المشروع الكبير 1: بناء أول تطبيق حديث - تطبيق كامل](docs/stage-2/assignments/2.1-fullstack-app/) : دمج الواجهة الأمامية والواجهة الخلفية ووحدات الدفع، إكمال تطبيق ويب كامل جاهز للإنتاج | 🚧 |
|
||||
| [المشروع الكبير 2: مكتبة مكونات الواجهة الأمامية الحديثة + Trae عملية](docs/stage-2/assignments/2.2-modern-frontend-trae/) : استخدام مكتبة مكونات الواجهة الأمامية الحديثة و Trae، إكمال منتج بشكل مستقل مع تسجيل/دعم الدفع | 🚧 |
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------ | :----- |
|
||||
| [الواجهة الخلفية 1: ما هي API](docs/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | فهم واجهات HTTP ونماذج الطلب-الاستجابة، الاستعداد للتكامل الخلفي والتنسيق | ✅ |
|
||||
| [الواجهة الخلفية 2: من قواعد البيانات إلى Supabase](docs/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) : تنفيذ قواعد البيانات وواجهات API على Supabase، ربط نماذج البيانات بصفحات الواجهة الأمامية | ✅ |
|
||||
| [الواجهة الخلفية 3: LLM المساعدة في كتابة كود الواجهة والتوثيق](docs/stage-2/backend/2.3-ai-interface-code/) : استخدام LLM للمساعدة في توليد التوثيق والكود للواجهات وقواعد البيانات، تحقيق خلفي قابل للقراءة والاختبار | 🚧 |
|
||||
| [الواجهة الخلفية 4: سير عمل Git](docs/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) : إدارة الكود في سير عمل Git، إجراء التحكم في الإصدار والتعاون | ✅ |
|
||||
| [الواجهة الخلفية 5: نشر Zeabur](docs/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) : نشر التطبيق على Zeabur لجعله أونلاين | ✅ |
|
||||
| [الواجهة الخلفية 6: أدوات تطوير CLI حديثة](docs/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) : استخدام أدوات برمجة الذكاء الاصطناعي من نوع CLI لتسريع التطوير وتصحيح الأخطاء، تشكيل سير عمل هندسي شخصي | ✅ |
|
||||
| [الواجهة الخلفية 7: كيفية دمج أنظمة الدفع مثل Stripe](docs/stage-2/backend/2.7-stripe-payment/) : توصيل أنظمة الدفع، إكمال تدفق الدفع وعملية التسوية الأساسية | 🚧 |
|
||||
| [المشروع الكبير 1: بناء أول تطبيق حديث - تطبيق كامل](docs/stage-2/assignments/2.1-fullstack-app/) : دمج الواجهة الأمامية والواجهة الخلفية ووحدات الدفع، إكمال تطبيق ويب كامل جاهز للإنتاج | 🚧 |
|
||||
| [المشروع الكبير 2: مكتبة مكونات الواجهة الأمامية الحديثة + Trae عملية](docs/stage-2/assignments/2.2-modern-frontend-trae/) : استخدام مكتبة مكونات الواجهة الأمامية الحديثة و Trae، إكمال منتج بشكل مستقل مع تسجيل/دعم الدفع | 🚧 |
|
||||
|
||||
#### ملحق قدرات الذكاء الاصطناعي
|
||||
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :--- | :--- | :--- |
|
||||
| [الذكاء الاصطناعي 1: مقدمة إلى Dify ودمج قاعدة المعرفة](docs/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) : استخدام Dify Workflow و RAG الأساسي لبناء منتجات نوع الأدوات، إنشاء مثال لترقيات التطبيقات المستقبلية | ✅ |
|
||||
| [الذكاء الاصطناعي 2: تعلم البحث في قاموس الذكاء الاصطناعي ودمج واجهات API متعددة الوسائط](docs/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) : تعلم البحث عن نماذج وواجهات API مناسبة، دمج القدرات متعددة الوسائط مثل النص والصورة في المنتجات | 🚧 |
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------- | :----- |
|
||||
| [الذكاء الاصطناعي 1: مقدمة إلى Dify ودمج قاعدة المعرفة](docs/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) : استخدام Dify Workflow و RAG الأساسي لبناء منتجات نوع الأدوات، إنشاء مثال لترقيات التطبيقات المستقبلية | ✅ |
|
||||
| [الذكاء الاصطناعي 2: تعلم البحث في قاموس الذكاء الاصطناعي ودمج واجهات API متعددة الوسائط](docs/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) : تعلم البحث عن نماذج وواجهات API مناسبة، دمج القدرات متعددة الوسائط مثل النص والصورة في المنتجات | 🚧 |
|
||||
|
||||
### 3. مهندس تطوير متقدم
|
||||
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :--- | :--- | :--- |
|
||||
| [المتقدم 1: MCP ومهارات ClaudeCode](docs/stage-3/core-skills/3.1-mcp-claudecode-skills/) : توسيع قدرات IDE من خلال MCP والمهارات، توصيل الخدمات الخارجية كأدوات | 🚧 |
|
||||
| [المتقدم 2: كيفية جعل Coding Tools تعمل لفترة طويلة](docs/stage-3/core-skills/3.2-long-running-tasks/) : تصميم وتكوين المهام طويلة التشغيل، جعل Coding Tools أكثر استقرارًا وموثوقية | 🚧 |
|
||||
| [المتقدم 3: التطوير متعدد المنصات: كيفية بناء برامج WeChat المصغرة](docs/stage-3/cross-platform/3.3-wechat-miniprogram/) : فهم نظام البرامج المصغرة WeChat، إكمال برنامج مصغر للواجهة الأمامية من القالب الرسمي إلى الإطلاق | ✅ |
|
||||
| [المتقدم 4: التطوير متعدد المنصات: كيفية بناء برامج WeChat المصغرة - بما في ذلك الخلفية](docs/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : دمج قاعدة البيانات ومنطق الواجهة الخلفية في البرامج المصغرة، تحقيق دورة نشاط كاملة | 🚧 |
|
||||
| [المتقدم 5: التطوير متعدد المنصات: كيفية بناء تطبيقات Android](docs/stage-3/cross-platform/3.5-android-app/) : استخدام أدوات مثل Expo، إكمال تطوير تطبيقات Android المتكاملة للويب/الأصلي | 🚧 |
|
||||
| [المتقدم 6: التطوير متعدد المنصات: كيفية بناء تطبيقات iOS](docs/stage-3/cross-platform/3.6-ios-app/) : استخدام أدوات مثل Expo، إكمال تطوير تطبيقات iOS المتكاملة للويب/الأصلي | 🚧 |
|
||||
| [المتقدم 7: كيفية بناء موقعك الشخصي ومブログ الأكاديمي](docs/stage-3/personal-brand/3.7-personal-website-blog/) : من الاختيار، البناء إلى النشر، بناء صفحة رئيسية أونلاين طويلة الأجل تعرض المشاريع الشخصية والنتائج الأكاديمية | 🚧 |
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------- | :----- |
|
||||
| [المتقدم 1: MCP ومهارات ClaudeCode](docs/stage-3/core-skills/3.1-mcp-claudecode-skills/) : توسيع قدرات IDE من خلال MCP والمهارات، توصيل الخدمات الخارجية كأدوات | 🚧 |
|
||||
| [المتقدم 2: كيفية جعل Coding Tools تعمل لفترة طويلة](docs/stage-3/core-skills/3.2-long-running-tasks/) : تصميم وتكوين المهام طويلة التشغيل، جعل Coding Tools أكثر استقرارًا وموثوقية | 🚧 |
|
||||
| [المتقدم 3: التطوير متعدد المنصات: كيفية بناء برامج WeChat المصغرة](docs/stage-3/cross-platform/3.3-wechat-miniprogram/) : فهم نظام البرامج المصغرة WeChat، إكمال برنامج مصغر للواجهة الأمامية من القالب الرسمي إلى الإطلاق | ✅ |
|
||||
| [المتقدم 4: التطوير متعدد المنصات: كيفية بناء برامج WeChat المصغرة - بما في ذلك الخلفية](docs/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : دمج قاعدة البيانات ومنطق الواجهة الخلفية في البرامج المصغرة، تحقيق دورة نشاط كاملة | 🚧 |
|
||||
| [المتقدم 5: التطوير متعدد المنصات: كيفية بناء تطبيقات Android](docs/stage-3/cross-platform/3.5-android-app/) : استخدام أدوات مثل Expo، إكمال تطوير تطبيقات Android المتكاملة للويب/الأصلي | 🚧 |
|
||||
| [المتقدم 6: التطوير متعدد المنصات: كيفية بناء تطبيقات iOS](docs/stage-3/cross-platform/3.6-ios-app/) : استخدام أدوات مثل Expo، إكمال تطوير تطبيقات iOS المتكاملة للويب/الأصلي | 🚧 |
|
||||
| [المتقدم 7: كيفية بناء موقعك الشخصي ومブログ الأكاديمي](docs/stage-3/personal-brand/3.7-personal-website-blog/) : من الاختيار، البناء إلى النشر، بناء صفحة رئيسية أونلاين طويلة الأجل تعرض المشاريع الشخصية والنتائج الأكاديمية | 🚧 |
|
||||
|
||||
#### ملحق قدرات الذكاء الاصطناعي
|
||||
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :--- | :--- | :--- |
|
||||
| [الذكاء الاصطناعي المتقدم 1: ما هو RAG وكيف يعمل](docs/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) : الفهم المنهجي لمبادئ RAG والهياكل الشائعة، توفير أساس استرجاع المعرفة للتطبيقات المعقدة | ✅ |
|
||||
| [الذكاء الاصطناعي المتقدم 2: RAG متوسط-متقدم وتنظيف سير العمل: مثال LangGraph](docs/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) : استخدام أدوات مثل LangGraph لتصميم سير العمل متعدد الخطوات وأنظمة RAG متوسطة-متقدمة | 🚧 |
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------- | :----- |
|
||||
| [الذكاء الاصطناعي المتقدم 1: ما هو RAG وكيف يعمل](docs/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) : الفهم المنهجي لمبادئ RAG والهياكل الشائعة، توفير أساس استرجاع المعرفة للتطبيقات المعقدة | ✅ |
|
||||
| [الذكاء الاصطناعي المتقدم 2: RAG متوسط-متقدم وتنظيف سير العمل: مثال LangGraph](docs/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) : استخدام أدوات مثل LangGraph لتصميم سير العمل متعدد الخطوات وأنظمة RAG متوسطة-متقدمة | 🚧 |
|
||||
|
||||
## 🛠️ كيفية التعلم
|
||||
|
||||
@@ -206,6 +206,7 @@
|
||||
|
||||
<pامسح رمز QR أدناه لمتابعة الحساب الرسمي: Datawhale</p>
|
||||
<img src="https://raw.githubusercontent.com/datawhalechina/pumpkin-book/master/res/qrcode.jpeg" width="180" height="180">
|
||||
|
||||
</div>
|
||||
|
||||
## 📄 الترخيص
|
||||
|
||||
+52
-52
@@ -11,7 +11,7 @@
|
||||
|
||||
# Easy-Vibe: Learn Vibe Coding from 0 to 1
|
||||
|
||||
### *Von Null, projektbasiertes Lernen, dein erstes KI-Produkt bauen*
|
||||
### _Von Null, projektbasiertes Lernen, dein erstes KI-Produkt bauen_
|
||||
|
||||
<p align="center">
|
||||
📌 <a href="https://datawhalechina.github.io/easy-vibe/">Online lesen starten (Start Reading Online)</a>
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
Wenn du versuchst, mit KI Code zu schreiben, ständig auf Fehler stößt, oft aufgeben möchtest und unklar ist, wie man Programme tatsächlich online bringt 😢.
|
||||
|
||||
Dieses Tutorial ist speziell dafür konzipiert, dich von 0 auf 1 zu bringen und Vibe Coding-Techniken schrittweise zu meistern:
|
||||
Dieses Tutorial ist speziell dafür konzipiert, dich von 0 auf 1 zu bringen und Vibe Coding-Techniken schrittweise zu meistern:
|
||||
|
||||
- **Stufe 0**: **Schneller Einstieg über Mini-Spiele** zum Erwerb von Vibe Coding-Fähigkeiten
|
||||
- **Stufe 1**: Aus der Perspektive eines Product Managers, **Vibe Coding-Fähigkeiten und Geschäftsverständnis**, Implementierung eines Web-App-Prototyps
|
||||
@@ -78,79 +78,79 @@ Wir glauben, dass durch die Beherrschung von Vibe Coding in Kombination mit syst
|
||||
|
||||
### 0. Kindergarten
|
||||
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Vorwort: Lernkarte](docs/zh-cn/stage-0/0.1-learning-map/index.md) | Gesamtführung durch den Lernpfad | ✅ |
|
||||
| [Anfänger 1: KI-Zeitalter, wenn du sprechen kannst, kannst du programmieren](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | Erste Erfahrung mit KI-Programmierfähigkeiten durch Fälle wie Snake | ✅ |
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------ | :----- |
|
||||
| [Vorwort: Lernkarte](docs/zh-cn/stage-0/0.1-learning-map/index.md) | Gesamtführung durch den Lernpfad | ✅ |
|
||||
| [Anfänger 1: KI-Zeitalter, wenn du sprechen kannst, kannst du programmieren](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | Erste Erfahrung mit KI-Programmierfähigkeiten durch Fälle wie Snake | ✅ |
|
||||
|
||||
### 1. KI-Produktmanager
|
||||
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Anfänger 2: KI-IDE-Tools kennenlernen](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | IDE-Nutzung lernen, lokal Mini-Spiele erstellen | ✅ |
|
||||
| [Anfänger 3: Einen Prototyp selbst bauen](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | Anforderungsanalyse, KI-generierte Einzelseite, bis zu Generierung von Mehrseiten-Produktprototypen | ✅ |
|
||||
| [Anfänger 4: KI-Fähigkeiten zum Prototyp hinzufügen](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | Verbindung gängiger KI-Fähigkeiten (Text, Bild, Video) lernen | ✅ |
|
||||
| [Anfänger 5: Komplettes Projektpraxis](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | Reale Szenarien simulieren, Benutzerfeedback zur Iteration akzeptieren, Projekt completed | ✅ |
|
||||
| [Großes Projekt: Einen kompletten Web-App-Prototyp erstellen und präsentieren](docs/zh-cn/stage-1/1.5-final-project/index.md) | Anwendung vollständig implementieren, Anwendungseffekte präsentieren | ✅ |
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Anfänger 2: KI-IDE-Tools kennenlernen](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | IDE-Nutzung lernen, lokal Mini-Spiele erstellen | ✅ |
|
||||
| [Anfänger 3: Einen Prototyp selbst bauen](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | Anforderungsanalyse, KI-generierte Einzelseite, bis zu Generierung von Mehrseiten-Produktprototypen | ✅ |
|
||||
| [Anfänger 4: KI-Fähigkeiten zum Prototyp hinzufügen](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | Verbindung gängiger KI-Fähigkeiten (Text, Bild, Video) lernen | ✅ |
|
||||
| [Anfänger 5: Komplettes Projektpraxis](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | Reale Szenarien simulieren, Benutzerfeedback zur Iteration akzeptieren, Projekt completed | ✅ |
|
||||
| [Großes Projekt: Einen kompletten Web-App-Prototyp erstellen und präsentieren](docs/zh-cn/stage-1/1.5-final-project/index.md) | Anwendung vollständig implementieren, Anwendungseffekte präsentieren | ✅ |
|
||||
|
||||
#### Anhänge
|
||||
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Anhang A: Produktdenken-Ergänzung](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | Denkrahmen, die für die Erstellung eines Produkts von Null auf Eins erforderlich sind | ✅ |
|
||||
| [Anhang B: Häufige Fehler und Lösungen](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Häufige Fehler in Vibe Coding und Methoden zur Fehlerbehebung | ✅ |
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :-------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | :----- |
|
||||
| [Anhang A: Produktdenken-Ergänzung](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | Denkrahmen, die für die Erstellung eines Produkts von Null auf Eins erforderlich sind | ✅ |
|
||||
| [Anhang B: Häufige Fehler und Lösungen](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Häufige Fehler in Vibe Coding und Methoden zur Fehlerbehebung | ✅ |
|
||||
|
||||
### 2. Entwicklungsingenieur auf Anfänger-Fortgeschrittenen-Niveau
|
||||
|
||||
#### Frontend-Teil
|
||||
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Frontend 0: lovart für Asset-Generierung verwenden](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | lovart für Batch-Generierung von visuellen Assets wie Charakteren und Szenen verwenden, Bereitstellung von Asset-Basis für UI-Design und Frontend-Entwicklung | 🚧 |
|
||||
| [Frontend 1: Einführung in Figma und MasterGo](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | Design-Tools zur Organisation der Informationsarchitektur und Seitenstruktur verwenden, Basis für Frontend-Implementierung schaffen | 🚧 |
|
||||
| [Frontend 2: Erste moderne Anwendung erstellen - UI-Design](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | Komponentenbasierte Schnittstelle basierend auf Entwürfen completed, erste Route von Design zu Code realisieren | 🚧 |
|
||||
| [Frontend 3: UI-Design-Spezifikationen und Multi-Produkt-UI-Design referenzieren](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | Multi-Produkt-Schnittstellen um ein einheitliches Hauptvisuelles erweitern, systematische Designfähigkeiten üben | 🚧 |
|
||||
| [Frontend 4: Gemeinsam Hogwarts-Porträts erstellen](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | Frontend-Anwendung mit integrierten KI-Fähigkeiten von 0 auf 1 erstellen, Design und Entwicklung verbinden | ✅ |
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----- |
|
||||
| [Frontend 0: lovart für Asset-Generierung verwenden](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | lovart für Batch-Generierung von visuellen Assets wie Charakteren und Szenen verwenden, Bereitstellung von Asset-Basis für UI-Design und Frontend-Entwicklung | 🚧 |
|
||||
| [Frontend 1: Einführung in Figma und MasterGo](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | Design-Tools zur Organisation der Informationsarchitektur und Seitenstruktur verwenden, Basis für Frontend-Implementierung schaffen | 🚧 |
|
||||
| [Frontend 2: Erste moderne Anwendung erstellen - UI-Design](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | Komponentenbasierte Schnittstelle basierend auf Entwürfen completed, erste Route von Design zu Code realisieren | 🚧 |
|
||||
| [Frontend 3: UI-Design-Spezifikationen und Multi-Produkt-UI-Design referenzieren](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | Multi-Produkt-Schnittstellen um ein einheitliches Hauptvisuelles erweitern, systematische Designfähigkeiten üben | 🚧 |
|
||||
| [Frontend 4: Gemeinsam Hogwarts-Porträts erstellen](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | Frontend-Anwendung mit integrierten KI-Fähigkeiten von 0 auf 1 erstellen, Design und Entwicklung verbinden | ✅ |
|
||||
|
||||
#### Backend- und Full-Stack-Teil
|
||||
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Backend 1: Was ist eine API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | HTTP-Schnittstellen und Request-Response-Modelle verstehen, Vorbereitung auf Backend-Integration und Abstimmung | ✅ |
|
||||
| [Backend 2: Von Datenbanken zu Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) : Datenbanken und APIs auf Supabase implementieren, Datenmodelle mit Frontend-Seiten verbinden | ✅ |
|
||||
| [Backend 3: LLM-unterstützte Schnittstellen- und Dokumentationscodierung](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) : LLM zur Unterstützung bei der Generierung von Dokumentation und Code für Schnittstellen und Datenbanken verwenden, lesbaren und testbaren Backend realisieren | 🚧 |
|
||||
| [Backend 4: Git-Workflow](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) : Code in Git-Workflow verwalten, Versionskontrolle und Zusammenarbeit durchführen | ✅ |
|
||||
| [Backend 5: Zeabur-Bereitstellung](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) : Anwendung auf Zeabur bereitstellen, um online zu gehen | ✅ |
|
||||
| [Backend 6: Moderne CLI-Entwicklungstools](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) : KI-Programmierertools vom Typ CLI verwenden, um Entwicklung und Debugging zu beschleunigen, persönlichen Engineering-Workflow bilden | ✅ |
|
||||
| [Backend 7: Zahlungssysteme wie Stripe integrieren](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) : Zahlungssysteme anschließen, Zahlungsablauf und grundlegenden Abrechnungsprozess completed | 🚧 |
|
||||
| [Großes Projekt 1: Erste moderne Anwendung erstellen - Full-Stack-App](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) : Frontend, Backend und Zahlungsmodule integrieren, produktionsreife Full-Stack-Web-Anwendung completed | 🚧 |
|
||||
| [Großes Projekt 2: Moderne Frontend-Komponentenbibliothek + Trae-Praxis](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) : Moderne Frontend-Komponentenbibliothek und Trae verwenden, unabhängig ein Produkt mit Anmeldung/Registrierung und Zahlungsunterstützung completed | 🚧 |
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Backend 1: Was ist eine API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | HTTP-Schnittstellen und Request-Response-Modelle verstehen, Vorbereitung auf Backend-Integration und Abstimmung | ✅ |
|
||||
| [Backend 2: Von Datenbanken zu Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) : Datenbanken und APIs auf Supabase implementieren, Datenmodelle mit Frontend-Seiten verbinden | ✅ |
|
||||
| [Backend 3: LLM-unterstützte Schnittstellen- und Dokumentationscodierung](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) : LLM zur Unterstützung bei der Generierung von Dokumentation und Code für Schnittstellen und Datenbanken verwenden, lesbaren und testbaren Backend realisieren | 🚧 |
|
||||
| [Backend 4: Git-Workflow](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) : Code in Git-Workflow verwalten, Versionskontrolle und Zusammenarbeit durchführen | ✅ |
|
||||
| [Backend 5: Zeabur-Bereitstellung](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) : Anwendung auf Zeabur bereitstellen, um online zu gehen | ✅ |
|
||||
| [Backend 6: Moderne CLI-Entwicklungstools](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) : KI-Programmierertools vom Typ CLI verwenden, um Entwicklung und Debugging zu beschleunigen, persönlichen Engineering-Workflow bilden | ✅ |
|
||||
| [Backend 7: Zahlungssysteme wie Stripe integrieren](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) : Zahlungssysteme anschließen, Zahlungsablauf und grundlegenden Abrechnungsprozess completed | 🚧 |
|
||||
| [Großes Projekt 1: Erste moderne Anwendung erstellen - Full-Stack-App](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) : Frontend, Backend und Zahlungsmodule integrieren, produktionsreife Full-Stack-Web-Anwendung completed | 🚧 |
|
||||
| [Großes Projekt 2: Moderne Frontend-Komponentenbibliothek + Trae-Praxis](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) : Moderne Frontend-Komponentenbibliothek und Trae verwenden, unabhängig ein Produkt mit Anmeldung/Registrierung und Zahlungsunterstützung completed | 🚧 |
|
||||
|
||||
#### KI-Fähigkeiten-Anhang
|
||||
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [KI 1: Einführung in Dify und Wissensbasis-Integration](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) : Dify Workflow und grundlegendes RAG verwenden, um werkzeugartige Produkte zu erstellen, Beispiel für zukünftige Anwendungs-Upgrades erstellen | ✅ |
|
||||
| [KI 2: KI-Lexikon abfragen und multimodale APIs integrieren lernen](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) : Lernen, geeignete Modelle und APIs zu finden, multimodale Fähigkeiten wie Text und Bild in Produkte integrieren | 🚧 |
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------- | :----- |
|
||||
| [KI 1: Einführung in Dify und Wissensbasis-Integration](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) : Dify Workflow und grundlegendes RAG verwenden, um werkzeugartige Produkte zu erstellen, Beispiel für zukünftige Anwendungs-Upgrades erstellen | ✅ |
|
||||
| [KI 2: KI-Lexikon abfragen und multimodale APIs integrieren lernen](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) : Lernen, geeignete Modelle und APIs zu finden, multimodale Fähigkeiten wie Text und Bild in Produkte integrieren | 🚧 |
|
||||
|
||||
### 3. Fortgeschrittener Entwicklungsingenieur
|
||||
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Fortgeschritten 1: MCP und ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) : IDE-Fähigkeiten durch MCP und Skills erweitern, externe Dienste als Tools anschließen | 🚧 |
|
||||
| [Fortgeschritten 2: Coding Tools lange laufen lassen](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) : Lang laufende Aufgaben entwerfen und konfigurieren, Coding Tools stabiler und zuverlässiger machen | 🚧 |
|
||||
| [Fortgeschritten 3: Plattformübergreifende Entwicklung: WeChat-Miniprogramme erstellen](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) : WeChat-Miniprogramm-Ökosystem verstehen, Frontend-Miniprogramm von offizieller Vorlage bis zum Start completed | ✅ |
|
||||
| [Fortgeschritten 4: Plattformübergreifende Entwicklung: WeChat-Miniprogramme erstellen - Mit Backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : Datenbank und Backend-Logik in Miniprogramme integrieren, vollständigen Geschäftskreislauf realisieren | 🚧 |
|
||||
| [Fortgeschritten 5: Plattformübergreifende Entwicklung: Android-Anwendungen erstellen](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) : Tools wie Expo verwenden, Web/native-integrierte Android-Anwendungsentwicklung completed | 🚧 |
|
||||
| [Fortgeschritten 6: Plattformübergreifende Entwicklung: iOS-Anwendungen erstellen](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) : Tools wie Expo verwenden, Web/native-integrierte iOS-Anwendungsentwicklung completed | 🚧 |
|
||||
| [Fortgeschritten 7: Eigene persönliche Website und akademischen Blog erstellen](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) : Von Auswahl, Erstellung bis Bereitstellung, langfristige Online-Homepage erstellen, die persönliche Projekte und akademische Ergebnisse präsentiert | 🚧 |
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------- | :----- |
|
||||
| [Fortgeschritten 1: MCP und ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) : IDE-Fähigkeiten durch MCP und Skills erweitern, externe Dienste als Tools anschließen | 🚧 |
|
||||
| [Fortgeschritten 2: Coding Tools lange laufen lassen](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) : Lang laufende Aufgaben entwerfen und konfigurieren, Coding Tools stabiler und zuverlässiger machen | 🚧 |
|
||||
| [Fortgeschritten 3: Plattformübergreifende Entwicklung: WeChat-Miniprogramme erstellen](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) : WeChat-Miniprogramm-Ökosystem verstehen, Frontend-Miniprogramm von offizieller Vorlage bis zum Start completed | ✅ |
|
||||
| [Fortgeschritten 4: Plattformübergreifende Entwicklung: WeChat-Miniprogramme erstellen - Mit Backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : Datenbank und Backend-Logik in Miniprogramme integrieren, vollständigen Geschäftskreislauf realisieren | 🚧 |
|
||||
| [Fortgeschritten 5: Plattformübergreifende Entwicklung: Android-Anwendungen erstellen](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) : Tools wie Expo verwenden, Web/native-integrierte Android-Anwendungsentwicklung completed | 🚧 |
|
||||
| [Fortgeschritten 6: Plattformübergreifende Entwicklung: iOS-Anwendungen erstellen](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) : Tools wie Expo verwenden, Web/native-integrierte iOS-Anwendungsentwicklung completed | 🚧 |
|
||||
| [Fortgeschritten 7: Eigene persönliche Website und akademischen Blog erstellen](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) : Von Auswahl, Erstellung bis Bereitstellung, langfristige Online-Homepage erstellen, die persönliche Projekte und akademische Ergebnisse präsentiert | 🚧 |
|
||||
|
||||
#### KI-Fähigkeiten-Anhang
|
||||
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Fortgeschrittene KI 1: Was ist RAG und wie funktioniert es](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) : Systematisches Verständnis von RAG-Prinzipien und gängigen Architekturen, Wissensretrieval-Basis für komplexe Anwendungen bereitstellen | ✅ |
|
||||
| [Fortgeschrittene KI 2: Mittel-fortgeschrittenes RAG und Workflow-Orchestrierung: LangGraph-Beispiel](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) : Tools wie LangGraph verwenden, um mehrstufige Workflows und mittel-fortgeschrittene RAG-Systeme zu entwerfen | 🚧 |
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------- | :----- |
|
||||
| [Fortgeschrittene KI 1: Was ist RAG und wie funktioniert es](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) : Systematisches Verständnis von RAG-Prinzipien und gängigen Architekturen, Wissensretrieval-Basis für komplexe Anwendungen bereitstellen | ✅ |
|
||||
| [Fortgeschrittene KI 2: Mittel-fortgeschrittenes RAG und Workflow-Orchestrierung: LangGraph-Beispiel](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) : Tools wie LangGraph verwenden, um mehrstufige Workflows und mittel-fortgeschrittene RAG-Systeme zu entwerfen | 🚧 |
|
||||
|
||||
## 🛠️ Lernmethode
|
||||
|
||||
|
||||
+51
-51
@@ -11,7 +11,7 @@
|
||||
|
||||
# Easy-Vibe: Learn Vibe Coding from 0 to 1
|
||||
|
||||
### *Zero Foundation, Project-Based Learning, Build Your First AI Product*
|
||||
### _Zero Foundation, Project-Based Learning, Build Your First AI Product_
|
||||
|
||||
<p align="center">
|
||||
📌 <a href="https://datawhalechina.github.io/easy-vibe/">Start Reading Online</a>
|
||||
@@ -81,79 +81,79 @@ We believe that by mastering Vibe Coding combined with systematic training, one
|
||||
|
||||
### Stage 0: Kindergarten
|
||||
|
||||
| Chapter | Key Content | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Preface: Learning Map](docs/zh-cn/stage-0/0.1-learning-map/index.md) | Overall learning path overview | ✅ |
|
||||
| [Primary 1: Programming by Speaking in the AI Era](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | Experience AI programming capabilities through Snake and other cases | ✅ |
|
||||
| Chapter | Key Content | Status |
|
||||
| :---------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------- | :----- |
|
||||
| [Preface: Learning Map](docs/zh-cn/stage-0/0.1-learning-map/index.md) | Overall learning path overview | ✅ |
|
||||
| [Primary 1: Programming by Speaking in the AI Era](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | Experience AI programming capabilities through Snake and other cases | ✅ |
|
||||
|
||||
### Stage 1: AI Product Manager
|
||||
|
||||
| Chapter | Key Content | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Primary 2: Understanding AI IDE Tools](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | Learn to use IDE, create small games locally | ✅ |
|
||||
| [Primary 3: Building Prototypes Hands-On](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | From requirement analysis, AI-generated single page to multi-page product prototype | ✅ |
|
||||
| [Primary 4: Adding AI Capabilities to Prototypes](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | Learn to integrate common AI capabilities (text, image, video) | ✅ |
|
||||
| [Primary 5: Complete Project Practice](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | Simulate real scenarios, accept user feedback iterations, complete projects | ✅ |
|
||||
| [Final Assignment: Build a Complete Web Application Prototype and Present](docs/zh-cn/stage-1/1.5-final-project/index.md) | Fully implement application, showcase application effects | ✅ |
|
||||
| Chapter | Key Content | Status |
|
||||
| :------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------- | :----- |
|
||||
| [Primary 2: Understanding AI IDE Tools](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | Learn to use IDE, create small games locally | ✅ |
|
||||
| [Primary 3: Building Prototypes Hands-On](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | From requirement analysis, AI-generated single page to multi-page product prototype | ✅ |
|
||||
| [Primary 4: Adding AI Capabilities to Prototypes](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | Learn to integrate common AI capabilities (text, image, video) | ✅ |
|
||||
| [Primary 5: Complete Project Practice](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | Simulate real scenarios, accept user feedback iterations, complete projects | ✅ |
|
||||
| [Final Assignment: Build a Complete Web Application Prototype and Present](docs/zh-cn/stage-1/1.5-final-project/index.md) | Fully implement application, showcase application effects | ✅ |
|
||||
|
||||
#### Appendix
|
||||
|
||||
| Chapter | Key Content | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Appendix A: Product Thinking Supplement](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | Thinking framework needed to build products from scratch | ✅ |
|
||||
| [Appendix B: Common Errors and Solutions](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Common errors in vibe coding and troubleshooting methods | ✅ |
|
||||
| Chapter | Key Content | Status |
|
||||
| :------------------------------------------------------------------------------------------------- | :------------------------------------------------------- | :----- |
|
||||
| [Appendix A: Product Thinking Supplement](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | Thinking framework needed to build products from scratch | ✅ |
|
||||
| [Appendix B: Common Errors and Solutions](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Common errors in vibe coding and troubleshooting methods | ✅ |
|
||||
|
||||
### Stage 2: Intermediate Developer
|
||||
|
||||
#### Frontend Part
|
||||
|
||||
| Chapter | Key Content | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Frontend 0: Using Lovart to Generate Assets](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | Learn to use Lovart to batch generate characters, scenes and other visual assets | 🚧 |
|
||||
| [Frontend 1: Figma & MasterGo Introduction](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | Use design tools to organize information architecture and page structure | 🚧 |
|
||||
| [Frontend 2: Building Your First Modern Application - UI Design](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | Complete componentized interface based on design specs, implement the first link from design to code | 🚧 |
|
||||
| [Frontend 3: Reference UI Design Specs and Multi-Product UI Design](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | Expand multi-product interfaces around unified visual design, practice systematic design capabilities | 🚧 |
|
||||
| [Frontend 4: Building Hogwarts Portraits Together](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | Build AI-capable frontend applications from scratch, connecting design and development | ✅ |
|
||||
| Chapter | Key Content | Status |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Frontend 0: Using Lovart to Generate Assets](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | Learn to use Lovart to batch generate characters, scenes and other visual assets | 🚧 |
|
||||
| [Frontend 1: Figma & MasterGo Introduction](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | Use design tools to organize information architecture and page structure | 🚧 |
|
||||
| [Frontend 2: Building Your First Modern Application - UI Design](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | Complete componentized interface based on design specs, implement the first link from design to code | 🚧 |
|
||||
| [Frontend 3: Reference UI Design Specs and Multi-Product UI Design](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | Expand multi-product interfaces around unified visual design, practice systematic design capabilities | 🚧 |
|
||||
| [Frontend 4: Building Hogwarts Portraits Together](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | Build AI-capable frontend applications from scratch, connecting design and development | ✅ |
|
||||
|
||||
#### Backend and Full Stack Part
|
||||
|
||||
| Chapter | Key Content | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Backend 1: What is API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | Understand HTTP interfaces and request-response models | ✅ |
|
||||
| [Backend 2: From Database to Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | Implement databases and APIs on Supabase, connect data models with frontend pages | ✅ |
|
||||
| [Backend 3: AI-Assisted Interface Code and Documentation](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | Use LLM to help generate interface and database documentation and code | 🚧 |
|
||||
| [Backend 4: Git Workflow](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | Manage code in Git workflow, version control and collaboration | ✅ |
|
||||
| [Backend 5: Zeabur Deployment](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | Deploy applications to Zeabur for production | ✅ |
|
||||
| [Backend 6: Modern CLI Development Tools](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | Use CLI-based AI programming tools to accelerate development and debugging | ✅ |
|
||||
| [Backend 7: Integrating Payment Systems like Stripe](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | Integrate payment systems, complete payment links and basic settlement processes | 🚧 |
|
||||
| [Assignment 1: Build Your First Modern Application - Full Stack](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | Integrate frontend, backend and payment modules, complete production-ready full stack web application | 🚧 |
|
||||
| [Assignment 2: Modern Frontend Component Library + Trae Practice](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | Use modern frontend component libraries with Trae, independently complete login/registration and payment-capable products | 🚧 |
|
||||
| Chapter | Key Content | Status |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ | :----- |
|
||||
| [Backend 1: What is API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | Understand HTTP interfaces and request-response models | ✅ |
|
||||
| [Backend 2: From Database to Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | Implement databases and APIs on Supabase, connect data models with frontend pages | ✅ |
|
||||
| [Backend 3: AI-Assisted Interface Code and Documentation](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | Use LLM to help generate interface and database documentation and code | 🚧 |
|
||||
| [Backend 4: Git Workflow](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | Manage code in Git workflow, version control and collaboration | ✅ |
|
||||
| [Backend 5: Zeabur Deployment](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | Deploy applications to Zeabur for production | ✅ |
|
||||
| [Backend 6: Modern CLI Development Tools](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | Use CLI-based AI programming tools to accelerate development and debugging | ✅ |
|
||||
| [Backend 7: Integrating Payment Systems like Stripe](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | Integrate payment systems, complete payment links and basic settlement processes | 🚧 |
|
||||
| [Assignment 1: Build Your First Modern Application - Full Stack](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | Integrate frontend, backend and payment modules, complete production-ready full stack web application | 🚧 |
|
||||
| [Assignment 2: Modern Frontend Component Library + Trae Practice](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | Use modern frontend component libraries with Trae, independently complete login/registration and payment-capable products | 🚧 |
|
||||
|
||||
#### AI Capabilities Appendix
|
||||
|
||||
| Chapter | Key Content | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [AI 1: Dify Introduction and Knowledge Base Integration](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | Use Dify Workflow and basic RAG to build utility products | ✅ |
|
||||
| [AI 2: Learn to Query AI Dictionary and Integrate Multimodal APIs](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | Learn to find suitable models and APIs, integrate text, image and other multimodal capabilities | 🚧 |
|
||||
| Chapter | Key Content | Status |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- | :----- |
|
||||
| [AI 1: Dify Introduction and Knowledge Base Integration](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | Use Dify Workflow and basic RAG to build utility products | ✅ |
|
||||
| [AI 2: Learn to Query AI Dictionary and Integrate Multimodal APIs](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | Learn to find suitable models and APIs, integrate text, image and other multimodal capabilities | 🚧 |
|
||||
|
||||
### Stage 3: Advanced Developer
|
||||
|
||||
| Chapter | Key Content | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Advanced 1: MCP and ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | Extend IDE capabilities through MCP and Skills, integrate external services as tools | 🚧 |
|
||||
| [Advanced 2: Making Coding Tools Work for Long Periods](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | Design and configure long-running tasks, make Coding Tools more stable and reliable | 🚧 |
|
||||
| [Advanced 3: Multi-Platform Development: Building WeChat Mini Programs](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | Understand WeChat mini program ecosystem, complete a frontend mini program from official template to launch | ✅ |
|
||||
| [Advanced 4: Multi-Platform Development: Building WeChat Mini Programs - With Backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | Integrate databases and backend logic in mini programs, complete business loops | 🚧 |
|
||||
| [Advanced 5: Multi-Platform Development: Building Android Apps](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | Use Expo and other tools to complete Web/native integrated Android application development | 🚧 |
|
||||
| [Advanced 6: Multi-Platform Development: Building iOS Apps](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | Use Expo and other tools to complete Web/native integrated iOS application development | 🚧 |
|
||||
| [Advanced 7: Building Your Personal Website and Academic Blog](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | From selection, building to deployment, create a permanent online homepage showcasing projects and academic achievements | 🚧 |
|
||||
| Chapter | Key Content | Status |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Advanced 1: MCP and ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | Extend IDE capabilities through MCP and Skills, integrate external services as tools | 🚧 |
|
||||
| [Advanced 2: Making Coding Tools Work for Long Periods](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | Design and configure long-running tasks, make Coding Tools more stable and reliable | 🚧 |
|
||||
| [Advanced 3: Multi-Platform Development: Building WeChat Mini Programs](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | Understand WeChat mini program ecosystem, complete a frontend mini program from official template to launch | ✅ |
|
||||
| [Advanced 4: Multi-Platform Development: Building WeChat Mini Programs - With Backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | Integrate databases and backend logic in mini programs, complete business loops | 🚧 |
|
||||
| [Advanced 5: Multi-Platform Development: Building Android Apps](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | Use Expo and other tools to complete Web/native integrated Android application development | 🚧 |
|
||||
| [Advanced 6: Multi-Platform Development: Building iOS Apps](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | Use Expo and other tools to complete Web/native integrated iOS application development | 🚧 |
|
||||
| [Advanced 7: Building Your Personal Website and Academic Blog](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | From selection, building to deployment, create a permanent online homepage showcasing projects and academic achievements | 🚧 |
|
||||
|
||||
#### AI Capabilities Appendix
|
||||
|
||||
| Chapter | Key Content | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| [Advanced AI 1: What is RAG and How It Works](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | Systematically understand RAG principles and common architectures | ✅ |
|
||||
| [Advanced AI 2: Intermediate/Advanced RAG and Workflow Orchestration with LangGraph](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | Use LangGraph and other tools to design multi-step workflows and intermediate/advanced RAG systems | 🚧 |
|
||||
| Chapter | Key Content | Status |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Advanced AI 1: What is RAG and How It Works](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | Systematically understand RAG principles and common architectures | ✅ |
|
||||
| [Advanced AI 2: Intermediate/Advanced RAG and Workflow Orchestration with LangGraph](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | Use LangGraph and other tools to design multi-step workflows and intermediate/advanced RAG systems | 🚧 |
|
||||
|
||||
## 🛠️ How to Learn
|
||||
|
||||
|
||||
+51
-51
@@ -11,7 +11,7 @@
|
||||
|
||||
# Easy-Vibe: Learn Vibe Coding from 0 to 1
|
||||
|
||||
### *Desde cero, aprendizaje basado en proyectos, construyendo tu primer producto de IA*
|
||||
### _Desde cero, aprendizaje basado en proyectos, construyendo tu primer producto de IA_
|
||||
|
||||
<p align="center">
|
||||
📌 <a href="https://datawhalechina.github.io/easy-vibe/">Comenzar a leer en línea (Start Reading Online)</a>
|
||||
@@ -78,79 +78,79 @@ Creemos que al dominar Vibe Coding y combinarlo con entrenamiento sistemático,
|
||||
|
||||
### 0. Jardín de Infancia
|
||||
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :--- | :--- | :--- |
|
||||
| [Prólogo: Mapa de aprendizaje](docs/zh-cn/stage-0/0.1-learning-map/index.md) | Guía general de la ruta de aprendizaje | ✅ |
|
||||
| [Principiante 1: La era de la IA, si puedes hablar puedes programar](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | Experimentar por primera vez las capacidades de programación con IA a través de casos como Snake | ✅ |
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Prólogo: Mapa de aprendizaje](docs/zh-cn/stage-0/0.1-learning-map/index.md) | Guía general de la ruta de aprendizaje | ✅ |
|
||||
| [Principiante 1: La era de la IA, si puedes hablar puedes programar](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | Experimentar por primera vez las capacidades de programación con IA a través de casos como Snake | ✅ |
|
||||
|
||||
### 1. Product Manager de IA
|
||||
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :--- | :--- | :--- |
|
||||
| [Principiante 2: Conociendo herramientas de IDE de IA](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | Aprender a usar el IDE, crear mini juegos localmente | ✅ |
|
||||
| [Principiante 3: Haz un prototipo tú mismo](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | Análisis de requisitos, generación de una sola página con IA, hasta generar prototipos de productos de múltiples páginas | ✅ |
|
||||
| [Principiante 4: Añade capacidades de IA al prototipo](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | Aprender a conectar capacidades comunes de IA (texto, imagen, video) | ✅ |
|
||||
| [Principiante 5: Proyecto completo práctico](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | Simular escenarios reales, aceptar feedback de usuarios para iterar, completar el proyecto | ✅ |
|
||||
| [Proyecto final: Haz un prototipo completo de aplicación web y preséntalo](docs/zh-cn/stage-1/1.5-final-project/index.md) | Implementar completamente la aplicación, mostrar los efectos de la aplicación | ✅ |
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Principiante 2: Conociendo herramientas de IDE de IA](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | Aprender a usar el IDE, crear mini juegos localmente | ✅ |
|
||||
| [Principiante 3: Haz un prototipo tú mismo](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | Análisis de requisitos, generación de una sola página con IA, hasta generar prototipos de productos de múltiples páginas | ✅ |
|
||||
| [Principiante 4: Añade capacidades de IA al prototipo](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | Aprender a conectar capacidades comunes de IA (texto, imagen, video) | ✅ |
|
||||
| [Principiante 5: Proyecto completo práctico](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | Simular escenarios reales, aceptar feedback de usuarios para iterar, completar el proyecto | ✅ |
|
||||
| [Proyecto final: Haz un prototipo completo de aplicación web y preséntalo](docs/zh-cn/stage-1/1.5-final-project/index.md) | Implementar completamente la aplicación, mostrar los efectos de la aplicación | ✅ |
|
||||
|
||||
#### Apéndices
|
||||
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :--- | :--- | :--- |
|
||||
| [Apéndice A: Suplemento de pensamiento de producto](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | Marcos de pensamiento necesarios para crear un producto de cero a uno | ✅ |
|
||||
| [Apéndice B: Errores comunes y soluciones](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Errores comunes en Vibe Coding y métodos de solución de problemas | ✅ |
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :----------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------- | :----- |
|
||||
| [Apéndice A: Suplemento de pensamiento de producto](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | Marcos de pensamiento necesarios para crear un producto de cero a uno | ✅ |
|
||||
| [Apéndice B: Errores comunes y soluciones](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Errores comunes en Vibe Coding y métodos de solución de problemas | ✅ |
|
||||
|
||||
### 2. Ingeniero de Desarrollo de Nivel Principiante-Intermedio
|
||||
|
||||
#### Parte Frontend
|
||||
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :--- | :--- | :--- |
|
||||
| [Frontend 0: Usar lovart para generar activos](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | Usar lovart para generar por lotes activos visuales como personajes y escenas, proporcionando base de activos para diseño UI y desarrollo frontend | 🚧 |
|
||||
| [Frontend 1: Introducción a Figma y MasterGo](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | Usar herramientas de diseño para organizar arquitectura de información y estructura de páginas, preparando la base para implementación frontend | 🚧 |
|
||||
| [Frontend 2: Construir la primera aplicación moderna - Diseño UI](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | Completar interfaz basada en componentes basada en diseños,实现 la primera ruta de diseño a código | 🚧 |
|
||||
| [Frontend 3: Referencia de especificaciones de diseño UI y diseño UI de múltiples productos](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | Expandir interfaces de múltiples productos en torno a un visual principal unificado, practicar capacidades de diseño sistemático | 🚧 |
|
||||
| [Frontend 4: Hagamos los retratos de Hogwarts juntos](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | Crear una aplicación frontend con capacidades de IA integradas de 0 a 1, conectando diseño y desarrollo | ✅ |
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Frontend 0: Usar lovart para generar activos](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | Usar lovart para generar por lotes activos visuales como personajes y escenas, proporcionando base de activos para diseño UI y desarrollo frontend | 🚧 |
|
||||
| [Frontend 1: Introducción a Figma y MasterGo](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | Usar herramientas de diseño para organizar arquitectura de información y estructura de páginas, preparando la base para implementación frontend | 🚧 |
|
||||
| [Frontend 2: Construir la primera aplicación moderna - Diseño UI](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | Completar interfaz basada en componentes basada en diseños,实现 la primera ruta de diseño a código | 🚧 |
|
||||
| [Frontend 3: Referencia de especificaciones de diseño UI y diseño UI de múltiples productos](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | Expandir interfaces de múltiples productos en torno a un visual principal unificado, practicar capacidades de diseño sistemático | 🚧 |
|
||||
| [Frontend 4: Hagamos los retratos de Hogwarts juntos](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | Crear una aplicación frontend con capacidades de IA integradas de 0 a 1, conectando diseño y desarrollo | ✅ |
|
||||
|
||||
#### Parte Backend y Full Stack
|
||||
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :--- | :--- | :--- |
|
||||
| [Backend 1: Qué es una API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | Entender interfaces HTTP y modelos de solicitud-respuesta, preparándose para integración backend y coordinación | ✅ |
|
||||
| [Backend 2: De bases de datos a Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | Implementar bases de datos y APIs en Supabase, conectando modelos de datos con páginas frontend | ✅ |
|
||||
| [Backend 3: LLM asistiendo en la escritura de código de interfaz y documentación](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | Usar LLM para ayudar a generar documentación y código para interfaces y bases de datos,实现 un backend legible y testeable | 🚧 |
|
||||
| [Backend 4: Flujo de trabajo Git](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | Gestionar código en flujo de trabajo Git, realizar control de versiones y colaboración | ✅ |
|
||||
| [Backend 5: Despliegue Zeabur](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | Desplegar la aplicación en Zeabur para ponerla en línea | ✅ |
|
||||
| [Backend 6: Herramientas de desarrollo CLI modernas](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | Usar herramientas de programación IA tipo CLI para acelerar desarrollo y depuración, formando flujo de ingeniería personal | ✅ |
|
||||
| [Backend 7: Cómo integrar sistemas de pago como Stripe](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | Conectar sistemas de pago, completar flujo de pago y proceso básico de liquidación | 🚧 |
|
||||
| [Proyecto final 1: Construir la primera aplicación moderna - Aplicación full stack](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | Integrar frontend, backend y módulos de pago, completar aplicación web full stack lista para producción | 🚧 |
|
||||
| [Proyecto final 2: Biblioteca de componentes frontend moderna + Trae práctico](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | Usar biblioteca de componentes frontend moderna y Trae, completar independientemente un producto con registro/login y soporte de pago | 🚧 |
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ | :----- |
|
||||
| [Backend 1: Qué es una API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | Entender interfaces HTTP y modelos de solicitud-respuesta, preparándose para integración backend y coordinación | ✅ |
|
||||
| [Backend 2: De bases de datos a Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | Implementar bases de datos y APIs en Supabase, conectando modelos de datos con páginas frontend | ✅ |
|
||||
| [Backend 3: LLM asistiendo en la escritura de código de interfaz y documentación](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | Usar LLM para ayudar a generar documentación y código para interfaces y bases de datos,实现 un backend legible y testeable | 🚧 |
|
||||
| [Backend 4: Flujo de trabajo Git](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | Gestionar código en flujo de trabajo Git, realizar control de versiones y colaboración | ✅ |
|
||||
| [Backend 5: Despliegue Zeabur](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | Desplegar la aplicación en Zeabur para ponerla en línea | ✅ |
|
||||
| [Backend 6: Herramientas de desarrollo CLI modernas](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | Usar herramientas de programación IA tipo CLI para acelerar desarrollo y depuración, formando flujo de ingeniería personal | ✅ |
|
||||
| [Backend 7: Cómo integrar sistemas de pago como Stripe](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | Conectar sistemas de pago, completar flujo de pago y proceso básico de liquidación | 🚧 |
|
||||
| [Proyecto final 1: Construir la primera aplicación moderna - Aplicación full stack](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | Integrar frontend, backend y módulos de pago, completar aplicación web full stack lista para producción | 🚧 |
|
||||
| [Proyecto final 2: Biblioteca de componentes frontend moderna + Trae práctico](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | Usar biblioteca de componentes frontend moderna y Trae, completar independientemente un producto con registro/login y soporte de pago | 🚧 |
|
||||
|
||||
#### Apéndice de Capacidades de IA
|
||||
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :--- | :--- | :--- |
|
||||
| [IA 1: Introducción a Dify e integración de base de conocimientos](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | Usar Dify Workflow y RAG básico para construir productos tipo herramientas, creando ejemplo para actualizaciones futuras de aplicaciones | ✅ |
|
||||
| [IA 2: Aprender a consultar diccionario de IA e integrar APIs multimodales](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | Aprender a buscar modelos y APIs apropiados, integrar capacidades multimodales como texto e imagen en productos | 🚧 |
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [IA 1: Introducción a Dify e integración de base de conocimientos](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | Usar Dify Workflow y RAG básico para construir productos tipo herramientas, creando ejemplo para actualizaciones futuras de aplicaciones | ✅ |
|
||||
| [IA 2: Aprender a consultar diccionario de IA e integrar APIs multimodales](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | Aprender a buscar modelos y APIs apropiados, integrar capacidades multimodales como texto e imagen en productos | 🚧 |
|
||||
|
||||
### 3. Ingeniero de Desarrollo Avanzado
|
||||
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :--- | :--- | :--- |
|
||||
| [Avanzado 1: MCP y ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | Extender capacidades del IDE a través de MCP y Skills, conectar servicios externos como herramientas | 🚧 |
|
||||
| [Avanzado 2: Cómo hacer que Coding Tools funcione durante mucho tiempo](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | Diseñar y configurar tareas de ejecución prolongada, hacer Coding Tools más estable y confiable | 🚧 |
|
||||
| [Avanzado 3: Desarrollo multiplataforma: Cómo construir mini programas de WeChat](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | Entender el ecosistema de mini programas de WeChat, completar un mini programa frontend desde plantilla oficial hasta lanzamiento | ✅ |
|
||||
| [Avanzado 4: Desarrollo multiplataforma: Cómo construir mini programas de WeChat - Incluyendo backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | Integrar base de datos y lógica backend en mini programas,实现 ciclo completo de negocio | 🚧 |
|
||||
| [Avanzado 5: Desarrollo multiplataforma: Cómo construir aplicaciones Android](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | Usar herramientas como Expo, completar desarrollo de aplicaciones Android integrando Web/nativo | 🚧 |
|
||||
| [Avanzado 6: Desarrollo multiplataforma: Cómo construir aplicaciones iOS](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | Usar herramientas como Expo, completar desarrollo de aplicaciones iOS integrando Web/nativo | 🚧 |
|
||||
| [Avanzado 7: Cómo construir tu propio sitio web personal y blog académico](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | Desde selección, construcción hasta despliegue, construir homepage en línea a largo plazo mostrando proyectos personales y logros académicos | 🚧 |
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Avanzado 1: MCP y ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | Extender capacidades del IDE a través de MCP y Skills, conectar servicios externos como herramientas | 🚧 |
|
||||
| [Avanzado 2: Cómo hacer que Coding Tools funcione durante mucho tiempo](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | Diseñar y configurar tareas de ejecución prolongada, hacer Coding Tools más estable y confiable | 🚧 |
|
||||
| [Avanzado 3: Desarrollo multiplataforma: Cómo construir mini programas de WeChat](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | Entender el ecosistema de mini programas de WeChat, completar un mini programa frontend desde plantilla oficial hasta lanzamiento | ✅ |
|
||||
| [Avanzado 4: Desarrollo multiplataforma: Cómo construir mini programas de WeChat - Incluyendo backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | Integrar base de datos y lógica backend en mini programas,实现 ciclo completo de negocio | 🚧 |
|
||||
| [Avanzado 5: Desarrollo multiplataforma: Cómo construir aplicaciones Android](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | Usar herramientas como Expo, completar desarrollo de aplicaciones Android integrando Web/nativo | 🚧 |
|
||||
| [Avanzado 6: Desarrollo multiplataforma: Cómo construir aplicaciones iOS](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | Usar herramientas como Expo, completar desarrollo de aplicaciones iOS integrando Web/nativo | 🚧 |
|
||||
| [Avanzado 7: Cómo construir tu propio sitio web personal y blog académico](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | Desde selección, construcción hasta despliegue, construir homepage en línea a largo plazo mostrando proyectos personales y logros académicos | 🚧 |
|
||||
|
||||
#### Apéndice de Capacidades de IA
|
||||
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :--- | :--- | :--- |
|
||||
| [IA Avanzada 1: Qué es RAG y cómo funciona](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | Entender sistemáticamente principios de RAG y arquitecturas comunes, proporcionar base de recuperación de conocimiento para aplicaciones complejas | ✅ |
|
||||
| [IA Avanzada 2: RAG intermedio-avanzado y orquestación de flujos de trabajo: Ejemplo con LangGraph](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | Usar herramientas como LangGraph para diseñar flujos de trabajo multipaso y sistemas RAG intermedio-avanzados | 🚧 |
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [IA Avanzada 1: Qué es RAG y cómo funciona](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | Entender sistemáticamente principios de RAG y arquitecturas comunes, proporcionar base de recuperación de conocimiento para aplicaciones complejas | ✅ |
|
||||
| [IA Avanzada 2: RAG intermedio-avanzado y orquestación de flujos de trabajo: Ejemplo con LangGraph](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | Usar herramientas como LangGraph para diseñar flujos de trabajo multipaso y sistemas RAG intermedio-avanzados | 🚧 |
|
||||
|
||||
## 🛠️ Cómo Aprender
|
||||
|
||||
|
||||
+51
-51
@@ -11,7 +11,7 @@
|
||||
|
||||
# Easy-Vibe: Learn Vibe Coding from 0 to 1
|
||||
|
||||
### *De zéro, apprentissage par projet, construire votre premier produit IA*
|
||||
### _De zéro, apprentissage par projet, construire votre premier produit IA_
|
||||
|
||||
<p align="center">
|
||||
📌 <a href="https://datawhalechina.github.io/easy-vibe/">Commencer la lecture en ligne (Start Reading Online)</a>
|
||||
@@ -78,79 +78,79 @@ Nous croyons qu'en maîtrisant Vibe Coding et en le combinant avec un entraînem
|
||||
|
||||
### 0. Maternelle
|
||||
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :--- | :--- | :--- |
|
||||
| [Préface : Carte d'apprentissage](docs/zh-cn/stage-0/0.1-learning-map/index.md) | Guide général du parcours d'apprentissage | ✅ |
|
||||
| [Débutant 1 : L'ère de l'IA, savoir parler c'est savoir programmer](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | Découvrir pour la première fois les capacités de programmation IA à travers des cas comme Snake | ✅ |
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Préface : Carte d'apprentissage](docs/zh-cn/stage-0/0.1-learning-map/index.md) | Guide général du parcours d'apprentissage | ✅ |
|
||||
| [Débutant 1 : L'ère de l'IA, savoir parler c'est savoir programmer](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | Découvrir pour la première fois les capacités de programmation IA à travers des cas comme Snake | ✅ |
|
||||
|
||||
### 1. Product Manager IA
|
||||
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :--- | :--- | :--- |
|
||||
| [Débutant 2 : Découvrir les outils IDE IA](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | Apprendre à utiliser l'IDE, créer des mini-jeux localement | ✅ |
|
||||
| [Débutant 3 : Créer un prototype soi-même](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | Analyse des besoins, génération d'une seule page par l'IA, jusqu'à générer des prototypes de produits multipages | ✅ |
|
||||
| [Débutant 4 : Ajouter des capacités IA au prototype](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | Apprendre à connecter des capacités IA courantes (texte, image, vidéo) | ✅ |
|
||||
| [Débutant 5 : Projet complet pratique](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | Simuler des scénarios réels, accepter les feedbacks des utilisateurs pour itérer, compléter le projet | ✅ |
|
||||
| [Projet final : Créer un prototype complet d'application web et le présenter](docs/zh-cn/stage-1/1.5-final-project/index.md) | Implémenter complètement l'application, présenter les effets de l'application | ✅ |
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Débutant 2 : Découvrir les outils IDE IA](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | Apprendre à utiliser l'IDE, créer des mini-jeux localement | ✅ |
|
||||
| [Débutant 3 : Créer un prototype soi-même](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | Analyse des besoins, génération d'une seule page par l'IA, jusqu'à générer des prototypes de produits multipages | ✅ |
|
||||
| [Débutant 4 : Ajouter des capacités IA au prototype](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | Apprendre à connecter des capacités IA courantes (texte, image, vidéo) | ✅ |
|
||||
| [Débutant 5 : Projet complet pratique](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | Simuler des scénarios réels, accepter les feedbacks des utilisateurs pour itérer, compléter le projet | ✅ |
|
||||
| [Projet final : Créer un prototype complet d'application web et le présenter](docs/zh-cn/stage-1/1.5-final-project/index.md) | Implémenter complètement l'application, présenter les effets de l'application | ✅ |
|
||||
|
||||
#### Annexes
|
||||
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :--- | :--- | :--- |
|
||||
| [Annexe A : Complément de pensée produit](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | Cadres de pensée nécessaires pour créer un produit de zéro à un | ✅ |
|
||||
| [Annexe B : Erreurs courantes et solutions](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Erreurs courantes en Vibe Coding et méthodes de dépannage | ✅ |
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | :----- |
|
||||
| [Annexe A : Complément de pensée produit](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | Cadres de pensée nécessaires pour créer un produit de zéro à un | ✅ |
|
||||
| [Annexe B : Erreurs courantes et solutions](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Erreurs courantes en Vibe Coding et méthodes de dépannage | ✅ |
|
||||
|
||||
### 2. Ingénieur de Développement Niveau Débutant-Intermédiaire
|
||||
|
||||
#### Partie Frontend
|
||||
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :--- | :--- | :--- |
|
||||
| [Frontend 0 : Utiliser lovart pour générer des assets](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | Utiliser lovart pour générer en lot des assets visuels comme des personnages et des scènes, fournissant une base d'assets pour la conception UI et le développement frontend | 🚧 |
|
||||
| [Frontend 1 : Introduction à Figma et MasterGo](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | Utiliser des outils de conception pour organiser l'architecture de l'information et la structure des pages, préparant la base pour l'implémentation frontend | 🚧 |
|
||||
| [Frontend 2 : Construire la première application moderne - Design UI](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | Compléter une interface basée sur des composants à partir de maquettes, réaliser le premier parcours du design au code | 🚧 |
|
||||
| [Frontend 3 : Référence des spécifications de design UI et design UI multiproduit](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | Étendre des interfaces multiproduits autour d'une visuelle principale unifiée, pratiquer les capacités de design systématique | 🚧 |
|
||||
| [Frontend 4 : Créons ensemble les portraits de Poudlard](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | Créer une application frontend avec capacités IA intégrées de 0 à 1, connecter conception et développement | ✅ |
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Frontend 0 : Utiliser lovart pour générer des assets](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | Utiliser lovart pour générer en lot des assets visuels comme des personnages et des scènes, fournissant une base d'assets pour la conception UI et le développement frontend | 🚧 |
|
||||
| [Frontend 1 : Introduction à Figma et MasterGo](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | Utiliser des outils de conception pour organiser l'architecture de l'information et la structure des pages, préparant la base pour l'implémentation frontend | 🚧 |
|
||||
| [Frontend 2 : Construire la première application moderne - Design UI](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | Compléter une interface basée sur des composants à partir de maquettes, réaliser le premier parcours du design au code | 🚧 |
|
||||
| [Frontend 3 : Référence des spécifications de design UI et design UI multiproduit](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | Étendre des interfaces multiproduits autour d'une visuelle principale unifiée, pratiquer les capacités de design systématique | 🚧 |
|
||||
| [Frontend 4 : Créons ensemble les portraits de Poudlard](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | Créer une application frontend avec capacités IA intégrées de 0 à 1, connecter conception et développement | ✅ |
|
||||
|
||||
#### Partie Backend et Full Stack
|
||||
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :--- | :--- | :--- |
|
||||
| [Backend 1 : Qu'est-ce qu'une API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | Comprendre les interfaces HTTP et les modèles requête-réponse, se préparer à l'intégration backend et à la coordination | ✅ |
|
||||
| [Backend 2 : Des bases de données à Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) : Implémenter des bases de données et des APIs sur Supabase, connecter des modèles de données aux pages frontend | ✅ |
|
||||
| [Backend 3 : LLM assistant l'écriture de code d'interface et de documentation](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) : Utiliser le LLM pour aider à générer de la documentation et du code pour les interfaces et bases de données, réaliser un backend lisible et testable | 🚧 |
|
||||
| [Backend 4 : Flux de travail Git](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) : Gérer le code dans un flux de travail Git, effectuer le contrôle de version et la collaboration | ✅ |
|
||||
| [Backend 5 : Déploiement Zeabur](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) : Déployer l'application sur Zeabur pour la mettre en ligne | ✅ |
|
||||
| [Backend 6 : Outils de développement CLI modernes](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) : Utiliser des outils de programmation IA de type CLI pour accélérer le développement et le débogage, former un flux d'ingénierie personnel | ✅ |
|
||||
| [Backend 7 : Comment intégrer des systèmes de paiement comme Stripe](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) : Connecter des systèmes de paiement, compléter le flux de paiement et le processus de base de règlement | 🚧 |
|
||||
| [Projet final 1 : Construire la première application moderne - Application full stack](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) : Intégrer frontend, backend et modules de paiement, compléter une application web full stack prête pour la production | 🚧 |
|
||||
| [Projet final 2 : Bibliothèque de composants frontend moderne + Trae pratique](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) : Utiliser une bibliothèque de composants frontend moderne et Trae, compléter indépendamment un produit avec inscription/connexion et support de paiement | 🚧 |
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Backend 1 : Qu'est-ce qu'une API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | Comprendre les interfaces HTTP et les modèles requête-réponse, se préparer à l'intégration backend et à la coordination | ✅ |
|
||||
| [Backend 2 : Des bases de données à Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) : Implémenter des bases de données et des APIs sur Supabase, connecter des modèles de données aux pages frontend | ✅ |
|
||||
| [Backend 3 : LLM assistant l'écriture de code d'interface et de documentation](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) : Utiliser le LLM pour aider à générer de la documentation et du code pour les interfaces et bases de données, réaliser un backend lisible et testable | 🚧 |
|
||||
| [Backend 4 : Flux de travail Git](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) : Gérer le code dans un flux de travail Git, effectuer le contrôle de version et la collaboration | ✅ |
|
||||
| [Backend 5 : Déploiement Zeabur](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) : Déployer l'application sur Zeabur pour la mettre en ligne | ✅ |
|
||||
| [Backend 6 : Outils de développement CLI modernes](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) : Utiliser des outils de programmation IA de type CLI pour accélérer le développement et le débogage, former un flux d'ingénierie personnel | ✅ |
|
||||
| [Backend 7 : Comment intégrer des systèmes de paiement comme Stripe](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) : Connecter des systèmes de paiement, compléter le flux de paiement et le processus de base de règlement | 🚧 |
|
||||
| [Projet final 1 : Construire la première application moderne - Application full stack](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) : Intégrer frontend, backend et modules de paiement, compléter une application web full stack prête pour la production | 🚧 |
|
||||
| [Projet final 2 : Bibliothèque de composants frontend moderne + Trae pratique](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) : Utiliser une bibliothèque de composants frontend moderne et Trae, compléter indépendamment un produit avec inscription/connexion et support de paiement | 🚧 |
|
||||
|
||||
#### Annexe des Capacités IA
|
||||
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :--- | :--- | :--- |
|
||||
| [IA 1 : Introduction à Dify et intégration de base de connaissances](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) : Utiliser Dify Workflow et le RAG de base pour construire des produits de type outils, créant un exemple pour les futures mises à jour d'applications | ✅ |
|
||||
| [IA 2 : Apprendre à consulter le dictionnaire IA et intégrer des APIs multimodales](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) : Apprendre à chercher des modèles et APIs appropriés, intégrer des capacités multimodales comme le texte et l'image dans des produits | 🚧 |
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------- | :----- |
|
||||
| [IA 1 : Introduction à Dify et intégration de base de connaissances](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) : Utiliser Dify Workflow et le RAG de base pour construire des produits de type outils, créant un exemple pour les futures mises à jour d'applications | ✅ |
|
||||
| [IA 2 : Apprendre à consulter le dictionnaire IA et intégrer des APIs multimodales](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) : Apprendre à chercher des modèles et APIs appropriés, intégrer des capacités multimodales comme le texte et l'image dans des produits | 🚧 |
|
||||
|
||||
### 3. Ingénieur de Développement Avancé
|
||||
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :--- | :--- | :--- |
|
||||
| [Avancé 1 : MCP et ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) : Étendre les capacités de l'IDE via MCP et Skills, connecter des services externes comme outils | 🚧 |
|
||||
| [Avancé 2 : Comment faire fonctionner Coding Tools longtemps](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) : Concevoir et configurer des tâches à longue exécution, rendre Coding Tools plus stable et fiable | 🚧 |
|
||||
| [Avancé 3 : Développement multiplateforme : Comment construire des mini-programmes WeChat](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) : Comprendre l'écosystème des mini-programmes WeChat, compléter un mini-programme frontend du modèle officiel au lancement | ✅ |
|
||||
| [Avancé 4 : Développement multiplateforme : Comment construire des mini-programmes WeChat - Y compris backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : Intégrer une base de données et une logique backend dans les mini-programmes, réaliser un cycle d'activité complet | 🚧 |
|
||||
| [Avancé 5 : Développement multiplateforme : Comment construire des applications Android](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) : Utiliser des outils comme Expo, compléter le développement d'applications Android intégrant Web natif | 🚧 |
|
||||
| [Avancé 6 : Développement multiplateforme : Comment construire des applications iOS](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) : Utiliser des outils comme Expo, compléter le développement d'applications iOS intégrant Web natif | 🚧 |
|
||||
| [Avancé 7 : Comment construire son propre site web personnel et blog académique](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) : Du choix, de la construction au déploiement, construire une homepage en ligne à long terme présentant des projets personnels et des réalisations académiques | 🚧 |
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------- | :----- |
|
||||
| [Avancé 1 : MCP et ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) : Étendre les capacités de l'IDE via MCP et Skills, connecter des services externes comme outils | 🚧 |
|
||||
| [Avancé 2 : Comment faire fonctionner Coding Tools longtemps](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) : Concevoir et configurer des tâches à longue exécution, rendre Coding Tools plus stable et fiable | 🚧 |
|
||||
| [Avancé 3 : Développement multiplateforme : Comment construire des mini-programmes WeChat](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) : Comprendre l'écosystème des mini-programmes WeChat, compléter un mini-programme frontend du modèle officiel au lancement | ✅ |
|
||||
| [Avancé 4 : Développement multiplateforme : Comment construire des mini-programmes WeChat - Y compris backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : Intégrer une base de données et une logique backend dans les mini-programmes, réaliser un cycle d'activité complet | 🚧 |
|
||||
| [Avancé 5 : Développement multiplateforme : Comment construire des applications Android](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) : Utiliser des outils comme Expo, compléter le développement d'applications Android intégrant Web natif | 🚧 |
|
||||
| [Avancé 6 : Développement multiplateforme : Comment construire des applications iOS](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) : Utiliser des outils comme Expo, compléter le développement d'applications iOS intégrant Web natif | 🚧 |
|
||||
| [Avancé 7 : Comment construire son propre site web personnel et blog académique](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) : Du choix, de la construction au déploiement, construire une homepage en ligne à long terme présentant des projets personnels et des réalisations académiques | 🚧 |
|
||||
|
||||
#### Annexe des Capacités IA
|
||||
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :--- | :--- | :--- |
|
||||
| [IA Avancée 1 : Qu'est-ce que RAG et comment il fonctionne](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) : Comprendre systématiquement les principes du RAG et les architectures courantes, fournir une base de récupération de connaissances pour des applications complexes | ✅ |
|
||||
| [IA Avancée 2 : RAG intermédiaire-avancé et orchestration de workflows : Exemple avec LangGraph](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) : Utiliser des outils comme LangGraph pour concevoir des workflows multi-étapes et des systèmes RAG intermédiaire-avancés | 🚧 |
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------- | :----- |
|
||||
| [IA Avancée 1 : Qu'est-ce que RAG et comment il fonctionne](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) : Comprendre systématiquement les principes du RAG et les architectures courantes, fournir une base de récupération de connaissances pour des applications complexes | ✅ |
|
||||
| [IA Avancée 2 : RAG intermédiaire-avancé et orchestration de workflows : Exemple avec LangGraph](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) : Utiliser des outils comme LangGraph pour concevoir des workflows multi-étapes et des systèmes RAG intermédiaire-avancés | 🚧 |
|
||||
|
||||
## 🛠️ Comment Apprendre
|
||||
|
||||
|
||||
+51
-51
@@ -11,7 +11,7 @@
|
||||
|
||||
# Easy-Vibe: Learn Vibe Coding from 0 to 1
|
||||
|
||||
### *ゼロから、プロジェクトベース学習、最初の AI 製品を構築*
|
||||
### _ゼロから、プロジェクトベース学習、最初の AI 製品を構築_
|
||||
|
||||
<p align="center">
|
||||
📌 <a href="https://datawhalechina.github.io/easy-vibe/">オンラインで読み始める (Start Reading Online)</a>
|
||||
@@ -78,79 +78,79 @@ AI でコードを書こうとしてエラーが続き、諦めかけたくな
|
||||
|
||||
### 零、幼児園
|
||||
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [前書:学習マップ](docs/zh-cn/stage-0/0.1-learning-map/index.md) | 全体学習パスのガイド | ✅ |
|
||||
| [初級一:AI 時代、話せるだけでプログラミングできる](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | スネークゲームなどの事例を通じて AI プログラミングの能力を初めて体験 | ✅ |
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :----------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------- | :--- |
|
||||
| [前書:学習マップ](docs/zh-cn/stage-0/0.1-learning-map/index.md) | 全体学習パスのガイド | ✅ |
|
||||
| [初級一:AI 時代、話せるだけでプログラミングできる](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | スネークゲームなどの事例を通じて AI プログラミングの能力を初めて体験 | ✅ |
|
||||
|
||||
### 一、AI プロダクトマネージャー
|
||||
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [初級二:AI IDE ツールを知る](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | IDE の使用方法を学び、ローカルでミニゲームを作成 | ✅ |
|
||||
| [初級三:手を動かしてプロトタイプを作る](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | ニーズ分析、AI による単一ページ生成、複数ページプロダクトプロトタイプの生成へ | ✅ |
|
||||
| [初級四:プロトタイプに AI 能力を追加](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | 一般的な AI 能力(テキスト、画像、動画)の接続方法を習得 | ✅ |
|
||||
| [初級五:完全プロジェクト実戦](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 実際のシーンをシミュレート、ユーザーフィードバックを受け反復改良、プロジェクトを完全に | ✅ |
|
||||
| [大課題:完全な Web アプリケーションプロトタイプを作成し展示](docs/zh-cn/stage-1/1.5-final-project/index.md) | アプリケーションを完全に実装し、アプリケーション効果を展示 | ✅ |
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :----------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | :--- |
|
||||
| [初級二:AI IDE ツールを知る](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | IDE の使用方法を学び、ローカルでミニゲームを作成 | ✅ |
|
||||
| [初級三:手を動かしてプロトタイプを作る](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | ニーズ分析、AI による単一ページ生成、複数ページプロダクトプロトタイプの生成へ | ✅ |
|
||||
| [初級四:プロトタイプに AI 能力を追加](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | 一般的な AI 能力(テキスト、画像、動画)の接続方法を習得 | ✅ |
|
||||
| [初級五:完全プロジェクト実戦](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 実際のシーンをシミュレート、ユーザーフィードバックを受け反復改良、プロジェクトを完全に | ✅ |
|
||||
| [大課題:完全な Web アプリケーションプロトタイプを作成し展示](docs/zh-cn/stage-1/1.5-final-project/index.md) | アプリケーションを完全に実装し、アプリケーション効果を展示 | ✅ |
|
||||
|
||||
#### 付録
|
||||
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [付録 A:プロダクト思考補充](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | ゼロからイチまでプロダクトを作る際に必要な思考フレームワーク | ✅ |
|
||||
| [付録 B:一般的なエラーと解決策](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Vibe Coding における一般的なエラーとトラブルシューティング方法 | ✅ |
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :------------------------------------------------------------------------------------- | :------------------------------------------------------------- | :--- |
|
||||
| [付録 A:プロダクト思考補充](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | ゼロからイチまでプロダクトを作る際に必要な思考フレームワーク | ✅ |
|
||||
| [付録 B:一般的なエラーと解決策](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Vibe Coding における一般的なエラーとトラブルシューティング方法 | ✅ |
|
||||
|
||||
### 二、初中級開発エンジニア
|
||||
|
||||
#### フロントエンド部分
|
||||
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [フロントエンド零:lovart を使用して素材を生成](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | lovart を使ってキャラクター、シーンなどの視覚素材を一括生成し、UI デザインとフロントエンド開発に素材基盤を提供 | 🚧 |
|
||||
| [フロントエンド一:Figma と MasterGo 入門](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | デザインツールを使って情報アーキテクチャとページ構造を整理し、フロントエンド実装の基礎を築く | 🚧 |
|
||||
| [フロントエンド二:最初のモダンアプリケーションを構築 - UI デザイン](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | デザイン画に基づいてコンポーネント化インターフェースを完成させ、デザインからコードへの最初のパスを実現 | 🚧 |
|
||||
| [フロントエンド三:UI デザイン仕様とマルチプロダクト UI デザインを参照](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 統一されたメインビジュアルを中心にマルチプロダクトインターフェースを拡張し、体系的デザイン能力を練習 | 🚧 |
|
||||
| [フロントエンド四:一緒にハリー・ポッターの肖像画を作ろう](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | 0 から 1 まで AI 能力を統合したフロントエンドアプリケーションを作成し、デザインと開発を接続 | ✅ |
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------- | :--- |
|
||||
| [フロントエンド零:lovart を使用して素材を生成](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | lovart を使ってキャラクター、シーンなどの視覚素材を一括生成し、UI デザインとフロントエンド開発に素材基盤を提供 | 🚧 |
|
||||
| [フロントエンド一:Figma と MasterGo 入門](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | デザインツールを使って情報アーキテクチャとページ構造を整理し、フロントエンド実装の基礎を築く | 🚧 |
|
||||
| [フロントエンド二:最初のモダンアプリケーションを構築 - UI デザイン](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | デザイン画に基づいてコンポーネント化インターフェースを完成させ、デザインからコードへの最初のパスを実現 | 🚧 |
|
||||
| [フロントエンド三:UI デザイン仕様とマルチプロダクト UI デザインを参照](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 統一されたメインビジュアルを中心にマルチプロダクトインターフェースを拡張し、体系的デザイン能力を練習 | 🚧 |
|
||||
| [フロントエンド四:一緒にハリー・ポッターの肖像画を作ろう](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | 0 から 1 まで AI 能力を統合したフロントエンドアプリケーションを作成し、デザインと開発を接続 | ✅ |
|
||||
|
||||
#### バックエンドとフルスタック部分
|
||||
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [バックエンド一:API とは](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | HTTP インターフェースとリクエスト・レスポンスモデルを理解し、バックエンド統合と連携調整の準備 | ✅ |
|
||||
| [バックエンド二:データベースから Supabase へ](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | Supabase 上でデータベースと API を実装し、データモデルとフロントエンドページを接続 | ✅ |
|
||||
| [バックエンド三:大規模言語モデル支援によるインターフェースコードとドキュメント作成](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | 大規模言語モデルを使ってインターフェースとデータベースのドキュメントおよびコードの生成を支援し、読みやすくテスト可能なバックエンドを実現 | 🚧 |
|
||||
| [バックエンド四:Git ワークフロー](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | Git ワークフローでコードを管理し、バージョン管理とコラボレーションを行う | ✅ |
|
||||
| [バックエンド五:Zeabur デプロイ](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | アプリケーションを Zeabur にデプロイしてオンライン化 | ✅ |
|
||||
| [バックエンド六:モダン CLI 開発ツール](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | CLI 系 AI プログラミングツールを使用して開発とデバッグを加速し、個人のエンジニアリングワークフローを形成 | ✅ |
|
||||
| [バックエンド七:Stripe などの課金システムの統合方法](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 決済システムを接続し、課金パスと基本決済フローを完成 | 🚧 |
|
||||
| [大課題 1:最初のモダンアプリケーションを構築 - フルスタックアプリ](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | フロントエンド、バックエンド、決済モジュールを統合し、オンライン可能なフルスタック Web アプリケーションを完成 | 🚧 |
|
||||
| [大課題 2:モダンフロントエンドコンポーネントライブラリ + Trae 実戦](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | モダンフロントエンドコンポーネントライブラリと Trae を使用し、ログイン・登録可能で課金対応のプロダクトを独立完成 | 🚧 |
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | :--- |
|
||||
| [バックエンド一:API とは](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | HTTP インターフェースとリクエスト・レスポンスモデルを理解し、バックエンド統合と連携調整の準備 | ✅ |
|
||||
| [バックエンド二:データベースから Supabase へ](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | Supabase 上でデータベースと API を実装し、データモデルとフロントエンドページを接続 | ✅ |
|
||||
| [バックエンド三:大規模言語モデル支援によるインターフェースコードとドキュメント作成](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | 大規模言語モデルを使ってインターフェースとデータベースのドキュメントおよびコードの生成を支援し、読みやすくテスト可能なバックエンドを実現 | 🚧 |
|
||||
| [バックエンド四:Git ワークフロー](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | Git ワークフローでコードを管理し、バージョン管理とコラボレーションを行う | ✅ |
|
||||
| [バックエンド五:Zeabur デプロイ](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | アプリケーションを Zeabur にデプロイしてオンライン化 | ✅ |
|
||||
| [バックエンド六:モダン CLI 開発ツール](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | CLI 系 AI プログラミングツールを使用して開発とデバッグを加速し、個人のエンジニアリングワークフローを形成 | ✅ |
|
||||
| [バックエンド七:Stripe などの課金システムの統合方法](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 決済システムを接続し、課金パスと基本決済フローを完成 | 🚧 |
|
||||
| [大課題 1:最初のモダンアプリケーションを構築 - フルスタックアプリ](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | フロントエンド、バックエンド、決済モジュールを統合し、オンライン可能なフルスタック Web アプリケーションを完成 | 🚧 |
|
||||
| [大課題 2:モダンフロントエンドコンポーネントライブラリ + Trae 実戦](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | モダンフロントエンドコンポーネントライブラリと Trae を使用し、ログイン・登録可能で課金対応のプロダクトを独立完成 | 🚧 |
|
||||
|
||||
#### AI 能力付録
|
||||
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [ AI 一:Dify 入門とナレッジベース統合](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | Dify Workflow と基本 RAG を使ってツール系プロダクトを構築し、以降のアプリケーションアップグレードのモデルケースを作成 | ✅ |
|
||||
| [ AI 二:AI 辞典の検索とマルチモーダル API 統合を学ぶ](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 適切なモデルと API を検索する方法を学び、テキスト、画像などのマルチモーダル能力をプロダクトに統合 | 🚧 |
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------- | :--- |
|
||||
| [ AI 一:Dify 入門とナレッジベース統合](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | Dify Workflow と基本 RAG を使ってツール系プロダクトを構築し、以降のアプリケーションアップグレードのモデルケースを作成 | ✅ |
|
||||
| [ AI 二:AI 辞典の検索とマルチモーダル API 統合を学ぶ](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 適切なモデルと API を検索する方法を学び、テキスト、画像などのマルチモーダル能力をプロダクトに統合 | 🚧 |
|
||||
|
||||
### 三、上級開発エンジニア
|
||||
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [上級一:MCP と ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | MCP と Skills を通じて IDE 能力を拡張し、外部サービスをツールとして接続 | 🚧 |
|
||||
| [上級二:Coding Tools を長時間動作させる方法](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 長時間動作するタスクを設計・設定し、Coding Tools をより安定して信頼性の高いものに | 🚧 |
|
||||
| [上級三:マルチプラットフォーム開発:WeChat ミニプログラムの構築方法](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | WeChat ミニプログラムのエコシステムを理解し、公式テンプレートからリリースまでフロントエンドミニプログラムを完成 | ✅ |
|
||||
| [上級四:マルチプラットフォーム開発:WeChat ミニプログラムの構築方法 - バックエンド含む](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | ミニプログラムにデータベースとバックエンドロジックを統合し、完全な業務クローズドループを実現 | 🚧 |
|
||||
| [上級五:マルチプラットフォーム開発:Android アプリの構築方法](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | Expo などのツールを使用し、Web/ネイティブ一体化の Android アプリ開発を完成 | 🚧 |
|
||||
| [上級六:マルチプラットフォーム開発:iOS アプリの構築方法](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | Expo などのツールを使用し、Web/ネイティブ一体化の iOS アプリ開発を完成 | 🚧 |
|
||||
| [上級七:自分専用の個人ウェブページと学術ブログの構築方法](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 選定、構築からデプロイまで、個人プロジェクトと学術成果を展示する長期的なオンラインホームページを構築 | 🚧 |
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | :--- |
|
||||
| [上級一:MCP と ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | MCP と Skills を通じて IDE 能力を拡張し、外部サービスをツールとして接続 | 🚧 |
|
||||
| [上級二:Coding Tools を長時間動作させる方法](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 長時間動作するタスクを設計・設定し、Coding Tools をより安定して信頼性の高いものに | 🚧 |
|
||||
| [上級三:マルチプラットフォーム開発:WeChat ミニプログラムの構築方法](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | WeChat ミニプログラムのエコシステムを理解し、公式テンプレートからリリースまでフロントエンドミニプログラムを完成 | ✅ |
|
||||
| [上級四:マルチプラットフォーム開発:WeChat ミニプログラムの構築方法 - バックエンド含む](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | ミニプログラムにデータベースとバックエンドロジックを統合し、完全な業務クローズドループを実現 | 🚧 |
|
||||
| [上級五:マルチプラットフォーム開発:Android アプリの構築方法](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | Expo などのツールを使用し、Web/ネイティブ一体化の Android アプリ開発を完成 | 🚧 |
|
||||
| [上級六:マルチプラットフォーム開発:iOS アプリの構築方法](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | Expo などのツールを使用し、Web/ネイティブ一体化の iOS アプリ開発を完成 | 🚧 |
|
||||
| [上級七:自分専用の個人ウェブページと学術ブログの構築方法](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 選定、構築からデプロイまで、個人プロジェクトと学術成果を展示する長期的なオンラインホームページを構築 | 🚧 |
|
||||
|
||||
#### AI 能力付録
|
||||
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [上級 AI 一:RAG とは、それがどのように動作するか](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | RAG 原理と一般的なアーキテクチャを体系的に理解し、複雑なアプリケーションに知識検索基盤を提供 | ✅ |
|
||||
| [上級 AI 二:中上級 RAG とワークフロー編成:LangGraph を例に](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | LangGraph などのツールを使用してマルチステップワークフローと中上級 RAG システムを設計 | 🚧 |
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | :--- |
|
||||
| [上級 AI 一:RAG とは、それがどのように動作するか](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | RAG 原理と一般的なアーキテクチャを体系的に理解し、複雑なアプリケーションに知識検索基盤を提供 | ✅ |
|
||||
| [上級 AI 二:中上級 RAG とワークフロー編成:LangGraph を例に](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | LangGraph などのツールを使用してマルチステップワークフローと中上級 RAG システムを設計 | 🚧 |
|
||||
|
||||
## 🛠️ 学習方法
|
||||
|
||||
|
||||
+51
-51
@@ -11,7 +11,7 @@
|
||||
|
||||
# Easy-Vibe: Learn Vibe Coding from 0 to 1
|
||||
|
||||
### *0부터, 프로젝트 기반 학습, 첫 번째 AI 제품 구축*
|
||||
### _0부터, 프로젝트 기반 학습, 첫 번째 AI 제품 구축_
|
||||
|
||||
<p align="center">
|
||||
📌 <a href="https://datawhalechina.github.io/easy-vibe/">온라인으로 읽기 시작하기 (Start Reading Online)</a>
|
||||
@@ -78,79 +78,79 @@ AI로 코드를 작성하려고 할 때 오류가 계속 발생하고, 포기하
|
||||
|
||||
### 0. 유치원
|
||||
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--- | :--- | :--- |
|
||||
| [서론: 학습 맵](docs/zh-cn/stage-0/0.1-learning-map/index.md) | 전체 학습 경로 가이드 | ✅ |
|
||||
| [초급 1: AI 시대, 말할 수 있으면 프로그래밍 가능](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 스네이크 게임 등 사례를 통해 AI 프로그래밍 능력을 처음 체험 | ✅ |
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------- | :--- |
|
||||
| [서론: 학습 맵](docs/zh-cn/stage-0/0.1-learning-map/index.md) | 전체 학습 경로 가이드 | ✅ |
|
||||
| [초급 1: AI 시대, 말할 수 있으면 프로그래밍 가능](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 스네이크 게임 등 사례를 통해 AI 프로그래밍 능력을 처음 체험 | ✅ |
|
||||
|
||||
### 1. AI 프로덕트 매니저
|
||||
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--- | :--- | :--- |
|
||||
| [초급 2: AI IDE 도구 이해](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | IDE 사용법 배우기, 로컬에서 미니게임 제작 | ✅ |
|
||||
| [초급 3: 직접 프로토타입 만들기](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | 요구사항 분석, AI 단일 페이지 생성, 다중 페이지 프로덕트 프로토타입 생성으로 | ✅ |
|
||||
| [초급 4: 프로토타입에 AI 능력 추가](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | 일반적인 AI 능력(텍스트, 이미지, 비디오) 연결 방법 습득 | ✅ |
|
||||
| [초급 5: 완전 프로젝트 실전](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 실제 시나리오 시뮬레이션, 사용자 피드백 수용 반복 개선, 프로젝트 완성 | ✅ |
|
||||
| [대 과제: 완전한 웹 애플리케이션 프로토타입 만들기 및 전시](docs/zh-cn/stage-1/1.5-final-project/index.md) | 애플리케이션 완전 구현, 애플리케이션 효과 전시 | ✅ |
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :--- |
|
||||
| [초급 2: AI IDE 도구 이해](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | IDE 사용법 배우기, 로컬에서 미니게임 제작 | ✅ |
|
||||
| [초급 3: 직접 프로토타입 만들기](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | 요구사항 분석, AI 단일 페이지 생성, 다중 페이지 프로덕트 프로토타입 생성으로 | ✅ |
|
||||
| [초급 4: 프로토타입에 AI 능력 추가](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | 일반적인 AI 능력(텍스트, 이미지, 비디오) 연결 방법 습득 | ✅ |
|
||||
| [초급 5: 완전 프로젝트 실전](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 실제 시나리오 시뮬레이션, 사용자 피드백 수용 반복 개선, 프로젝트 완성 | ✅ |
|
||||
| [대 과제: 완전한 웹 애플리케이션 프로토타입 만들기 및 전시](docs/zh-cn/stage-1/1.5-final-project/index.md) | 애플리케이션 완전 구현, 애플리케이션 효과 전시 | ✅ |
|
||||
|
||||
#### 부록
|
||||
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--- | :--- | :--- |
|
||||
| [부록 A: 프로덕트 사고 보충](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | 0에서 1까지 프로덕트 만들 때 필요한 사고 프레임워크 | ✅ |
|
||||
| [부록 B: 일반적인 오류 및 해결 방안](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Vibe Coding의 일반적인 오류 및 문제 해결 방법 | ✅ |
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :----------------------------------------------------------------------------------------- | :-------------------------------------------------- | :--- |
|
||||
| [부록 A: 프로덕트 사고 보충](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | 0에서 1까지 프로덕트 만들 때 필요한 사고 프레임워크 | ✅ |
|
||||
| [부록 B: 일반적인 오류 및 해결 방안](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Vibe Coding의 일반적인 오류 및 문제 해결 방법 | ✅ |
|
||||
|
||||
### 2. 초중급 개발 엔지니어
|
||||
|
||||
#### 프론트엔드 부분
|
||||
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--- | :--- | :--- |
|
||||
| [프론트엔드 0: lovart 사용하여 소재 생성](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | lovart를 사용하여 캐릭터, 장면 등 시각 자료 일괄 생성, UI 디자인과 프론트엔드 개발에 자료 기반 제공 | 🚧 |
|
||||
| [프론트엔드 1: Figma와 MasterGo 입문](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | 디자인 도구로 정보 아키텍처와 페이지 구조 정리, 프론트엔드 구현 기반 마련 | 🚧 |
|
||||
| [프론트엔드 2: 첫 번째 현대 애플리케이션 구축 - UI 디자인](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 디자인 안을 기반으로 컴포넌트화 인터페이스 완성, 디자인에서 코드까지의 첫 번째 경로 실현 | 🚧 |
|
||||
| [프론트엔드 3: UI 디자인 사양 및 다중 프로덕트 UI 디자인 참조](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 통합된 메인 비주얼을 중심으로 다중 프로덕트 인터페이스 확장, 체계적 디자인 능력 연습 | 🚧 |
|
||||
| [프론트엔드 4: 함께 해리포트 초상화 만들기](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | 0에서 1까지 AI 능력을 통합한 프론트엔드 애플리케이션 제작, 디자인과 개발 연결 | ✅ |
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :--- |
|
||||
| [프론트엔드 0: lovart 사용하여 소재 생성](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | lovart를 사용하여 캐릭터, 장면 등 시각 자료 일괄 생성, UI 디자인과 프론트엔드 개발에 자료 기반 제공 | 🚧 |
|
||||
| [프론트엔드 1: Figma와 MasterGo 입문](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | 디자인 도구로 정보 아키텍처와 페이지 구조 정리, 프론트엔드 구현 기반 마련 | 🚧 |
|
||||
| [프론트엔드 2: 첫 번째 현대 애플리케이션 구축 - UI 디자인](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 디자인 안을 기반으로 컴포넌트화 인터페이스 완성, 디자인에서 코드까지의 첫 번째 경로 실현 | 🚧 |
|
||||
| [프론트엔드 3: UI 디자인 사양 및 다중 프로덕트 UI 디자인 참조](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 통합된 메인 비주얼을 중심으로 다중 프로덕트 인터페이스 확장, 체계적 디자인 능력 연습 | 🚧 |
|
||||
| [프론트엔드 4: 함께 해리포트 초상화 만들기](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | 0에서 1까지 AI 능력을 통합한 프론트엔드 애플리케이션 제작, 디자인과 개발 연결 | ✅ |
|
||||
|
||||
#### 백엔드와 풀스택 부분
|
||||
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--- | :--- | :--- |
|
||||
| [백엔드 1: API란](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | HTTP 인터페이스와 요청-응답 모델 이해, 백엔드 통합과 연동 조정 준비 | ✅ |
|
||||
| [백엔드 2: 데이터베이스에서 Supabase로](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | Supabase상에서 데이터베이스와 API 구현, 데이터 모델과 프론트엔드 페이지 연결 | ✅ |
|
||||
| [백엔드 3: 대규모 언어 모델 지원 인터페이스 코드 및 문서 작성](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | 대규모 언어 모델을 활용하여 인터페이스와 데이터베이스 문서 및 코드 생성 지원, 읽기 쉽고 테스트 가능한 백엔드 실현 | 🚧 |
|
||||
| [백엔드 4: Git 워크플로우](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | Git 워크플로우에서 코드 관리, 버전 관리 및 협업 수행 | ✅ |
|
||||
| [백엔드 5: Zeabur 배포](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | 애플리케이션을 Zeabur에 배포하여 온라인화 | ✅ |
|
||||
| [백엔드 6: 현대 CLI 개발 도구](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | CLI 계열 AI 프로그래밍 도구 사용하여 개발과 디버깅 가속화, 개인 엔지니어링 워크플로우 형성 | ✅ |
|
||||
| [백엔드 7: Stripe 등 유료 시스템 통합 방법](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 결제 시스템 연결, 유료 경로와 기본 정산 흐름 완성 | 🚧 |
|
||||
| [대 과제 1: 첫 번째 현대 애플리케이션 구축 - 풀스택 앱](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 프론트엔드, 백엔드, 결제 모듈 통합, 온라인 가능한 풀스택 웹 애플리케이션 완성 | 🚧 |
|
||||
| [대 과제 2: 현대 프론트엔드 컴포넌트 라이브러리 + Trae 실전](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 현대 프론트엔드 컴포넌트 라이브러리와 Trae 사용, 로그인/등록 가능하고 유료 지원 프로덕트 독립 완성 | 🚧 |
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------- | :--- |
|
||||
| [백엔드 1: API란](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | HTTP 인터페이스와 요청-응답 모델 이해, 백엔드 통합과 연동 조정 준비 | ✅ |
|
||||
| [백엔드 2: 데이터베이스에서 Supabase로](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | Supabase상에서 데이터베이스와 API 구현, 데이터 모델과 프론트엔드 페이지 연결 | ✅ |
|
||||
| [백엔드 3: 대규모 언어 모델 지원 인터페이스 코드 및 문서 작성](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | 대규모 언어 모델을 활용하여 인터페이스와 데이터베이스 문서 및 코드 생성 지원, 읽기 쉽고 테스트 가능한 백엔드 실현 | 🚧 |
|
||||
| [백엔드 4: Git 워크플로우](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | Git 워크플로우에서 코드 관리, 버전 관리 및 협업 수행 | ✅ |
|
||||
| [백엔드 5: Zeabur 배포](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | 애플리케이션을 Zeabur에 배포하여 온라인화 | ✅ |
|
||||
| [백엔드 6: 현대 CLI 개발 도구](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | CLI 계열 AI 프로그래밍 도구 사용하여 개발과 디버깅 가속화, 개인 엔지니어링 워크플로우 형성 | ✅ |
|
||||
| [백엔드 7: Stripe 등 유료 시스템 통합 방법](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 결제 시스템 연결, 유료 경로와 기본 정산 흐름 완성 | 🚧 |
|
||||
| [대 과제 1: 첫 번째 현대 애플리케이션 구축 - 풀스택 앱](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 프론트엔드, 백엔드, 결제 모듈 통합, 온라인 가능한 풀스택 웹 애플리케이션 완성 | 🚧 |
|
||||
| [대 과제 2: 현대 프론트엔드 컴포넌트 라이브러리 + Trae 실전](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 현대 프론트엔드 컴포넌트 라이브러리와 Trae 사용, 로그인/등록 가능하고 유료 지원 프로덕트 독립 완성 | 🚧 |
|
||||
|
||||
#### AI 능력 부록
|
||||
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--- | :--- | :--- |
|
||||
| [AI 1: Dify 입문과 지식 베이스 통합](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | Dify Workflow와 기본 RAG 사용하여 도구 계열 프로덕트 구축, 이후 애플리케이션 업그레이드 모델 사례 작성 | ✅ |
|
||||
| [AI 2: AI 사전 검색 및 멀티모달 API 통합 학습](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 적절한 모델과 API 검색 방법 학습, 텍스트, 이미지 등 멀티모달 능력을 프로덕트에 통합 | 🚧 |
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------- | :--- |
|
||||
| [AI 1: Dify 입문과 지식 베이스 통합](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | Dify Workflow와 기본 RAG 사용하여 도구 계열 프로덕트 구축, 이후 애플리케이션 업그레이드 모델 사례 작성 | ✅ |
|
||||
| [AI 2: AI 사전 검색 및 멀티모달 API 통합 학습](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 적절한 모델과 API 검색 방법 학습, 텍스트, 이미지 등 멀티모달 능력을 프로덕트에 통합 | 🚧 |
|
||||
|
||||
### 3. 고급 개발 엔지니어
|
||||
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--- | :--- | :--- |
|
||||
| [고급 1: MCP와 ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | MCP와 Skills를 통해 IDE 능력 확장, 외부 서비스를 도구로 연결 | 🚧 |
|
||||
| [고급 2: Coding Tools를 장시간 작동시키는 방법](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 장시간 실행되는 작업 설계 및 구성, Coding Tools를 더 안정적이고 신뢰할 수 있게 | 🚧 |
|
||||
| [고급 3: 멀티플랫폼 개발: 위챗 미니 프로그램 구축 방법](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | 위챗 미니 프로그램 생태계 이해, 공식 템플릿부터 출시까지 프론트엔드 미니 프로그램 완성 | ✅ |
|
||||
| [고급 4: 멀티플랫폼 개발: 위챗 미니 프로그램 구축 방법 - 백엔드 포함](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 미니 프로그램에 데이터베이스와 백엔드 로직 통합, 완전한 비즈니스 폐루프 실현 | 🚧 |
|
||||
| [고급 5: 멀티플랫폼 개발: 안드로이드 앱 구축 방법](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | Expo 등 도구 사용, Web/네이티브 일체화 안드로이드 앱 개발 완성 | 🚧 |
|
||||
| [고급 6: 멀티플랫폼 개발: iOS 앱 구축 방법](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | Expo 등 도구 사용, Web/네이티브 일체화 iOS 앱 개발 완성 | 🚧 |
|
||||
| [고급 7: 자신만의 개인 웹페이지와 학술 블로그 구축 방법](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 선정, 구축부터 배포까지, 개인 프로젝트와 학술 성과를 전시하는 장기 온라인 홈페이지 구축 | 🚧 |
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | :--- |
|
||||
| [고급 1: MCP와 ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | MCP와 Skills를 통해 IDE 능력 확장, 외부 서비스를 도구로 연결 | 🚧 |
|
||||
| [고급 2: Coding Tools를 장시간 작동시키는 방법](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 장시간 실행되는 작업 설계 및 구성, Coding Tools를 더 안정적이고 신뢰할 수 있게 | 🚧 |
|
||||
| [고급 3: 멀티플랫폼 개발: 위챗 미니 프로그램 구축 방법](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | 위챗 미니 프로그램 생태계 이해, 공식 템플릿부터 출시까지 프론트엔드 미니 프로그램 완성 | ✅ |
|
||||
| [고급 4: 멀티플랫폼 개발: 위챗 미니 프로그램 구축 방법 - 백엔드 포함](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 미니 프로그램에 데이터베이스와 백엔드 로직 통합, 완전한 비즈니스 폐루프 실현 | 🚧 |
|
||||
| [고급 5: 멀티플랫폼 개발: 안드로이드 앱 구축 방법](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | Expo 등 도구 사용, Web/네이티브 일체화 안드로이드 앱 개발 완성 | 🚧 |
|
||||
| [고급 6: 멀티플랫폼 개발: iOS 앱 구축 방법](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | Expo 등 도구 사용, Web/네이티브 일체화 iOS 앱 개발 완성 | 🚧 |
|
||||
| [고급 7: 자신만의 개인 웹페이지와 학술 블로그 구축 방법](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 선정, 구축부터 배포까지, 개인 프로젝트와 학술 성과를 전시하는 장기 온라인 홈페이지 구축 | 🚧 |
|
||||
|
||||
#### AI 능력 부록
|
||||
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--- | :--- | :--- |
|
||||
| [고급 AI 1: RAG란 무엇이며 어떻게 작동하는가](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | RAG 원리와 일반적인 아키텍처 체계적 이해, 복잡한 애플리케이션에 지식 검색 기반 제공 | ✅ |
|
||||
| [고급 AI 2: 중고급 RAG와 워크플로우 편성: LangGraph 예시](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | LangGraph 등 도구 사용하여 다단계 워크플로우와 중고급 RAG 시스템 설계 | 🚧 |
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------- | :--- |
|
||||
| [고급 AI 1: RAG란 무엇이며 어떻게 작동하는가](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | RAG 원리와 일반적인 아키텍처 체계적 이해, 복잡한 애플리케이션에 지식 검색 기반 제공 | ✅ |
|
||||
| [고급 AI 2: 중고급 RAG와 워크플로우 편성: LangGraph 예시](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | LangGraph 등 도구 사용하여 다단계 워크플로우와 중고급 RAG 시스템 설계 | 🚧 |
|
||||
|
||||
## 🛠️ 학습 방법
|
||||
|
||||
|
||||
+51
-51
@@ -11,7 +11,7 @@
|
||||
|
||||
# Easy-Vibe: Learn Vibe Coding from 0 to 1
|
||||
|
||||
### *Từ số không, học tập dựa trên dự án, xây dựng sản phẩm AI đầu tiên của bạn*
|
||||
### _Từ số không, học tập dựa trên dự án, xây dựng sản phẩm AI đầu tiên của bạn_
|
||||
|
||||
<p align="center">
|
||||
📌 <a href="https://datawhalechina.github.io/easy-vibe/">Bắt đầu đọc online (Start Reading Online)</a>
|
||||
@@ -78,79 +78,79 @@ Chúng tôi tin rằng thông qua việc làm chủ Vibe Coding kết hợp vớ
|
||||
|
||||
### 0. Mẫu giáo
|
||||
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :--- | :--- | :--- |
|
||||
| [Lời nói đầu: Bản đồ học tập](docs/stage-0/0.1-learning-map/index.md) | Hướng dẫn lộ trình học tập tổng thể | ✅ |
|
||||
| [Sơ cấp 1: Kỷ nguyên AI, biết nói là biết lập trình](docs/stage-0/0.2-ai-capabilities-through-games/index.md) | Trải nghiệm khả năng lập trình AI lần đầu qua các trường hợp như Snake | ✅ |
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------- | :--------- |
|
||||
| [Lời nói đầu: Bản đồ học tập](docs/stage-0/0.1-learning-map/index.md) | Hướng dẫn lộ trình học tập tổng thể | ✅ |
|
||||
| [Sơ cấp 1: Kỷ nguyên AI, biết nói là biết lập trình](docs/stage-0/0.2-ai-capabilities-through-games/index.md) | Trải nghiệm khả năng lập trình AI lần đầu qua các trường hợp như Snake | ✅ |
|
||||
|
||||
### 1. Quản lý sản phẩm AI
|
||||
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :--- | :--- | :--- |
|
||||
| [Sơ cấp 2: Biết các công cụ IDE AI](docs/stage-1/1.1-introduction-to-ai-ide/index.md) | Học sử dụng IDE, tạo mini-game cục bộ | ✅ |
|
||||
| [Sơ cấp 3: Tự làm nguyên mẫu](docs/stage-1/1.2-building-prototype/index.md) | Phân tích yêu cầu, tạo trang đơn bằng AI, đến tạo nguyên mẫu sản phẩm đa trang | ✅ |
|
||||
| [Sơ cấp 4: Thêm khả năng AI vào nguyên mẫu](docs/stage-1/1.3-integrating-ai-capabilities/index.md) | Học kết nối các khả năng AI phổ biến (văn bản, hình ảnh, video) | ✅ |
|
||||
| [Sơ cấp 5: Dự án hoàn chỉnh thực chiến](docs/stage-1/1.4-complete-project-practice/index.md) | Mô phỏng kịch bản thực tế, chấp nhận phản hồi người dùng để cải tiến, hoàn thành dự án | ✅ |
|
||||
| [Đề tài lớn: Tạo nguyên mẫu ứng dụng web hoàn chỉnh và trưng bày](docs/stage-1/1.5-final-project/index.md) | Thực hiện ứng dụng hoàn chỉnh, trình bày hiệu ứng ứng dụng | ✅ |
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :--------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | :--------- |
|
||||
| [Sơ cấp 2: Biết các công cụ IDE AI](docs/stage-1/1.1-introduction-to-ai-ide/index.md) | Học sử dụng IDE, tạo mini-game cục bộ | ✅ |
|
||||
| [Sơ cấp 3: Tự làm nguyên mẫu](docs/stage-1/1.2-building-prototype/index.md) | Phân tích yêu cầu, tạo trang đơn bằng AI, đến tạo nguyên mẫu sản phẩm đa trang | ✅ |
|
||||
| [Sơ cấp 4: Thêm khả năng AI vào nguyên mẫu](docs/stage-1/1.3-integrating-ai-capabilities/index.md) | Học kết nối các khả năng AI phổ biến (văn bản, hình ảnh, video) | ✅ |
|
||||
| [Sơ cấp 5: Dự án hoàn chỉnh thực chiến](docs/stage-1/1.4-complete-project-practice/index.md) | Mô phỏng kịch bản thực tế, chấp nhận phản hồi người dùng để cải tiến, hoàn thành dự án | ✅ |
|
||||
| [Đề tài lớn: Tạo nguyên mẫu ứng dụng web hoàn chỉnh và trưng bày](docs/stage-1/1.5-final-project/index.md) | Thực hiện ứng dụng hoàn chỉnh, trình bày hiệu ứng ứng dụng | ✅ |
|
||||
|
||||
#### Phụ lục
|
||||
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :--- | :--- | :--- |
|
||||
| [Phụ lục A: Bổ sung tư duy sản phẩm](docs/stage-1/appendix-a-product-thinking/index.md) | Các khung tư duy cần thiết để tạo sản phẩm từ số không đến một | ✅ |
|
||||
| [Phụ lục B: Lỗi phổ biến và giải pháp](docs/stage-1/appendix-b-common-errors/index.md) | Các lỗi phổ biến trong Vibe Coding và phương pháp khắc phục sự cố | ✅ |
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :-------------------------------------------------------------------------------------- | :---------------------------------------------------------------- | :--------- |
|
||||
| [Phụ lục A: Bổ sung tư duy sản phẩm](docs/stage-1/appendix-a-product-thinking/index.md) | Các khung tư duy cần thiết để tạo sản phẩm từ số không đến một | ✅ |
|
||||
| [Phụ lục B: Lỗi phổ biến và giải pháp](docs/stage-1/appendix-b-common-errors/index.md) | Các lỗi phổ biến trong Vibe Coding và phương pháp khắc phục sự cố | ✅ |
|
||||
|
||||
### 2. Kỹ sư phát triển cấp sơ-trung
|
||||
|
||||
#### Phần Frontend
|
||||
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :--- | :--- | :--- |
|
||||
| [Frontend 0: Sử dụng lovart tạo tài sản](docs/stage-2/frontend/2.0-lovart-assets/) | Sử dụng lovart tạo hàng loạt tài sản hình ảnh như nhân vật, cảnh, cung cấp cơ sở tài sản cho thiết kế UI và phát triển frontend | 🚧 |
|
||||
| [Frontend 1: Nhập môn Figma và MasterGo](docs/stage-2/frontend/2.1-figma-mastergo/) | Sử dụng công cụ thiết kế tổ chức kiến trúc thông tin và cấu trúc trang, chuẩn bị cơ sở cho triển khai frontend | 🚧 |
|
||||
| [Frontend 2: Xây dựng ứng dụng hiện đại đầu tiên - Thiết kế UI](docs/stage-2/frontend/2.2-ui-design/) : Hoàn thành giao diện dựa trên thành phần từ thiết kế, thực hiện lộ trình đầu tiên từ thiết kế đến code | 🚧 |
|
||||
| [Frontend 3: Tham khảo thông số thiết kế UI và thiết kế UI đa sản phẩm](docs/stage-2/frontend/2.3-multi-product-ui/) : Mở rộng giao diện đa sản phẩm quanh một hình ảnh chính thống nhất, thực hành khả năng thiết kế có hệ thống | 🚧 |
|
||||
| [Frontend 4: Cùng làm chân dung Hogwarts](docs/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) : Tạo ứng dụng frontend có khả năng AI tích hợp từ 0 đến 1, kết nối thiết kế và phát triển | ✅ |
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | :--------- |
|
||||
| [Frontend 0: Sử dụng lovart tạo tài sản](docs/stage-2/frontend/2.0-lovart-assets/) | Sử dụng lovart tạo hàng loạt tài sản hình ảnh như nhân vật, cảnh, cung cấp cơ sở tài sản cho thiết kế UI và phát triển frontend | 🚧 |
|
||||
| [Frontend 1: Nhập môn Figma và MasterGo](docs/stage-2/frontend/2.1-figma-mastergo/) | Sử dụng công cụ thiết kế tổ chức kiến trúc thông tin và cấu trúc trang, chuẩn bị cơ sở cho triển khai frontend | 🚧 |
|
||||
| [Frontend 2: Xây dựng ứng dụng hiện đại đầu tiên - Thiết kế UI](docs/stage-2/frontend/2.2-ui-design/) : Hoàn thành giao diện dựa trên thành phần từ thiết kế, thực hiện lộ trình đầu tiên từ thiết kế đến code | 🚧 |
|
||||
| [Frontend 3: Tham khảo thông số thiết kế UI và thiết kế UI đa sản phẩm](docs/stage-2/frontend/2.3-multi-product-ui/) : Mở rộng giao diện đa sản phẩm quanh một hình ảnh chính thống nhất, thực hành khả năng thiết kế có hệ thống | 🚧 |
|
||||
| [Frontend 4: Cùng làm chân dung Hogwarts](docs/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) : Tạo ứng dụng frontend có khả năng AI tích hợp từ 0 đến 1, kết nối thiết kế và phát triển | ✅ |
|
||||
|
||||
#### Phần Backend và Full Stack
|
||||
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :--- | :--- | :--- |
|
||||
| [Backend 1: API là gì](docs/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) : Hiểu giao diện HTTP và mô hình yêu cầu-phản hồi, chuẩn bị tích hợp backend và phối hợp | ✅ |
|
||||
| [Backend 2: Từ cơ sở dữ liệu đến Supabase](docs/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) : Triển khai cơ sở dữ liệu và API trên Supabase, kết nối mô hình dữ liệu với trang frontend | ✅ |
|
||||
| [Backend 3: LLM hỗ trợ viết code giao diện và tài liệu](docs/stage-2/backend/2.3-ai-interface-code/) : Sử dụng LLM hỗ trợ tạo tài liệu và code cho giao diện và cơ sở dữ liệu, thực hiện backend có thể đọc và kiểm tra | 🚧 |
|
||||
| [Backend 4: Quy trình làm việc Git](docs/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) : Quản lý code trong quy trình làm việc Git, thực hiện kiểm soát phiên bản và cộng tác | ✅ |
|
||||
| [Backend 5: Triển khai Zeabur](docs/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) : Triển khai ứng dụng lên Zeabur để đưa vào hoạt động | ✅ |
|
||||
| [Backend 6: Công cụ phát triển CLI hiện đại](docs/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) : Sử dụng công cụ lập trình AI loại CLI để tăng tốc phát triển và gỡ lỗi, hình thành quy trình kỹ thuật cá nhân | ✅ |
|
||||
| [Backend 7: Cách tích hợp hệ thống thanh toán như Stripe](docs/stage-2/backend/2.7-stripe-payment/) : Kết nối hệ thống thanh toán, hoàn thành quy trình thanh toán và quy trình thanh toán cơ bản | 🚧 |
|
||||
| [Đề tài lớn 1: Xây dựng ứng dụng hiện đại đầu tiên - Ứng dụng full stack](docs/stage-2/assignments/2.1-fullstack-app/) : Tích hợp frontend, backend và module thanh toán, hoàn thành ứng dụng web full stack sẵn sàng sản xuất | 🚧 |
|
||||
| [Đề tài lớn 2: Thư viện thành phần frontend hiện đại + Trae thực chiến](docs/stage-2/assignments/2.2-modern-frontend-trae/) : Sử dụng thư viện thành phần frontend hiện đại và Trae, hoàn thành độc lập một sản phẩm có đăng nhập/đăng ký và hỗ trợ thanh toán | 🚧 |
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | :--------- |
|
||||
| [Backend 1: API là gì](docs/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) : Hiểu giao diện HTTP và mô hình yêu cầu-phản hồi, chuẩn bị tích hợp backend và phối hợp | ✅ |
|
||||
| [Backend 2: Từ cơ sở dữ liệu đến Supabase](docs/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) : Triển khai cơ sở dữ liệu và API trên Supabase, kết nối mô hình dữ liệu với trang frontend | ✅ |
|
||||
| [Backend 3: LLM hỗ trợ viết code giao diện và tài liệu](docs/stage-2/backend/2.3-ai-interface-code/) : Sử dụng LLM hỗ trợ tạo tài liệu và code cho giao diện và cơ sở dữ liệu, thực hiện backend có thể đọc và kiểm tra | 🚧 |
|
||||
| [Backend 4: Quy trình làm việc Git](docs/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) : Quản lý code trong quy trình làm việc Git, thực hiện kiểm soát phiên bản và cộng tác | ✅ |
|
||||
| [Backend 5: Triển khai Zeabur](docs/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) : Triển khai ứng dụng lên Zeabur để đưa vào hoạt động | ✅ |
|
||||
| [Backend 6: Công cụ phát triển CLI hiện đại](docs/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) : Sử dụng công cụ lập trình AI loại CLI để tăng tốc phát triển và gỡ lỗi, hình thành quy trình kỹ thuật cá nhân | ✅ |
|
||||
| [Backend 7: Cách tích hợp hệ thống thanh toán như Stripe](docs/stage-2/backend/2.7-stripe-payment/) : Kết nối hệ thống thanh toán, hoàn thành quy trình thanh toán và quy trình thanh toán cơ bản | 🚧 |
|
||||
| [Đề tài lớn 1: Xây dựng ứng dụng hiện đại đầu tiên - Ứng dụng full stack](docs/stage-2/assignments/2.1-fullstack-app/) : Tích hợp frontend, backend và module thanh toán, hoàn thành ứng dụng web full stack sẵn sàng sản xuất | 🚧 |
|
||||
| [Đề tài lớn 2: Thư viện thành phần frontend hiện đại + Trae thực chiến](docs/stage-2/assignments/2.2-modern-frontend-trae/) : Sử dụng thư viện thành phần frontend hiện đại và Trae, hoàn thành độc lập một sản phẩm có đăng nhập/đăng ký và hỗ trợ thanh toán | 🚧 |
|
||||
|
||||
#### Phụ lục khả năng AI
|
||||
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :--- | :--- | :--- |
|
||||
| [AI 1: Nhập môn Dify và tích hợp cơ sở kiến thức](docs/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) : Sử dụng Dify Workflow và RAG cơ bản để xây dựng sản phẩm loại công cụ, tạo ví dụ cho nâng cấp ứng dụng trong tương lai | ✅ |
|
||||
| [AI 2: Học tra cứu từ điển AI và tích hợp API đa phương thức](docs/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) : Học tìm kiếm mô hình và API phù hợp, tích hợp khả năng đa phương thức như văn bản, hình ảnh vào sản phẩm | 🚧 |
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | :--------- |
|
||||
| [AI 1: Nhập môn Dify và tích hợp cơ sở kiến thức](docs/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) : Sử dụng Dify Workflow và RAG cơ bản để xây dựng sản phẩm loại công cụ, tạo ví dụ cho nâng cấp ứng dụng trong tương lai | ✅ |
|
||||
| [AI 2: Học tra cứu từ điển AI và tích hợp API đa phương thức](docs/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) : Học tìm kiếm mô hình và API phù hợp, tích hợp khả năng đa phương thức như văn bản, hình ảnh vào sản phẩm | 🚧 |
|
||||
|
||||
### 3. Kỹ sư phát triển cấp cao
|
||||
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :--- | :--- | :--- |
|
||||
| [Cao cấp 1: MCP và ClaudeCode Skills](docs/stage-3/core-skills/3.1-mcp-claudecode-skills/) : Mở rộng khả năng IDE qua MCP và Skills, kết nối dịch vụ bên ngoài như công cụ | 🚧 |
|
||||
| [Cao cấp 2: Cách để Coding Tools hoạt động lâu](docs/stage-3/core-skills/3.2-long-running-tasks/) : Thiết kế và cấu hình nhiệm vụ chạy dài, làm Coding Tools ổn định và đáng tin cậy hơn | 🚧 |
|
||||
| [Cao cấp 3: Phát triển đa nền tảng: Cách xây dựng chương trình nhỏ WeChat](docs/stage-3/cross-platform/3.3-wechat-miniprogram/) : Hiểu hệ sinh thái chương trình nhỏ WeChat, hoàn thành chương trình nhỏ frontend từ mẫu chính thức đến phát hành | ✅ |
|
||||
| [Cao cấp 4: Phát triển đa nền tảng: Cách xây dựng chương trình nhỏ WeChat - Bao gồm backend](docs/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : Tích hợp cơ sở dữ liệu và logic backend vào chương trình nhỏ, thực hiện vòng tuần hoàn hoạt động hoàn chỉnh | 🚧 |
|
||||
| [Cao cấp 5: Phát triển đa nền tảng: Cách xây dựng ứng dụng Android](docs/stage-3/cross-platform/3.5-android-app/) : Sử dụng công cụ như Expo, hoàn thành phát triển ứng dụng Android tích hợp Web/native | 🚧 |
|
||||
| [Cao cấp 6: Phát triển đa nền tảng: Cách xây dựng ứng dụng iOS](docs/stage-3/cross-platform/3.6-ios-app/) : Sử dụng công cụ như Expo, hoàn thành phát triển ứng dụng iOS tích hợp Web/native | 🚧 |
|
||||
| [Cao cấp 7: Cách xây dựng trang web cá nhân và blog học thuật của riêng bạn](docs/stage-3/personal-brand/3.7-personal-website-blog/) : Từ lựa chọn, xây dựng đến triển khai, xây dựng trang chủ online dài hạn trưng bày dự án cá nhân và kết quả học thuật | 🚧 |
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | :--------- |
|
||||
| [Cao cấp 1: MCP và ClaudeCode Skills](docs/stage-3/core-skills/3.1-mcp-claudecode-skills/) : Mở rộng khả năng IDE qua MCP và Skills, kết nối dịch vụ bên ngoài như công cụ | 🚧 |
|
||||
| [Cao cấp 2: Cách để Coding Tools hoạt động lâu](docs/stage-3/core-skills/3.2-long-running-tasks/) : Thiết kế và cấu hình nhiệm vụ chạy dài, làm Coding Tools ổn định và đáng tin cậy hơn | 🚧 |
|
||||
| [Cao cấp 3: Phát triển đa nền tảng: Cách xây dựng chương trình nhỏ WeChat](docs/stage-3/cross-platform/3.3-wechat-miniprogram/) : Hiểu hệ sinh thái chương trình nhỏ WeChat, hoàn thành chương trình nhỏ frontend từ mẫu chính thức đến phát hành | ✅ |
|
||||
| [Cao cấp 4: Phát triển đa nền tảng: Cách xây dựng chương trình nhỏ WeChat - Bao gồm backend](docs/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : Tích hợp cơ sở dữ liệu và logic backend vào chương trình nhỏ, thực hiện vòng tuần hoàn hoạt động hoàn chỉnh | 🚧 |
|
||||
| [Cao cấp 5: Phát triển đa nền tảng: Cách xây dựng ứng dụng Android](docs/stage-3/cross-platform/3.5-android-app/) : Sử dụng công cụ như Expo, hoàn thành phát triển ứng dụng Android tích hợp Web/native | 🚧 |
|
||||
| [Cao cấp 6: Phát triển đa nền tảng: Cách xây dựng ứng dụng iOS](docs/stage-3/cross-platform/3.6-ios-app/) : Sử dụng công cụ như Expo, hoàn thành phát triển ứng dụng iOS tích hợp Web/native | 🚧 |
|
||||
| [Cao cấp 7: Cách xây dựng trang web cá nhân và blog học thuật của riêng bạn](docs/stage-3/personal-brand/3.7-personal-website-blog/) : Từ lựa chọn, xây dựng đến triển khai, xây dựng trang chủ online dài hạn trưng bày dự án cá nhân và kết quả học thuật | 🚧 |
|
||||
|
||||
#### Phụ lục khả năng AI
|
||||
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :--- | :--- | :--- |
|
||||
| [AI cao cấp 1: RAG là gì và nó hoạt động như thế nào](docs/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) : Hiểu có hệ thống nguyên lý RAG và kiến trúc phổ biến, cung cấp cơ sở truy xuất kiến thức cho ứng dụng phức tạp | ✅ |
|
||||
| [AI cao cấp 2: RAG trung cấp-cao cấp và điều phối quy trình làm việc: Ví dụ LangGraph](docs/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) : Sử dụng công cụ như LangGraph để thiết kế quy trình làm việc đa bước và hệ thống RAG trung cấp-cao cấp | 🚧 |
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | :--------- |
|
||||
| [AI cao cấp 1: RAG là gì và nó hoạt động như thế nào](docs/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) : Hiểu có hệ thống nguyên lý RAG và kiến trúc phổ biến, cung cấp cơ sở truy xuất kiến thức cho ứng dụng phức tạp | ✅ |
|
||||
| [AI cao cấp 2: RAG trung cấp-cao cấp và điều phối quy trình làm việc: Ví dụ LangGraph](docs/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) : Sử dụng công cụ như LangGraph để thiết kế quy trình làm việc đa bước và hệ thống RAG trung cấp-cao cấp | 🚧 |
|
||||
|
||||
## 🛠️ Cách học
|
||||
|
||||
|
||||
+51
-51
@@ -11,7 +11,7 @@
|
||||
|
||||
# Easy-Vibe: Learn Vibe Coding from 0 to 1
|
||||
|
||||
### *零基础、项目制学习、构建第一个 AI 产品*
|
||||
### _零基础、项目制学习、构建第一个 AI 产品_
|
||||
|
||||
<p align="center">
|
||||
📌 <a href="https://datawhalechina.github.io/easy-vibe/">开始在线阅读 (Start Reading Online)</a>
|
||||
@@ -81,79 +81,79 @@
|
||||
|
||||
### 零、幼儿园
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [前言:学习地图](docs/zh-cn/stage-0/0.1-learning-map/index.md) | 整体学习路径导览 | ✅ |
|
||||
| [初级一:AI 时代,会说话就会编程](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 通过贪吃蛇等案例初步感受 AI 编程的能力 | ✅ |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :----------------------------------------------------------------------------------------------- | :------------------------------------- | :--- |
|
||||
| [前言:学习地图](docs/zh-cn/stage-0/0.1-learning-map/index.md) | 整体学习路径导览 | ✅ |
|
||||
| [初级一:AI 时代,会说话就会编程](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 通过贪吃蛇等案例初步感受 AI 编程的能力 | ✅ |
|
||||
|
||||
### 一、AI 产品经理
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [初级二:认识 AI IDE 工具](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | 学会使用 IDE,在本地制作小游戏 | ✅ |
|
||||
| [初级三:动手做出原型](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | 从需求分析、AI 生成单页面,再到生成多页面产品原型 | ✅ |
|
||||
| [初级四:给原型加上 AI 能力](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | 学会接入常见 AI 能力(文本、图片、视频) | ✅ |
|
||||
| [初级五:完整项目实战](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 模拟真实场景、接受用户反馈迭代,完整化项目 | ✅ |
|
||||
| [大作业:做一个完整的 Web 应用原型并展示](docs/zh-cn/stage-1/1.5-final-project/index.md) | 完整实现应用,展示应用效果 | ✅ |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :---------------------------------------------------------------------------------------- | :------------------------------------------------ | :--- |
|
||||
| [初级二:认识 AI IDE 工具](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | 学会使用 IDE,在本地制作小游戏 | ✅ |
|
||||
| [初级三:动手做出原型](docs/zh-cn/stage-1/1.2-building-prototype/index.md) | 从需求分析、AI 生成单页面,再到生成多页面产品原型 | ✅ |
|
||||
| [初级四:给原型加上 AI 能力](docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | 学会接入常见 AI 能力(文本、图片、视频) | ✅ |
|
||||
| [初级五:完整项目实战](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 模拟真实场景、接受用户反馈迭代,完整化项目 | ✅ |
|
||||
| [大作业:做一个完整的 Web 应用原型并展示](docs/zh-cn/stage-1/1.5-final-project/index.md) | 完整实现应用,展示应用效果 | ✅ |
|
||||
|
||||
#### 附录
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [附录A:产品思维补充](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | 从零到一做产品需要考虑的思维框架 | ✅ |
|
||||
| [附录B:常见报错及解决方案](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | vibe coding 中的常见错误及排查方法 | ✅ |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :-------------------------------------------------------------------------------- | :--------------------------------- | :--- |
|
||||
| [附录A:产品思维补充](docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | 从零到一做产品需要考虑的思维框架 | ✅ |
|
||||
| [附录B:常见报错及解决方案](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | vibe coding 中的常见错误及排查方法 | ✅ |
|
||||
|
||||
### 二、初中级开发工程师
|
||||
|
||||
#### 前端部分
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [前端零:使用 lovart 生产素材](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 |
|
||||
| [前端一:Figma 与 MasterGo 入门](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 |
|
||||
| [前端二:构建第一个现代应用程序-UI 设计](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 |
|
||||
| [前端三:参考 UI 设计规范与多产品 UI 设计](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 围绕统一主视觉扩展多产品界面,练习系统化设计能力 | 🚧 |
|
||||
| [前端四:一起做霍格沃茨画像](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | 从 0 到 1 做出接入 AI 能力的前端应用,串联设计与开发 | ✅ |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :--- |
|
||||
| [前端零:使用 lovart 生产素材](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 |
|
||||
| [前端一:Figma 与 MasterGo 入门](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 |
|
||||
| [前端二:构建第一个现代应用程序-UI 设计](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 |
|
||||
| [前端三:参考 UI 设计规范与多产品 UI 设计](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 围绕统一主视觉扩展多产品界面,练习系统化设计能力 | 🚧 |
|
||||
| [前端四:一起做霍格沃茨画像](docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | 从 0 到 1 做出接入 AI 能力的前端应用,串联设计与开发 | ✅ |
|
||||
|
||||
#### 后端与全栈部分
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [后端一:什么是 API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | 理解 HTTP 接口与请求响应模型,为后端集成与联调做准备 | ✅ |
|
||||
| [后端二:从数据库到 Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | 在 Supabase 上落地数据库和 API,打通数据模型与前端页面 | ✅ |
|
||||
| [后端三:大模型辅助编写接口代码与接口文档](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | 用大模型协助生成接口与数据库文档及代码,实现可读可测的后端 | 🚧 |
|
||||
| [后端四:Git 工作流](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | 在 Git 工作流中管理代码,进行版本控制和协作 | ✅ |
|
||||
| [后端五:Zeabur 部署](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | 将应用部署到 Zeabur 上线 | ✅ |
|
||||
| [后端六:现代 CLI 开发工具](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | 使用 CLI 类 AI 编程工具加速开发与调试,形成个人工程化工作流 | ✅ |
|
||||
| [后端七:如何集成 Stripe 等收费系统](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 接入支付系统,完成收费链路与基础结算流程 | 🚧 |
|
||||
| [大作业 1:构建第一个现代应用程序-全栈应用](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 综合前端、后端与支付模块,完成可上线的全栈 Web 应用 | 🚧 |
|
||||
| [大作业 2:现代前端组件库 + Trae 实战](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 使用现代前端组件库与 Trae,独立完成可登录注册并支持收费的产品 | 🚧 |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------ | :--- |
|
||||
| [后端一:什么是 API](docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | 理解 HTTP 接口与请求响应模型,为后端集成与联调做准备 | ✅ |
|
||||
| [后端二:从数据库到 Supabase](docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | 在 Supabase 上落地数据库和 API,打通数据模型与前端页面 | ✅ |
|
||||
| [后端三:大模型辅助编写接口代码与接口文档](docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | 用大模型协助生成接口与数据库文档及代码,实现可读可测的后端 | 🚧 |
|
||||
| [后端四:Git 工作流](docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | 在 Git 工作流中管理代码,进行版本控制和协作 | ✅ |
|
||||
| [后端五:Zeabur 部署](docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | 将应用部署到 Zeabur 上线 | ✅ |
|
||||
| [后端六:现代 CLI 开发工具](docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | 使用 CLI 类 AI 编程工具加速开发与调试,形成个人工程化工作流 | ✅ |
|
||||
| [后端七:如何集成 Stripe 等收费系统](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 接入支付系统,完成收费链路与基础结算流程 | 🚧 |
|
||||
| [大作业 1:构建第一个现代应用程序-全栈应用](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 综合前端、后端与支付模块,完成可上线的全栈 Web 应用 | 🚧 |
|
||||
| [大作业 2:现代前端组件库 + Trae 实战](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 使用现代前端组件库与 Trae,独立完成可登录注册并支持收费的产品 | 🚧 |
|
||||
|
||||
#### AI 能力附录
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [AI 一:Dify 入门与知识库集成](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | 用 Dify Workflow 与基础 RAG 搭建工具类产品,为后续应用升级打样 | ✅ |
|
||||
| [AI 二:学会查询 AI 词典与集成多模态 API](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 学会查找合适的模型与 API,并把文本、图像等多模态能力接入产品 | 🚧 |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------- | :--- |
|
||||
| [AI 一:Dify 入门与知识库集成](docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | 用 Dify Workflow 与基础 RAG 搭建工具类产品,为后续应用升级打样 | ✅ |
|
||||
| [AI 二:学会查询 AI 词典与集成多模态 API](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 学会查找合适的模型与 API,并把文本、图像等多模态能力接入产品 | 🚧 |
|
||||
|
||||
### 三、高级开发工程师
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [高级一:MCP 与 ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | 通过 MCP 与 Skills 扩展 IDE 能力,把外部服务接成工具 | 🚧 |
|
||||
| [高级二:如何让 Coding Tools 长时间工作](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 设计和配置长时间运行的任务,让 Coding Tools 更稳定可靠 | 🚧 |
|
||||
| [高级三:多平台开发:如何构建微信小程序](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | 了解微信小程序生态,从官方模板到上线完成一个前端小程序 | ✅ |
|
||||
| [高级四:多平台开发:如何构建微信小程序-包含后端](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 在小程序中接入数据库与后端逻辑,打通完整业务闭环 | 🚧 |
|
||||
| [高级五:多平台开发:如何构建安卓程序](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | 使用 Expo 等工具,完成 Web/原生一体化的安卓应用开发 | 🚧 |
|
||||
| [高级六:多平台开发:如何构建 iOS 程序](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | 使用 Expo 等工具,完成 Web/原生一体化的 iOS 应用开发 | 🚧 |
|
||||
| [高级七:如何构建属于自己的个人网页与学术博客](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 从选型、搭建到部署,构建展示个人项目与学术成果的长久在线主页 | 🚧 |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :--- |
|
||||
| [高级一:MCP 与 ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | 通过 MCP 与 Skills 扩展 IDE 能力,把外部服务接成工具 | 🚧 |
|
||||
| [高级二:如何让 Coding Tools 长时间工作](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 设计和配置长时间运行的任务,让 Coding Tools 更稳定可靠 | 🚧 |
|
||||
| [高级三:多平台开发:如何构建微信小程序](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | 了解微信小程序生态,从官方模板到上线完成一个前端小程序 | ✅ |
|
||||
| [高级四:多平台开发:如何构建微信小程序-包含后端](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 在小程序中接入数据库与后端逻辑,打通完整业务闭环 | 🚧 |
|
||||
| [高级五:多平台开发:如何构建安卓程序](docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | 使用 Expo 等工具,完成 Web/原生一体化的安卓应用开发 | 🚧 |
|
||||
| [高级六:多平台开发:如何构建 iOS 程序](docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | 使用 Expo 等工具,完成 Web/原生一体化的 iOS 应用开发 | 🚧 |
|
||||
| [高级七:如何构建属于自己的个人网页与学术博客](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 从选型、搭建到部署,构建展示个人项目与学术成果的长久在线主页 | 🚧 |
|
||||
|
||||
#### AI 能力附录
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| [高级 AI 一:什么是 RAG 以及它如何工作](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | 系统理解 RAG 原理与常见架构,为复杂应用提供知识检索基础 | ✅ |
|
||||
| [高级 AI 二:中高级 RAG 与工作流编排:以 LangGraph 为例](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | 使用 LangGraph 等工具设计多步工作流与中高级 RAG 系统 | 🚧 |
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :--- |
|
||||
| [高级 AI 一:什么是 RAG 以及它如何工作](docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | 系统理解 RAG 原理与常见架构,为复杂应用提供知识检索基础 | ✅ |
|
||||
| [高级 AI 二:中高级 RAG 与工作流编排:以 LangGraph 为例](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | 使用 LangGraph 等工具设计多步工作流与中高级 RAG 系统 | 🚧 |
|
||||
|
||||
## 🛠️ 如何学习
|
||||
|
||||
|
||||
+51
-51
@@ -11,7 +11,7 @@
|
||||
|
||||
# Easy-Vibe: Learn Vibe Coding from 0 to 1
|
||||
|
||||
### *零基礎、專案制學習、建構第一個 AI 產品*
|
||||
### _零基礎、專案制學習、建構第一個 AI 產品_
|
||||
|
||||
<p align="center">
|
||||
📌 <a href="https://datawhalechina.github.io/easy-vibe/">開始線上閱讀 (Start Reading Online)</a>
|
||||
@@ -78,79 +78,79 @@
|
||||
|
||||
### 零、幼兒園
|
||||
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [前言:學習地圖](../docs/zh-cn/stage-0/0.1-learning-map/index.md) | 整體學習路徑導覽 | ✅ |
|
||||
| [初級一:AI 時代,會說話就會編程](../docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 通過貪吃蛇等案例初步感受 AI 編程的能力 | ✅ |
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :-------------------------------------------------------------------------------------------------- | :------------------------------------- | :--- |
|
||||
| [前言:學習地圖](../docs/zh-cn/stage-0/0.1-learning-map/index.md) | 整體學習路徑導覽 | ✅ |
|
||||
| [初級一:AI 時代,會說話就會編程](../docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 通過貪吃蛇等案例初步感受 AI 編程的能力 | ✅ |
|
||||
|
||||
### 一、AI 產品經理
|
||||
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [初級二:認識 AI IDE 工具](../docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | 學會使用 IDE,在本地製作小遊戲 | ✅ |
|
||||
| [初級三:動手做出原型](../docs/zh-cn/stage-1/1.2-building-prototype/index.md) | 從需求分析、AI 生成單頁面,再到生成多頁面產品原型 | ✅ |
|
||||
| [初級四:給原型加上 AI 能力](../docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | 學會接入常見 AI 能力(文本、圖片、影片) | ✅ |
|
||||
| [初級五:完整專案實戰](../docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 模擬真實場景、接受用戶反饋迭代,完整化專案 | ✅ |
|
||||
| [大作業:做一個完整的 Web 應用原型並展示](../docs/zh-cn/stage-1/1.5-final-project/index.md) | 完整實現應用,展示應用效果 | ✅ |
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :------------------------------------------------------------------------------------------- | :------------------------------------------------ | :--- |
|
||||
| [初級二:認識 AI IDE 工具](../docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | 學會使用 IDE,在本地製作小遊戲 | ✅ |
|
||||
| [初級三:動手做出原型](../docs/zh-cn/stage-1/1.2-building-prototype/index.md) | 從需求分析、AI 生成單頁面,再到生成多頁面產品原型 | ✅ |
|
||||
| [初級四:給原型加上 AI 能力](../docs/zh-cn/stage-1/1.3-integrating-ai-capabilities/index.md) | 學會接入常見 AI 能力(文本、圖片、影片) | ✅ |
|
||||
| [初級五:完整專案實戰](../docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 模擬真實場景、接受用戶反饋迭代,完整化專案 | ✅ |
|
||||
| [大作業:做一個完整的 Web 應用原型並展示](../docs/zh-cn/stage-1/1.5-final-project/index.md) | 完整實現應用,展示應用效果 | ✅ |
|
||||
|
||||
#### 附錄
|
||||
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [附錄A:產品思維補充](../docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | 從零到一做產品需要考慮的思維框架 | ✅ |
|
||||
| [附錄B:常見報錯及解決方案](../docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | vibe coding 中的常見錯誤及排查方法 | ✅ |
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :----------------------------------------------------------------------------------- | :--------------------------------- | :--- |
|
||||
| [附錄A:產品思維補充](../docs/zh-cn/stage-1/appendix-a-product-thinking/index.md) | 從零到一做產品需要考慮的思維框架 | ✅ |
|
||||
| [附錄B:常見報錯及解決方案](../docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | vibe coding 中的常見錯誤及排查方法 | ✅ |
|
||||
|
||||
### 二、初中級開發工程師
|
||||
|
||||
#### 前端部分
|
||||
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [前端零:使用 lovart 生產素材](../docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | 學會用 lovart 批量生成人物、場景等視覺素材,為 UI 設計和前端開發提供素材基礎 | 🚧 |
|
||||
| [前端一:Figma 與 MasterGo 入門](../docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | 用設計工具梳理信息架構和頁面結構,為前端實現打基礎 | 🚧 |
|
||||
| [前端二:建構第一個現代應用程式-UI 設計](../docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基於設計稿完成組件化界面,實現從設計到代碼的第一條鏈路 | 🚧 |
|
||||
| [前端三:參考 UI 設計規範與多產品 UI 設計](../docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 圍繞統一主視覺擴展多產品界面,練習系統化設計能力 | 🚧 |
|
||||
| [前端四:一起做霍格沃茨肖像](../docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | 從 0 到 1 做出接入 AI 能力的前端應用,串聯設計與開發 | ✅ |
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :--- |
|
||||
| [前端零:使用 lovart 生產素材](../docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | 學會用 lovart 批量生成人物、場景等視覺素材,為 UI 設計和前端開發提供素材基礎 | 🚧 |
|
||||
| [前端一:Figma 與 MasterGo 入門](../docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | 用設計工具梳理信息架構和頁面結構,為前端實現打基礎 | 🚧 |
|
||||
| [前端二:建構第一個現代應用程式-UI 設計](../docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基於設計稿完成組件化界面,實現從設計到代碼的第一條鏈路 | 🚧 |
|
||||
| [前端三:參考 UI 設計規範與多產品 UI 設計](../docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 圍繞統一主視覺擴展多產品界面,練習系統化設計能力 | 🚧 |
|
||||
| [前端四:一起做霍格沃茨肖像](../docs/zh-cn/stage-2/frontend/2.4-hogwarts-portraits/chapter4-lets-build-hogwarts-portraits.md) | 從 0 到 1 做出接入 AI 能力的前端應用,串聯設計與開發 | ✅ |
|
||||
|
||||
#### 後端與全棧部分
|
||||
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [後端一:什麼是 API](../docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | 理解 HTTP 接口與請求響應模型,為後端整合與聯調做準備 | ✅ |
|
||||
| [後端二:從數據庫到 Supabase](../docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | 在 Supabase 上落地數據庫和 API,打通數據模型與前端頁面 | ✅ |
|
||||
| [後端三:大模型輔助編寫接口代碼與接口文檔](../docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | 用大模型協助生成接口與數據庫文檔及代碼,實現可讀可測的後端 | 🚧 |
|
||||
| [後端四:Git 工作流](../docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | 在 Git 工作流中管理代碼,進行版本控制和協作 | ✅ |
|
||||
| [後端五:Zeabur 部署](../docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | 將應用部署到 Zeabur 上線 | ✅ |
|
||||
| [後端六:現代 CLI 開發工具](../docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | 使用 CLI 類 AI 編程工具加速開發與調試,形成個人工程化工作流 | ✅ |
|
||||
| [後端七:如何整合 Stripe 等收費系統](../docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 接入支付系統,完成收費鏈路與基礎結算流程 | 🚧 |
|
||||
| [大作業 1:建構第一個現代應用程式-全棧應用](../docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 綜合前端、後端與支付模塊,完成可上線的全棧 Web 應用 | 🚧 |
|
||||
| [大作業 2:現代前端組件庫 + Trae 實戰](../docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 使用現代前端組件庫與 Trae,獨立完成可登錄註冊並支持收費的產品 | 🚧 |
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------ | :--- |
|
||||
| [後端一:什麼是 API](../docs/zh-cn/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | 理解 HTTP 接口與請求響應模型,為後端整合與聯調做準備 | ✅ |
|
||||
| [後端二:從數據庫到 Supabase](../docs/zh-cn/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | 在 Supabase 上落地數據庫和 API,打通數據模型與前端頁面 | ✅ |
|
||||
| [後端三:大模型輔助編寫接口代碼與接口文檔](../docs/zh-cn/stage-2/backend/2.3-ai-interface-code/) | 用大模型協助生成接口與數據庫文檔及代碼,實現可讀可測的後端 | 🚧 |
|
||||
| [後端四:Git 工作流](../docs/zh-cn/stage-2/backend/2.4-git-workflow/extra1/extra1-what-is-git-and-what-is-github.md) | 在 Git 工作流中管理代碼,進行版本控制和協作 | ✅ |
|
||||
| [後端五:Zeabur 部署](../docs/zh-cn/stage-2/backend/2.5-zeabur-deployment/extra6/extra6-zeabur-what-is-it-and-how-to-deploy-web-applications.md) | 將應用部署到 Zeabur 上線 | ✅ |
|
||||
| [後端六:現代 CLI 開發工具](../docs/zh-cn/stage-2/backend/2.6-modern-cli/extra7/extra7-cli-ai-coding-tools-and-the-principles-of-test-driven-development.md) | 使用 CLI 類 AI 編程工具加速開發與調試,形成個人工程化工作流 | ✅ |
|
||||
| [後端七:如何整合 Stripe 等收費系統](../docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 接入支付系統,完成收費鏈路與基礎結算流程 | 🚧 |
|
||||
| [大作業 1:建構第一個現代應用程式-全棧應用](../docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 綜合前端、後端與支付模塊,完成可上線的全棧 Web 應用 | 🚧 |
|
||||
| [大作業 2:現代前端組件庫 + Trae 實戰](../docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 使用現代前端組件庫與 Trae,獨立完成可登錄註冊並支持收費的產品 | 🚧 |
|
||||
|
||||
#### AI 能力附錄
|
||||
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [AI 一:Dify 入門與知識庫整合](../docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | 用 Dify Workflow 與基礎 RAG 搭建工具類產品,為後續應用升級打樣 | ✅ |
|
||||
| [AI 二:學會查詢 AI 詞典與整合多模態 API](../docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 學會查找合適的模型與 API,並把文本、圖像等多模態能力接入產品 | 🚧 |
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------- | :--- |
|
||||
| [AI 一:Dify 入門與知識庫整合](../docs/zh-cn/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | 用 Dify Workflow 與基礎 RAG 搭建工具類產品,為後續應用升級打樣 | ✅ |
|
||||
| [AI 二:學會查詢 AI 詞典與整合多模態 API](../docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 學會查找合適的模型與 API,並把文本、圖像等多模態能力接入產品 | 🚧 |
|
||||
|
||||
### 三、高級開發工程師
|
||||
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [高級一:MCP 與 ClaudeCode Skills](../docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | 通過 MCP 與 Skills 擴展 IDE 能力,把外部服務接成工具 | 🚧 |
|
||||
| [高級二:如何讓 Coding Tools 長時間工作](../docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 設計和配置長時間運行的任務,讓 Coding Tools 更穩定可靠 | 🚧 |
|
||||
| [高級三:多平台開發:如何建構微信小程式](../docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | 了解微信小程式生態,從官方模板到上線完成一個前端小程式 | ✅ |
|
||||
| [高級四:多平台開發:如何建構微信小程式-包含後端](../docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 在小程式中接入數據庫與後端邏輯,打通完整業務閉環 | 🚧 |
|
||||
| [高級五:多平台開發:如何建構安卓程式](../docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | 使用 Expo 等工具,完成 Web/原生一體化的安卓應用開發 | 🚧 |
|
||||
| [高級六:多平台開發:如何建構 iOS 程式](../docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | 使用 Expo 等工具,完成 Web/原生一體化的 iOS 應用開發 | 🚧 |
|
||||
| [高級七:如何建構屬於自己的個人網頁與學術博客](../docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 從選型、搭建到部署,建構展示個人專案與學術成果的長久在線主頁 | 🚧 |
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :---------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :--- |
|
||||
| [高級一:MCP 與 ClaudeCode Skills](../docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | 通過 MCP 與 Skills 擴展 IDE 能力,把外部服務接成工具 | 🚧 |
|
||||
| [高級二:如何讓 Coding Tools 長時間工作](../docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 設計和配置長時間運行的任務,讓 Coding Tools 更穩定可靠 | 🚧 |
|
||||
| [高級三:多平台開發:如何建構微信小程式](../docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | 了解微信小程式生態,從官方模板到上線完成一個前端小程式 | ✅ |
|
||||
| [高級四:多平台開發:如何建構微信小程式-包含後端](../docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 在小程式中接入數據庫與後端邏輯,打通完整業務閉環 | 🚧 |
|
||||
| [高級五:多平台開發:如何建構安卓程式](../docs/zh-cn/stage-3/cross-platform/3.5-android-app/) | 使用 Expo 等工具,完成 Web/原生一體化的安卓應用開發 | 🚧 |
|
||||
| [高級六:多平台開發:如何建構 iOS 程式](../docs/zh-cn/stage-3/cross-platform/3.6-ios-app/) | 使用 Expo 等工具,完成 Web/原生一體化的 iOS 應用開發 | 🚧 |
|
||||
| [高級七:如何建構屬於自己的個人網頁與學術博客](../docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 從選型、搭建到部署,建構展示個人專案與學術成果的長久在線主頁 | 🚧 |
|
||||
|
||||
#### AI 能力附錄
|
||||
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :--- | :--- | :--- |
|
||||
| [高級 AI 一:什麼是 RAG 以及它如何工作](../docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | 系統理解 RAG 原理與常見架構,為複雜應用提供知識檢索基礎 | ✅ |
|
||||
| [高級 AI 二:中高級 RAG 與工作流編排:以 LangGraph 為例](../docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | 使用 LangGraph 等工具設計多步工作流與中高級 RAG 系統 | 🚧 |
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :--- |
|
||||
| [高級 AI 一:什麼是 RAG 以及它如何工作](../docs/zh-cn/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | 系統理解 RAG 原理與常見架構,為複雜應用提供知識檢索基礎 | ✅ |
|
||||
| [高級 AI 二:中高級 RAG 與工作流編排:以 LangGraph 為例](../docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | 使用 LangGraph 等工具設計多步工作流與中高級 RAG 系統 | 🚧 |
|
||||
|
||||
## 🛠️ 如何學習
|
||||
|
||||
|
||||
+221
-51
@@ -14,30 +14,88 @@ const base = process.env.BASE || (isVercel || isEdgeOne ? '/' : '/easy-vibe/')
|
||||
|
||||
// 语言映射配置
|
||||
const localeMap = {
|
||||
'zh-cn': { ogLocale: 'zh_CN', twitterSite: '@datawhale', lang: 'zh-CN', hreflang: 'zh-CN' },
|
||||
'en-us': { ogLocale: 'en_US', twitterSite: '@datawhale', lang: 'en-US', hreflang: 'en' },
|
||||
'ja-jp': { ogLocale: 'ja_JP', twitterSite: '@datawhale', lang: 'ja-JP', hreflang: 'ja' },
|
||||
'zh-tw': { ogLocale: 'zh_TW', twitterSite: '@datawhale', lang: 'zh-TW', hreflang: 'zh-TW' },
|
||||
'ko-kr': { ogLocale: 'ko_KR', twitterSite: '@datawhale', lang: 'ko-KR', hreflang: 'ko' },
|
||||
'es-es': { ogLocale: 'es_ES', twitterSite: '@datawhale', lang: 'es-ES', hreflang: 'es' },
|
||||
'fr-fr': { ogLocale: 'fr_FR', twitterSite: '@datawhale', lang: 'fr-FR', hreflang: 'fr' },
|
||||
'de-de': { ogLocale: 'de_DE', twitterSite: '@datawhale', lang: 'de-DE', hreflang: 'de' },
|
||||
'ar-sa': { ogLocale: 'ar_SA', twitterSite: '@datawhale', lang: 'ar-SA', hreflang: 'ar' },
|
||||
'vi-vn': { ogLocale: 'vi_VN', twitterSite: '@datawhale', lang: 'vi-VN', hreflang: 'vi' }
|
||||
'zh-cn': {
|
||||
ogLocale: 'zh_CN',
|
||||
twitterSite: '@datawhale',
|
||||
lang: 'zh-CN',
|
||||
hreflang: 'zh-CN'
|
||||
},
|
||||
'en-us': {
|
||||
ogLocale: 'en_US',
|
||||
twitterSite: '@datawhale',
|
||||
lang: 'en-US',
|
||||
hreflang: 'en'
|
||||
},
|
||||
'ja-jp': {
|
||||
ogLocale: 'ja_JP',
|
||||
twitterSite: '@datawhale',
|
||||
lang: 'ja-JP',
|
||||
hreflang: 'ja'
|
||||
},
|
||||
'zh-tw': {
|
||||
ogLocale: 'zh_TW',
|
||||
twitterSite: '@datawhale',
|
||||
lang: 'zh-TW',
|
||||
hreflang: 'zh-TW'
|
||||
},
|
||||
'ko-kr': {
|
||||
ogLocale: 'ko_KR',
|
||||
twitterSite: '@datawhale',
|
||||
lang: 'ko-KR',
|
||||
hreflang: 'ko'
|
||||
},
|
||||
'es-es': {
|
||||
ogLocale: 'es_ES',
|
||||
twitterSite: '@datawhale',
|
||||
lang: 'es-ES',
|
||||
hreflang: 'es'
|
||||
},
|
||||
'fr-fr': {
|
||||
ogLocale: 'fr_FR',
|
||||
twitterSite: '@datawhale',
|
||||
lang: 'fr-FR',
|
||||
hreflang: 'fr'
|
||||
},
|
||||
'de-de': {
|
||||
ogLocale: 'de_DE',
|
||||
twitterSite: '@datawhale',
|
||||
lang: 'de-DE',
|
||||
hreflang: 'de'
|
||||
},
|
||||
'ar-sa': {
|
||||
ogLocale: 'ar_SA',
|
||||
twitterSite: '@datawhale',
|
||||
lang: 'ar-SA',
|
||||
hreflang: 'ar'
|
||||
},
|
||||
'vi-vn': {
|
||||
ogLocale: 'vi_VN',
|
||||
twitterSite: '@datawhale',
|
||||
lang: 'vi-VN',
|
||||
hreflang: 'vi'
|
||||
}
|
||||
}
|
||||
|
||||
// SEO 相关配置
|
||||
const getSeoHead = (locale, title, description, path = '') => {
|
||||
const seoConfig = localeMap[locale] || localeMap['zh-cn']
|
||||
const siteUrl = isVercel ? 'https://your-project.vercel.app' : 'https://datawhalechina.github.io/easy-vibe'
|
||||
const siteUrl = isVercel
|
||||
? 'https://your-project.vercel.app'
|
||||
: 'https://datawhalechina.github.io/easy-vibe'
|
||||
const canonicalUrl = path ? `${siteUrl}${path}` : `${siteUrl}/${locale}/`
|
||||
const ogImageUrl = `${siteUrl}${base}logo.png`
|
||||
|
||||
const head = [
|
||||
['link', { rel: 'icon', href: `${base}logo.png`.replace('//', '/') }],
|
||||
['link', { rel: 'stylesheet', href: `${base}style.css`.replace('//', '/') }],
|
||||
[
|
||||
'link',
|
||||
{ rel: 'stylesheet', href: `${base}style.css`.replace('//', '/') }
|
||||
],
|
||||
['meta', { name: 'theme-color', content: '#3eaf7c' }],
|
||||
['meta', { name: 'viewport', content: 'width=device-width, initial-scale=1.0' }],
|
||||
[
|
||||
'meta',
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }
|
||||
],
|
||||
['meta', { name: 'format-detection', content: 'telephone=no' }],
|
||||
['link', { rel: 'canonical', href: canonicalUrl }],
|
||||
// Open Graph / Facebook
|
||||
@@ -60,7 +118,14 @@ const getSeoHead = (locale, title, description, path = '') => {
|
||||
['meta', { name: 'twitter:image', content: ogImageUrl }],
|
||||
['meta', { name: 'twitter:image:alt', content: title }],
|
||||
// Additional SEO
|
||||
['meta', { name: 'keywords', content: 'AI编程,Vibe Coding,Claude Code,Cursor,Trae,AI IDE,零基础学编程,AI辅助开发,产品经理,全栈开发,编程教程,编程工具,Datawhale,Supabase,React,大模型,LLM,人工智能,微信小程序,Android开发,iOS开发,MCP,RAG,LangGraph,Dify,跨平台开发,AI应用开发' }],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
name: 'keywords',
|
||||
content:
|
||||
'AI编程,Vibe Coding,Claude Code,Cursor,Trae,AI IDE,零基础学编程,AI辅助开发,产品经理,全栈开发,编程教程,编程工具,Datawhale,Supabase,React,大模型,LLM,人工智能,微信小程序,Android开发,iOS开发,MCP,RAG,LangGraph,Dify,跨平台开发,AI应用开发'
|
||||
}
|
||||
],
|
||||
['meta', { name: 'author', content: 'Datawhale' }],
|
||||
['meta', { name: 'robots', content: 'index,follow' }],
|
||||
['meta', { name: 'googlebot', content: 'index,follow' }],
|
||||
@@ -71,10 +136,20 @@ const getSeoHead = (locale, title, description, path = '') => {
|
||||
]
|
||||
|
||||
// 添加 hreflang 标签
|
||||
Object.keys(localeMap).forEach(lang => {
|
||||
head.push(['link', { rel: 'alternate', hreflang: localeMap[lang].hreflang, href: `${siteUrl}/${lang}/` }])
|
||||
Object.keys(localeMap).forEach((lang) => {
|
||||
head.push([
|
||||
'link',
|
||||
{
|
||||
rel: 'alternate',
|
||||
hreflang: localeMap[lang].hreflang,
|
||||
href: `${siteUrl}/${lang}/`
|
||||
}
|
||||
])
|
||||
})
|
||||
head.push(['link', { rel: 'alternate', hreflang: 'x-default', href: `${siteUrl}/zh-cn/` }])
|
||||
head.push([
|
||||
'link',
|
||||
{ rel: 'alternate', hreflang: 'x-default', href: `${siteUrl}/zh-cn/` }
|
||||
])
|
||||
|
||||
// 添加 JSON-LD 结构化数据
|
||||
const jsonLd = {
|
||||
@@ -138,7 +213,11 @@ export default defineConfig({
|
||||
return items.filter((item) => {
|
||||
const url = item.url
|
||||
// 排除旧版内容目录
|
||||
if (url.includes('/extra/') || url.includes('/examples/') || url.includes('/project/')) {
|
||||
if (
|
||||
url.includes('/extra/') ||
|
||||
url.includes('/examples/') ||
|
||||
url.includes('/project/')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
// 包含所有语言版本
|
||||
@@ -155,8 +234,13 @@ export default defineConfig({
|
||||
lang: 'zh-CN',
|
||||
link: '/zh-cn/',
|
||||
title: 'Easy-Vibe 教程',
|
||||
description: '从零到一学习 Vibe Coding - 零基础学会用 AI 编程,掌握 Claude Code、Cursor 等 AI IDE 工具',
|
||||
head: getSeoHead('zh-cn', 'Easy-Vibe 教程', '从零到一学习 Vibe Coding - 零基础学会用 AI 编程,掌握 Claude Code、Cursor 等 AI IDE 工具'),
|
||||
description:
|
||||
'从零到一学习 Vibe Coding - 零基础学会用 AI 编程,掌握 Claude Code、Cursor 等 AI IDE 工具',
|
||||
head: getSeoHead(
|
||||
'zh-cn',
|
||||
'Easy-Vibe 教程',
|
||||
'从零到一学习 Vibe Coding - 零基础学会用 AI 编程,掌握 Claude Code、Cursor 等 AI IDE 工具'
|
||||
),
|
||||
themeConfig: {
|
||||
...commonThemeConfig,
|
||||
outline: {
|
||||
@@ -166,7 +250,10 @@ export default defineConfig({
|
||||
nav: [
|
||||
{ text: '首页', link: '/zh-cn/' },
|
||||
{ text: '新手入门', link: '/zh-cn/stage-0/0.1-learning-map/' },
|
||||
{ text: '产品经理', link: '/zh-cn/stage-1/1.1-introduction-to-ai-ide/' },
|
||||
{
|
||||
text: '产品经理',
|
||||
link: '/zh-cn/stage-1/1.1-introduction-to-ai-ide/'
|
||||
},
|
||||
{
|
||||
text: '初中级开发',
|
||||
link: '/zh-cn/stage-2/frontend/2.0-lovart-assets/'
|
||||
@@ -449,29 +536,52 @@ export default defineConfig({
|
||||
items: [
|
||||
{ text: '大语言模型', link: '/zh-cn/appendix/llm-intro' },
|
||||
{ text: '多模态大模型', link: '/zh-cn/appendix/vlm-intro' },
|
||||
{ text: 'AI 绘画原理', link: '/zh-cn/appendix/image-gen-intro' },
|
||||
{
|
||||
text: 'AI 绘画原理',
|
||||
link: '/zh-cn/appendix/image-gen-intro'
|
||||
},
|
||||
{ text: 'AI 音频模型', link: '/zh-cn/appendix/audio-intro' },
|
||||
{ text: '提示词工程', link: '/zh-cn/appendix/prompt-engineering' },
|
||||
{ text: '上下文工程', link: '/zh-cn/appendix/context-engineering' },
|
||||
{
|
||||
text: '提示词工程',
|
||||
link: '/zh-cn/appendix/prompt-engineering'
|
||||
},
|
||||
{
|
||||
text: '上下文工程',
|
||||
link: '/zh-cn/appendix/context-engineering'
|
||||
},
|
||||
{ text: 'Agent 智能体', link: '/zh-cn/appendix/agent-intro' },
|
||||
{ text: 'AI 能力词典', link: '/zh-cn/appendix/ai-capability-dictionary' }
|
||||
{
|
||||
text: 'AI 能力词典',
|
||||
link: '/zh-cn/appendix/ai-capability-dictionary'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Web 基础',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'HTML/CSS/JS 基础', link: '/zh-cn/appendix/web-basics' },
|
||||
{ text: 'URL 到浏览器显示', link: '/zh-cn/appendix/url-to-browser' }
|
||||
{
|
||||
text: 'HTML/CSS/JS 基础',
|
||||
link: '/zh-cn/appendix/web-basics'
|
||||
},
|
||||
{
|
||||
text: 'URL 到浏览器显示',
|
||||
link: '/zh-cn/appendix/url-to-browser'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '开发基础',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'IDE 原理', link: '/zh-cn/appendix/ide-intro' },
|
||||
{ text: '终端入门', link: '/zh-cn/appendix/terminal-intro' },
|
||||
{ text: 'Git 详细介绍', link: '/zh-cn/appendix/git-intro' },
|
||||
{ text: '计算机网络', link: '/zh-cn/appendix/computer-networks' },
|
||||
{ text: '数据库原理', link: '/zh-cn/appendix/database-intro' },
|
||||
{
|
||||
text: '计算机网络',
|
||||
link: '/zh-cn/appendix/computer-networks'
|
||||
},
|
||||
{ text: '部署与上线', link: '/zh-cn/appendix/deployment' }
|
||||
]
|
||||
}
|
||||
@@ -486,8 +596,13 @@ export default defineConfig({
|
||||
lang: 'en-US',
|
||||
link: '/en-us/',
|
||||
title: 'Easy-Vibe Tutorial',
|
||||
description: 'Learn Vibe Coding from Zero to Advanced - Master AI programming with Claude Code, Cursor, and other AI IDE tools',
|
||||
head: getSeoHead('en-us', 'Easy-Vibe Tutorial', 'Learn Vibe Coding from Zero to Advanced - Master AI programming with Claude Code, Cursor, and other AI IDE tools'),
|
||||
description:
|
||||
'Learn Vibe Coding from Zero to Advanced - Master AI programming with Claude Code, Cursor, and other AI IDE tools',
|
||||
head: getSeoHead(
|
||||
'en-us',
|
||||
'Easy-Vibe Tutorial',
|
||||
'Learn Vibe Coding from Zero to Advanced - Master AI programming with Claude Code, Cursor, and other AI IDE tools'
|
||||
),
|
||||
themeConfig: {
|
||||
...commonThemeConfig,
|
||||
outline: {
|
||||
@@ -497,7 +612,10 @@ export default defineConfig({
|
||||
nav: [
|
||||
{ text: 'Home', link: '/en-us/' },
|
||||
{ text: 'Getting Started', link: '/en-us/stage-0/0.1-learning-map/' },
|
||||
{ text: 'AI Product Manager', link: '/en-us/stage-1/1.1-introduction-to-ai-ide/' },
|
||||
{
|
||||
text: 'AI Product Manager',
|
||||
link: '/en-us/stage-1/1.1-introduction-to-ai-ide/'
|
||||
},
|
||||
{
|
||||
text: 'Full-Stack Development',
|
||||
link: '/en-us/stage-2/frontend/2.0-lovart-assets/'
|
||||
@@ -519,8 +637,13 @@ export default defineConfig({
|
||||
lang: 'ja-JP',
|
||||
link: '/ja-jp/',
|
||||
title: 'Easy-Vibe チュートリアル',
|
||||
description: 'ゼロから学ぶ Vibe Coding - AIプログラミングを初めから体系的に学習',
|
||||
head: getSeoHead('ja-jp', 'Easy-Vibe チュートリアル', 'ゼロから学ぶ Vibe Coding - AIプログラミングを初めから体系的に学習'),
|
||||
description:
|
||||
'ゼロから学ぶ Vibe Coding - AIプログラミングを初めから体系的に学習',
|
||||
head: getSeoHead(
|
||||
'ja-jp',
|
||||
'Easy-Vibe チュートリアル',
|
||||
'ゼロから学ぶ Vibe Coding - AIプログラミングを初めから体系的に学習'
|
||||
),
|
||||
themeConfig: {
|
||||
...commonThemeConfig,
|
||||
outline: {
|
||||
@@ -530,7 +653,10 @@ export default defineConfig({
|
||||
nav: [
|
||||
{ text: 'ホーム', link: '/ja-jp/' },
|
||||
{ text: '入門', link: '/ja-jp/stage-0/0.1-learning-map/' },
|
||||
{ text: 'AI プロダクトマネージャー', link: '/ja-jp/stage-1/1.1-introduction-to-ai-ide/' },
|
||||
{
|
||||
text: 'AI プロダクトマネージャー',
|
||||
link: '/ja-jp/stage-1/1.1-introduction-to-ai-ide/'
|
||||
},
|
||||
{
|
||||
text: 'フルスタック開発',
|
||||
link: '/ja-jp/stage-2/frontend/2.0-lovart-assets/'
|
||||
@@ -550,8 +676,13 @@ export default defineConfig({
|
||||
lang: 'zh-TW',
|
||||
link: '/zh-tw/',
|
||||
title: 'Easy-Vibe 教程',
|
||||
description: '從零到一學習 Vibe Coding - 零基礎學會用 AI 編程,掌握 Claude Code、Cursor 等 AI IDE 工具',
|
||||
head: getSeoHead('zh-tw', 'Easy-Vibe 教程', '從零到一學習 Vibe Coding - 零基礎學會用 AI 編程,掌握 Claude Code、Cursor 等 AI IDE 工具'),
|
||||
description:
|
||||
'從零到一學習 Vibe Coding - 零基礎學會用 AI 編程,掌握 Claude Code、Cursor 等 AI IDE 工具',
|
||||
head: getSeoHead(
|
||||
'zh-tw',
|
||||
'Easy-Vibe 教程',
|
||||
'從零到一學習 Vibe Coding - 零基礎學會用 AI 編程,掌握 Claude Code、Cursor 等 AI IDE 工具'
|
||||
),
|
||||
themeConfig: {
|
||||
...commonThemeConfig,
|
||||
outline: {
|
||||
@@ -561,7 +692,10 @@ export default defineConfig({
|
||||
nav: [
|
||||
{ text: '首頁', link: '/zh-tw/' },
|
||||
{ text: '新手入門', link: '/zh-tw/stage-0/0.1-learning-map/' },
|
||||
{ text: '產品經理', link: '/zh-tw/stage-1/1.1-introduction-to-ai-ide/' },
|
||||
{
|
||||
text: '產品經理',
|
||||
link: '/zh-tw/stage-1/1.1-introduction-to-ai-ide/'
|
||||
},
|
||||
{
|
||||
text: '初中級開發',
|
||||
link: '/zh-tw/stage-2/frontend/2.0-lovart-assets/'
|
||||
@@ -580,8 +714,13 @@ export default defineConfig({
|
||||
lang: 'ko-KR',
|
||||
link: '/ko-kr/',
|
||||
title: 'Easy-Vibe 튜토리얼',
|
||||
description: 'Vibe Coding을 처음부터 체계적으로 학습합니다 - AI 프로그래밍을 처음부터 고급까지',
|
||||
head: getSeoHead('ko-kr', 'Easy-Vibe 튜토리얼', 'Vibe Coding을 처음부터 체계적으로 학습합니다 - AI 프로그래밍을 처음부터 고급까지'),
|
||||
description:
|
||||
'Vibe Coding을 처음부터 체계적으로 학습합니다 - AI 프로그래밍을 처음부터 고급까지',
|
||||
head: getSeoHead(
|
||||
'ko-kr',
|
||||
'Easy-Vibe 튜토리얼',
|
||||
'Vibe Coding을 처음부터 체계적으로 학습합니다 - AI 프로그래밍을 처음부터 고급까지'
|
||||
),
|
||||
themeConfig: {
|
||||
...commonThemeConfig,
|
||||
outline: {
|
||||
@@ -610,8 +749,13 @@ export default defineConfig({
|
||||
lang: 'es-ES',
|
||||
link: '/es-es/',
|
||||
title: 'Tutorial de Easy-Vibe',
|
||||
description: 'Aprende Vibe Coding desde cero hasta avanzado - Domina la programación con IA desde el principio',
|
||||
head: getSeoHead('es-es', 'Tutorial de Easy-Vibe', 'Aprende Vibe Coding desde cero hasta avanzado - Domina la programación con IA desde el principio'),
|
||||
description:
|
||||
'Aprende Vibe Coding desde cero hasta avanzado - Domina la programación con IA desde el principio',
|
||||
head: getSeoHead(
|
||||
'es-es',
|
||||
'Tutorial de Easy-Vibe',
|
||||
'Aprende Vibe Coding desde cero hasta avanzado - Domina la programación con IA desde el principio'
|
||||
),
|
||||
themeConfig: {
|
||||
...commonThemeConfig,
|
||||
outline: {
|
||||
@@ -621,7 +765,10 @@ export default defineConfig({
|
||||
nav: [
|
||||
{ text: 'Inicio', link: '/es-es/' },
|
||||
{ text: 'Principiante', link: '/es-es/stage-0/0.1-learning-map/' },
|
||||
{ text: 'PM de IA', link: '/es-es/stage-1/1.1-introduction-to-ai-ide/' },
|
||||
{
|
||||
text: 'PM de IA',
|
||||
link: '/es-es/stage-1/1.1-introduction-to-ai-ide/'
|
||||
},
|
||||
{
|
||||
text: 'Desarrollo Full Stack',
|
||||
link: '/es-es/stage-2/frontend/2.0-lovart-assets/'
|
||||
@@ -640,8 +787,13 @@ export default defineConfig({
|
||||
lang: 'fr-FR',
|
||||
link: '/fr-fr/',
|
||||
title: 'Tutoriel Easy-Vibe',
|
||||
description: 'Apprenez Vibe Coding de zéro à avancé - Maîtrisez la programmation IA du début au niveau avancé',
|
||||
head: getSeoHead('fr-fr', 'Tutoriel Easy-Vibe', 'Apprenez Vibe Coding de zéro à avancé - Maîtrisez la programmation IA du début au niveau avancé'),
|
||||
description:
|
||||
'Apprenez Vibe Coding de zéro à avancé - Maîtrisez la programmation IA du début au niveau avancé',
|
||||
head: getSeoHead(
|
||||
'fr-fr',
|
||||
'Tutoriel Easy-Vibe',
|
||||
'Apprenez Vibe Coding de zéro à avancé - Maîtrisez la programmation IA du début au niveau avancé'
|
||||
),
|
||||
themeConfig: {
|
||||
...commonThemeConfig,
|
||||
outline: {
|
||||
@@ -670,8 +822,13 @@ export default defineConfig({
|
||||
lang: 'de-DE',
|
||||
link: '/de-de/',
|
||||
title: 'Easy-Vibe Tutorial',
|
||||
description: 'Lernen Sie Vibe Coding von Null bis Fortgeschritten - Meistern Sie die KI-Programmierung von Grund auf',
|
||||
head: getSeoHead('de-de', 'Easy-Vibe Tutorial', 'Lernen Sie Vibe Coding von Null bis Fortgeschritten - Meistern Sie die KI-Programmierung von Grund auf'),
|
||||
description:
|
||||
'Lernen Sie Vibe Coding von Null bis Fortgeschritten - Meistern Sie die KI-Programmierung von Grund auf',
|
||||
head: getSeoHead(
|
||||
'de-de',
|
||||
'Easy-Vibe Tutorial',
|
||||
'Lernen Sie Vibe Coding von Null bis Fortgeschritten - Meistern Sie die KI-Programmierung von Grund auf'
|
||||
),
|
||||
themeConfig: {
|
||||
...commonThemeConfig,
|
||||
outline: {
|
||||
@@ -700,8 +857,13 @@ export default defineConfig({
|
||||
lang: 'ar-SA',
|
||||
link: '/ar-sa/',
|
||||
title: 'دروس Easy-Vibe',
|
||||
description: 'تعلم Vibe Coding من الصفر إلى المتقدم - إتقان البرمجة بالذكاء الاصطناعي من البداية',
|
||||
head: getSeoHead('ar-sa', 'دروس Easy-Vibe', 'تعلم Vibe Coding من الصفر إلى المتقدم - إتقان البرمجة بالذكاء الاصطناعي من البداية'),
|
||||
description:
|
||||
'تعلم Vibe Coding من الصفر إلى المتقدم - إتقان البرمجة بالذكاء الاصطناعي من البداية',
|
||||
head: getSeoHead(
|
||||
'ar-sa',
|
||||
'دروس Easy-Vibe',
|
||||
'تعلم Vibe Coding من الصفر إلى المتقدم - إتقان البرمجة بالذكاء الاصطناعي من البداية'
|
||||
),
|
||||
themeConfig: {
|
||||
...commonThemeConfig,
|
||||
outline: {
|
||||
@@ -711,7 +873,10 @@ export default defineConfig({
|
||||
nav: [
|
||||
{ text: 'الرئيسية', link: '/ar-sa/' },
|
||||
{ text: 'المبتدئين', link: '/ar-sa/stage-0/0.1-learning-map/' },
|
||||
{ text: 'مدير منتج AI', link: '/ar-sa/stage-1/1.1-introduction-to-ai-ide/' },
|
||||
{
|
||||
text: 'مدير منتج AI',
|
||||
link: '/ar-sa/stage-1/1.1-introduction-to-ai-ide/'
|
||||
},
|
||||
{
|
||||
text: 'تطوير Full Stack',
|
||||
link: '/ar-sa/stage-2/frontend/2.0-lovart-assets/'
|
||||
@@ -730,8 +895,13 @@ export default defineConfig({
|
||||
lang: 'vi-VN',
|
||||
link: '/vi-vn/',
|
||||
title: 'Hướng dẫn Easy-Vibe',
|
||||
description: 'Học Vibe Coding từ cơ bản đến nâng cao - Làm chủ lập trình AI từ cơ bản đến chuyên sâu',
|
||||
head: getSeoHead('vi-vn', 'Hướng dẫn Easy-Vibe', 'Học Vibe Coding từ cơ bản đến nâng cao - Làm chủ lập trình AI từ cơ bản đến chuyên sâu'),
|
||||
description:
|
||||
'Học Vibe Coding từ cơ bản đến nâng cao - Làm chủ lập trình AI từ cơ bản đến chuyên sâu',
|
||||
head: getSeoHead(
|
||||
'vi-vn',
|
||||
'Hướng dẫn Easy-Vibe',
|
||||
'Học Vibe Coding từ cơ bản đến nâng cao - Làm chủ lập trình AI từ cơ bản đến chuyên sâu'
|
||||
),
|
||||
themeConfig: {
|
||||
...commonThemeConfig,
|
||||
outline: {
|
||||
|
||||
@@ -30,14 +30,16 @@ const lineHeight = ref(DEFAULT_LINE_HEIGHT)
|
||||
const isHydrated = ref(false)
|
||||
|
||||
const clampFontSize = (value) => {
|
||||
if (value === null || value === undefined || value === '') return DEFAULT_FONT_SIZE
|
||||
if (value === null || value === undefined || value === '')
|
||||
return DEFAULT_FONT_SIZE
|
||||
const numeric = Number(value)
|
||||
if (!Number.isFinite(numeric)) return DEFAULT_FONT_SIZE
|
||||
return Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, numeric))
|
||||
}
|
||||
|
||||
const clampLineHeight = (value) => {
|
||||
if (value === null || value === undefined || value === '') return DEFAULT_LINE_HEIGHT
|
||||
if (value === null || value === undefined || value === '')
|
||||
return DEFAULT_LINE_HEIGHT
|
||||
const numeric = Number(value)
|
||||
if (!Number.isFinite(numeric)) return DEFAULT_LINE_HEIGHT
|
||||
return Math.min(MAX_LINE_HEIGHT, Math.max(MIN_LINE_HEIGHT, numeric))
|
||||
@@ -50,7 +52,10 @@ const applyFontSize = (size) => {
|
||||
|
||||
const applyLineHeight = (value) => {
|
||||
if (typeof document === 'undefined') return
|
||||
document.documentElement.style.setProperty('--ev-doc-line-height', String(value))
|
||||
document.documentElement.style.setProperty(
|
||||
'--ev-doc-line-height',
|
||||
String(value)
|
||||
)
|
||||
}
|
||||
|
||||
const decreaseFontSize = () => {
|
||||
@@ -71,7 +76,9 @@ const resetLineHeight = () => {
|
||||
|
||||
onMounted(() => {
|
||||
const saved = clampFontSize(localStorage.getItem(FONT_SIZE_STORAGE_KEY))
|
||||
const savedLineHeight = clampLineHeight(localStorage.getItem(LINE_HEIGHT_STORAGE_KEY))
|
||||
const savedLineHeight = clampLineHeight(
|
||||
localStorage.getItem(LINE_HEIGHT_STORAGE_KEY)
|
||||
)
|
||||
fontSize.value = saved
|
||||
lineHeight.value = savedLineHeight
|
||||
applyFontSize(saved)
|
||||
@@ -105,7 +112,7 @@ watch(lineHeight, (next) => {
|
||||
class="ev-fontsize-button"
|
||||
type="button"
|
||||
aria-label="阅读设置"
|
||||
style="margin-left: 16px; padding: 0; width: 32px;"
|
||||
style="margin-left: 16px; padding: 0; width: 32px"
|
||||
>
|
||||
<el-icon :size="16"><Setting /></el-icon>
|
||||
</button>
|
||||
@@ -139,7 +146,12 @@ watch(lineHeight, (next) => {
|
||||
A+
|
||||
</button>
|
||||
</div>
|
||||
<el-slider v-model="fontSize" :min="MIN_FONT_SIZE" :max="MAX_FONT_SIZE" :step="1" />
|
||||
<el-slider
|
||||
v-model="fontSize"
|
||||
:min="MIN_FONT_SIZE"
|
||||
:max="MAX_FONT_SIZE"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ev-setting-group">
|
||||
@@ -148,7 +160,11 @@ watch(lineHeight, (next) => {
|
||||
<div class="ev-setting-value">{{ lineHeight.toFixed(2) }}</div>
|
||||
</div>
|
||||
<div class="ev-fontsize-actions">
|
||||
<button class="ev-fontsize-action" type="button" @click="resetLineHeight">
|
||||
<button
|
||||
class="ev-fontsize-action"
|
||||
type="button"
|
||||
@click="resetLineHeight"
|
||||
>
|
||||
默认
|
||||
</button>
|
||||
<button
|
||||
|
||||
@@ -24,7 +24,13 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const hasMeta = computed(() => props.duration || props.expectedOutput || props.coreOutput || props.assignment)
|
||||
const hasMeta = computed(
|
||||
() =>
|
||||
props.duration ||
|
||||
props.expectedOutput ||
|
||||
props.coreOutput ||
|
||||
props.assignment
|
||||
)
|
||||
const hasTags = computed(() => props.tags && props.tags.length > 0)
|
||||
</script>
|
||||
|
||||
@@ -69,7 +75,11 @@ const hasTags = computed(() => props.tags && props.tags.length > 0)
|
||||
<div class="card-label">预期产出</div>
|
||||
<div class="output-container">
|
||||
<div v-if="coreOutput" class="core-output">{{ coreOutput }}</div>
|
||||
<div v-if="expectedOutput" class="output-desc" v-html="expectedOutput"></div>
|
||||
<div
|
||||
v-if="expectedOutput"
|
||||
class="output-desc"
|
||||
v-html="expectedOutput"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,7 +108,11 @@ const hasTags = computed(() => props.tags && props.tags.length > 0)
|
||||
|
||||
.objective-section {
|
||||
padding: 24px 28px;
|
||||
background: linear-gradient(to right, rgba(var(--vp-c-brand-rgb), 0.05), transparent);
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
rgba(var(--vp-c-brand-rgb), 0.05),
|
||||
transparent
|
||||
);
|
||||
border-bottom: 1px dashed var(--vp-c-divider);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,16 +26,21 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetch('https://api.github.com/repos/datawhalechina/easy-vibe')
|
||||
const res = await fetch(
|
||||
'https://api.github.com/repos/datawhalechina/easy-vibe'
|
||||
)
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
stars.value = data.stargazers_count
|
||||
formattedStars.value = formatStars(stars.value)
|
||||
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify({
|
||||
count: stars.value,
|
||||
timestamp: Date.now()
|
||||
}))
|
||||
localStorage.setItem(
|
||||
CACHE_KEY,
|
||||
JSON.stringify({
|
||||
count: stars.value,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch GitHub stars:', e)
|
||||
@@ -53,9 +58,21 @@ onMounted(async () => {
|
||||
aria-label="GitHub"
|
||||
>
|
||||
<span class="icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33c.85 0 1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2Z"/></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33c.85 0 1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span v-if="formattedStars" class="stars-count">{{ formattedStars }}</span>
|
||||
<span v-if="formattedStars" class="stars-count">{{
|
||||
formattedStars
|
||||
}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,59 +1,45 @@
|
||||
<!--
|
||||
AgentArchitectureDemo.vue
|
||||
Agent 架构“点哪看哪”:点击模块,右侧展示它负责什么 + 典型输入输出。
|
||||
-->
|
||||
<template>
|
||||
<div class="agent-architecture-demo">
|
||||
<div class="architecture-diagram">
|
||||
<div class="diagram-center">
|
||||
<div class="agent-core">🤖 Agent</div>
|
||||
<div class="arch">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">Agent 由哪些模块拼起来?</div>
|
||||
<div class="subtitle">点一下模块,看它“负责什么”。</div>
|
||||
</div>
|
||||
|
||||
<div class="modules-container">
|
||||
<div
|
||||
v-for="(module, index) in modules"
|
||||
:key="module.name"
|
||||
class="module-card"
|
||||
:class="{ active: selectedModule === index }"
|
||||
@click="selectedModule = index"
|
||||
:style="getModulePosition(index)"
|
||||
>
|
||||
<div class="module-icon">{{ module.icon }}</div>
|
||||
<div class="module-name">{{ module.name }}</div>
|
||||
<div class="module-desc">{{ module.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svg class="connections">
|
||||
<line
|
||||
v-for="(module, index) in modules"
|
||||
:key="'line-' + index"
|
||||
x1="50%"
|
||||
y1="50%"
|
||||
x2="0"
|
||||
y2="0"
|
||||
:stroke="selectedModule === index ? 'var(--vp-c-brand)' : 'var(--vp-c-divider)'"
|
||||
stroke-width="2"
|
||||
:class="{ 'line-active': selectedModule === index }"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="module-details">
|
||||
<div class="detail-header">
|
||||
<span class="detail-icon">{{ modules[selectedModule].icon }}</span>
|
||||
<h3>{{ modules[selectedModule].name }}</h3>
|
||||
<div class="grid">
|
||||
<div class="diagram">
|
||||
<button
|
||||
v-for="m in modules"
|
||||
:key="m.id"
|
||||
:class="['node', { active: current.id === m.id }]"
|
||||
@click="current = m"
|
||||
>
|
||||
<span class="icon">{{ m.icon }}</span>
|
||||
<span class="name">{{ m.name }}</span>
|
||||
</button>
|
||||
|
||||
<div class="pipes">
|
||||
<div class="pipe">用户目标 → 计划 → 工具调用 → 结果 → 再计划…</div>
|
||||
<div class="pipe small">(记忆会贯穿整个过程)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-content">
|
||||
<p>{{ modules[selectedModule].description }}</p>
|
||||
<div class="panel">
|
||||
<div class="panel-title">{{ current.icon }} {{ current.name }}</div>
|
||||
<div class="panel-body">{{ current.desc }}</div>
|
||||
|
||||
<div class="code-example">
|
||||
<div class="code-title">💻 示例代码</div>
|
||||
<pre><code>{{ modules[selectedModule].code }}</code></pre>
|
||||
<div class="io">
|
||||
<div class="io-title">典型输入</div>
|
||||
<pre><code>{{ current.input }}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="key-points">
|
||||
<div class="point-title">🎯 关键要点</div>
|
||||
<ul>
|
||||
<li v-for="point in modules[selectedModule].points" :key="point">{{ point }}</li>
|
||||
</ul>
|
||||
<div class="io">
|
||||
<div class="io-title">典型输出</div>
|
||||
<pre><code>{{ current.output }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,346 +49,104 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const selectedModule = ref(0)
|
||||
|
||||
const modules = [
|
||||
{
|
||||
name: 'Profile',
|
||||
icon: '👤',
|
||||
desc: '角色设定',
|
||||
description: 'Profile 定义了 Agent 的身份、角色、目标和约束条件。它决定了 Agent 的行为方式和能力范围。就像给演员设定角色一样,Profile 让 Agent 知道"我是谁"和"我应该做什么"。',
|
||||
code: `profile = {
|
||||
"name": "Web Researcher",
|
||||
"role": "网络搜索助手",
|
||||
"goal": "帮助用户搜索和总结网络信息",
|
||||
"constraints": [
|
||||
"只能使用公开信息",
|
||||
"必须注明信息来源",
|
||||
"不能访问付费内容"
|
||||
],
|
||||
"style": "专业、简洁、准确"
|
||||
}`,
|
||||
points: [
|
||||
'明确定义 Agent 的职责范围',
|
||||
'设定合理的目标和约束',
|
||||
'塑造 Agent 的沟通风格',
|
||||
'防止 Agent 超出权限范围'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Memory',
|
||||
id: 'llm',
|
||||
icon: '🧠',
|
||||
desc: '记忆系统',
|
||||
description: 'Memory 是 Agent 的"大脑",用于存储和检索信息。它包括短期记忆(当前对话)、长期记忆(持久化知识)和工作记忆(当前任务状态)。好的记忆系统能让 Agent 从历史经验中学习。',
|
||||
code: `memory = {
|
||||
# 短期记忆:当前对话
|
||||
"short_term": [
|
||||
{"role": "user", "content": "搜索 AI 文章"},
|
||||
{"role": "assistant", "content": "已找到 5 篇"}
|
||||
],
|
||||
|
||||
# 长期记忆:持久化知识
|
||||
"long_term": {
|
||||
"user_preferences": {...},
|
||||
"previous_tasks": [...]
|
||||
},
|
||||
|
||||
# 工作记忆:当前任务状态
|
||||
"working_memory": {
|
||||
"current_goal": "总结第 3 篇文章",
|
||||
"completed_steps": [1, 2],
|
||||
"pending_steps": [3, 4, 5]
|
||||
}
|
||||
}`,
|
||||
points: [
|
||||
'短期记忆:存储当前对话历史',
|
||||
'长期记忆:保存跨任务的知识',
|
||||
'工作记忆:追踪当前任务进度',
|
||||
'支持信息的快速检索和更新'
|
||||
]
|
||||
name: 'LLM(大脑)',
|
||||
desc: '负责理解目标、生成计划、选择动作、组织语言输出。',
|
||||
input: '用户目标 + 当前状态 + 可用工具列表',
|
||||
output: '下一步计划 / 工具调用参数 / 最终回答'
|
||||
},
|
||||
{
|
||||
name: 'Planning',
|
||||
icon: '📋',
|
||||
desc: '规划模块',
|
||||
description: 'Planning 负责将复杂任务分解为可执行的步骤。它能制定计划、调整策略、评估进度。好的规划能力是 Agent 完成复杂任务的关键。',
|
||||
code: `planning = {
|
||||
"goal": "搜索并总结 AI 文章",
|
||||
|
||||
"steps": [
|
||||
{
|
||||
"id": 1,
|
||||
"action": "web_search",
|
||||
"params": {"query": "AI 技术 2024"},
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"action": "filter_results",
|
||||
"params": {"top_n": 5},
|
||||
"status": "in_progress"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"action": "read_pages",
|
||||
"params": {"urls": [...]},
|
||||
"status": "pending"
|
||||
}
|
||||
],
|
||||
|
||||
"current_step": 2,
|
||||
"total_steps": 5
|
||||
}`,
|
||||
points: [
|
||||
'将复杂任务分解为小步骤',
|
||||
'动态调整执行计划',
|
||||
'跟踪每个步骤的执行状态',
|
||||
'支持并行和串行任务执行'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Action',
|
||||
id: 'tools',
|
||||
icon: '🔧',
|
||||
desc: '执行模块',
|
||||
description: 'Action 模块负责执行具体的操作,包括调用工具、修改文件、发送请求等。它是 Agent 与外部环境交互的接口,将"想法"转化为"行动"。',
|
||||
code: `action = {
|
||||
"tool": "web_search",
|
||||
"input": {
|
||||
"query": "AI 技术 2024",
|
||||
"max_results": 10,
|
||||
"time_range": "last_month"
|
||||
name: 'Tools(手脚)',
|
||||
desc: '负责真正“做事”:搜索、读写文件、调用 API、运行命令。',
|
||||
input: 'tool_name + input_schema 参数',
|
||||
output: '工具执行结果(文本/数据/文件变更)'
|
||||
},
|
||||
"output": {
|
||||
"status": "success",
|
||||
"results": [
|
||||
{
|
||||
"title": "...",
|
||||
"url": "...",
|
||||
"snippet": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# 可用工具
|
||||
tools = [
|
||||
"web_search", # 搜索引擎
|
||||
"read_page", # 读取网页
|
||||
"write_file", # 写入文件
|
||||
"run_code" # 执行代码
|
||||
]`,
|
||||
points: [
|
||||
'提供丰富的工具集',
|
||||
'处理工具调用的输入输出',
|
||||
'管理工具的权限和安全',
|
||||
'支持自定义工具扩展'
|
||||
]
|
||||
{
|
||||
id: 'memory',
|
||||
icon: '💾',
|
||||
name: 'Memory(记忆)',
|
||||
desc: '把“已经做过什么、得到什么结果”存起来,避免重复与跑偏。',
|
||||
input: '对话历史 / 工具结果 / 当前任务状态',
|
||||
output: '可检索的上下文(短期/长期/工作记忆)'
|
||||
},
|
||||
{
|
||||
id: 'planner',
|
||||
icon: '🧩',
|
||||
name: 'Planning(规划)',
|
||||
desc: '把大目标拆成小步骤,并在失败时改计划(计划不是一次性的)。',
|
||||
input: '目标 + 约束(预算/时间/安全) + 当前进度',
|
||||
output: '步骤清单 / 下一步动作 / 停止条件'
|
||||
},
|
||||
{
|
||||
id: 'guard',
|
||||
icon: '🛡️',
|
||||
name: 'Guardrails(护栏)',
|
||||
desc: '限制风险:权限白名单、预算上限、敏感操作确认、沙箱执行。',
|
||||
input: '请求执行的动作 + 安全策略',
|
||||
output: '允许/拒绝/要求确认 + 审计日志'
|
||||
}
|
||||
]
|
||||
|
||||
const getModulePosition = (index) => {
|
||||
const positions = [
|
||||
{ top: '0', left: '50%', transform: 'translate(-50%, -50%)' },
|
||||
{ top: '50%', right: '0', transform: 'translate(50%, -50%)' },
|
||||
{ bottom: '0', left: '50%', transform: 'translate(-50%, 50%)' },
|
||||
{ top: '50%', left: '0', transform: 'translate(-50%, -50%)' }
|
||||
]
|
||||
return positions[index]
|
||||
}
|
||||
const current = ref(modules[0])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agent-architecture-demo {
|
||||
.arch {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.architecture-diagram {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 4/3;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 32px;
|
||||
}
|
||||
|
||||
.diagram-center {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.agent-core {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
padding: 20px 30px;
|
||||
border-radius: 50%;
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
box-shadow: 0 4px 20px rgba(66, 153, 225, 0.4);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.modules-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.module-card {
|
||||
position: absolute;
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.module-card:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
transform: scale(1.05) !important;
|
||||
}
|
||||
.header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
|
||||
.module-card.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-soft);
|
||||
box-shadow: 0 4px 20px rgba(66, 153, 225, 0.3);
|
||||
}
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
|
||||
|
||||
.module-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.module-name {
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.module-desc {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.connections {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.connections line {
|
||||
transition: stroke 0.3s;
|
||||
}
|
||||
|
||||
.line-active {
|
||||
stroke-width: 3;
|
||||
stroke-dasharray: 5, 5;
|
||||
animation: dash 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
to {
|
||||
stroke-dashoffset: -10;
|
||||
}
|
||||
}
|
||||
|
||||
.module-details {
|
||||
.diagram {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
.node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 2px solid var(--vp-c-divider);
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.detail-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.node.active { border-color: var(--vp-c-brand); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06); }
|
||||
.icon { width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center; background: var(--vp-c-bg-soft); border: 1px solid var(--vp-c-divider); }
|
||||
.name { font-weight: 800; }
|
||||
|
||||
.detail-header h3 {
|
||||
margin: 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.pipes { margin-top: 6px; padding-top: 10px; border-top: 1px dashed var(--vp-c-divider); }
|
||||
.pipe { color: var(--vp-c-text-2); font-size: 13px; line-height: 1.5; }
|
||||
.pipe.small { font-size: 12px; color: var(--vp-c-text-3); }
|
||||
|
||||
.detail-content > p {
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.code-example {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.code-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #1e1e1e;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: 'Monaco', 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: #d4d4d4;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.key-points {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.point-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.key-points ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.key-points li {
|
||||
padding: 4px 0;
|
||||
color: var(--vp-c-text-2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.key-points li::before {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
}
|
||||
.panel { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
|
||||
.panel-title { font-weight: 800; }
|
||||
.panel-body { color: var(--vp-c-text-2); line-height: 1.6; }
|
||||
.io-title { font-weight: 700; margin-bottom: 6px; }
|
||||
pre { margin: 0; background: #0b1221; color: #e5e7eb; border-radius: 10px; padding: 12px; font-family: var(--vp-font-family-mono); font-size: 13px; overflow-x: auto; white-space: pre-wrap; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,490 +1,108 @@
|
||||
<!--
|
||||
AgentChallengesDemo.vue
|
||||
挑战不是“列清单”,而是“能感受到风险”:
|
||||
- 开关护栏(步数上限/预算/确认/沙箱)
|
||||
- 看风险分数怎么变化
|
||||
-->
|
||||
<template>
|
||||
<div class="agent-challenges-demo">
|
||||
<div class="challenges-grid">
|
||||
<div
|
||||
v-for="(challenge, index) in challenges"
|
||||
:key="challenge.title"
|
||||
class="challenge-card"
|
||||
:class="{ active: selectedChallenge === index }"
|
||||
@click="selectedChallenge = index"
|
||||
>
|
||||
<div class="challenge-icon">{{ challenge.icon }}</div>
|
||||
<div class="challenge-title">{{ challenge.title }}</div>
|
||||
<div class="challenge-level">
|
||||
<span
|
||||
v-for="i in challenge.difficulty"
|
||||
:key="i"
|
||||
class="difficulty-star"
|
||||
>⭐</span>
|
||||
</div>
|
||||
<div class="risk">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">Agent 的挑战:没护栏就容易“翻车”</div>
|
||||
<div class="subtitle">打开这些护栏,风险会明显下降。</div>
|
||||
</div>
|
||||
<div class="score" :class="scoreClass">风险分数:{{ score }}/100</div>
|
||||
</div>
|
||||
|
||||
<div class="challenge-detail">
|
||||
<div class="detail-header">
|
||||
<span class="detail-icon">{{ challenges[selectedChallenge].icon }}</span>
|
||||
<h3>{{ challenges[selectedChallenge].title }}</h3>
|
||||
<div class="controls">
|
||||
<label class="toggle"><input type="checkbox" v-model="maxSteps" /> 最大迭代次数(防死循环)</label>
|
||||
<label class="toggle"><input type="checkbox" v-model="budget" /> 预算上限(防烧钱)</label>
|
||||
<label class="toggle"><input type="checkbox" v-model="confirm" /> 危险操作二次确认</label>
|
||||
<label class="toggle"><input type="checkbox" v-model="sandbox" /> 沙箱执行(隔离系统)</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="k">常见风险</div>
|
||||
<ul>
|
||||
<li>重复尝试 → 死循环</li>
|
||||
<li>乱用工具 → 误删/误发</li>
|
||||
<li>外部内容注入 → 被带偏</li>
|
||||
<li>调用太多 → 成本失控</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="detail-sections">
|
||||
<div class="detail-section">
|
||||
<h4>📖 问题描述</h4>
|
||||
<p>{{ challenges[selectedChallenge].description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4>💡 为什么困难?</h4>
|
||||
<ul>
|
||||
<li v-for="reason in challenges[selectedChallenge].reasons" :key="reason">
|
||||
{{ reason }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4>🔧 解决方案</h4>
|
||||
<div class="solutions">
|
||||
<div
|
||||
v-for="solution in challenges[selectedChallenge].solutions"
|
||||
:key="solution.title"
|
||||
class="solution-item"
|
||||
>
|
||||
<div class="solution-title">{{ solution.title }}</div>
|
||||
<div class="solution-desc">{{ solution.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4>📊 当前进展</h4>
|
||||
<div class="progress-item">
|
||||
<div class="progress-label">解决进度</div>
|
||||
<div class="progress-bar">
|
||||
<div
|
||||
class="progress-fill"
|
||||
:style="{ width: challenges[selectedChallenge].progress + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="progress-value">{{ challenges[selectedChallenge].progress }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4>🔗 相关资源</h4>
|
||||
<div class="resources">
|
||||
<a
|
||||
v-for="resource in challenges[selectedChallenge].resources"
|
||||
:key="resource.title"
|
||||
:href="resource.url"
|
||||
target="_blank"
|
||||
class="resource-link"
|
||||
>
|
||||
{{ resource.title }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="k">你现在开启了什么?</div>
|
||||
<div class="v">{{ enabledList }}</div>
|
||||
<div class="note">建议:最少也要有“最大步数 + 确认”。</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="k">一句话建议</div>
|
||||
<div class="v">{{ advice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const selectedChallenge = ref(0)
|
||||
const maxSteps = ref(true)
|
||||
const budget = ref(false)
|
||||
const confirm = ref(true)
|
||||
const sandbox = ref(false)
|
||||
|
||||
const challenges = [
|
||||
{
|
||||
title: '任务规划',
|
||||
icon: '📋',
|
||||
difficulty: 5,
|
||||
description: 'Agent 需要将复杂的用户任务分解为可执行的步骤,并动态调整计划。这要求 Agent 具备强大的推理能力和前瞻性思维。',
|
||||
reasons: [
|
||||
'任务分解需要深度理解用户意图',
|
||||
'长期规划容易偏离目标',
|
||||
'动态调整计划增加了复杂性',
|
||||
'缺乏反馈时难以评估进度'
|
||||
],
|
||||
solutions: [
|
||||
{
|
||||
title: '层次化规划',
|
||||
description: '将大任务分解为子任务,子任务再分解为具体步骤,形成层次结构。'
|
||||
},
|
||||
{
|
||||
title: '反思机制',
|
||||
description: '定期回顾已完成的步骤,评估计划的有效性,及时调整策略。'
|
||||
},
|
||||
{
|
||||
title: '外部记忆',
|
||||
description: '使用 todo.md 等文件记录计划,将目标保持在 Agent 的"视野"中。'
|
||||
}
|
||||
],
|
||||
progress: 40,
|
||||
resources: [
|
||||
{ title: 'ReAct 论文', url: 'https://arxiv.org/abs/2210.03629' },
|
||||
{ title: 'Tree of Thoughts', url: 'https://arxiv.org/abs/2305.10601' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '上下文管理',
|
||||
icon: '🧠',
|
||||
difficulty: 5,
|
||||
description: 'Agent 在多次迭代中会积累大量上下文,如何有效管理、压缩和检索这些信息是一个巨大挑战。',
|
||||
reasons: [
|
||||
'上下文长度受限(128K-200K)',
|
||||
'长上下文会降低模型性能',
|
||||
'重要信息可能被"淹没"',
|
||||
'成本随长度线性增长'
|
||||
],
|
||||
solutions: [
|
||||
{
|
||||
title: 'KV 缓存优化',
|
||||
description: '保持前缀稳定,只追加不修改,提高缓存命中率,降低 90% 成本。'
|
||||
},
|
||||
{
|
||||
title: '外部记忆',
|
||||
description: '大内容写入文件系统,上下文只保留引用和路径。'
|
||||
},
|
||||
{
|
||||
title: '智能压缩',
|
||||
description: '使用摘要、选择性保留、语义压缩等技术减少上下文长度。'
|
||||
}
|
||||
],
|
||||
progress: 60,
|
||||
resources: [
|
||||
{ title: '上下文工程指南', url: '#' },
|
||||
{ title: 'Manus 最佳实践', url: '#' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '工具使用',
|
||||
icon: '🔧',
|
||||
difficulty: 4,
|
||||
description: 'Agent 需要从众多工具中选择正确的工具,并正确调用它们。工具选择错误或调用失败都会导致任务失败。',
|
||||
reasons: [
|
||||
'工具数量多,选择困难',
|
||||
'工具参数复杂,容易出错',
|
||||
'工具调用失败需要恢复',
|
||||
'工具之间可能存在依赖关系'
|
||||
],
|
||||
solutions: [
|
||||
{
|
||||
title: 'Logits 遮蔽',
|
||||
description: '使用前缀限制模型只能调用特定工具,避免选择错误的工具。'
|
||||
},
|
||||
{
|
||||
title: '工具分组',
|
||||
description: '将工具按功能分类(如 browser_、shell_),便于选择和管理。'
|
||||
},
|
||||
{
|
||||
title: '错误恢复',
|
||||
description: '保留失败尝试在上下文中,让 Agent 从错误中学习。'
|
||||
}
|
||||
],
|
||||
progress: 70,
|
||||
resources: [
|
||||
{ title: 'Function Calling 指南', url: '#' },
|
||||
{ title: '工具设计最佳实践', url: '#' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '记忆系统',
|
||||
icon: '💾',
|
||||
difficulty: 4,
|
||||
description: 'Agent 需要记住历史信息、学习经验、识别模式。设计一个高效的记忆系统对 Agent 的长期性能至关重要。',
|
||||
reasons: [
|
||||
'需要区分即时、短期、长期记忆',
|
||||
'信息检索和更新的效率',
|
||||
'记忆的准确性和相关性',
|
||||
'跨任务的知识迁移'
|
||||
],
|
||||
solutions: [
|
||||
{
|
||||
title: '三层记忆架构',
|
||||
description: '即时上下文(当前对话)+ 短期记忆(会话级)+ 长期记忆(持久化)。'
|
||||
},
|
||||
{
|
||||
title: '向量检索',
|
||||
description: '使用嵌入和向量数据库实现语义相似度检索。'
|
||||
},
|
||||
{
|
||||
title: '记忆整合',
|
||||
description: '定期将短期记忆中的重要信息转移到长期记忆。'
|
||||
}
|
||||
],
|
||||
progress: 50,
|
||||
resources: [
|
||||
{ title: 'RAG 技术', url: '#' },
|
||||
{ title: '向量数据库指南', url: '#' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '错误处理',
|
||||
icon: '⚠️',
|
||||
difficulty: 3,
|
||||
description: 'Agent 在执行过程中会遇到各种错误:工具失败、网络超时、无效响应等。如何优雅地处理这些错误是一个挑战。',
|
||||
reasons: [
|
||||
'错误类型多样',
|
||||
'需要区分可恢复和不可恢复的错误',
|
||||
'错误可能级联传播',
|
||||
'重试策略需要优化'
|
||||
],
|
||||
solutions: [
|
||||
{
|
||||
title: '保留错误信息',
|
||||
description: '将失败的尝试保留在上下文中,让 Agent 学习并避免重复错误。'
|
||||
},
|
||||
{
|
||||
title: '重试机制',
|
||||
description: '对于可恢复的错误,实现指数退避的重试策略。'
|
||||
},
|
||||
{
|
||||
title: '回滚和恢复',
|
||||
description: '支持任务状态的保存和恢复,避免完全重新开始。'
|
||||
}
|
||||
],
|
||||
progress: 65,
|
||||
resources: [
|
||||
{ title: '错误处理最佳实践', url: '#' },
|
||||
{ title: '容错设计模式', url: '#' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '安全防护',
|
||||
icon: '🛡️',
|
||||
difficulty: 5,
|
||||
description: 'Agent 具有执行能力,如果被恶意利用可能造成严重后果。提示注入、工具滥用、数据泄露都是需要防范的安全风险。',
|
||||
reasons: [
|
||||
'提示注入攻击难以检测',
|
||||
'Agent 可能被诱导执行危险操作',
|
||||
'敏感信息可能泄露',
|
||||
'攻击面广,难以全面防护'
|
||||
],
|
||||
solutions: [
|
||||
{
|
||||
title: '输入清理',
|
||||
description: '严格清理和验证用户输入,分离系统和用户消息。'
|
||||
},
|
||||
{
|
||||
title: '权限控制',
|
||||
description: '使用白名单限制工具访问,敏感操作需要二次确认。'
|
||||
},
|
||||
{
|
||||
title: '沙箱环境',
|
||||
description: '在隔离的沙箱中执行危险操作,限制资源访问。'
|
||||
},
|
||||
{
|
||||
title: '输出过滤',
|
||||
description: '过滤敏感信息,加密存储数据,定期审计日志。'
|
||||
}
|
||||
],
|
||||
progress: 55,
|
||||
resources: [
|
||||
{ title: 'AI 安全指南', url: '#' },
|
||||
{ title: 'OWASP LLM Top 10', url: '#' }
|
||||
]
|
||||
}
|
||||
]
|
||||
const score = computed(() => {
|
||||
let s = 85
|
||||
if (maxSteps.value) s -= 18
|
||||
if (budget.value) s -= 15
|
||||
if (confirm.value) s -= 22
|
||||
if (sandbox.value) s -= 18
|
||||
return Math.max(0, s)
|
||||
})
|
||||
|
||||
const scoreClass = computed(() => {
|
||||
if (score.value <= 35) return 'good'
|
||||
if (score.value <= 60) return 'mid'
|
||||
return 'bad'
|
||||
})
|
||||
|
||||
const enabledList = computed(() => {
|
||||
const items = []
|
||||
if (maxSteps.value) items.push('最大步数')
|
||||
if (budget.value) items.push('预算上限')
|
||||
if (confirm.value) items.push('二次确认')
|
||||
if (sandbox.value) items.push('沙箱')
|
||||
return items.length ? items.join('、') : '(都没开)'
|
||||
})
|
||||
|
||||
const advice = computed(() => {
|
||||
if (!maxSteps.value && !confirm.value) return '先加“最大步数”和“二次确认”,这是最低成本的安全感。'
|
||||
if (score.value <= 35) return '很稳了:可以开始做更复杂的任务,但记得加日志与监控。'
|
||||
if (score.value <= 60) return '还不错:建议再加预算或沙箱,避免极端情况。'
|
||||
return '风险偏高:建议优先补护栏,再让 Agent 真去执行。'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agent-challenges-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
}
|
||||
.risk { border: 1px solid var(--vp-c-divider); border-radius: 12px; background: var(--vp-c-bg-soft); padding: 16px; margin: 20px 0; display: flex; flex-direction: column; gap: 12px; }
|
||||
.header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; align-items: center; }
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
.score { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 999px; padding: 8px 12px; font-weight: 900; }
|
||||
.score.good { color: #22c55e; border-color: rgba(34, 197, 94, 0.4); }
|
||||
.score.mid { color: #f59e0b; border-color: rgba(245, 158, 11, 0.4); }
|
||||
.score.bad { color: #ef4444; border-color: rgba(239, 68, 68, 0.4); }
|
||||
|
||||
.challenges-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.controls { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 10px 12px; display: flex; flex-wrap: wrap; gap: 12px; }
|
||||
.toggle { display: flex; gap: 8px; align-items: center; }
|
||||
input { accent-color: var(--vp-c-brand); }
|
||||
|
||||
.challenge-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.challenge-card:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 4px 20px rgba(66, 153, 225, 0.2);
|
||||
}
|
||||
|
||||
.challenge-card.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.challenge-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.challenge-title {
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 8px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.challenge-level {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.difficulty-star {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.challenge-detail {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 2px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.detail-icon {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.detail-header h3 {
|
||||
margin: 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.detail-sections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.detail-section h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.detail-section p {
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.detail-section ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.detail-section li {
|
||||
padding: 4px 0;
|
||||
color: var(--vp-c-text-2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.detail-section li::before {
|
||||
content: '•';
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.solutions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.solution-item {
|
||||
padding: 16px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.solution-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.solution-desc {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.progress-item {
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr 60px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--vp-c-brand), var(--vp-c-brand-light));
|
||||
border-radius: 12px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.progress-value {
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-brand);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.resources {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.resource-link {
|
||||
padding: 10px 20px;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.resource-link:hover {
|
||||
background: var(--vp-c-brand-dark);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
|
||||
.card { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; }
|
||||
.k { font-weight: 900; margin-bottom: 6px; }
|
||||
.v { color: var(--vp-c-text-2); line-height: 1.6; }
|
||||
.note { margin-top: 6px; color: var(--vp-c-text-3); font-size: 12px; }
|
||||
ul { margin: 0; padding-left: 18px; color: var(--vp-c-text-2); line-height: 1.6; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,85 +1,38 @@
|
||||
<!--
|
||||
AgentFutureDemo.vue
|
||||
Agent 未来方向:点选趋势,看看“会带来什么变化”和“现在就能做的准备”。
|
||||
-->
|
||||
<template>
|
||||
<div class="agent-future-demo">
|
||||
<div class="future-intro">
|
||||
<h3>🚀 Agent 的未来展望</h3>
|
||||
<p>探索 Agent 技术的发展趋势和应用前景</p>
|
||||
<div class="future">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">Agent 的未来:更稳、更强、更协作</div>
|
||||
<div class="subtitle">点一个趋势,看它意味着什么。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline">
|
||||
<div class="timeline-line"></div>
|
||||
|
||||
<div
|
||||
v-for="(era, index) in timeline"
|
||||
:key="era.period"
|
||||
class="timeline-item"
|
||||
:class="{ active: selectedEra === index }"
|
||||
@click="selectedEra = index"
|
||||
<div class="chips">
|
||||
<button
|
||||
v-for="t in trends"
|
||||
:key="t.id"
|
||||
:class="['chip', { active: current.id === t.id }]"
|
||||
@click="current = t"
|
||||
>
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="timeline-content">
|
||||
<div class="era-period">{{ era.period }}</div>
|
||||
<div class="era-title">{{ era.title }}</div>
|
||||
<div class="era-description">{{ era.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ t.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="era-details">
|
||||
<div class="detail-card">
|
||||
<h4>{{ timeline[selectedEra].title }} ({{ timeline[selectedEra].period }})</h4>
|
||||
<p class="era-detail-desc">{{ timeline[selectedEra].detailDescription }}</p>
|
||||
|
||||
<div class="era-features">
|
||||
<div class="features-title">🎯 关键特征</div>
|
||||
<div class="features-list">
|
||||
<div
|
||||
v-for="feature in timeline[selectedEra].features"
|
||||
:key="feature"
|
||||
class="feature-tag"
|
||||
>
|
||||
{{ feature }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="p-title">{{ current.label }}</div>
|
||||
<div class="p-body">{{ current.desc }}</div>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="k">会带来什么?</div>
|
||||
<div class="v">{{ current.impact }}</div>
|
||||
</div>
|
||||
|
||||
<div class="era-applications">
|
||||
<div class="applications-title">💼 典型应用</div>
|
||||
<div class="applications-grid">
|
||||
<div
|
||||
v-for="app in timeline[selectedEra].applications"
|
||||
:key="app.name"
|
||||
class="app-item"
|
||||
>
|
||||
<div class="app-icon">{{ app.icon }}</div>
|
||||
<div class="app-name">{{ app.name }}</div>
|
||||
<div class="app-desc">{{ app.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="era-challenges">
|
||||
<div class="challenges-title">⚠️ 面临挑战</div>
|
||||
<ul>
|
||||
<li v-for="challenge in timeline[selectedEra].challenges" :key="challenge">
|
||||
{{ challenge }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="future-predictions">
|
||||
<h4>🔮 未来预测</h4>
|
||||
<div class="predictions-grid">
|
||||
<div
|
||||
v-for="(prediction, index) in predictions"
|
||||
:key="prediction.title"
|
||||
class="prediction-card"
|
||||
>
|
||||
<div class="prediction-icon">{{ prediction.icon }}</div>
|
||||
<div class="prediction-title">{{ prediction.title }}</div>
|
||||
<div class="prediction-desc">{{ prediction.description }}</div>
|
||||
<div class="prediction-time">预计实现:{{ prediction.timeline }}</div>
|
||||
<div class="card">
|
||||
<div class="k">你现在能做什么准备?</div>
|
||||
<div class="v">{{ current.prepare }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,435 +42,56 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const selectedEra = ref(3)
|
||||
|
||||
const timeline = [
|
||||
const trends = [
|
||||
{
|
||||
period: '2023-2024',
|
||||
title: '萌芽期',
|
||||
description: '单 Agent,简单工具调用',
|
||||
detailDescription: 'Agent 技术的起步阶段,主要是简单的单 Agent 系统,能够调用有限工具完成基础任务。这个阶段证明了 Agent 的可行性,但能力还比较有限。',
|
||||
features: [
|
||||
'单一 Agent 执行',
|
||||
'基础工具调用',
|
||||
'简单的任务规划',
|
||||
'有限的记忆能力'
|
||||
],
|
||||
applications: [
|
||||
{ icon: '💬', name: '聊天机器人', description: '增强型对话助手' },
|
||||
{ icon: '🔍', name: '搜索助手', description: '信息检索和汇总' },
|
||||
{ icon: '📝', name: '写作助手', description: '内容生成辅助' }
|
||||
],
|
||||
challenges: [
|
||||
'规划能力弱',
|
||||
'容易迷失目标',
|
||||
'上下文管理困难',
|
||||
'错误恢复能力差'
|
||||
]
|
||||
id: 'planning',
|
||||
label: '更强规划',
|
||||
desc: '把大目标拆成更合理的子任务,并能动态改计划。',
|
||||
impact: '更少跑题、更少漏步骤,复杂任务成功率更高。',
|
||||
prepare: '学会写“计划/检查点”,并把任务拆成可验收小块。'
|
||||
},
|
||||
{
|
||||
period: '2024-2025',
|
||||
title: '成长期',
|
||||
description: '多工具,复杂任务处理',
|
||||
detailDescription: 'Agent 开始能够处理更复杂的任务,使用多个工具,具备基本的规划能力。框架和工具日趋成熟,开始出现实际应用。',
|
||||
features: [
|
||||
'多工具协作',
|
||||
'层次化任务分解',
|
||||
'短期记忆管理',
|
||||
'基础的反思能力'
|
||||
],
|
||||
applications: [
|
||||
{ icon: '💻', name: '编程助手', description: '代码编写和调试' },
|
||||
{ icon: '📊', name: '数据分析', description: '自动化报告生成' },
|
||||
{ icon: '🌐', name: 'Web Agent', description: '网页自动化操作' }
|
||||
],
|
||||
challenges: [
|
||||
'长期规划困难',
|
||||
'记忆容量有限',
|
||||
'工具选择不准确',
|
||||
'安全风险增加'
|
||||
]
|
||||
id: 'memory',
|
||||
label: '更好记忆',
|
||||
desc: '长期记住偏好、事实与项目状态,跨任务复用。',
|
||||
impact: '更像长期同事:越用越懂你,重复工作更少。',
|
||||
prepare: '设计记忆结构:短期/长期/工作记忆,并做好隐私与脱敏。'
|
||||
},
|
||||
{
|
||||
period: '2025-2026',
|
||||
title: '成熟期',
|
||||
description: '多 Agent 协作,专业化分工',
|
||||
detailDescription: '多个专业化的 Agent 开始协作,每个 Agent 专注于特定领域。通过通信和协作完成复杂任务,形成 AI 团队。',
|
||||
features: [
|
||||
'多 Agent 协作',
|
||||
'专业化分工',
|
||||
'持久化记忆',
|
||||
'主动学习和改进'
|
||||
],
|
||||
applications: [
|
||||
{ icon: '👥', name: 'AI 团队', description: '协作完成复杂项目' },
|
||||
{ icon: '🔬', name: '研究助手', description: '自动化科研流程' },
|
||||
{ icon: '🏢', name: '企业助手', description: '业务流程自动化' }
|
||||
],
|
||||
challenges: [
|
||||
'Agent 间通信效率',
|
||||
'协作策略优化',
|
||||
'资源调度复杂',
|
||||
'责任归属问题'
|
||||
]
|
||||
id: 'multi',
|
||||
label: '多 Agent 协作',
|
||||
desc: '多个角色并行处理,再由协调者合并输出。',
|
||||
impact: '大任务并行化,质量更稳(研究/实现/评审分工)。',
|
||||
prepare: '先把“角色边界”和“交付格式”定义清楚。'
|
||||
},
|
||||
{
|
||||
period: '2026-2028',
|
||||
title: '进化期',
|
||||
description: '自主 Agent,持续学习',
|
||||
detailDescription: 'Agent 具备强大的自主学习和改进能力,能够从经验中学习,优化自己的行为。可以适应新环境,掌握新技能,实现真正的智能。',
|
||||
features: [
|
||||
'自主学习和优化',
|
||||
'跨任务知识迁移',
|
||||
'多模态理解',
|
||||
'情感和个性'
|
||||
],
|
||||
applications: [
|
||||
{ icon: '🤖', name: '个人助理', description: '全天候智能助手' },
|
||||
{ icon: '🎨', name: '创意专家', description: '艺术创作和设计' },
|
||||
{ icon: '🔬', name: '科学家', description: '独立开展研究' }
|
||||
],
|
||||
challenges: [
|
||||
'伦理和道德',
|
||||
'可控性和安全性',
|
||||
'社会接受度',
|
||||
'法律监管'
|
||||
]
|
||||
},
|
||||
{
|
||||
period: '2028+',
|
||||
title: '融合期',
|
||||
description: '人机共生,Agent 社会',
|
||||
detailDescription: 'Agent 深度融入人类社会,成为工作、生活不可或缺的伙伴。形成复杂的 Agent 社会,与人类共同创造价值。',
|
||||
features: [
|
||||
'人机深度融合',
|
||||
'Agent 社会形成',
|
||||
'集体智能涌现',
|
||||
'通用人工智能'
|
||||
],
|
||||
applications: [
|
||||
{ icon: '🌍', name: '全球协作', description: '跨区域 Agent 协作' },
|
||||
{ icon: '🧠', name: '知识网络', description: '全人类知识整合' },
|
||||
{ icon: '🚀', name: '创新引擎', description: '加速科技发展' }
|
||||
],
|
||||
challenges: [
|
||||
'人类身份认同',
|
||||
'社会结构变化',
|
||||
'AI 治理',
|
||||
'存在性风险'
|
||||
]
|
||||
id: 'safety',
|
||||
label: '更强安全护栏',
|
||||
desc: '更细的权限、确认与审计,降低工具滥用风险。',
|
||||
impact: '更容易上线到真实业务场景,减少事故。',
|
||||
prepare: '默认开启:最大步数、预算上限、危险操作确认、沙箱。'
|
||||
}
|
||||
]
|
||||
|
||||
const predictions = [
|
||||
{
|
||||
icon: '🧠',
|
||||
title: '通用 Agent',
|
||||
description: '能够处理几乎所有类型的任务,达到人类专家水平',
|
||||
timeline: '2027-2030'
|
||||
},
|
||||
{
|
||||
icon: '👥',
|
||||
title: 'Agent 社会',
|
||||
description: '数百万 Agent 协作工作,形成复杂的经济系统',
|
||||
timeline: '2028-2032'
|
||||
},
|
||||
{
|
||||
icon: '🔬',
|
||||
title: '科学突破',
|
||||
description: 'Agent 帮助人类在药物、材料、能源等领域取得重大突破',
|
||||
timeline: '2026-2028'
|
||||
},
|
||||
{
|
||||
icon: '🎨',
|
||||
title: '创意革命',
|
||||
description: 'Agent 在艺术、音乐、文学等创作领域达到大师水准',
|
||||
timeline: '2025-2027'
|
||||
},
|
||||
{
|
||||
icon: '🏥',
|
||||
title: '医疗革命',
|
||||
description: 'Agent 医生提供个性化、精准化的医疗服务',
|
||||
timeline: '2026-2029'
|
||||
},
|
||||
{
|
||||
icon: '🌍',
|
||||
title: '全球协作',
|
||||
description: 'Agent 打破语言和文化障碍,实现真正的全球协作',
|
||||
timeline: '2027-2030'
|
||||
}
|
||||
]
|
||||
const current = ref(trends[0])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agent-future-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
}
|
||||
.future { border: 1px solid var(--vp-c-divider); border-radius: 12px; background: var(--vp-c-bg-soft); padding: 16px; margin: 20px 0; display: flex; flex-direction: column; gap: 12px; }
|
||||
.header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
|
||||
.future-intro {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.chips { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.chip { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); padding: 8px 12px; border-radius: 999px; cursor: pointer; }
|
||||
.chip.active { border-color: var(--vp-c-brand); color: var(--vp-c-brand); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
|
||||
|
||||
.future-intro h3 {
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.future-intro p {
|
||||
margin: 0;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding: 20px 0;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.timeline-line {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 4px;
|
||||
background: linear-gradient(180deg, var(--vp-c-brand), var(--vp-c-brand-light));
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
padding-left: 60px;
|
||||
padding-bottom: 24px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.timeline-item:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.timeline-item.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 4px solid var(--vp-c-divider);
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.timeline-item.active .timeline-dot {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand);
|
||||
box-shadow: 0 0 20px rgba(66, 153, 225, 0.5);
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.timeline-item.active .timeline-content {
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 4px 20px rgba(66, 153, 225, 0.2);
|
||||
}
|
||||
|
||||
.era-period {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.era-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.era-description {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.era-details {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.detail-card h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.era-detail-desc {
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.era-features {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.features-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.features-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.feature-tag {
|
||||
padding: 6px 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 6px;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.era-applications {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.applications-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.applications-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.app-item {
|
||||
padding: 16px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.app-desc {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.era-challenges {
|
||||
padding: 16px;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border-left: 4px solid #ef4444;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.challenges-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.era-challenges ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.era-challenges li {
|
||||
padding: 4px 0;
|
||||
color: var(--vp-c-text-2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.era-challenges li::before {
|
||||
content: '⚠️';
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
}
|
||||
|
||||
.future-predictions h4 {
|
||||
margin: 0 0 20px 0;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.predictions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.prediction-card {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.prediction-card:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 4px 20px rgba(66, 153, 225, 0.2);
|
||||
}
|
||||
|
||||
.prediction-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.prediction-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 8px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.prediction-desc {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.prediction-time {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
.panel { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; }
|
||||
.p-title { font-weight: 900; margin-bottom: 6px; }
|
||||
.p-body { color: var(--vp-c-text-2); line-height: 1.6; margin-bottom: 10px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px; }
|
||||
.card { background: var(--vp-c-bg-soft); border: 1px dashed var(--vp-c-divider); border-radius: 12px; padding: 10px; }
|
||||
.k { font-weight: 900; margin-bottom: 4px; }
|
||||
.v { color: var(--vp-c-text-2); line-height: 1.6; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,348 +1,117 @@
|
||||
<!--
|
||||
AgentLevelDemo.vue
|
||||
Agent 分级(L0-L5)交互:拖动等级,看到“能做什么/不能做什么/典型任务”。
|
||||
-->
|
||||
<template>
|
||||
<div class="agent-level-demo">
|
||||
<div class="levels-container">
|
||||
<div
|
||||
v-for="(level, index) in levels"
|
||||
:key="level.id"
|
||||
class="level-card"
|
||||
:class="{ active: selectedLevel === index }"
|
||||
@click="selectedLevel = index"
|
||||
>
|
||||
<div class="level-header">
|
||||
<div class="level-badge">{{ level.id }}</div>
|
||||
<div class="level-name">{{ level.name }}</div>
|
||||
</div>
|
||||
<div class="levels">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">Agent 能力分级(从聊天到协作)</div>
|
||||
<div class="subtitle">拖动看看:等级越高,越像“能独立干活的同事”。</div>
|
||||
</div>
|
||||
<div class="badge">当前:{{ current.name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="level-features">
|
||||
<div v-for="feature in level.features" :key="feature" class="feature-item">
|
||||
<span class="feature-icon">✓</span>
|
||||
{{ feature }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="level-example">
|
||||
<div class="example-label">典型应用</div>
|
||||
<div class="example-text">{{ level.example }}</div>
|
||||
</div>
|
||||
<div class="slider">
|
||||
<input type="range" min="0" max="5" step="1" v-model.number="level" />
|
||||
<div class="ticks">
|
||||
<span v-for="n in 6" :key="n">{{ n - 1 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="level-comparison">
|
||||
<h3>📊 能力对比</h3>
|
||||
|
||||
<div class="comparison-grid">
|
||||
<div class="comparison-item">
|
||||
<div class="item-label">工具使用</div>
|
||||
<div class="item-bar">
|
||||
<div
|
||||
class="bar-fill"
|
||||
:style="{ width: levels[selectedLevel].capabilities.tools + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="item-value">{{ levels[selectedLevel].capabilities.tools }}%</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-item">
|
||||
<div class="item-label">规划能力</div>
|
||||
<div class="item-bar">
|
||||
<div
|
||||
class="bar-fill"
|
||||
:style="{ width: levels[selectedLevel].capabilities.planning + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="item-value">{{ levels[selectedLevel].capabilities.planning }}%</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-item">
|
||||
<div class="item-label">自主性</div>
|
||||
<div class="item-bar">
|
||||
<div
|
||||
class="bar-fill"
|
||||
:style="{ width: levels[selectedLevel].capabilities.autonomy + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="item-value">{{ levels[selectedLevel].capabilities.autonomy }}%</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-item">
|
||||
<div class="item-label">复杂度</div>
|
||||
<div class="item-bar">
|
||||
<div
|
||||
class="bar-fill"
|
||||
:style="{ width: levels[selectedLevel].capabilities.complexity + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="item-value">{{ levels[selectedLevel].capabilities.complexity }}%</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="k">能做什么</div>
|
||||
<ul>
|
||||
<li v-for="x in current.can" :key="x">{{ x }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="level-description">
|
||||
<h4>{{ levels[selectedLevel].name }}</h4>
|
||||
<p>{{ levels[selectedLevel].description }}</p>
|
||||
|
||||
<div class="use-cases">
|
||||
<div class="use-case-title">🎯 适用场景</div>
|
||||
<ul>
|
||||
<li v-for="use in levels[selectedLevel].useCases" :key="use">{{ use }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="k">容易出的问题</div>
|
||||
<ul>
|
||||
<li v-for="x in current.risk" :key="x">{{ x }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="k">典型任务</div>
|
||||
<div class="v">{{ current.example }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const selectedLevel = ref(2)
|
||||
const level = ref(2)
|
||||
|
||||
const levels = [
|
||||
{
|
||||
id: 'L0',
|
||||
name: '无工具',
|
||||
features: ['只能对话', '不能执行操作', '被动响应'],
|
||||
example: 'ChatGPT 聊天',
|
||||
description: '最基础的 LLM 应用,只能进行对话,无法执行任何实际操作。所有的"行动"都需要人工完成。',
|
||||
capabilities: { tools: 0, planning: 0, autonomy: 0, complexity: 10 },
|
||||
useCases: ['问答系统', '内容生成', '语言翻译']
|
||||
name: 'L0:纯对话',
|
||||
can: ['回答问题', '写文本/代码(但不执行)'],
|
||||
risk: ['只能“说”,不能“做”', '需要你手动分步骤'],
|
||||
example: '解释概念、写一段文案'
|
||||
},
|
||||
{
|
||||
id: 'L1',
|
||||
name: '单工具',
|
||||
features: ['使用一个固定工具', '有限的操作能力', '简单任务执行'],
|
||||
example: '代码解释器',
|
||||
description: '可以使用一个特定的工具来扩展能力,但工具选择是固定的,无法自主切换。',
|
||||
capabilities: { tools: 20, planning: 10, autonomy: 20, complexity: 30 },
|
||||
useCases: ['代码执行', '数据计算', '文件分析']
|
||||
name: 'L1:单工具',
|
||||
can: ['调用一个固定工具', '把结果解释给你'],
|
||||
risk: ['工具用错参数', '缺少复杂规划'],
|
||||
example: '只会查一次搜索/只会跑一次代码'
|
||||
},
|
||||
{
|
||||
id: 'L2',
|
||||
name: '多工具',
|
||||
features: ['可以选择多个工具', '工具切换能力', '灵活的任务处理'],
|
||||
example: 'Web Agent',
|
||||
description: '可以使用多个不同的工具,并能根据任务需要自主选择合适的工具。',
|
||||
capabilities: { tools: 60, planning: 30, autonomy: 40, complexity: 50 },
|
||||
useCases: ['网页浏览', '数据采集', '信息检索']
|
||||
name: 'L2:多工具',
|
||||
can: ['在多个工具间选择', '按需要组合调用'],
|
||||
risk: ['选择工具不稳', '权限与安全需要控制'],
|
||||
example: '搜索 + 打开网页 + 摘要'
|
||||
},
|
||||
{
|
||||
id: 'L3',
|
||||
name: '多步骤',
|
||||
features: ['复杂任务规划', '多步骤执行', '状态跟踪'],
|
||||
example: '数据分析 Agent',
|
||||
description: '能够将复杂任务分解为多个步骤,按照计划逐步执行,并跟踪整体进度。',
|
||||
capabilities: { tools: 70, planning: 60, autonomy: 60, complexity: 70 },
|
||||
useCases: ['数据分析', '报告生成', '工作流自动化']
|
||||
name: 'L3:多步骤执行',
|
||||
can: ['先计划后执行', '完成一串步骤', '记录中间结果'],
|
||||
risk: ['步骤漏/顺序错', '成本上升(更多调用)'],
|
||||
example: '读代码 → 改代码 → 跑测试 → 出报告'
|
||||
},
|
||||
{
|
||||
id: 'L4',
|
||||
name: '自主迭代',
|
||||
features: ['主动反思和改进', '从错误中学习', '策略调整'],
|
||||
example: '研究 Agent',
|
||||
description: '不仅能执行任务,还能主动反思结果,从错误中学习,不断优化自己的策略。',
|
||||
capabilities: { tools: 80, planning: 80, autonomy: 80, complexity: 85 },
|
||||
useCases: ['科学研究', '复杂问题求解', '自适应系统']
|
||||
name: 'L4:自我纠错',
|
||||
can: ['失败后换策略', '用检查点避免跑偏'],
|
||||
risk: ['可能反复尝试(需要上限)', '更依赖监控与日志'],
|
||||
example: '测试失败后自动定位并尝试修复'
|
||||
},
|
||||
{
|
||||
id: 'L5',
|
||||
name: '多 Agent 协作',
|
||||
features: ['Agent 间通信', '分工协作', '集体智能'],
|
||||
example: '企业级系统',
|
||||
description: '多个专业化的 Agent 协同工作,通过通信和协作完成单个 Agent 无法完成的复杂任务。',
|
||||
capabilities: { tools: 100, planning: 100, autonomy: 100, complexity: 100 },
|
||||
useCases: ['企业自动化', '软件开发团队', '智能组织']
|
||||
name: 'L5:多 Agent 协作',
|
||||
can: ['多个角色分工', '并行处理任务', '合并结果'],
|
||||
risk: ['协作成本更高', '需要清晰协议与仲裁机制'],
|
||||
example: '研究员找资料 + 工程师实现 + 编辑写总结'
|
||||
}
|
||||
]
|
||||
|
||||
const current = computed(() => levels[level.value])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agent-level-demo {
|
||||
.levels {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.levels-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.level-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.level-card:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.level-card.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-soft);
|
||||
box-shadow: 0 4px 20px rgba(66, 153, 225, 0.2);
|
||||
}
|
||||
|
||||
.level-header {
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.level-badge {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
.badge { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 999px; padding: 8px 12px; font-weight: 800; }
|
||||
|
||||
.level-name {
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
.slider { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 10px 12px; }
|
||||
input[type='range'] { width: 100%; }
|
||||
.ticks { display: flex; justify-content: space-between; color: var(--vp-c-text-2); font-size: 12px; margin-top: 6px; }
|
||||
|
||||
.level-features {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.level-example {
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.example-label {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.example-text {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.level-comparison {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.level-comparison h3 {
|
||||
margin: 0 0 24px 0;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.comparison-grid {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.comparison-item {
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr 60px;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.item-bar {
|
||||
height: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--vp-c-brand), var(--vp-c-brand-light));
|
||||
border-radius: 12px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-brand);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.level-description {
|
||||
padding: 20px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.level-description h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.level-description p {
|
||||
margin: 0 0 16px 0;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.use-cases {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.use-case-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.use-cases ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.use-cases li {
|
||||
padding: 4px 0;
|
||||
color: var(--vp-c-text-2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.use-cases li::before {
|
||||
content: '•';
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
}
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
|
||||
.card { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; }
|
||||
.k { font-weight: 800; margin-bottom: 6px; }
|
||||
.v { color: var(--vp-c-text-2); line-height: 1.6; }
|
||||
ul { margin: 0; padding-left: 18px; color: var(--vp-c-text-2); line-height: 1.6; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,421 +1,117 @@
|
||||
<!--
|
||||
AgentTaskFlowDemo.vue
|
||||
任务执行流:像看“回放”一样看 Agent 一步步完成一个任务。
|
||||
-->
|
||||
<template>
|
||||
<div class="agent-task-flow-demo">
|
||||
<div class="task-input">
|
||||
<div class="input-label">🎯 用户任务</div>
|
||||
<div class="input-content">"搜索并总结最新的 AI 技术文章"</div>
|
||||
</div>
|
||||
|
||||
<div class="flow-timeline">
|
||||
<div class="timeline-line"></div>
|
||||
|
||||
<div
|
||||
v-for="(step, index) in steps"
|
||||
:key="index"
|
||||
class="timeline-item"
|
||||
:class="{ active: currentStep === index, completed: currentStep > index }"
|
||||
>
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="timeline-content">
|
||||
<div class="step-number">步骤 {{ index + 1 }}</div>
|
||||
<div class="step-title">{{ step.title }}</div>
|
||||
<div class="step-description">{{ step.description }}</div>
|
||||
|
||||
<div v-if="step.code" class="step-code">
|
||||
<div class="code-label">执行代码</div>
|
||||
<pre><code>{{ step.code }}</code></pre>
|
||||
</div>
|
||||
|
||||
<div v-if="step.result" class="step-result">
|
||||
<div class="result-label">执行结果</div>
|
||||
<div class="result-content">{{ step.result }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flow">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">任务回放:Agent 怎么一步步做完?</div>
|
||||
<div class="subtitle">点步骤,看“工具调用”和“中间结果”。</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn" @click="step = Math.max(0, step - 1)" :disabled="step === 0">上一步</button>
|
||||
<button class="btn primary" @click="step = Math.min(steps.length - 1, step + 1)" :disabled="step === steps.length - 1">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flow-controls">
|
||||
<button @click="prevStep" :disabled="currentStep === 0" class="control-btn">
|
||||
← 上一步
|
||||
</button>
|
||||
<button @click="togglePlay" class="control-btn primary">
|
||||
{{ isPlaying ? '⏸ 暂停' : '▶ 自动演示' }}
|
||||
</button>
|
||||
<button @click="nextStep" :disabled="currentStep === steps.length - 1" class="control-btn">
|
||||
下一步 →
|
||||
</button>
|
||||
<button @click="reset" class="control-btn">
|
||||
↺ 重置
|
||||
<div class="timeline">
|
||||
<button
|
||||
v-for="(s, i) in steps"
|
||||
:key="s.title"
|
||||
:class="['t', { active: i === step }]"
|
||||
@click="step = i"
|
||||
>
|
||||
<span class="n">{{ i + 1 }}</span>
|
||||
<span class="txt">{{ s.title }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flow-explanation">
|
||||
<div class="explanation-card">
|
||||
<h4>{{ steps[currentStep].title }}</h4>
|
||||
<p>{{ steps[currentStep].explanation }}</p>
|
||||
|
||||
<div class="tips">
|
||||
<div class="tip-icon">💡</div>
|
||||
<div>{{ steps[currentStep].tip }}</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="panel">
|
||||
<div class="panel-title">当前步骤</div>
|
||||
<div class="panel-body">{{ steps[step].desc }}</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-title">工具调用(示意)</div>
|
||||
<pre><code>{{ steps[step].tool }}</code></pre>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-title">结果(示意)</div>
|
||||
<pre><code>{{ steps[step].result }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const currentStep = ref(0)
|
||||
const isPlaying = ref(false)
|
||||
let playInterval = null
|
||||
const step = ref(0)
|
||||
|
||||
const steps = [
|
||||
{
|
||||
title: '理解任务',
|
||||
description: 'Agent 分析用户需求,明确目标',
|
||||
code: null,
|
||||
result: null,
|
||||
explanation: 'Agent 首先理解用户的意图,识别出这是一个需要搜索和总结的任务。它会分析关键词: "搜索"、"最新"、"AI 技术"、"文章"、"总结"。',
|
||||
tip: '好的任务理解是成功的一半。Agent 需要识别出核心需求和约束条件。'
|
||||
title: '理解目标',
|
||||
desc: '把用户需求拆成“可交付”的输出结构。',
|
||||
tool: 'LLM: parse_goal({ task, constraints, output_format })',
|
||||
result: '目标:找 3 篇文章;输出:标题 + 一句话总结(Markdown 列表)'
|
||||
},
|
||||
{
|
||||
title: '制定计划',
|
||||
description: '分解任务,制定执行步骤',
|
||||
code: `plan = [
|
||||
"搜索关键词:'AI 技术 2024'",
|
||||
"筛选前 5 篇文章",
|
||||
"阅读每篇文章摘要",
|
||||
"生成综合总结"
|
||||
]`,
|
||||
result: '✅ 计划已制定:4 个步骤',
|
||||
explanation: 'Agent 将复杂任务分解为可执行的小步骤。这个计划会动态调整,比如如果搜索结果质量不高,可能会重新搜索。',
|
||||
tip: '任务分解是 Agent 的核心能力。复杂任务必须拆解为可管理的步骤。'
|
||||
title: '搜索',
|
||||
desc: '先用搜索工具拿到候选链接。',
|
||||
tool: 'tool:web_search({ query: \"agent introduction\" })',
|
||||
result: '- link1\n- link2\n- link3\n- link4 ...'
|
||||
},
|
||||
{
|
||||
title: '执行搜索',
|
||||
description: '调用搜索工具,获取文章列表',
|
||||
code: `results = web_search(
|
||||
query="AI 技术 2024",
|
||||
max_results=10,
|
||||
time_range="last_month"
|
||||
)`,
|
||||
result: '✅ 找到 15 篇相关文章',
|
||||
explanation: 'Agent 调用 web_search 工具,使用合适的搜索关键词和参数。搜索结果会被保存到短期记忆中,供后续步骤使用。',
|
||||
tip: '工具调用需要选择合适的参数。Agent 会根据任务需求动态调整。'
|
||||
title: '读取页面',
|
||||
desc: '打开前三个链接,取出核心段落。',
|
||||
tool: 'tool:read_page({ url: link1/link2/link3 })',
|
||||
result: '每篇文章的核心段落(已截取)'
|
||||
},
|
||||
{
|
||||
title: '筛选结果',
|
||||
description: '根据相关性筛选最佳文章',
|
||||
code: `top_articles = filter_by_relevance(
|
||||
results,
|
||||
top_n=5,
|
||||
criteria=["date", "citations", "source"]
|
||||
)`,
|
||||
result: '✅ 筛选出 5 篇高质量文章',
|
||||
explanation: '不是所有搜索结果都有用。Agent 会根据日期、引用数、来源权威性等指标筛选出最有价值的文章。',
|
||||
tip: '信息筛选能力决定了 Agent 的输出质量。需要多维度的评估标准。'
|
||||
title: '压缩与整理',
|
||||
desc: '把每篇文章压缩成“一句话总结”,统一格式。',
|
||||
tool: 'LLM: summarize_each({ paragraphs, max_len: 25 })',
|
||||
result: '- 标题A:一句话…\n- 标题B:一句话…\n- 标题C:一句话…'
|
||||
},
|
||||
{
|
||||
title: '阅读内容',
|
||||
description: '读取并理解每篇文章的内容',
|
||||
code: `for article in top_articles:
|
||||
content = read_page(article.url)
|
||||
summary = extract_key_points(content)
|
||||
memory.store(article.id, summary)`,
|
||||
result: '✅ 已阅读 5 篇文章,提取关键信息',
|
||||
explanation: 'Agent 依次阅读每篇文章,提取关键信息并存储到记忆系统中。这样可以在生成总结时快速检索相关信息。',
|
||||
tip: '记忆管理很重要。只保留关键信息,避免上下文膨胀。'
|
||||
},
|
||||
{
|
||||
title: '生成总结',
|
||||
description: '综合所有信息,生成最终报告',
|
||||
code: `summary = generate_report(
|
||||
memories=memory.get_all(),
|
||||
format="markdown",
|
||||
style="concise"
|
||||
)
|
||||
|
||||
summary.add_references(top_articles)`,
|
||||
result: '✅ 总结已完成,包含 5 个参考文献',
|
||||
explanation: 'Agent 从记忆中检索所有关键信息,生成一份结构化的总结报告,并附上参考文献,确保信息的可追溯性。',
|
||||
tip: '输出质量取决于信息的整合能力。结构化输出更易读、更专业。'
|
||||
title: '自检与交付',
|
||||
desc: '检查是否满足“3 条 + 一句话 + 格式正确”,再输出。',
|
||||
tool: 'LLM: self_check({ checklist })',
|
||||
result: '✅ 满足要求;输出已就绪'
|
||||
}
|
||||
]
|
||||
|
||||
const nextStep = () => {
|
||||
if (currentStep.value < steps.length - 1) {
|
||||
currentStep.value++
|
||||
}
|
||||
}
|
||||
|
||||
const prevStep = () => {
|
||||
if (currentStep.value > 0) {
|
||||
currentStep.value--
|
||||
}
|
||||
}
|
||||
|
||||
const togglePlay = () => {
|
||||
isPlaying.value = !isPlaying.value
|
||||
if (isPlaying.value) {
|
||||
playInterval = setInterval(() => {
|
||||
if (currentStep.value < steps.length - 1) {
|
||||
currentStep.value++
|
||||
} else {
|
||||
currentStep.value = 0
|
||||
}
|
||||
}, 2500)
|
||||
} else {
|
||||
clearInterval(playInterval)
|
||||
}
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
currentStep.value = 0
|
||||
isPlaying.value = false
|
||||
clearInterval(playInterval)
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(playInterval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agent-task-flow-demo {
|
||||
.flow {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.task-input {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.input-content {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.flow-timeline {
|
||||
position: relative;
|
||||
padding: 20px 0;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.timeline-line {
|
||||
position: absolute;
|
||||
left: 24px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 4px;
|
||||
background: var(--vp-c-divider);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
padding-left: 60px;
|
||||
padding-bottom: 32px;
|
||||
opacity: 0.5;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.timeline-item.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.timeline-item.completed {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 4px solid var(--vp-c-divider);
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.timeline-item.active .timeline-dot {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand);
|
||||
box-shadow: 0 0 20px rgba(66, 153, 225, 0.5);
|
||||
}
|
||||
|
||||
.timeline-item.completed .timeline-dot {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.timeline-item.active .timeline-content {
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 4px 20px rgba(66, 153, 225, 0.2);
|
||||
}
|
||||
|
||||
.step-number {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.step-description {
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.step-code {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.code-label {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-code pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.step-code code {
|
||||
font-family: 'Monaco', 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-1);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.step-result {
|
||||
background: rgba(66, 153, 225, 0.1);
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-brand);
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
color: var(--vp-c-text-1);
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.flow-controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 10px 20px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.control-btn:hover:not(:disabled) {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.control-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.control-btn.primary {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.control-btn.primary:hover:not(:disabled) {
|
||||
background: var(--vp-c-brand-dark);
|
||||
}
|
||||
|
||||
.flow-explanation {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.explanation-card h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.explanation-card p {
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
.actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.btn { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); padding: 8px 12px; border-radius: 10px; cursor: pointer; }
|
||||
.btn.primary { border-color: var(--vp-c-brand); color: var(--vp-c-brand); }
|
||||
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
|
||||
.tip-icon {
|
||||
font-size: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.timeline { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; }
|
||||
.t { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 10px; display: flex; gap: 10px; align-items: center; cursor: pointer; text-align: left; }
|
||||
.t.active { border-color: var(--vp-c-brand); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06); }
|
||||
.n { width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center; background: var(--vp-c-bg-soft); border: 1px solid var(--vp-c-divider); font-weight: 800; }
|
||||
.txt { font-weight: 800; }
|
||||
|
||||
.tips > div:last-child {
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
}
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
|
||||
.panel { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; }
|
||||
.panel-title { font-weight: 700; margin-bottom: 6px; }
|
||||
.panel-body { color: var(--vp-c-text-2); line-height: 1.6; }
|
||||
pre { margin: 0; background: #0b1221; color: #e5e7eb; border-radius: 10px; padding: 12px; font-family: var(--vp-font-family-mono); font-size: 13px; overflow-x: auto; white-space: pre-wrap; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,372 +1,163 @@
|
||||
<!--
|
||||
AgentWorkflowDemo.vue
|
||||
Agent 核心循环(更像“先玩后讲”的演示):
|
||||
- 点步骤:看这一轮 Agent “在干什么”
|
||||
- 点“下一轮”:看它如何反复迭代直到完成
|
||||
-->
|
||||
<template>
|
||||
<div class="agent-workflow-demo">
|
||||
<div class="workflow-container">
|
||||
<div class="cycle-diagram">
|
||||
<div class="center-label">Agent 核心循环</div>
|
||||
|
||||
<div
|
||||
v-for="(step, index) in steps"
|
||||
:key="step.name"
|
||||
class="cycle-step"
|
||||
:class="{
|
||||
active: currentStep === index,
|
||||
completed: currentStep > index
|
||||
}"
|
||||
:style="getStepPosition(index)"
|
||||
>
|
||||
<div class="step-icon">{{ step.icon }}</div>
|
||||
<div class="step-name">{{ step.name }}</div>
|
||||
<div class="step-desc">{{ step.desc }}</div>
|
||||
</div>
|
||||
|
||||
<svg class="arrows" v-if="currentStep < steps.length">
|
||||
<circle cx="200" cy="200" r="130" fill="none" :stroke="arrowColor" stroke-width="2" stroke-dasharray="5,5">
|
||||
<animate
|
||||
v-if="isPlaying"
|
||||
attributeName="stroke-dashoffset"
|
||||
from="0"
|
||||
to="-20"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</circle>
|
||||
</svg>
|
||||
<div class="workflow">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">先玩一下:Agent 不是“聊天”,是“循环行动”</div>
|
||||
<div class="subtitle">它会反复:观察 → 计划 → 用工具 → 检查结果。</div>
|
||||
</div>
|
||||
|
||||
<div class="step-details">
|
||||
<div class="current-action">
|
||||
<div class="action-label">当前步骤</div>
|
||||
<div class="action-content">
|
||||
<span class="action-icon">{{ steps[currentStep]?.icon }}</span>
|
||||
<span class="action-text">{{ steps[currentStep]?.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-explanation">
|
||||
<h4>{{ steps[currentStep]?.name }}</h4>
|
||||
<p>{{ steps[currentStep]?.detail }}</p>
|
||||
|
||||
<div v-if="currentStep > 0 && currentStep <= steps.length" class="example-box">
|
||||
<div class="example-title">📝 示例</div>
|
||||
<div class="example-content">{{ steps[currentStep]?.example }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn" @click="reset">重置</button>
|
||||
<button class="btn primary" @click="nextRound">下一轮 ({{ round }}/3)</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button @click="prevStep" :disabled="currentStep === 0" class="control-btn">
|
||||
← 上一步
|
||||
</button>
|
||||
<button @click="togglePlay" class="control-btn primary">
|
||||
{{ isPlaying ? '⏸ 暂停' : '▶ 自动播放' }}
|
||||
</button>
|
||||
<button @click="nextStep" :disabled="currentStep === steps.length" class="control-btn">
|
||||
下一步 →
|
||||
</button>
|
||||
<button @click="reset" class="control-btn">
|
||||
↺ 重置
|
||||
<div class="cycle">
|
||||
<button
|
||||
v-for="s in steps"
|
||||
:key="s.id"
|
||||
:class="['step', { active: currentStep === s.id }]"
|
||||
@click="currentStep = s.id"
|
||||
>
|
||||
<span class="icon">{{ s.icon }}</span>
|
||||
<span class="name">{{ s.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="panels">
|
||||
<div class="panel">
|
||||
<div class="panel-title">任务</div>
|
||||
<div class="panel-body">
|
||||
帮我找 3 篇 “Agent” 入门文章,并输出:标题 + 一句话总结。
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-title">这一轮发生了什么?</div>
|
||||
<div class="panel-body">{{ detail }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="log">
|
||||
<div class="log-title">Agent 运行日志(示意)</div>
|
||||
<pre><code>{{ logText }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const currentStep = ref(0)
|
||||
const isPlaying = ref(false)
|
||||
let playInterval = null
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const steps = [
|
||||
{ id: 'observe', name: '观察', icon: '👀' },
|
||||
{ id: 'plan', name: '计划', icon: '🧩' },
|
||||
{ id: 'act', name: '行动', icon: '🔧' },
|
||||
{ id: 'check', name: '检查', icon: '✅' }
|
||||
]
|
||||
|
||||
const round = ref(1)
|
||||
const currentStep = ref('observe')
|
||||
|
||||
const scenarios = [
|
||||
{
|
||||
name: '感知',
|
||||
icon: '👁️',
|
||||
desc: 'Perceive',
|
||||
detail: 'Agent 从环境中接收信息,包括用户输入、文件内容、网页数据等。',
|
||||
example: '用户说:帮我搜索最新的 AI 文章'
|
||||
observe: '看到用户目标:要 3 篇入门文章 + 简短总结。',
|
||||
plan: '计划:1) 搜索关键词 2) 打开前几条 3) 抽取标题与要点。',
|
||||
act: '调用工具:web_search(query="agent introduction")。',
|
||||
check: '检查:结果里有 3 条可用链接,还缺“每条一句话总结”。'
|
||||
},
|
||||
{
|
||||
name: '决策',
|
||||
icon: '🤔',
|
||||
desc: 'Reason',
|
||||
detail: '分析当前状态,制定行动计划,选择合适的工具来完成任务。',
|
||||
example: '分析:需要搜索 → 应该使用 web_search 工具'
|
||||
observe: '拿到链接列表,准备逐条打开并提取要点。',
|
||||
plan: '计划:依次 read_page 3 次,把内容压缩成一句话。',
|
||||
act: '调用工具:read_page(url=...) × 3。',
|
||||
check: '检查:信息够了,但标题格式不统一,需要整理输出。'
|
||||
},
|
||||
{
|
||||
name: '行动',
|
||||
icon: '🔧',
|
||||
desc: 'Act',
|
||||
detail: '执行决策,调用工具,修改文件,发送请求等具体操作。',
|
||||
example: '执行:web_search("AI 文章 2024")'
|
||||
},
|
||||
{
|
||||
name: '观察',
|
||||
icon: '👀',
|
||||
desc: 'Observe',
|
||||
detail: '查看行动结果,评估是否达成目标,决定是继续还是结束。',
|
||||
example: '观察:找到 10 篇相关文章 → 继续阅读'
|
||||
observe: '材料齐全:标题 + 文章要点都已提取。',
|
||||
plan: '计划:统一格式,输出 Markdown 列表。',
|
||||
act: '组织输出:每条“标题 - 一句话总结”。',
|
||||
check: '完成:满足“3 条 + 一句话总结 + 可直接复制”。'
|
||||
}
|
||||
]
|
||||
|
||||
const arrowColor = computed(() => {
|
||||
if (currentStep.value === 0) return 'var(--vp-c-divider)'
|
||||
return 'var(--vp-c-brand)'
|
||||
const current = computed(() => scenarios[round.value - 1])
|
||||
|
||||
const detail = computed(() => current.value[currentStep.value])
|
||||
|
||||
const logText = computed(() => {
|
||||
const logs = []
|
||||
for (let i = 0; i < round.value; i++) {
|
||||
logs.push(`--- Round ${i + 1} ---`)
|
||||
logs.push(`OBS: ${scenarios[i].observe}`)
|
||||
logs.push(`PLAN: ${scenarios[i].plan}`)
|
||||
logs.push(`ACT: ${scenarios[i].act}`)
|
||||
logs.push(`CHECK: ${scenarios[i].check}`)
|
||||
logs.push('')
|
||||
}
|
||||
return logs.join('\n')
|
||||
})
|
||||
|
||||
const getStepPosition = (index) => {
|
||||
const positions = [
|
||||
{ top: '10%', left: '50%', transform: 'translateX(-50%)' }, // Top
|
||||
{ right: '10%', top: '50%', transform: 'translateY(-50%)' }, // Right
|
||||
{ bottom: '10%', left: '50%', transform: 'translateX(-50%)' }, // Bottom
|
||||
{ left: '10%', top: '50%', transform: 'translateY(-50%)' } // Left
|
||||
]
|
||||
return positions[index]
|
||||
}
|
||||
|
||||
const nextStep = () => {
|
||||
if (currentStep.value < steps.length) {
|
||||
currentStep.value++
|
||||
}
|
||||
}
|
||||
|
||||
const prevStep = () => {
|
||||
if (currentStep.value > 0) {
|
||||
currentStep.value--
|
||||
}
|
||||
}
|
||||
|
||||
const togglePlay = () => {
|
||||
isPlaying.value = !isPlaying.value
|
||||
if (isPlaying.value) {
|
||||
playInterval = setInterval(() => {
|
||||
if (currentStep.value < steps.length) {
|
||||
currentStep.value++
|
||||
} else {
|
||||
currentStep.value = 0
|
||||
}
|
||||
}, 2000)
|
||||
} else {
|
||||
clearInterval(playInterval)
|
||||
}
|
||||
const nextRound = () => {
|
||||
if (round.value >= 3) return
|
||||
round.value++
|
||||
currentStep.value = 'observe'
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
currentStep.value = 0
|
||||
isPlaying.value = false
|
||||
clearInterval(playInterval)
|
||||
round.value = 1
|
||||
currentStep.value = 'observe'
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(playInterval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agent-workflow-demo {
|
||||
.workflow {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.workflow-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 32px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.workflow-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.cycle-diagram {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.center-label {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
color: var(--vp-c-brand);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cycle-step {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
transition: all 0.3s ease;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.cycle-step.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-soft);
|
||||
box-shadow: 0 0 20px rgba(66, 153, 225, 0.3);
|
||||
transform: scale(1.1) !important;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.cycle-step.completed {
|
||||
border-color: var(--vp-c-brand);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.step-name {
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.step-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.arrows {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.step-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.current-action {
|
||||
padding: 20px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.action-label {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.action-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.step-explanation {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.step-explanation h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.step-explanation p {
|
||||
margin: 0 0 16px 0;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.example-box {
|
||||
padding: 16px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.example-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.example-content {
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 10px 20px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
.header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
.actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.btn { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); padding: 8px 12px; border-radius: 10px; cursor: pointer; }
|
||||
.btn.primary { border-color: var(--vp-c-brand); color: var(--vp-c-brand); }
|
||||
|
||||
.cycle {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.step {
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
text-align: left;
|
||||
}
|
||||
.step.active { border-color: var(--vp-c-brand); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06); }
|
||||
.icon { width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center; background: var(--vp-c-bg-soft); border: 1px solid var(--vp-c-divider); }
|
||||
.name { font-weight: 800; }
|
||||
|
||||
.control-btn:hover:not(:disabled) {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
.panels { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
|
||||
.panel { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; }
|
||||
.panel-title { font-weight: 700; margin-bottom: 6px; }
|
||||
.panel-body { color: var(--vp-c-text-2); line-height: 1.6; }
|
||||
|
||||
.control-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.control-btn.primary {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.control-btn.primary:hover:not(:disabled) {
|
||||
background: var(--vp-c-brand-dark);
|
||||
border-color: var(--vp-c-brand-dark);
|
||||
}
|
||||
.log { background: var(--vp-c-bg); border: 1px dashed var(--vp-c-divider); border-radius: 12px; padding: 12px; }
|
||||
.log-title { font-weight: 700; margin-bottom: 8px; }
|
||||
pre { margin: 0; background: #0b1221; color: #e5e7eb; border-radius: 10px; padding: 12px; font-family: var(--vp-font-family-mono); font-size: 13px; overflow-x: auto; white-space: pre-wrap; }
|
||||
</style>
|
||||
|
||||
|
||||
+78
-509
@@ -1,529 +1,98 @@
|
||||
<!--
|
||||
FrameworkComparisonDemo.vue
|
||||
框架对比(更直观):选择关注点,表格高亮适配度。
|
||||
-->
|
||||
<template>
|
||||
<div class="framework-comparison-demo">
|
||||
<div class="framework-tabs">
|
||||
<button
|
||||
v-for="(framework, index) in frameworks"
|
||||
:key="framework.name"
|
||||
class="tab-btn"
|
||||
:class="{ active: selectedFramework === index }"
|
||||
@click="selectedFramework = index"
|
||||
>
|
||||
{{ framework.name }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="framework-content">
|
||||
<div class="framework-header">
|
||||
<div class="framework-icon">{{ frameworks[selectedFramework].icon }}</div>
|
||||
<div>
|
||||
<h3>{{ frameworks[selectedFramework].name }}</h3>
|
||||
<p class="framework-tagline">{{ frameworks[selectedFramework].tagline }}</p>
|
||||
</div>
|
||||
<div class="cmp">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">主流框架对比(先看“适配度”)</div>
|
||||
<div class="subtitle">先选你的关注点,再看推荐。</div>
|
||||
</div>
|
||||
|
||||
<div class="framework-details">
|
||||
<div class="detail-section">
|
||||
<h4>✨ 特点</h4>
|
||||
<ul>
|
||||
<li v-for="feature in frameworks[selectedFramework].features" :key="feature">
|
||||
{{ feature }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4>🎯 适用场景</h4>
|
||||
<div class="scenarios">
|
||||
<div
|
||||
v-for="scenario in frameworks[selectedFramework].scenarios"
|
||||
:key="scenario"
|
||||
class="scenario-tag"
|
||||
>
|
||||
{{ scenario }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4>⚖️ 优缺点</h4>
|
||||
<div class="pros-cons">
|
||||
<div class="pros">
|
||||
<div class="pros-title">✅ 优点</div>
|
||||
<ul>
|
||||
<li v-for="pro in frameworks[selectedFramework].pros" :key="pro">{{ pro }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="cons">
|
||||
<div class="cons-title">❌ 缺点</div>
|
||||
<ul>
|
||||
<li v-for="con in frameworks[selectedFramework].cons" :key="con">{{ con }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4>📊 能力评分</h4>
|
||||
<div class="capabilities">
|
||||
<div
|
||||
v-for="(value, key) in frameworks[selectedFramework].scores"
|
||||
:key="key"
|
||||
class="capability-item"
|
||||
>
|
||||
<div class="capability-label">{{ key }}</div>
|
||||
<div class="capability-bar">
|
||||
<div class="bar-fill" :style="{ width: value + '%' }"></div>
|
||||
</div>
|
||||
<div class="capability-value">{{ value }}/100</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4>🔗 相关链接</h4>
|
||||
<div class="links">
|
||||
<a :href="frameworks[selectedFramework].website" target="_blank" class="link-item">
|
||||
🌐 官网
|
||||
</a>
|
||||
<a :href="frameworks[selectedFramework].github" target="_blank" class="link-item">
|
||||
💻 GitHub
|
||||
</a>
|
||||
<a :href="frameworks[selectedFramework].docs" target="_blank" class="link-item">
|
||||
📚 文档
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus">
|
||||
<button
|
||||
v-for="f in focuses"
|
||||
:key="f.id"
|
||||
:class="['chip', { active: focus === f.id }]"
|
||||
@click="focus = f.id"
|
||||
>
|
||||
{{ f.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-table">
|
||||
<h3>📋 快速对比</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>特性</th>
|
||||
<th v-for="fw in frameworks" :key="fw.name">{{ fw.name }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>学习曲线</td>
|
||||
<td v-for="fw in frameworks" :key="fw.name">{{ fw.learningCurve }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>社区规模</td>
|
||||
<td v-for="fw in frameworks" :key="fw.name">{{ fw.community }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>最佳用途</td>
|
||||
<td v-for="fw in frameworks" :key="fw.name">{{ fw.bestFor }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GitHub Stars</td>
|
||||
<td v-for="fw in frameworks" :key="fw.name">{{ fw.stars }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="table">
|
||||
<div class="row head">
|
||||
<div>框架</div>
|
||||
<div>上手</div>
|
||||
<div>可控</div>
|
||||
<div>多 Agent</div>
|
||||
<div>适合做什么</div>
|
||||
</div>
|
||||
<div v-for="fw in frameworks" :key="fw.name" :class="['row', { best: fw.name === best }]">
|
||||
<div class="name">{{ fw.name }}</div>
|
||||
<div>{{ fw.learn }}</div>
|
||||
<div>{{ fw.control }}</div>
|
||||
<div>{{ fw.multi }}</div>
|
||||
<div class="use">{{ fw.use }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rec">
|
||||
<div class="rec-title">此刻更推荐:{{ best }}</div>
|
||||
<div class="rec-body">{{ reason }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const selectedFramework = ref(0)
|
||||
const focuses = [
|
||||
{ id: 'start', label: '快速上手' },
|
||||
{ id: 'control', label: '可控可调试' },
|
||||
{ id: 'team', label: '多 Agent 协作' }
|
||||
]
|
||||
|
||||
const focus = ref('control')
|
||||
|
||||
const frameworks = [
|
||||
{
|
||||
name: 'LangChain',
|
||||
icon: '🦜',
|
||||
tagline: '最流行的 LLM 应用开发框架',
|
||||
features: [
|
||||
'组件化设计,高度灵活',
|
||||
'丰富的集成(100+ 工具)',
|
||||
'LangGraph 专门用于构建 Agent',
|
||||
'支持多种 LLM 提供商',
|
||||
'活跃的社区和生态系统'
|
||||
],
|
||||
scenarios: ['需要高度定制', '企业级应用', '与现有系统集成', '快速原型开发'],
|
||||
pros: [
|
||||
'生态系统最完善',
|
||||
'文档齐全,示例丰富',
|
||||
'灵活性强',
|
||||
'社区支持好'
|
||||
],
|
||||
cons: [
|
||||
'学习曲线陡峭',
|
||||
'概念较多',
|
||||
'版本更新快',
|
||||
'有些抽象难以理解'
|
||||
],
|
||||
scores: {
|
||||
'灵活性': 95,
|
||||
'易用性': 70,
|
||||
'性能': 80,
|
||||
'文档': 90,
|
||||
'社区': 95
|
||||
},
|
||||
website: 'https://langchain.com',
|
||||
github: 'https://github.com/langchain-ai/langchain',
|
||||
docs: 'https://python.langchain.com',
|
||||
learningCurve: '⭐⭐⭐⭐',
|
||||
community: '⭐⭐⭐⭐⭐',
|
||||
bestFor: '通用 LLM 应用',
|
||||
stars: '95k+'
|
||||
},
|
||||
{
|
||||
name: 'AutoGen',
|
||||
icon: '🤖',
|
||||
tagline: '微软出品的 Agent 协作框架',
|
||||
features: [
|
||||
'多 Agent 协作',
|
||||
'Agent 之间可以对话',
|
||||
'强大的代码执行能力',
|
||||
'支持人类介入',
|
||||
'内置错误恢复'
|
||||
],
|
||||
scenarios: ['编程辅助', '多 Agent 协作', '数据分析', '代码审查'],
|
||||
pros: [
|
||||
'协作模式独特',
|
||||
'代码执行能力强',
|
||||
'微软支持',
|
||||
'易于调试'
|
||||
],
|
||||
cons: [
|
||||
'相对小众',
|
||||
'文档不够完善',
|
||||
'社区较小',
|
||||
'主要用于编程场景'
|
||||
],
|
||||
scores: {
|
||||
'灵活性': 75,
|
||||
'易用性': 80,
|
||||
'性能': 85,
|
||||
'文档': 70,
|
||||
'社区': 70
|
||||
},
|
||||
website: 'https://microsoft.github.io/autogen',
|
||||
github: 'https://github.com/microsoft/autogen',
|
||||
docs: 'https://microsoft.github.io/autogen/docs',
|
||||
learningCurve: '⭐⭐⭐',
|
||||
community: '⭐⭐⭐',
|
||||
bestFor: '多 Agent 编程',
|
||||
stars: '30k+'
|
||||
},
|
||||
{
|
||||
name: 'CrewAI',
|
||||
icon: '👥',
|
||||
tagline: '角色驱动的多 Agent 系统',
|
||||
features: [
|
||||
'角色驱动的 Agent 设计',
|
||||
'团队协作模式',
|
||||
'直观的任务定义',
|
||||
'支持复杂的协作流程',
|
||||
'易于理解和使用'
|
||||
],
|
||||
scenarios: ['内容创作', '研究团队', '营销团队', '业务流程自动化'],
|
||||
pros: [
|
||||
'概念易于理解',
|
||||
'角色设计直观',
|
||||
'协作流程清晰',
|
||||
'快速上手'
|
||||
],
|
||||
cons: [
|
||||
'生态相对较小',
|
||||
'定制性有限',
|
||||
'性能优化不足',
|
||||
'社区较小'
|
||||
],
|
||||
scores: {
|
||||
'灵活性': 70,
|
||||
'易用性': 90,
|
||||
'性能': 70,
|
||||
'文档': 80,
|
||||
'社区': 65
|
||||
},
|
||||
website: 'https://crewai.com',
|
||||
github: 'https://github.com/joaomdmoura/crewAI',
|
||||
docs: 'https://docs.crewai.com',
|
||||
learningCurve: '⭐⭐',
|
||||
community: '⭐⭐⭐',
|
||||
bestFor: '角色协作',
|
||||
stars: '12k+'
|
||||
},
|
||||
{
|
||||
name: 'AgentScope',
|
||||
icon: '🔭',
|
||||
tagline: '阿里开源的 Agent 框架',
|
||||
features: [
|
||||
'中文友好',
|
||||
'简单易用',
|
||||
'支持多模态',
|
||||
'分布式执行',
|
||||
'可视化调试'
|
||||
],
|
||||
scenarios: ['中文应用', '多模态 Agent', '分布式系统', '国内部署'],
|
||||
pros: [
|
||||
'中文文档完善',
|
||||
'国内部署友好',
|
||||
'上手简单',
|
||||
'多模态支持好'
|
||||
],
|
||||
cons: [
|
||||
'生态较新',
|
||||
'社区较小',
|
||||
'功能相对有限',
|
||||
'国际化不足'
|
||||
],
|
||||
scores: {
|
||||
'灵活性': 70,
|
||||
'易用性': 85,
|
||||
'性能': 75,
|
||||
'文档': 80,
|
||||
'社区': 60
|
||||
},
|
||||
website: 'https://github.com/modelscope/agentscope',
|
||||
github: 'https://github.com/modelscope/agentscope',
|
||||
docs: 'https://modelscope.github.io/agentscope',
|
||||
learningCurve: '⭐⭐',
|
||||
community: '⭐⭐',
|
||||
bestFor: '中文多模态',
|
||||
stars: '5k+'
|
||||
}
|
||||
{ name: 'LangChain / LangGraph', learn: '中', control: '高', multi: '中', use: '可控的工具调用、工作流、企业集成' },
|
||||
{ name: 'AutoGen', learn: '中', control: '中', multi: '高', use: '多 Agent 对话协作、编程/分析助手' },
|
||||
{ name: 'CrewAI', learn: '低', control: '中', multi: '高', use: '角色分工清晰的团队协作任务' }
|
||||
]
|
||||
|
||||
const best = computed(() => {
|
||||
if (focus.value === 'start') return 'CrewAI'
|
||||
if (focus.value === 'team') return 'AutoGen'
|
||||
return 'LangChain / LangGraph'
|
||||
})
|
||||
|
||||
const reason = computed(() => {
|
||||
if (focus.value === 'start') return '概念更直观(角色+任务),适合先跑通一个最小团队。'
|
||||
if (focus.value === 'team') return '多 Agent 对话与协作是强项,适合需要分工的场景。'
|
||||
return '把流程“画成图/写成步骤”,更利于调试、上线与长期维护。'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.framework-comparison-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
}
|
||||
.cmp { border: 1px solid var(--vp-c-divider); border-radius: 12px; background: var(--vp-c-bg-soft); padding: 16px; margin: 20px 0; display: flex; flex-direction: column; gap: 12px; }
|
||||
.header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
.focus { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.chip { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); padding: 8px 12px; border-radius: 999px; cursor: pointer; }
|
||||
.chip.active { border-color: var(--vp-c-brand); color: var(--vp-c-brand); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); }
|
||||
|
||||
.framework-tabs {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.table { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; overflow: hidden; }
|
||||
.row { display: grid; grid-template-columns: 1.4fr 0.8fr 0.8fr 0.9fr 2.1fr; gap: 10px; padding: 10px 12px; border-top: 1px solid var(--vp-c-divider); align-items: center; }
|
||||
.row.head { border-top: none; font-weight: 800; color: var(--vp-c-text-2); background: var(--vp-c-bg-soft); }
|
||||
.name { font-weight: 800; }
|
||||
.use { color: var(--vp-c-text-2); }
|
||||
.row.best { outline: 2px solid var(--vp-c-brand); outline-offset: -2px; background: rgba(0, 0, 0, 0.02); }
|
||||
|
||||
.tab-btn {
|
||||
padding: 12px 24px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.framework-content {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.framework-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.framework-icon {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.framework-header h3 {
|
||||
margin: 0 0 4px 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.framework-tagline {
|
||||
margin: 0;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.framework-details {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.detail-section h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.detail-section ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.detail-section li {
|
||||
padding: 4px 0;
|
||||
color: var(--vp-c-text-2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.detail-section li::before {
|
||||
content: '•';
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.scenarios {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.scenario-tag {
|
||||
padding: 8px 16px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 6px;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.pros-cons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pros-cons {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.pros-title {
|
||||
color: #10b981;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.cons-title {
|
||||
color: #ef4444;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.capabilities {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.capability-item {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr 60px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.capability-label {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.capability-bar {
|
||||
height: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--vp-c-brand), var(--vp-c-brand-light));
|
||||
border-radius: 12px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.capability-value {
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-brand);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.link-item {
|
||||
padding: 10px 20px;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.link-item:hover {
|
||||
background: var(--vp-c-brand-dark);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.comparison-table {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.comparison-table h3 {
|
||||
margin: 0 0 20px 0;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
thead th {
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
border-bottom: 2px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
tbody td {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.rec { background: var(--vp-c-bg); border: 1px dashed var(--vp-c-divider); border-radius: 12px; padding: 12px; }
|
||||
.rec-title { font-weight: 800; margin-bottom: 6px; }
|
||||
.rec-body { color: var(--vp-c-text-2); line-height: 1.6; }
|
||||
</style>
|
||||
|
||||
|
||||
+101
-385
@@ -1,407 +1,123 @@
|
||||
<!--
|
||||
FrameworkSelectionDemo.vue
|
||||
框架选择小向导:回答 3 个问题,给出推荐 + 适配理由 + 你需要注意什么。
|
||||
-->
|
||||
<template>
|
||||
<div class="framework-selection-demo">
|
||||
<div class="selection-quiz">
|
||||
<h3>🤔 选择合适的 Agent 框架</h3>
|
||||
<p class="quiz-intro">回答几个问题,帮你找到最适合的框架!</p>
|
||||
|
||||
<div v-if="currentQuestion < questions.length" class="question-container">
|
||||
<div class="question-header">
|
||||
<span class="question-number">问题 {{ currentQuestion + 1 }}/{{ questions.length }}</span>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: ((currentQuestion + 1) / questions.length * 100) + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="question-text">{{ questions[currentQuestion].question }}</h4>
|
||||
|
||||
<div class="options">
|
||||
<button
|
||||
v-for="(option, index) in questions[currentQuestion].options"
|
||||
:key="index"
|
||||
class="option-btn"
|
||||
@click="selectOption(index)"
|
||||
>
|
||||
{{ option.text }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="sel">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">三问选框架</div>
|
||||
<div class="subtitle">目标:先跑通一个最小 Agent,再逐步增强。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="recommendation">
|
||||
<div class="result-header">
|
||||
<div class="result-icon">🎯</div>
|
||||
<h4>推荐框架:{{ recommendedFramework }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="result-description">
|
||||
{{ getRecommendationDescription() }}
|
||||
</div>
|
||||
|
||||
<div class="result-reasons">
|
||||
<div class="reasons-title">为什么推荐这个?</div>
|
||||
<ul>
|
||||
<li v-for="reason in getRecommendationReasons()" :key="reason">{{ reason }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="next-steps">
|
||||
<div class="steps-title">📚 下一步</div>
|
||||
<div class="step-links">
|
||||
<a :href="getFrameworkInfo().website" target="_blank" class="step-link">
|
||||
🌐 访问官网
|
||||
</a>
|
||||
<a :href="getFrameworkInfo().docs" target="_blank" class="step-link">
|
||||
📖 阅读文档
|
||||
</a>
|
||||
<a :href="getFrameworkInfo().github" target="_blank" class="step-link">
|
||||
💻 查看代码
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button @click="resetQuiz" class="restart-btn">
|
||||
↺ 重新选择
|
||||
</button>
|
||||
<div class="q">
|
||||
<div class="q-title">1) 你更在乎什么?</div>
|
||||
<div class="opts">
|
||||
<button v-for="o in q1" :key="o.id" :class="['opt', { active: a1 === o.id }]" @click="a1 = o.id">{{ o.label }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="q">
|
||||
<div class="q-title">2) 你的任务像哪种?</div>
|
||||
<div class="opts">
|
||||
<button v-for="o in q2" :key="o.id" :class="['opt', { active: a2 === o.id }]" @click="a2 = o.id">{{ o.label }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="q">
|
||||
<div class="q-title">3) 需要多 Agent 分工吗?</div>
|
||||
<div class="opts">
|
||||
<button v-for="o in q3" :key="o.id" :class="['opt', { active: a3 === o.id }]" @click="a3 = o.id">{{ o.label }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result">
|
||||
<div class="r-title">推荐:{{ rec.name }}</div>
|
||||
<div class="r-body">{{ rec.reason }}</div>
|
||||
<div class="r-note"><strong>注意:</strong>{{ rec.note }}</div>
|
||||
<div class="r-next"><strong>下一步:</strong>{{ rec.next }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const currentQuestion = ref(0)
|
||||
const answers = ref([])
|
||||
|
||||
const questions = [
|
||||
{
|
||||
question: '你的主要使用场景是什么?',
|
||||
options: [
|
||||
{ text: '🤖 编程和代码开发', scores: { LangChain: 2, AutoGen: 5, CrewAI: 2, AgentScope: 2 } },
|
||||
{ text: '📝 内容创作和文案', scores: { LangChain: 3, AutoGen: 1, CrewAI: 5, AgentScope: 3 } },
|
||||
{ text: '🔍 数据分析和研究', scores: { LangChain: 4, AutoGen: 4, CrewAI: 3, AgentScope: 3 } },
|
||||
{ text: '🌐 通用应用开发', scores: { LangChain: 5, AutoGen: 2, CrewAI: 3, AgentScope: 4 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: '你更看重什么?',
|
||||
options: [
|
||||
{ text: '⚡ 快速上手', scores: { LangChain: 2, AutoGen: 3, CrewAI: 5, AgentScope: 4 } },
|
||||
{ text: '🔧 高度定制', scores: { LangChain: 5, AutoGen: 3, CrewAI: 2, AgentScope: 2 } },
|
||||
{ text: '👥 团队协作', scores: { LangChain: 3, AutoGen: 4, CrewAI: 5, AgentScope: 2 } },
|
||||
{ text: '📚 文档完善', scores: { LangChain: 5, AutoGen: 3, CrewAI: 3, AgentScope: 3 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: '你的技术水平?',
|
||||
options: [
|
||||
{ text: '🌱 初学者', scores: { LangChain: 2, AutoGen: 2, CrewAI: 4, AgentScope: 5 } },
|
||||
{ text: '🌿 有一些经验', scores: { LangChain: 4, AutoGen: 3, CrewAI: 4, AgentScope: 4 } },
|
||||
{ text: '🌳 经验丰富', scores: { LangChain: 5, AutoGen: 4, CrewAI: 3, AgentScope: 3 } },
|
||||
{ text: '🏆 专家级别', scores: { LangChain: 5, AutoGen: 5, CrewAI: 3, AgentScope: 3 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: '项目规模?',
|
||||
options: [
|
||||
{ text: '📦 个人项目', scores: { LangChain: 3, AutoGen: 3, CrewAI: 4, AgentScope: 5 } },
|
||||
{ text: '🏢 小团队项目', scores: { LangChain: 4, AutoGen: 4, CrewAI: 5, AgentScope: 3 } },
|
||||
{ text: '🏛️ 企业级应用', scores: { LangChain: 5, AutoGen: 3, CrewAI: 3, AgentScope: 2 } },
|
||||
{ text: '🌍 大规模分布式', scores: { LangChain: 4, AutoGen: 2, CrewAI: 2, AgentScope: 3 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: '是否需要中文支持?',
|
||||
options: [
|
||||
{ text: '🇨🇳 非常重要', scores: { LangChain: 2, AutoGen: 2, CrewAI: 2, AgentScope: 5 } },
|
||||
{ text: '🌏 最好有', scores: { LangChain: 3, AutoGen: 2, CrewAI: 2, AgentScope: 4 } },
|
||||
{ text: '🌐 不重要', scores: { LangChain: 4, AutoGen: 4, CrewAI: 4, AgentScope: 2 } },
|
||||
{ text: '🚫 不需要', scores: { LangChain: 5, AutoGen: 5, CrewAI: 4, AgentScope: 2 } }
|
||||
]
|
||||
}
|
||||
const q1 = [
|
||||
{ id: 'easy', label: '快速上手' },
|
||||
{ id: 'stable', label: '可控可上线' },
|
||||
{ id: 'team', label: '团队协作' }
|
||||
]
|
||||
const q2 = [
|
||||
{ id: 'workflow', label: '有明确流程(步骤/图)' },
|
||||
{ id: 'chat', label: '偏对话与协商' },
|
||||
{ id: 'explore', label: '探索式试错' }
|
||||
]
|
||||
const q3 = [
|
||||
{ id: 'no', label: '不需要' },
|
||||
{ id: 'maybe', label: '可能需要' },
|
||||
{ id: 'yes', label: '必须需要' }
|
||||
]
|
||||
|
||||
const frameworkInfo = {
|
||||
LangChain: {
|
||||
website: 'https://langchain.com',
|
||||
docs: 'https://python.langchain.com',
|
||||
github: 'https://github.com/langchain-ai/langchain',
|
||||
description: 'LangChain 是最流行的 LLM 应用开发框架,拥有最完善的生态系统和社区支持。适合需要高度定制和集成的场景。',
|
||||
reasons: [
|
||||
'最强大的生态系统',
|
||||
'高度可定制',
|
||||
'丰富的集成选项',
|
||||
'活跃的社区支持'
|
||||
]
|
||||
},
|
||||
AutoGen: {
|
||||
website: 'https://microsoft.github.io/autogen',
|
||||
docs: 'https://microsoft.github.io/autogen/docs',
|
||||
github: 'https://github.com/microsoft/autogen',
|
||||
description: 'AutoGen 是微软开发的多 Agent 协作框架,特别擅长编程和代码相关任务。如果你需要多个 Agent 协作完成编程任务,这是最佳选择。',
|
||||
reasons: [
|
||||
'独特的协作模式',
|
||||
'强大的代码执行能力',
|
||||
'微软官方支持',
|
||||
'适合编程辅助场景'
|
||||
]
|
||||
},
|
||||
CrewAI: {
|
||||
website: 'https://crewai.com',
|
||||
docs: 'https://docs.crewai.com',
|
||||
github: 'https://github.com/joaomdmoura/crewAI',
|
||||
description: 'CrewAI 采用角色驱动的 Agent 设计,概念直观易懂。非常适合快速组建 AI 团队来完成内容创作、研究等任务。',
|
||||
reasons: [
|
||||
'直观的角色设计',
|
||||
'易于上手',
|
||||
'团队协作模式清晰',
|
||||
'适合快速原型开发'
|
||||
]
|
||||
},
|
||||
AgentScope: {
|
||||
website: 'https://github.com/modelscope/agentscope',
|
||||
docs: 'https://modelscope.github.io/agentscope',
|
||||
github: 'https://github.com/modelscope/agentscope',
|
||||
description: 'AgentScope 是阿里开源的 Agent 框架,中文友好,简单易用。特别适合国内开发者和需要中文支持的项目。',
|
||||
reasons: [
|
||||
'完善的中文文档',
|
||||
'国内部署友好',
|
||||
'上手非常简单',
|
||||
'多模态支持良好'
|
||||
]
|
||||
const a1 = ref('stable')
|
||||
const a2 = ref('workflow')
|
||||
const a3 = ref('maybe')
|
||||
|
||||
const rec = computed(() => {
|
||||
// Multi-agent first
|
||||
if (a3.value === 'yes' || a1.value === 'team') {
|
||||
if (a2.value === 'chat') {
|
||||
return {
|
||||
name: 'AutoGen',
|
||||
reason: '多 Agent 对话协作是强项,适合“互相讨论、分工协作”。',
|
||||
note: '先把角色边界写清楚,否则容易重复劳动或互怼。',
|
||||
next: '从 2 个 Agent 开始:研究员 + 执行者。'
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: 'CrewAI',
|
||||
reason: '角色+任务模型很直观,适合“分工明确”的团队工作流。',
|
||||
note: '先把输入/输出格式定死,避免多人输出难合并。',
|
||||
next: '先搭 2-3 个角色:Researcher/Writer/Reviewer。'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const recommendedFramework = computed(() => {
|
||||
const scores = { LangChain: 0, AutoGen: 0, CrewAI: 0, AgentScope: 0 }
|
||||
// Single-agent / controllable workflow
|
||||
if (a1.value === 'stable' || a2.value === 'workflow') {
|
||||
return {
|
||||
name: 'LangChain / LangGraph',
|
||||
reason: '更适合把 Agent 写成“可控流程”,便于调试、上线、加护栏。',
|
||||
note: '别一上来做大系统,先把 1 个工具调用跑通。',
|
||||
next: '用 LangGraph 画一个 3-5 节点的小图。'
|
||||
}
|
||||
}
|
||||
|
||||
answers.value.forEach((answerIndex, questionIndex) => {
|
||||
const optionScores = questions[questionIndex].options[answerIndex].scores
|
||||
Object.keys(optionScores).forEach(framework => {
|
||||
scores[framework] += optionScores[framework]
|
||||
})
|
||||
})
|
||||
|
||||
return Object.keys(scores).reduce((a, b) => scores[a] > scores[b] ? a : b)
|
||||
// Easy start
|
||||
return {
|
||||
name: 'CrewAI',
|
||||
reason: '上手快、概念直观,适合先做出一个“能跑”的 demo。',
|
||||
note: 'demo 能跑不代表可上线,后续要补安全与可观测。',
|
||||
next: '先做一个“研究+写作”的最小团队。'
|
||||
}
|
||||
})
|
||||
|
||||
const selectOption = (index) => {
|
||||
answers.value.push(index)
|
||||
if (currentQuestion.value < questions.length - 1) {
|
||||
currentQuestion.value++
|
||||
}
|
||||
}
|
||||
|
||||
const resetQuiz = () => {
|
||||
currentQuestion.value = 0
|
||||
answers.value = []
|
||||
}
|
||||
|
||||
const getRecommendationDescription = () => {
|
||||
return frameworkInfo[recommendedFramework.value].description
|
||||
}
|
||||
|
||||
const getRecommendationReasons = () => {
|
||||
return frameworkInfo[recommendedFramework.value].reasons
|
||||
}
|
||||
|
||||
const getFrameworkInfo = () => {
|
||||
return frameworkInfo[recommendedFramework.value]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.framework-selection-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
}
|
||||
.sel { border: 1px solid var(--vp-c-divider); border-radius: 12px; background: var(--vp-c-bg-soft); padding: 16px; margin: 20px 0; display: flex; flex-direction: column; gap: 12px; }
|
||||
.header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
|
||||
.selection-quiz h3 {
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
.q { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; }
|
||||
.q-title { font-weight: 800; margin-bottom: 8px; }
|
||||
.opts { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.opt { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); padding: 8px 12px; border-radius: 999px; cursor: pointer; }
|
||||
.opt.active { border-color: var(--vp-c-brand); color: var(--vp-c-brand); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
|
||||
|
||||
.quiz-intro {
|
||||
text-align: center;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.question-container {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--vp-c-brand);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
margin: 0 0 24px 0;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.option-btn {
|
||||
padding: 16px 20px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-1);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.option-btn:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.recommendation {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.result-header h4 {
|
||||
margin: 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.result-description {
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
margin-bottom: 24px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.result-reasons {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.reasons-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.result-reasons ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.result-reasons li {
|
||||
padding: 4px 0;
|
||||
color: var(--vp-c-text-2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.result-reasons li::before {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.next-steps {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.steps-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.step-links {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.step-link {
|
||||
padding: 12px 24px;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.step-link:hover {
|
||||
background: var(--vp-c-brand-dark);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.restart-btn {
|
||||
padding: 12px 32px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.restart-btn:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
.result { background: var(--vp-c-bg); border: 1px dashed var(--vp-c-divider); border-radius: 12px; padding: 12px; }
|
||||
.r-title { font-weight: 900; margin-bottom: 6px; }
|
||||
.r-body { color: var(--vp-c-text-2); line-height: 1.6; margin-bottom: 6px; }
|
||||
.r-note, .r-next { color: var(--vp-c-text-2); line-height: 1.6; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-steps :active="activeStep" align-center finish-status="success" class="steps">
|
||||
<el-steps
|
||||
:active="activeStep"
|
||||
align-center
|
||||
finish-status="success"
|
||||
class="steps"
|
||||
>
|
||||
<el-step title="音频信号" description="连续波形" />
|
||||
<el-step title="切片 (Chunking)" description="20ms/帧" />
|
||||
<el-step title="量化 (Quantization)" description="查字典" />
|
||||
@@ -18,8 +23,15 @@
|
||||
<!-- Stage 0: Audio -->
|
||||
<div v-if="activeStep === 0" class="stage-content audio-stage">
|
||||
<div class="waveform-viz">
|
||||
<div class="wave-bar" v-for="n in 20" :key="n"
|
||||
:style="{ height: (30 + Math.random() * 50) + '%', animationDelay: n * 0.1 + 's' }"></div>
|
||||
<div
|
||||
class="wave-bar"
|
||||
v-for="n in 20"
|
||||
:key="n"
|
||||
:style="{
|
||||
height: 30 + Math.random() * 50 + '%',
|
||||
animationDelay: n * 0.1 + 's'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<div class="stage-desc">原始的连续模拟信号或高采样率数字信号</div>
|
||||
</div>
|
||||
@@ -28,26 +40,41 @@
|
||||
<div v-if="activeStep === 1" class="stage-content chunks-stage">
|
||||
<div class="chunks-container">
|
||||
<div class="chunk-item" v-for="n in 5" :key="n">
|
||||
<span class="chunk-label">Frame {{n}}</span>
|
||||
<span class="chunk-label">Frame {{ n }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stage-desc">将音频切分为固定长度的小片段(例如 20ms)</div>
|
||||
<div class="stage-desc">
|
||||
将音频切分为固定长度的小片段(例如 20ms)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stage 2: Codebook -->
|
||||
<div v-if="activeStep === 2" class="stage-content codebook-stage">
|
||||
<div class="codebook-grid">
|
||||
<div class="codebook-entry" v-for="n in 9" :key="n" :class="{ 'highlight': n === currentMatch }">
|
||||
<div
|
||||
class="codebook-entry"
|
||||
v-for="n in 9"
|
||||
:key="n"
|
||||
:class="{ highlight: n === currentMatch }"
|
||||
>
|
||||
{{ 1024 + n * 50 }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stage-desc">在预训练的"声音字典"中寻找最接近的特征向量</div>
|
||||
<div class="stage-desc">
|
||||
在预训练的"声音字典"中寻找最接近的特征向量
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stage 3: Tokens -->
|
||||
<div v-if="activeStep === 3" class="stage-content token-stage">
|
||||
<div class="token-list">
|
||||
<el-tag v-for="(token, index) in tokens" :key="index" effect="dark" size="large" class="token-tag">
|
||||
<el-tag
|
||||
v-for="(token, index) in tokens"
|
||||
:key="index"
|
||||
effect="dark"
|
||||
size="large"
|
||||
class="token-tag"
|
||||
>
|
||||
{{ token }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@@ -130,7 +157,7 @@ const playDemo = async () => {
|
||||
isPlaying.value = false
|
||||
}
|
||||
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -186,8 +213,15 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
@keyframes wave {
|
||||
0%, 100% { height: 30%; opacity: 0.5; }
|
||||
50% { height: 100%; opacity: 1; }
|
||||
0%,
|
||||
100% {
|
||||
height: 30%;
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
height: 100%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Chunks Stage */
|
||||
|
||||
@@ -34,7 +34,9 @@
|
||||
<div class="step-box">
|
||||
<div class="label">🔢 数字化</div>
|
||||
<div class="digital-visual">
|
||||
<div v-for="n in 8" :key="n" class="bit">{{ Math.floor(Math.random() * 2) }}</div>
|
||||
<div v-for="n in 8" :key="n" class="bit">
|
||||
{{ Math.floor(Math.random() * 2) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc">PCM 数据</div>
|
||||
</div>
|
||||
@@ -43,8 +45,9 @@
|
||||
<div class="explanation">
|
||||
<p>
|
||||
<span class="icon">💡</span>
|
||||
计算机无法直接处理连续的声波,需要把它转换成数字。
|
||||
这个过程叫<strong>模数转换 (ADC)</strong>:每隔一小段时间测量一次声音的强度,记录成数字。
|
||||
计算机无法直接处理连续的声波,需要把它转换成数字。 这个过程叫<strong
|
||||
>模数转换 (ADC)</strong
|
||||
>:每隔一小段时间测量一次声音的强度,记录成数字。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
<div class="ar-comparison">
|
||||
<el-card shadow="never">
|
||||
<div class="controls">
|
||||
<el-button type="primary" @click="playDemo" :loading="isPlaying" icon="VideoPlay">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="playDemo"
|
||||
:loading="isPlaying"
|
||||
icon="VideoPlay"
|
||||
>
|
||||
开始对比演示
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -34,7 +39,9 @@
|
||||
</div>
|
||||
<div class="stats">
|
||||
<el-descriptions :column="1" size="small" border>
|
||||
<el-descriptions-item label="生成方式">串行 (Serial)</el-descriptions-item>
|
||||
<el-descriptions-item label="生成方式"
|
||||
>串行 (Serial)</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item label="速度">
|
||||
<el-tag type="danger" size="small">慢 (Slow)</el-tag>
|
||||
</el-descriptions-item>
|
||||
@@ -54,17 +61,29 @@
|
||||
<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' }"></div>
|
||||
<div
|
||||
v-for="n in 20"
|
||||
:key="n"
|
||||
class="flow-bar"
|
||||
:style="{
|
||||
height: flowProgress * (30 + Math.random() * 70) + '%',
|
||||
transitionDelay: n * 0.02 + 's'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<div class="flow-overlay" v-if="flowProgress < 1 && flowProgress > 0">
|
||||
<div
|
||||
class="flow-overlay"
|
||||
v-if="flowProgress < 1 && flowProgress > 0"
|
||||
>
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>Denoising...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<el-descriptions :column="1" size="small" border>
|
||||
<el-descriptions-item label="生成方式">并行 (Parallel)</el-descriptions-item>
|
||||
<el-descriptions-item label="生成方式"
|
||||
>并行 (Parallel)</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item label="速度">
|
||||
<el-tag type="success" size="small">极快 (Fast)</el-tag>
|
||||
</el-descriptions-item>
|
||||
@@ -76,17 +95,14 @@
|
||||
|
||||
<el-divider />
|
||||
|
||||
<el-alert
|
||||
title="技术演进"
|
||||
type="success"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<el-alert title="技术演进" type="success" :closable="false" show-icon>
|
||||
<template #default>
|
||||
<p>
|
||||
<strong>自回归</strong> (如 VALL-E) 像人说话一样,必须说完上一个字才能说下一个字,所以很慢。
|
||||
<br>
|
||||
<strong>流匹配</strong> (如 F5-TTS) 像画画一样,可以同时在画布的所有角落开始上色,效率提升了 10-20 倍。
|
||||
<strong>自回归</strong> (如 VALL-E)
|
||||
像人说话一样,必须说完上一个字才能说下一个字,所以很慢。
|
||||
<br />
|
||||
<strong>流匹配</strong> (如 F5-TTS)
|
||||
像画画一样,可以同时在画布的所有角落开始上色,效率提升了 10-20 倍。
|
||||
</p>
|
||||
</template>
|
||||
</el-alert>
|
||||
@@ -110,7 +126,7 @@ const playDemo = async () => {
|
||||
flowProgress.value = 0
|
||||
|
||||
// Start Flow Matching (Fast)
|
||||
const flowPromise = new Promise(resolve => {
|
||||
const flowPromise = new Promise((resolve) => {
|
||||
let p = 0
|
||||
const interval = setInterval(() => {
|
||||
p += 0.05
|
||||
@@ -123,9 +139,9 @@ const playDemo = async () => {
|
||||
})
|
||||
|
||||
// Start AR (Slow)
|
||||
const arPromise = new Promise(async resolve => {
|
||||
const arPromise = new Promise(async (resolve) => {
|
||||
for (const token of arTokensSource) {
|
||||
await new Promise(r => setTimeout(r, 400)) // 400ms per token
|
||||
await new Promise((r) => setTimeout(r, 400)) // 400ms per token
|
||||
displayedArTokens.value.push(token)
|
||||
}
|
||||
resolve()
|
||||
@@ -220,7 +236,7 @@ const playDemo = async () => {
|
||||
|
||||
.flow-bar {
|
||||
flex: 1;
|
||||
background: linear-gradient(to top, #67C23A, #95d475);
|
||||
background: linear-gradient(to top, #67c23a, #95d475);
|
||||
border-radius: 2px 2px 0 0;
|
||||
transition: height 0.5s ease;
|
||||
}
|
||||
@@ -231,7 +247,7 @@ const playDemo = async () => {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(255,255,255,0.5);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -10,8 +10,15 @@
|
||||
</div>
|
||||
<div class="viz-content waveform-container">
|
||||
<div class="wave-bars">
|
||||
<div v-for="n in 30" :key="n" class="wave-bar"
|
||||
:style="{ height: 20 + Math.random() * 60 + '%', animationDelay: n * 0.05 + 's' }"></div>
|
||||
<div
|
||||
v-for="n in 30"
|
||||
:key="n"
|
||||
class="wave-bar"
|
||||
:style="{
|
||||
height: 20 + Math.random() * 60 + '%',
|
||||
animationDelay: n * 0.05 + 's'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<div class="axis-label x-axis">时间 (Time) →</div>
|
||||
<div class="axis-label y-axis">振幅 (Amplitude) ↑</div>
|
||||
@@ -50,13 +57,18 @@
|
||||
<template #default>
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<div class="color-box low"></div> 低能量 (安静)
|
||||
<div class="color-box low"></div>
|
||||
低能量 (安静)
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="color-box high"></div> 高能量 (响亮)
|
||||
<div class="color-box high"></div>
|
||||
高能量 (响亮)
|
||||
</div>
|
||||
</div>
|
||||
<p>频谱图将一维的声音信号变成了二维图像,这样我们就可以用 <strong>CNN (卷积神经网络)</strong> 等图像模型来处理声音了!</p>
|
||||
<p>
|
||||
频谱图将一维的声音信号变成了二维图像,这样我们就可以用
|
||||
<strong>CNN (卷积神经网络)</strong> 等图像模型来处理声音了!
|
||||
</p>
|
||||
</template>
|
||||
</el-alert>
|
||||
</el-card>
|
||||
@@ -165,8 +177,15 @@ const drawSpectrogram = () => {
|
||||
}
|
||||
|
||||
@keyframes wave {
|
||||
0%, 100% { height: 20%; opacity: 0.6; }
|
||||
50% { height: 90%; opacity: 1; }
|
||||
0%,
|
||||
100% {
|
||||
height: 20%;
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
height: 90%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.transform-arrow {
|
||||
|
||||
+58
-18
@@ -92,9 +92,7 @@
|
||||
<div class="metric-card">
|
||||
<div class="metric-title">上下文长度</div>
|
||||
<div class="metric-value">{{ contextTokens }}</div>
|
||||
<div class="metric-desc">
|
||||
迭代: {{ iteration }} 步
|
||||
</div>
|
||||
<div class="metric-desc">迭代: {{ iteration }} 步</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
@@ -118,7 +116,7 @@
|
||||
<div class="principle-icon">1️⃣</div>
|
||||
<div class="principle-content">
|
||||
<strong>保持前缀稳定</strong>
|
||||
<br>
|
||||
<br />
|
||||
系统提示和工具定义不要频繁变化,提高 KV 缓存命中率
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,7 +124,7 @@
|
||||
<div class="principle-icon">2️⃣</div>
|
||||
<div class="principle-content">
|
||||
<strong>只追加不修改</strong>
|
||||
<br>
|
||||
<br />
|
||||
上下文应该只追加新的动作和观察,不修改历史内容
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,7 +132,7 @@
|
||||
<div class="principle-icon">3️⃣</div>
|
||||
<div class="principle-content">
|
||||
<strong>遮蔽而非移除</strong>
|
||||
<br>
|
||||
<br />
|
||||
不动态添加/删除工具,而是通过 logits 掩码控制可用工具
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,7 +140,7 @@
|
||||
<div class="principle-icon">4️⃣</div>
|
||||
<div class="principle-content">
|
||||
<strong>文件系统作为外部记忆</strong>
|
||||
<br>
|
||||
<br />
|
||||
大型内容(网页、PDF)写入文件,上下文只保留路径
|
||||
</div>
|
||||
</div>
|
||||
@@ -166,9 +164,27 @@ const observation = ref('')
|
||||
const toolsUsed = ref([])
|
||||
|
||||
const contextItems = ref([
|
||||
{ type: '系统提示', content: '你是一个 AI 助手,可以使用搜索和文件工具', tokens: 150, cached: true, active: false },
|
||||
{ type: '工具定义', content: 'search: 搜索网络信息', tokens: 80, cached: true, active: false },
|
||||
{ type: '工具定义', content: 'write_file: 写入文件', tokens: 75, cached: true, active: false }
|
||||
{
|
||||
type: '系统提示',
|
||||
content: '你是一个 AI 助手,可以使用搜索和文件工具',
|
||||
tokens: 150,
|
||||
cached: true,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
type: '工具定义',
|
||||
content: 'search: 搜索网络信息',
|
||||
tokens: 80,
|
||||
cached: true,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
type: '工具定义',
|
||||
content: 'write_file: 写入文件',
|
||||
tokens: 75,
|
||||
cached: true,
|
||||
active: false
|
||||
}
|
||||
])
|
||||
|
||||
const steps = [
|
||||
@@ -177,7 +193,8 @@ const steps = [
|
||||
action: '分析用户需求',
|
||||
tool: '',
|
||||
obs: '',
|
||||
explanation: 'Agent 首先解析用户的请求,决定需要采取什么行动。系统提示和工具定义从缓存读取(绿色),节省成本!',
|
||||
explanation:
|
||||
'Agent 首先解析用户的请求,决定需要采取什么行动。系统提示和工具定义从缓存读取(绿色),节省成本!',
|
||||
addTokens: 50
|
||||
},
|
||||
{
|
||||
@@ -185,7 +202,8 @@ const steps = [
|
||||
action: '选择工具: search',
|
||||
tool: 'search',
|
||||
obs: '',
|
||||
explanation: 'Agent 根据用户需求选择合适的工具。注意:工具定义在缓存中,不需要重新计算!',
|
||||
explanation:
|
||||
'Agent 根据用户需求选择合适的工具。注意:工具定义在缓存中,不需要重新计算!',
|
||||
addTokens: 30
|
||||
},
|
||||
{
|
||||
@@ -201,7 +219,8 @@ const steps = [
|
||||
action: '决定保存摘要',
|
||||
tool: 'write_file',
|
||||
obs: '文件已保存',
|
||||
explanation: 'Agent 将搜索结果写入文件,而不是在上下文中保留所有内容。这样上下文保持精简!',
|
||||
explanation:
|
||||
'Agent 将搜索结果写入文件,而不是在上下文中保留所有内容。这样上下文保持精简!',
|
||||
addTokens: 60
|
||||
},
|
||||
{
|
||||
@@ -209,13 +228,16 @@ const steps = [
|
||||
action: '完成任务',
|
||||
tool: '',
|
||||
obs: '已保存到 summary.md',
|
||||
explanation: '任务完成!整个过程中,系统提示和工具定义只缓存一次,每次迭代只追加新的动作和观察结果。',
|
||||
explanation:
|
||||
'任务完成!整个过程中,系统提示和工具定义只缓存一次,每次迭代只追加新的动作和观察结果。',
|
||||
addTokens: 40
|
||||
}
|
||||
]
|
||||
|
||||
const cacheHitRate = computed(() => {
|
||||
const cachedTokens = contextItems.value.filter(item => item.cached).reduce((sum, item) => sum + item.tokens, 0)
|
||||
const cachedTokens = contextItems.value
|
||||
.filter((item) => item.cached)
|
||||
.reduce((sum, item) => sum + item.tokens, 0)
|
||||
const totalTokens = contextTokens.value
|
||||
return totalTokens > 0 ? ((cachedTokens / totalTokens) * 100).toFixed(1) : 0
|
||||
})
|
||||
@@ -292,9 +314,27 @@ const reset = () => {
|
||||
observation.value = ''
|
||||
toolsUsed.value = []
|
||||
contextItems.value = [
|
||||
{ type: '系统提示', content: '你是一个 AI 助手,可以使用搜索和文件工具', tokens: 150, cached: true, active: false },
|
||||
{ type: '工具定义', content: 'search: 搜索网络信息', tokens: 80, cached: true, active: false },
|
||||
{ type: '工具定义', content: 'write_file: 写入文件', tokens: 75, cached: true, active: false }
|
||||
{
|
||||
type: '系统提示',
|
||||
content: '你是一个 AI 助手,可以使用搜索和文件工具',
|
||||
tokens: 150,
|
||||
cached: true,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
type: '工具定义',
|
||||
content: 'search: 搜索网络信息',
|
||||
tokens: 80,
|
||||
cached: true,
|
||||
active: false
|
||||
},
|
||||
{
|
||||
type: '工具定义',
|
||||
content: 'write_file: 写入文件',
|
||||
tokens: 75,
|
||||
cached: true,
|
||||
active: false
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div class="context-compression-demo">
|
||||
<div class="input-section">
|
||||
<div class="label">Original Text (Long)</div>
|
||||
<textarea v-model="originalText" rows="6"></textarea>
|
||||
<div class="stats">Length: {{ originalText.length }} chars</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button
|
||||
@click="compress('summary')"
|
||||
:class="{ active: mode === 'summary' }"
|
||||
>
|
||||
📝 Summarize
|
||||
</button>
|
||||
<button
|
||||
@click="compress('extract')"
|
||||
:class="{ active: mode === 'extract' }"
|
||||
>
|
||||
🔑 Extract Key Points
|
||||
</button>
|
||||
<button @click="compress('json')" :class="{ active: mode === 'json' }">
|
||||
JSON Structure
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<div class="label">Compressed Context</div>
|
||||
<div class="result-box">
|
||||
<div v-if="compressedText" class="result-content">
|
||||
{{ compressedText }}
|
||||
</div>
|
||||
<div v-else class="placeholder">Select a compression strategy...</div>
|
||||
</div>
|
||||
<div class="stats" v-if="compressedText">
|
||||
Length: {{ compressedText.length }} chars
|
||||
<span class="ratio">(Ratio: {{ compressionRatio }}%)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const originalText = ref(
|
||||
`Context engineering involves optimizing the prompt given to a large language model (LLM) to ensure it has the necessary information to generate accurate and relevant responses. One of the main challenges is the limited context window of LLMs, which restricts the amount of text they can process at once. To overcome this, developers use techniques like summarization, where long documents are condensed into shorter versions retaining key information. Another technique is retrieval-augmented generation (RAG), which fetches only the most relevant pieces of information from a database based on the user's query.`
|
||||
)
|
||||
|
||||
const compressedText = ref('')
|
||||
const mode = ref('')
|
||||
|
||||
const compressionRatio = computed(() => {
|
||||
if (!originalText.value.length) return 0
|
||||
return Math.round(
|
||||
(compressedText.value.length / originalText.value.length) * 100
|
||||
)
|
||||
})
|
||||
|
||||
const compress = (strategy) => {
|
||||
mode.value = strategy
|
||||
|
||||
if (strategy === 'summary') {
|
||||
compressedText.value =
|
||||
'Context engineering optimizes LLM prompts to handle limited context windows. Key techniques include summarization (condensing text) and RAG (retrieving relevant info dynamically).'
|
||||
} else if (strategy === 'extract') {
|
||||
compressedText.value =
|
||||
'- Goal: Optimize prompts for LLMs\n- Challenge: Limited context window\n- Solution 1: Summarization\n- Solution 2: RAG (Retrieval-Augmented Generation)'
|
||||
} else if (strategy === 'json') {
|
||||
compressedText.value = JSON.stringify(
|
||||
{
|
||||
topic: 'Context Engineering',
|
||||
problem: 'Limited Context Window',
|
||||
solutions: ['Summarization', 'RAG']
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.context-compression-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.stats {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 0.3rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin: 1.5rem 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
button.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.result-box {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
white-space: pre-wrap;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: var(--vp-c-text-3);
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.ratio {
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
@@ -83,13 +83,17 @@
|
||||
<div class="tips">
|
||||
<div class="tip">
|
||||
<span class="tip-icon">💡</span>
|
||||
<span><strong>选择合适的模型</strong>:短对话用短上下文模型(更快更便宜),
|
||||
长文档分析用长上下文模型(避免信息丢失)</span>
|
||||
<span
|
||||
><strong>选择合适的模型</strong>:短对话用短上下文模型(更快更便宜),
|
||||
长文档分析用长上下文模型(避免信息丢失)</span
|
||||
>
|
||||
</div>
|
||||
<div class="tip">
|
||||
<span class="tip-icon">📏</span>
|
||||
<span><strong>注意 Token 计算</strong>:1 Token ≈ 0.75 个英文单词 或 0.5-1 个汉字。
|
||||
100K tokens 大约等于一本 300 页的书</span>
|
||||
<span
|
||||
><strong>注意 Token 计算</strong>:1 Token ≈ 0.75 个英文单词 或 0.5-1
|
||||
个汉字。 100K tokens 大约等于一本 300 页的书</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div class="context-window-visualizer">
|
||||
<div class="control-panel">
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">Token Usage</div>
|
||||
<div class="stat-value" :class="{ error: isOverflow }">
|
||||
{{ usedTokens }} / {{ maxTokens }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div
|
||||
class="progress-bar"
|
||||
:style="{
|
||||
width: usagePercentage + '%',
|
||||
backgroundColor: progressBarColor
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="visualization-area">
|
||||
<div class="window-frame">
|
||||
<div class="window-header">Context Window (Model Memory)</div>
|
||||
<div class="token-stream">
|
||||
<transition-group name="token-list">
|
||||
<span
|
||||
v-for="(token, index) in tokenizedText"
|
||||
:key="index"
|
||||
class="token-chip"
|
||||
:class="{ overflow: index >= maxTokens }"
|
||||
>
|
||||
{{ token }}
|
||||
</span>
|
||||
</transition-group>
|
||||
</div>
|
||||
<div v-if="isOverflow" class="overflow-warning">
|
||||
⚠️ Context Overflow! The model ignores everything beyond this point.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<textarea
|
||||
v-model="inputText"
|
||||
placeholder="Type here to see how tokens fill up the context window..."
|
||||
rows="4"
|
||||
></textarea>
|
||||
<div class="presets">
|
||||
<button @click="fillLorem(50)">Add Short Text</button>
|
||||
<button @click="fillLorem(200)">Add Long Text</button>
|
||||
<button @click="clear">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const maxTokens = 100
|
||||
const inputText = ref('Context engineering is the art of managing information.')
|
||||
|
||||
// Simple mock tokenizer: split by space for demonstration
|
||||
const tokenizedText = computed(() => {
|
||||
return inputText.value
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter((t) => t)
|
||||
})
|
||||
|
||||
const usedTokens = computed(() => tokenizedText.value.length)
|
||||
const isOverflow = computed(() => usedTokens.value > maxTokens)
|
||||
const usagePercentage = computed(() =>
|
||||
Math.min((usedTokens.value / maxTokens) * 100, 100)
|
||||
)
|
||||
|
||||
const progressBarColor = computed(() => {
|
||||
if (isOverflow.value) return '#ef4444'
|
||||
if (usagePercentage.value > 80) return '#f59e0b'
|
||||
return '#10b981'
|
||||
})
|
||||
|
||||
const fillLorem = (count) => {
|
||||
const words = [
|
||||
'lorem',
|
||||
'ipsum',
|
||||
'dolor',
|
||||
'sit',
|
||||
'amet',
|
||||
'consectetur',
|
||||
'adipiscing',
|
||||
'elit',
|
||||
'sed',
|
||||
'do',
|
||||
'eiusmod',
|
||||
'tempor',
|
||||
'incididunt',
|
||||
'ut',
|
||||
'labore',
|
||||
'et',
|
||||
'dolore',
|
||||
'magna',
|
||||
'aliqua'
|
||||
]
|
||||
let text = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
text.push(words[Math.floor(Math.random() * words.length)])
|
||||
}
|
||||
inputText.value += (inputText.value ? ' ' : '') + text.join(' ')
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
inputText.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.context-window-visualizer {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stat-value.error {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
height: 10px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.visualization-area {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.window-frame {
|
||||
border: 2px dashed var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
position: relative;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.window-header {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 10px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 0 10px;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.token-stream {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.token-chip {
|
||||
padding: 2px 6px;
|
||||
background: #e0f2fe;
|
||||
color: #0369a1;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.token-chip.overflow {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
opacity: 0.5;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.overflow-warning {
|
||||
margin-top: 1rem;
|
||||
padding: 0.5rem;
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
color: #b91c1c;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.presets {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.4rem 0.8rem;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--vp-c-brand-dark);
|
||||
}
|
||||
|
||||
.token-list-enter-active,
|
||||
.token-list-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.token-list-enter-from,
|
||||
.token-list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
</style>
|
||||
@@ -60,9 +60,15 @@
|
||||
<div class="example-section">
|
||||
<span class="section-label">检索到的文档:</span>
|
||||
<div class="retrieved-docs">
|
||||
<div class="doc-item">📄 Doc 1: "RAG 是一种结合检索和生成的技术..."</div>
|
||||
<div class="doc-item">📄 Doc 2: "RAG 的优势是减少幻觉、提高准确性..."</div>
|
||||
<div class="doc-item">📄 Doc 3: "常见的 RAG 框架包括 LangChain..."</div>
|
||||
<div class="doc-item">
|
||||
📄 Doc 1: "RAG 是一种结合检索和生成的技术..."
|
||||
</div>
|
||||
<div class="doc-item">
|
||||
📄 Doc 2: "RAG 的优势是减少幻觉、提高准确性..."
|
||||
</div>
|
||||
<div class="doc-item">
|
||||
📄 Doc 3: "常见的 RAG 框架包括 LangChain..."
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="example-section">
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<div class="rag-simulation-demo">
|
||||
<div class="layout">
|
||||
<!-- Left: Long-term Memory (Vector DB) -->
|
||||
<div class="panel vector-db">
|
||||
<div class="panel-header">📚 Long-term Memory (Vector DB)</div>
|
||||
<div class="documents">
|
||||
<div
|
||||
v-for="doc in documents"
|
||||
:key="doc.id"
|
||||
class="doc-card"
|
||||
:class="{ retrieved: doc.retrieved }"
|
||||
>
|
||||
<div class="doc-icon">📄</div>
|
||||
<div class="doc-content">{{ doc.content }}</div>
|
||||
<div class="doc-meta">
|
||||
ID: {{ doc.id }} | Vector: {{ doc.vector }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Center: Query & Retrieval Process -->
|
||||
<div class="process-area">
|
||||
<div class="search-box">
|
||||
<input
|
||||
v-model="query"
|
||||
placeholder="Ask a question..."
|
||||
@keyup.enter="search"
|
||||
/>
|
||||
<button @click="search" :disabled="isSearching">
|
||||
{{ isSearching ? 'Searching...' : '🔍 Retrieve' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="arrow-down">⬇️</div>
|
||||
|
||||
<div class="retrieval-status" :class="{ active: isSearching }">
|
||||
<div class="status-step" v-if="step >= 1">1. Embed Query</div>
|
||||
<div class="status-step" v-if="step >= 2">2. Semantic Search</div>
|
||||
<div class="status-step" v-if="step >= 3">3. Retrieve Top-K</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow-down">⬇️</div>
|
||||
|
||||
<!-- Right: Augmented Context -->
|
||||
<div class="panel context-builder">
|
||||
<div class="panel-header">📦 Augmented Context</div>
|
||||
<div class="context-content">
|
||||
<div class="context-section system">
|
||||
<span class="label">System:</span>
|
||||
You are a helpful assistant. Use the following context to answer
|
||||
the user.
|
||||
</div>
|
||||
<div
|
||||
class="context-section retrieved"
|
||||
v-if="retrievedDocs.length > 0"
|
||||
>
|
||||
<span class="label">Retrieved Context:</span>
|
||||
<div
|
||||
v-for="doc in retrievedDocs"
|
||||
:key="doc.id"
|
||||
class="retrieved-item"
|
||||
>
|
||||
- {{ doc.content }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="context-section user">
|
||||
<span class="label">User:</span>
|
||||
{{ lastQuery }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const query = ref('How do I reset my password?')
|
||||
const lastQuery = ref('')
|
||||
const isSearching = ref(false)
|
||||
const step = ref(0)
|
||||
|
||||
const documents = ref([
|
||||
{
|
||||
id: 1,
|
||||
content: 'To reset password, go to settings page.',
|
||||
vector: '[0.1, 0.9]',
|
||||
retrieved: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
content: 'Pricing starts at $10/month.',
|
||||
vector: '[0.8, 0.2]',
|
||||
retrieved: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
content: 'Contact support at support@example.com.',
|
||||
vector: '[0.3, 0.5]',
|
||||
retrieved: false
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
content: 'Click "Forgot Password" on login screen.',
|
||||
vector: '[0.2, 0.8]',
|
||||
retrieved: false
|
||||
}
|
||||
])
|
||||
|
||||
const retrievedDocs = ref([])
|
||||
|
||||
const search = async () => {
|
||||
if (isSearching.value) return
|
||||
isSearching.value = true
|
||||
lastQuery.value = query.value
|
||||
step.value = 0
|
||||
|
||||
// Reset previous state
|
||||
documents.value.forEach((d) => (d.retrieved = false))
|
||||
retrievedDocs.value = []
|
||||
|
||||
// Step 1: Embedding
|
||||
await wait(500)
|
||||
step.value = 1
|
||||
|
||||
// Step 2: Search
|
||||
await wait(500)
|
||||
step.value = 2
|
||||
|
||||
// Mock semantic search logic (simple keyword match for demo)
|
||||
const keywords = query.value.toLowerCase().split(' ')
|
||||
const matches = documents.value
|
||||
.map((doc) => {
|
||||
let score = 0
|
||||
keywords.forEach((k) => {
|
||||
if (doc.content.toLowerCase().includes(k)) score++
|
||||
})
|
||||
return { ...doc, score }
|
||||
})
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, 2) // Top 2
|
||||
|
||||
// Step 3: Retrieve
|
||||
await wait(500)
|
||||
step.value = 3
|
||||
|
||||
matches.forEach((m) => {
|
||||
const doc = documents.value.find((d) => d.id === m.id)
|
||||
if (doc) doc.retrieved = true
|
||||
})
|
||||
|
||||
retrievedDocs.value = matches
|
||||
|
||||
isSearching.value = false
|
||||
}
|
||||
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rag-simulation-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.panel {
|
||||
flex: 1;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.doc-card {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0.5rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
transition: all 0.3s;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.doc-card.retrieved {
|
||||
border-color: #10b981;
|
||||
background: #ecfdf5;
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.2);
|
||||
}
|
||||
|
||||
.doc-meta {
|
||||
font-size: 0.7rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.process-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 0.6rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.retrieval-status {
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px dashed var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.status-step {
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
margin: 0.2rem 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.context-content {
|
||||
font-size: 0.85rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.context-section {
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-left: 3px solid #ccc;
|
||||
}
|
||||
|
||||
.context-section.system {
|
||||
border-color: #f59e0b;
|
||||
}
|
||||
.context-section.retrieved {
|
||||
border-color: #10b981;
|
||||
background: #ecfdf5;
|
||||
}
|
||||
.context-section.user {
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 0.3rem;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.retrieved-item {
|
||||
margin-bottom: 0.3rem;
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
color: var(--vp-c-text-3);
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
</style>
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div class="selective-context-demo">
|
||||
<div class="viz-container">
|
||||
<div class="window-frame">
|
||||
<div class="window-header">
|
||||
<span>Smart Context Window</span>
|
||||
<span class="capacity">{{ usedSlots }} / {{ maxSlots }} Slots</span>
|
||||
</div>
|
||||
|
||||
<!-- Pinned Messages -->
|
||||
<div class="section pinned">
|
||||
<div class="section-label">📌 Pinned (Always Kept)</div>
|
||||
<div
|
||||
v-for="msg in pinnedMessages"
|
||||
:key="msg.id"
|
||||
class="message-bubble pinned"
|
||||
>
|
||||
<div class="msg-content">
|
||||
<span class="role">{{ msg.role }}:</span> {{ msg.content }}
|
||||
</div>
|
||||
<button class="pin-btn active" @click="togglePin(msg)">📌</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scrolling Messages -->
|
||||
<div class="section scrolling">
|
||||
<div class="section-label">📜 Scrolling (FIFO)</div>
|
||||
<transition-group name="list">
|
||||
<div
|
||||
v-for="msg in scrollingMessages"
|
||||
:key="msg.id"
|
||||
class="message-bubble"
|
||||
>
|
||||
<div class="msg-content">
|
||||
<span class="role">{{ msg.role }}:</span> {{ msg.content }}
|
||||
</div>
|
||||
<button class="pin-btn" @click="togglePin(msg)">📌</button>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="newMessage"
|
||||
@keyup.enter="sendMessage"
|
||||
placeholder="Add a fact or message..."
|
||||
/>
|
||||
<button @click="sendMessage">Add</button>
|
||||
</div>
|
||||
<div class="info-text">
|
||||
<p>
|
||||
Try pinning a message. Pinned messages stay in the window even as new
|
||||
messages push old ones out.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const maxSlots = 5
|
||||
const messages = ref([
|
||||
{
|
||||
id: 1,
|
||||
role: 'System',
|
||||
content: 'You are a helpful assistant.',
|
||||
pinned: true
|
||||
},
|
||||
{ id: 2, role: 'User', content: 'My name is Alice.', pinned: false },
|
||||
{ id: 3, role: 'AI', content: 'Hello Alice!', pinned: false }
|
||||
])
|
||||
const newMessage = ref('')
|
||||
let msgId = 4
|
||||
|
||||
const pinnedMessages = computed(() => messages.value.filter((m) => m.pinned))
|
||||
const scrollingMessages = computed(() =>
|
||||
messages.value.filter((m) => !m.pinned)
|
||||
)
|
||||
|
||||
const usedSlots = computed(() => messages.value.length)
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!newMessage.value.trim()) return
|
||||
|
||||
// Add new message
|
||||
messages.value.push({
|
||||
id: msgId++,
|
||||
role: 'User',
|
||||
content: newMessage.value,
|
||||
pinned: false
|
||||
})
|
||||
|
||||
newMessage.value = ''
|
||||
|
||||
// Enforce limit logic:
|
||||
// If total > max, remove oldest NON-PINNED message
|
||||
if (messages.value.length > maxSlots) {
|
||||
const unpinned = messages.value.filter((m) => !m.pinned)
|
||||
if (unpinned.length > 0) {
|
||||
// Find index of oldest unpinned
|
||||
const oldestUnpinned = unpinned[0]
|
||||
const indexToRemove = messages.value.findIndex(
|
||||
(m) => m.id === oldestUnpinned.id
|
||||
)
|
||||
if (indexToRemove !== -1) {
|
||||
messages.value.splice(indexToRemove, 1)
|
||||
}
|
||||
} else {
|
||||
// If all are pinned and we add one more, we can't remove anything (in this simple logic),
|
||||
// or we reject the new one. Let's just remove the newly added one to show "Full".
|
||||
messages.value.pop()
|
||||
alert('Context Window Full with Pinned Messages!')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const togglePin = (msg) => {
|
||||
msg.pinned = !msg.pinned
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.selective-context-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.window-frame {
|
||||
border: 2px solid var(--vp-c-brand);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.window-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.section.pinned {
|
||||
background: #fffbeb;
|
||||
border: 1px solid #fcd34d;
|
||||
}
|
||||
|
||||
.section.scrolling {
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #e5e7eb;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.message-bubble.pinned {
|
||||
border-left: 3px solid #f59e0b;
|
||||
}
|
||||
|
||||
.pin-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
opacity: 0.3;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.pin-btn:hover,
|
||||
.pin-btn.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 0.6rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.list-enter-from,
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="sliding-window-demo">
|
||||
<div class="viz-container">
|
||||
<!-- Hidden Messages (History) -->
|
||||
<div class="message-zone history">
|
||||
<div class="zone-label">History (Forgotten)</div>
|
||||
<transition-group name="list">
|
||||
<div
|
||||
v-for="msg in historyMessages"
|
||||
:key="msg.id"
|
||||
class="message-bubble faded"
|
||||
>
|
||||
<span class="role">{{ msg.role }}:</span> {{ msg.content }}
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
|
||||
<!-- Active Window -->
|
||||
<div class="window-frame">
|
||||
<div class="window-header">
|
||||
<span>Active Context Window</span>
|
||||
<span class="capacity">Capacity: {{ windowSize }} msgs</span>
|
||||
</div>
|
||||
<div class="message-zone active">
|
||||
<transition-group name="list">
|
||||
<div
|
||||
v-for="msg in activeMessages"
|
||||
:key="msg.id"
|
||||
class="message-bubble"
|
||||
:class="msg.role"
|
||||
>
|
||||
<span class="role">{{ msg.role }}:</span> {{ msg.content }}
|
||||
</div>
|
||||
</transition-group>
|
||||
<div v-if="activeMessages.length === 0" class="empty-state">
|
||||
Start chatting to fill the window...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="newMessage"
|
||||
@keyup.enter="sendMessage"
|
||||
placeholder="Type a message..."
|
||||
/>
|
||||
<button @click="sendMessage">Send</button>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="secondary" @click="reset">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const windowSize = 4
|
||||
const messages = ref([])
|
||||
const newMessage = ref('')
|
||||
let msgId = 0
|
||||
|
||||
const activeMessages = computed(() => {
|
||||
return messages.value.slice(-windowSize)
|
||||
})
|
||||
|
||||
const historyMessages = computed(() => {
|
||||
return messages.value.slice(
|
||||
0,
|
||||
Math.max(0, messages.value.length - windowSize)
|
||||
)
|
||||
})
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!newMessage.value.trim()) return
|
||||
|
||||
messages.value.push({
|
||||
id: msgId++,
|
||||
role: 'User',
|
||||
content: newMessage.value
|
||||
})
|
||||
|
||||
// Simulate AI response
|
||||
setTimeout(() => {
|
||||
messages.value.push({
|
||||
id: msgId++,
|
||||
role: 'AI',
|
||||
content: `Response to "${newMessage.value}"`
|
||||
})
|
||||
}, 500)
|
||||
|
||||
newMessage.value = ''
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
messages.value = []
|
||||
msgId = 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sliding-window-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.viz-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.message-zone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.message-zone.history {
|
||||
opacity: 0.5;
|
||||
border-bottom: 2px dashed var(--vp-c-divider);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.window-frame {
|
||||
border: 2px solid var(--vp-c-brand);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.window-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-brand);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 0.5rem 0.8rem;
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg-alt);
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.message-bubble.User {
|
||||
align-self: flex-end;
|
||||
background: #eff6ff;
|
||||
border-color: #bfdbfe;
|
||||
color: #1e3a8a;
|
||||
}
|
||||
|
||||
.message-bubble.AI {
|
||||
align-self: flex-start;
|
||||
background: #f0fdf4;
|
||||
border-color: #bbf7d0;
|
||||
color: #14532d;
|
||||
}
|
||||
|
||||
.message-bubble.faded {
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-3);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 0.6rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
background: var(--vp-c-bg-alt);
|
||||
color: var(--vp-c-text-1);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
/* Transitions */
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,263 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const searchQuery = ref(55)
|
||||
const isSearching = ref(false)
|
||||
const scanCurrentIndex = ref(-1)
|
||||
const treeActiveNodes = ref([])
|
||||
const searchResult = ref(null)
|
||||
const mode = ref('scan') // 'scan' or 'index'
|
||||
|
||||
const DATA_SIZE = 64
|
||||
const data = Array.from({ length: DATA_SIZE }, (_, i) => ({
|
||||
id: i + 1,
|
||||
value: `Data-${i + 1}`
|
||||
}))
|
||||
|
||||
// Simplified Tree Search Simulation (Binary Search steps)
|
||||
const startSearch = async () => {
|
||||
if (isSearching.value) return
|
||||
isSearching.value = true
|
||||
scanCurrentIndex.value = -1
|
||||
treeActiveNodes.value = []
|
||||
searchResult.value = null
|
||||
|
||||
const target = Number(searchQuery.value)
|
||||
|
||||
if (mode.value === 'scan') {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
scanCurrentIndex.value = i
|
||||
await new Promise((r) => setTimeout(r, 30)) // 30ms per step
|
||||
if (data[i].id === target) {
|
||||
searchResult.value = data[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Tree Search Simulation (Binary Search steps)
|
||||
let start = 0
|
||||
let end = data.length - 1
|
||||
|
||||
while (start <= end) {
|
||||
let mid = Math.floor((start + end) / 2)
|
||||
treeActiveNodes.value.push(mid) // Highlight the "node" we are checking
|
||||
await new Promise((r) => setTimeout(r, 400)) // Slower steps for tree to be visible
|
||||
|
||||
if (data[mid].id === target) {
|
||||
searchResult.value = data[mid]
|
||||
break
|
||||
} else if (data[mid].id < target) {
|
||||
start = mid + 1
|
||||
} else {
|
||||
end = mid - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isSearching.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="db-demo">
|
||||
<div class="controls">
|
||||
<div class="control-item">
|
||||
<span>查找 ID: </span>
|
||||
<el-input-number
|
||||
v-model="searchQuery"
|
||||
:min="1"
|
||||
:max="DATA_SIZE"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<el-radio-group v-model="mode" size="small">
|
||||
<el-radio-button label="scan">全表扫描 (O(n))</el-radio-button>
|
||||
<el-radio-button label="index">索引查找 (O(log n))</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="startSearch"
|
||||
:loading="isSearching"
|
||||
size="small"
|
||||
>开始查询</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="visualization-area">
|
||||
<!-- Full Scan Visualization -->
|
||||
<div v-if="mode === 'scan'" class="view-container">
|
||||
<div class="grid">
|
||||
<div
|
||||
v-for="(item, index) in data"
|
||||
:key="item.id"
|
||||
class="data-block"
|
||||
:class="{
|
||||
active: index === scanCurrentIndex,
|
||||
found: searchResult && searchResult.id === item.id
|
||||
}"
|
||||
>
|
||||
{{ item.id }}
|
||||
</div>
|
||||
</div>
|
||||
<p class="desc">
|
||||
全表扫描:数据库必须逐行检查数据,直到找到匹配项。数据越多,速度越慢。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Index Visualization -->
|
||||
<div v-else class="view-container">
|
||||
<div class="grid">
|
||||
<div
|
||||
v-for="(item, index) in data"
|
||||
:key="item.id"
|
||||
class="data-block tree-node"
|
||||
:class="{
|
||||
visited: treeActiveNodes.includes(index),
|
||||
found: searchResult && searchResult.id === item.id,
|
||||
dimmed:
|
||||
treeActiveNodes.length > 0 && !treeActiveNodes.includes(index)
|
||||
}"
|
||||
>
|
||||
{{ item.id }}
|
||||
</div>
|
||||
</div>
|
||||
<p class="desc">
|
||||
索引查找:类似二分查找或 B+
|
||||
树,每次比较都能排除掉一半(或更多)的数据,极快地定位目标。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats" v-if="!isSearching && searchResult">
|
||||
<div class="stat-item">
|
||||
<span class="label">查找结果:</span>
|
||||
<span class="value">{{ searchResult.value }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="label">操作次数:</span>
|
||||
<span class="value highlight"
|
||||
>{{
|
||||
mode === 'scan' ? scanCurrentIndex + 1 : treeActiveNodes.length
|
||||
}}
|
||||
次</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.db-demo {
|
||||
padding: 20px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.control-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.view-container {
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
justify-content: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.data-block {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: #f0f2f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
color: #606266;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.data-block.active {
|
||||
background: #409eff;
|
||||
color: white;
|
||||
transform: scale(1.15);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
border-color: #409eff;
|
||||
z-index: 1;
|
||||
}
|
||||
.data-block.found {
|
||||
background: #67c23a;
|
||||
color: white;
|
||||
transform: scale(1.2);
|
||||
box-shadow: 0 0 15px rgba(103, 194, 58, 0.5);
|
||||
border-color: #67c23a;
|
||||
z-index: 2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tree-node.visited {
|
||||
background: #e6a23c;
|
||||
color: white;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 2px 8px rgba(230, 162, 60, 0.4);
|
||||
z-index: 1;
|
||||
}
|
||||
.tree-node.dimmed {
|
||||
opacity: 0.2;
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background: #fdf6ec;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
.label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
.value {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
.value.highlight {
|
||||
color: #e6a23c;
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,312 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const activeTab = ref('excel') // 'excel' or 'db'
|
||||
|
||||
// Excel Data (Flat, Redundant)
|
||||
const excelData = [
|
||||
{
|
||||
id: 1,
|
||||
date: '2023-10-01',
|
||||
book: 'AI 入门',
|
||||
price: 59,
|
||||
user: '张三',
|
||||
phone: '13800138000'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
date: '2023-10-02',
|
||||
book: 'Python 编程',
|
||||
price: 89,
|
||||
user: '李四',
|
||||
phone: '13900139000'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
date: '2023-10-03',
|
||||
book: '算法导论',
|
||||
price: 120,
|
||||
user: '张三',
|
||||
phone: '13800138000'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
date: '2023-10-03',
|
||||
book: '数据库原理',
|
||||
price: 45,
|
||||
user: '王五',
|
||||
phone: '13700137000'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
date: '2023-10-04',
|
||||
book: 'Vue.js 实战',
|
||||
price: 78,
|
||||
user: '张三',
|
||||
phone: '13800138000'
|
||||
}
|
||||
]
|
||||
|
||||
// DB Data (Normalized)
|
||||
const usersTable = [
|
||||
{ id: 101, name: '张三', phone: '13800138000' },
|
||||
{ id: 102, name: '李四', phone: '13900139000' },
|
||||
{ id: 103, name: '王五', phone: '13700137000' }
|
||||
]
|
||||
|
||||
const ordersTable = [
|
||||
{ id: 1, date: '2023-10-01', book: 'AI 入门', price: 59, user_id: 101 },
|
||||
{ id: 2, date: '2023-10-02', book: 'Python 编程', price: 89, user_id: 102 },
|
||||
{ id: 3, date: '2023-10-03', book: '算法导论', price: 120, user_id: 101 },
|
||||
{ id: 4, date: '2023-10-03', book: '数据库原理', price: 45, user_id: 103 },
|
||||
{ id: 5, date: '2023-10-04', book: 'Vue.js 实战', price: 78, user_id: 101 }
|
||||
]
|
||||
|
||||
const hoveredUserId = ref(null)
|
||||
|
||||
const setHover = (id) => {
|
||||
hoveredUserId.value = id
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relational-demo">
|
||||
<div class="tabs">
|
||||
<div
|
||||
class="tab"
|
||||
:class="{ active: activeTab === 'excel' }"
|
||||
@click="activeTab = 'excel'"
|
||||
>
|
||||
Excel 模式 (单表)
|
||||
</div>
|
||||
<div
|
||||
class="tab"
|
||||
:class="{ active: activeTab === 'db' }"
|
||||
@click="activeTab = 'db'"
|
||||
>
|
||||
数据库模式 (多表关联)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-area">
|
||||
<!-- Excel Mode -->
|
||||
<div v-if="activeTab === 'excel'" class="excel-view">
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>订单号</th>
|
||||
<th>日期</th>
|
||||
<th>书名</th>
|
||||
<th>价格</th>
|
||||
<th class="highlight-col">购买者</th>
|
||||
<th class="highlight-col">电话</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in excelData" :key="row.id">
|
||||
<td>{{ row.id }}</td>
|
||||
<td>{{ row.date }}</td>
|
||||
<td>{{ row.book }}</td>
|
||||
<td>{{ row.price }}</td>
|
||||
<td class="highlight-cell">{{ row.user }}</td>
|
||||
<td class="highlight-cell">{{ row.phone }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="note error">
|
||||
<p>❌ <strong>问题:</strong> "张三"的信息重复存储了 3 次。</p>
|
||||
<p>如果张三换了电话,你需要修改 3 行数据,很容易漏改!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DB Mode -->
|
||||
<div v-else class="db-view">
|
||||
<div class="db-layout">
|
||||
<!-- Users Table -->
|
||||
<div class="db-table users-table">
|
||||
<div class="table-title">用户表 (Users)</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID (主键)</th>
|
||||
<th>姓名</th>
|
||||
<th>电话</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="u in usersTable"
|
||||
:key="u.id"
|
||||
:class="{ active: hoveredUserId === u.id }"
|
||||
@mouseenter="setHover(u.id)"
|
||||
@mouseleave="setHover(null)"
|
||||
>
|
||||
<td>{{ u.id }}</td>
|
||||
<td>{{ u.name }}</td>
|
||||
<td>{{ u.phone }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Connection Lines (Visual only, simplified) -->
|
||||
<div class="connector">
|
||||
<div class="arrow">⬅️ 关联 (Join) ➡️</div>
|
||||
</div>
|
||||
|
||||
<!-- Orders Table -->
|
||||
<div class="db-table orders-table">
|
||||
<div class="table-title">订单表 (Orders)</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>订单号</th>
|
||||
<th>书名</th>
|
||||
<th>价格</th>
|
||||
<th class="highlight-col">用户 ID (外键)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="o in ordersTable"
|
||||
:key="o.id"
|
||||
:class="{ active: hoveredUserId === o.user_id }"
|
||||
@mouseenter="setHover(o.user_id)"
|
||||
@mouseleave="setHover(null)"
|
||||
>
|
||||
<td>{{ o.id }}</td>
|
||||
<td>{{ o.book }}</td>
|
||||
<td>{{ o.price }}</td>
|
||||
<td class="highlight-cell">{{ o.user_id }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note success">
|
||||
<p>✅ <strong>优势:</strong> 订单表只存 "用户 ID"。</p>
|
||||
<p>
|
||||
鼠标悬停在某一行,看看它们是如何自动关联的。修改用户表一次,所有订单都会自动更新!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.relational-demo {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
margin: 20px 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #f5f7fa;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
.tab {
|
||||
padding: 12px 24px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
border-right: 1px solid #e4e7ed;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.tab.active {
|
||||
background: #fff;
|
||||
color: #409eff;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #409eff;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.content-area {
|
||||
padding: 20px;
|
||||
}
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ebeef5;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: #fafafa;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
.highlight-col {
|
||||
background: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
.highlight-cell {
|
||||
background: #f0f9eb;
|
||||
color: #67c23a;
|
||||
font-weight: bold;
|
||||
}
|
||||
.excel-view .highlight-cell {
|
||||
background: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.db-layout {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.db-table {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.table-title {
|
||||
background: #f2f6fc;
|
||||
padding: 8px 12px;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
.connector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 50px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
tr.active {
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
.note {
|
||||
margin-top: 15px;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.note.error {
|
||||
background: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
.note.success {
|
||||
background: #f0f9eb;
|
||||
color: #67c23a;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,302 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const users = ref([
|
||||
{ id: 101, name: '张三', score: 100 },
|
||||
{ id: 102, name: '李四', score: 85 },
|
||||
{ id: 103, name: '王五', score: 120 },
|
||||
{ id: 104, name: '赵六', score: 90 }
|
||||
])
|
||||
|
||||
const commands = [
|
||||
{
|
||||
label: '查询所有用户',
|
||||
sql: 'SELECT * FROM users;',
|
||||
action: 'read_all'
|
||||
},
|
||||
{
|
||||
label: '查询分数 > 90',
|
||||
sql: 'SELECT * FROM users WHERE score > 90;',
|
||||
action: 'read_filter'
|
||||
},
|
||||
{
|
||||
label: '给张三加 10 分',
|
||||
sql: 'UPDATE users SET score = score + 10 WHERE name = "张三";',
|
||||
action: 'update_add'
|
||||
}
|
||||
]
|
||||
|
||||
const currentSql = ref('')
|
||||
const terminalOutput = ref([])
|
||||
const displayedUsers = ref([...users.value])
|
||||
const isAnimating = ref(false)
|
||||
|
||||
const execute = async (cmd) => {
|
||||
if (isAnimating.value) return
|
||||
isAnimating.value = true
|
||||
currentSql.value = cmd.sql
|
||||
|
||||
// Reset display for read operations
|
||||
if (cmd.action.startsWith('read')) {
|
||||
displayedUsers.value = [] // clear first
|
||||
await new Promise((r) => setTimeout(r, 300))
|
||||
}
|
||||
|
||||
terminalOutput.value.push({ type: 'cmd', text: `> ${cmd.sql}` })
|
||||
|
||||
await new Promise((r) => setTimeout(r, 500))
|
||||
|
||||
if (cmd.action === 'read_all') {
|
||||
displayedUsers.value = [...users.value]
|
||||
terminalOutput.value.push({
|
||||
type: 'result',
|
||||
text: `Returned ${users.value.length} rows.`
|
||||
})
|
||||
} else if (cmd.action === 'read_filter') {
|
||||
displayedUsers.value = users.value.filter((u) => u.score > 90)
|
||||
terminalOutput.value.push({
|
||||
type: 'result',
|
||||
text: `Returned ${displayedUsers.value.length} rows.`
|
||||
})
|
||||
} else if (cmd.action === 'update_add') {
|
||||
const target = users.value.find((u) => u.name === '张三')
|
||||
if (target) {
|
||||
target.score += 10
|
||||
displayedUsers.value = [...users.value] // refresh display
|
||||
terminalOutput.value.push({ type: 'success', text: `Updated 1 row.` })
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll terminal to bottom
|
||||
setTimeout(() => {
|
||||
const term = document.querySelector('.sql-terminal-body')
|
||||
if (term) term.scrollTop = term.scrollHeight
|
||||
}, 100)
|
||||
|
||||
isAnimating.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sql-playground">
|
||||
<div class="left-panel">
|
||||
<div class="panel-header">数据库表: users</div>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>id</th>
|
||||
<th>name</th>
|
||||
<th>score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="u in displayedUsers" :key="u.id" class="fade-in">
|
||||
<td>{{ u.id }}</td>
|
||||
<td>{{ u.name }}</td>
|
||||
<td>
|
||||
<span
|
||||
:class="{ 'score-up': u.name === '张三' && u.score > 100 }"
|
||||
>{{ u.score }}</span
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="displayedUsers.length === 0">
|
||||
<td colspan="3" class="empty-hint">(无数据或正在查询...)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-panel">
|
||||
<div class="panel-header">SQL 终端</div>
|
||||
<div class="sql-terminal-body">
|
||||
<div v-for="(line, i) in terminalOutput" :key="i" :class="line.type">
|
||||
{{ line.text }}
|
||||
</div>
|
||||
<div class="cursor-line">_</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<div class="action-label">常用指令:</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
v-for="cmd in commands"
|
||||
:key="cmd.label"
|
||||
@click="execute(cmd)"
|
||||
:disabled="isAnimating"
|
||||
class="cmd-btn"
|
||||
>
|
||||
{{ cmd.label }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="current-sql" v-if="currentSql">
|
||||
执行中: <code>{{ currentSql }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sql-playground {
|
||||
display: flex;
|
||||
border: 1px solid #333;
|
||||
border-radius: 8px;
|
||||
background: #1e1e1e;
|
||||
color: #ccc;
|
||||
font-family: 'Consolas', monospace;
|
||||
overflow: hidden;
|
||||
min-height: 350px;
|
||||
}
|
||||
.left-panel {
|
||||
flex: 1;
|
||||
border-right: 1px solid #444;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.right-panel {
|
||||
flex: 1.2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #252526;
|
||||
}
|
||||
.panel-header {
|
||||
background: #333;
|
||||
padding: 8px 15px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
padding: 15px;
|
||||
flex: 1;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #444;
|
||||
padding: 6px 10px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
color: #569cd6;
|
||||
}
|
||||
td {
|
||||
color: #ce9178;
|
||||
}
|
||||
td:first-child {
|
||||
color: #b5cea8;
|
||||
} /* id color */
|
||||
|
||||
.sql-terminal-body {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
font-size: 13px;
|
||||
overflow-y: auto;
|
||||
max-height: 200px;
|
||||
}
|
||||
.cmd {
|
||||
color: #fff;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.result {
|
||||
color: #aaa;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.success {
|
||||
color: #67c23a;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.cursor-line {
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: 15px;
|
||||
background: #2d2d2d;
|
||||
border-top: 1px solid #444;
|
||||
}
|
||||
.action-label {
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
color: #999;
|
||||
}
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.cmd-btn {
|
||||
background: #0e639c;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 6px 12px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.cmd-btn:hover {
|
||||
background: #1177bb;
|
||||
}
|
||||
.cmd-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.current-sql {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
.current-sql code {
|
||||
color: #dcdcaa;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.score-up {
|
||||
color: #67c23a;
|
||||
font-weight: bold;
|
||||
animation: pulse 0.5s;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.4);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
.empty-hint {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
padding: 20px;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,277 @@
|
||||
<!--
|
||||
CdnCacheDemo.vue
|
||||
CDN 加速原理:快递柜隐喻
|
||||
-->
|
||||
<template>
|
||||
<div class="cdn">
|
||||
<div class="header">
|
||||
<div class="title">CDN 加速演示</div>
|
||||
<div class="subtitle">就像在小区楼下装了个“丰巢快递柜”</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="control">
|
||||
<label>你要取什么东西?(资源类型)</label>
|
||||
<div class="chips">
|
||||
<button
|
||||
v-for="r in resourceTypes"
|
||||
:key="r.id"
|
||||
:class="['chip', { active: r.id === resourceType }]"
|
||||
@click="resourceType = r.id"
|
||||
>
|
||||
{{ r.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<label>快递柜里有吗?(命中率)</label>
|
||||
<input type="range" min="0" max="100" v-model.number="hit" />
|
||||
<div class="hint">当前概率:{{ hit }}% ({{ hit > 80 ? '大部分都有' : '经常要跑远路' }})</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary">
|
||||
<div class="card">
|
||||
<div class="label">跑总仓库的次数 (回源)</div>
|
||||
<div class="value">{{ miss }}%</div>
|
||||
<div class="note">次数越少,总仓库越轻松</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">省下的路费 (带宽节省)</div>
|
||||
<div class="value">{{ saved }}%</div>
|
||||
<div class="note">省到就是赚到</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">老司机的建议</div>
|
||||
<div class="value">{{ cacheAdvice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flow">
|
||||
<div class="step" v-for="(s, idx) in flow" :key="idx">
|
||||
<div class="head">
|
||||
<span class="dot" :style="{ background: s.color }"></span>
|
||||
<span class="name">{{ s.name }}</span>
|
||||
<span class="time">{{ s.time }}</span>
|
||||
</div>
|
||||
<div class="desc">{{ s.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const resourceTypes = [
|
||||
{ id: 'static', label: '标准件 (图片/CSS/JS)' },
|
||||
{ id: 'html', label: '信件 (HTML)' },
|
||||
{ id: 'api', label: '生鲜 (API数据)' }
|
||||
]
|
||||
|
||||
const resourceType = ref('static')
|
||||
const hit = ref(85)
|
||||
|
||||
const miss = computed(() => 100 - hit.value)
|
||||
const saved = computed(() => hit.value)
|
||||
|
||||
const cacheAdvice = computed(() => {
|
||||
if (resourceType.value === 'static')
|
||||
return '标准件保质期长,建议放柜子里一年 (max-age=1年)'
|
||||
if (resourceType.value === 'html')
|
||||
return '信件可能随时更新,每次取之前问一下 (no-cache)'
|
||||
return '生鲜容易坏,不要放柜子,直接去产地拿 (no-store)'
|
||||
})
|
||||
|
||||
const flow = computed(() => {
|
||||
const base = [
|
||||
{
|
||||
name: '用户 🙋♂️',
|
||||
time: '0ms',
|
||||
desc: '我想取个包裹',
|
||||
color: '#6366f1'
|
||||
},
|
||||
{
|
||||
name: '家门口快递柜 📦',
|
||||
time: '15ms',
|
||||
desc: '看看柜子里有没有...',
|
||||
color: '#6366f1'
|
||||
}
|
||||
]
|
||||
if (hit.value >= 70 && resourceType.value === 'static') {
|
||||
base.push({
|
||||
name: '有货!✅',
|
||||
time: '+5ms',
|
||||
desc: '直接拿走,不用跑远路',
|
||||
color: '#22c55e'
|
||||
})
|
||||
} else {
|
||||
base.push({
|
||||
name: '没货... ❌',
|
||||
time: '+10ms',
|
||||
desc: '柜子是空的,得去总仓库',
|
||||
color: '#f59e0b'
|
||||
})
|
||||
base.push({
|
||||
name: '总仓库 (源站) 🏭',
|
||||
time: resourceType.value === 'api' ? '+60ms' : '+40ms',
|
||||
desc: '翻山越岭把货取回来',
|
||||
color: '#e11d48'
|
||||
})
|
||||
if (resourceType.value !== 'api') {
|
||||
base.push({
|
||||
name: '顺手存柜子',
|
||||
time: '+8ms',
|
||||
desc: '下次邻居来拿就不用跑了',
|
||||
color: '#22c55e'
|
||||
})
|
||||
}
|
||||
}
|
||||
base.push({
|
||||
name: '拿到手 🎁',
|
||||
time: 'Total',
|
||||
desc: '交易完成',
|
||||
color: '#0ea5e9'
|
||||
})
|
||||
return base
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cdn {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.header .title {
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
}
|
||||
.header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.control {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.chip {
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.chip.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
background: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px dashed var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 800;
|
||||
margin-top: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.note {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.flow {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.step {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.time {
|
||||
margin-left: auto;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,292 @@
|
||||
<!--
|
||||
CicdPipelineDemo.vue
|
||||
CI/CD 流水线:自动炒菜机隐喻
|
||||
-->
|
||||
<template>
|
||||
<div class="cicd">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">自动化流水线 (CI/CD)</div>
|
||||
<div class="subtitle">就像一台“全自动炒菜机”</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<label class="fail-toggle"><input type="checkbox" v-model="failTest" /> 混入一颗烂菜 (模拟报错)</label>
|
||||
<button :disabled="running" @click="run" class="run-btn">
|
||||
{{ running ? '机器运转中...' : '开始做菜 (触发构建)' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="steps">
|
||||
<div class="step" v-for="step in steps" :key="step.id">
|
||||
<div class="step-head">
|
||||
<span class="badge" :class="step.status">{{
|
||||
statusIcon(step.status)
|
||||
}}</span>
|
||||
<span class="name">{{ step.name }}</span>
|
||||
</div>
|
||||
<div class="analogy">{{ step.analogy }}</div>
|
||||
<div class="desc">{{ step.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="log" v-if="log">
|
||||
<div class="log-title">🖥️ 机器日志</div>
|
||||
<pre><code>{{ log }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const steps = ref([
|
||||
{
|
||||
id: 'install',
|
||||
name: '安装依赖 (Install)',
|
||||
analogy: '🥬 准备食材',
|
||||
desc: 'npm install',
|
||||
status: 'idle'
|
||||
},
|
||||
{
|
||||
id: 'test',
|
||||
name: '自动测试 (Test)',
|
||||
analogy: '🔍 食品安检',
|
||||
desc: 'npm test',
|
||||
status: 'idle'
|
||||
},
|
||||
{
|
||||
id: 'build',
|
||||
name: '打包构建 (Build)',
|
||||
analogy: '🍳 下锅烹饪',
|
||||
desc: 'npm run build',
|
||||
status: 'idle'
|
||||
},
|
||||
{
|
||||
id: 'deploy',
|
||||
name: '自动部署 (Deploy)',
|
||||
analogy: '🍽️ 端上桌',
|
||||
desc: 'pm2 restart',
|
||||
status: 'idle'
|
||||
}
|
||||
])
|
||||
|
||||
const running = ref(false)
|
||||
const failTest = ref(false)
|
||||
const log = ref('')
|
||||
|
||||
const wait = (ms) => new Promise((r) => setTimeout(r, ms))
|
||||
|
||||
const statusIcon = (status) => {
|
||||
if (status === 'done') return '✔'
|
||||
if (status === 'running') return '⏳'
|
||||
if (status === 'fail') return '✖'
|
||||
return '•'
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
steps.value = steps.value.map((s) => ({ ...s, status: 'idle' }))
|
||||
log.value = ''
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
if (running.value) return
|
||||
running.value = true
|
||||
reset()
|
||||
|
||||
const timeline = [
|
||||
{
|
||||
id: 'install',
|
||||
ms: 1000,
|
||||
log: '> 正在去菜市场买菜...\n> 成功买到 842 个包裹 (node_modules)'
|
||||
},
|
||||
{
|
||||
id: 'test',
|
||||
ms: 800,
|
||||
log: '> 正在检查食材新鲜度...\n> 单元测试运行中...'
|
||||
},
|
||||
{
|
||||
id: 'build',
|
||||
ms: 1200,
|
||||
log: '> 开始烹饪...\n> 正在压缩混淆代码...\n> 产出 dist/ 目录 (一盘好菜)'
|
||||
},
|
||||
{
|
||||
id: 'deploy',
|
||||
ms: 1000,
|
||||
log: '> 正在把菜端给顾客...\n> 重启服务器...\n> 上线成功!'
|
||||
}
|
||||
]
|
||||
|
||||
for (const item of timeline) {
|
||||
const step = steps.value.find((s) => s.id === item.id)
|
||||
step.status = 'running'
|
||||
log.value = item.log
|
||||
await wait(item.ms)
|
||||
|
||||
if (item.id === 'test' && failTest.value) {
|
||||
step.status = 'fail'
|
||||
log.value = '❌ 警告:发现一颗烂白菜!(测试失败)\n❌ 立即停机,防止端给顾客。'
|
||||
steps.value
|
||||
.filter((s) => s.id !== 'test')
|
||||
.forEach((s) => (s.status = 'idle'))
|
||||
running.value = false
|
||||
return
|
||||
}
|
||||
|
||||
step.status = 'done'
|
||||
}
|
||||
|
||||
log.value = '✅ 流程结束:大家吃得很开心 (服务正常运行)'
|
||||
running.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cicd {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
}
|
||||
.subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.fail-toggle {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.run-btn {
|
||||
background: var(--vp-c-brand);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.run-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.steps {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.step {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.step-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.badge.running {
|
||||
border-color: #f59e0b;
|
||||
color: #f59e0b;
|
||||
}
|
||||
.badge.done {
|
||||
border-color: #22c55e;
|
||||
color: #22c55e;
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
.badge.fail {
|
||||
border-color: #ef4444;
|
||||
color: #ef4444;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.analogy {
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
margin-left: 32px;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.log {
|
||||
background: #1e1e20;
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
color: #eee;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.log-title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
color: #aaa;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,308 @@
|
||||
<!--
|
||||
DeploymentArchitecture.vue
|
||||
全景图:快递配送隐喻
|
||||
-->
|
||||
<template>
|
||||
<div class="arch">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">全景演示:一个请求的“奇幻漂流”</div>
|
||||
<div class="subtitle">
|
||||
点击下方按钮,看看三种模式的“配送路线”有什么不同
|
||||
</div>
|
||||
</div>
|
||||
<div class="modes">
|
||||
<button
|
||||
v-for="mode in modes"
|
||||
:key="mode.id"
|
||||
:class="['mode', { active: mode.id === currentMode }]"
|
||||
@click="currentMode = mode.id"
|
||||
>
|
||||
<span class="icon">{{ mode.icon }}</span>
|
||||
{{ mode.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flow">
|
||||
<div
|
||||
v-for="(node, idx) in nodes"
|
||||
:key="node.name"
|
||||
class="node"
|
||||
:style="{ borderColor: node.color }"
|
||||
>
|
||||
<div class="node-head">
|
||||
<div class="dot" :style="{ background: node.color }">
|
||||
{{ node.icon }}
|
||||
</div>
|
||||
<div class="name-box">
|
||||
<div class="role">{{ node.role }}</div>
|
||||
<div class="tech-name">{{ node.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc">{{ node.desc }}</div>
|
||||
<div v-if="idx < nodes.length - 1" class="arrow">→</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary">
|
||||
<div class="metric">
|
||||
<div class="label">当前场景</div>
|
||||
<div class="value">{{ currentModeLabel }}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="label">瓶颈环节</div>
|
||||
<div class="value">{{ bottleneck }}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="label">通俗解释</div>
|
||||
<div class="value">{{ advice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const modes = [
|
||||
{ id: 'static', label: '看海报 (静态)', icon: '🖼️' },
|
||||
{ id: 'spa', label: '玩 App (SPA)', icon: '📱' },
|
||||
{ id: 'ssr', label: '刷动态 (SSR)', icon: '🔄' },
|
||||
]
|
||||
|
||||
const currentMode = ref('spa')
|
||||
|
||||
const currentModeLabel = computed(() =>
|
||||
modes.find(m => m.id === currentMode.value)?.label
|
||||
)
|
||||
|
||||
// 角色:User(寄件人), DNS(查号台), CDN(快递柜), WAF(保安), LB(大堂经理), Server(办事员), DB(档案室)
|
||||
const commonNodes = {
|
||||
user: { role: '寄件人', name: 'User', icon: '🧑', color: '#64748b', desc: '发出请求' },
|
||||
dns: { role: '查号台', name: 'DNS', icon: '📒', color: '#0ea5e9', desc: '查询 IP 地址' },
|
||||
cdn: { role: '快递柜', name: 'CDN', icon: '📦', color: '#22c55e', desc: '就近取货' },
|
||||
waf: { role: '保安', name: 'WAF', icon: '🛡️', color: '#ef4444', desc: '拦截黑客' },
|
||||
lb: { role: '大堂经理', name: 'LB', icon: '💁', color: '#f59e0b', desc: '分配窗口' },
|
||||
server: { role: '办事员', name: 'Server', icon: '👨💼', color: '#8b5cf6', desc: '处理业务' },
|
||||
db: { role: '档案室', name: 'Database', icon: '🗄️', color: '#d946ef', desc: '存取数据' },
|
||||
obj: { role: '仓库', name: 'OSS', icon: '🏭', color: '#f97316', desc: '拿静态文件' }
|
||||
}
|
||||
|
||||
const flowMap = {
|
||||
static: [
|
||||
{ ...commonNodes.user, desc: '想看一张图片' },
|
||||
{ ...commonNodes.dns, desc: '找到图片仓库地址' },
|
||||
{ ...commonNodes.cdn, desc: '家门口就有?直接拿走!' },
|
||||
{ ...commonNodes.obj, desc: '没有?去总仓库拿' }
|
||||
],
|
||||
spa: [
|
||||
{ ...commonNodes.user, desc: '打开网页 App' },
|
||||
{ ...commonNodes.dns, desc: '找到服务器地址' },
|
||||
{ ...commonNodes.cdn, desc: '先拿网页外壳 (HTML/JS)' },
|
||||
{ ...commonNodes.server, desc: '再拿动态数据 (API)' },
|
||||
{ ...commonNodes.db, desc: '查用户数据' }
|
||||
],
|
||||
ssr: [
|
||||
{ ...commonNodes.user, desc: '打开复杂网页' },
|
||||
{ ...commonNodes.dns, desc: '找到服务器地址' },
|
||||
{ ...commonNodes.lb, desc: '人多排队,你以此去 2 号窗口' },
|
||||
{ ...commonNodes.server, desc: '现场拼装好整个页面' },
|
||||
{ ...commonNodes.db, desc: '查所有需要的数据' }
|
||||
]
|
||||
}
|
||||
|
||||
const nodes = computed(() => flowMap[currentMode.value])
|
||||
|
||||
const bottleneck = computed(() => {
|
||||
switch (currentMode.value) {
|
||||
case 'static': return '几乎没有瓶颈,起飞!'
|
||||
case 'spa': return 'API 接口响应速度'
|
||||
case 'ssr': return '办事员 (Server) 拼装页面的速度'
|
||||
default: return ''
|
||||
}
|
||||
})
|
||||
|
||||
const advice = computed(() => {
|
||||
switch (currentMode.value) {
|
||||
case 'static':
|
||||
return '这是最简单的模式。就像去看公告栏的海报(或者发传单),内容印死在上面了,所有人看到的都一样。速度最快!'
|
||||
case 'spa':
|
||||
return '就像送你一套乐高积木。先给你个空盒子和图纸(网页壳子),你的浏览器自己在本地把页面拼出来。拼好后怎么玩都快。'
|
||||
case 'ssr':
|
||||
return '就像点了一份热披萨。厨师(服务器)必须现场烤好,再热乎乎地送给你。虽然慢点,但保证新鲜、不仅能吃(能看)还能闻到香味(SEO友好)。'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.arch {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.header {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 14px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.modes {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
background: var(--vp-c-bg);
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.mode {
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mode:hover {
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.mode.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.flow {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.node {
|
||||
position: relative;
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.node:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.node-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
color: #fff;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.name-box {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.role {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.tech-name {
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
right: -14px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--vp-c-divider);
|
||||
font-size: 18px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.arrow {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.metric {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,216 @@
|
||||
<!--
|
||||
DnsFlowDemo.vue
|
||||
DNS 记录操练台:地址簿隐喻
|
||||
-->
|
||||
<template>
|
||||
<div class="dns">
|
||||
<div class="header">
|
||||
<div class="title">DNS 查号台</div>
|
||||
<div class="subtitle">把“好记的名字”变成“机器的 IP”</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="field">
|
||||
<label>你要配哪个域名?</label>
|
||||
<input v-model="domain" placeholder="例如:baidu.com" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>你要做什么?(记录类型)</label>
|
||||
<div class="chips">
|
||||
<button
|
||||
v-for="r in recordTypes"
|
||||
:key="r.type"
|
||||
:class="['chip', { active: recordType === r.type }]"
|
||||
@click="recordType = r.type"
|
||||
>
|
||||
{{ r.desc }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<span>类型 (Type)</span>
|
||||
<code class="highlight">{{ recordType }}</code>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>前缀 (Host)</span>
|
||||
<code>{{ hostLabel }}</code>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>目标 (Value)</span>
|
||||
<code>{{ recordValue }}</code>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>记忆时间 (TTL)</span>
|
||||
<code>{{ ttlSuggestion }}</code>
|
||||
</div>
|
||||
|
||||
<div class="human-speak">
|
||||
<span class="emoji">💡</span>
|
||||
<div class="text">
|
||||
<strong>人话解释:</strong>
|
||||
{{ humanExplanation }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const domain = ref('my-site.com')
|
||||
const recordType = ref('A')
|
||||
|
||||
const recordTypes = [
|
||||
{
|
||||
type: 'A',
|
||||
desc: '直接指向 IP',
|
||||
value: '1.2.3.4',
|
||||
explanation: '告诉查号台:我家住在“1.2.3.4”这个门牌号。最常用!',
|
||||
ttl: '600s (10分钟)'
|
||||
},
|
||||
{
|
||||
type: 'CNAME',
|
||||
desc: '指向别名',
|
||||
value: 'shops.myshopify.com',
|
||||
explanation: '告诉查号台:我搬家了,你去问问“shops.myshopify.com”我在哪。',
|
||||
ttl: '600s (10分钟)'
|
||||
},
|
||||
{
|
||||
type: 'MX',
|
||||
desc: '配置邮箱',
|
||||
value: 'mxbiz1.qq.com',
|
||||
explanation: '告诉邮递员:寄给我的信,请送到“mxbiz1.qq.com”这个邮局去。',
|
||||
ttl: '3600s (1小时)'
|
||||
}
|
||||
]
|
||||
|
||||
const currentRecord = computed(() => recordTypes.find(r => r.type === recordType.value))
|
||||
|
||||
const hostLabel = computed(() => (recordType.value === 'CNAME' ? 'www' : '@'))
|
||||
const recordValue = computed(() => currentRecord.value?.value || '')
|
||||
const ttlSuggestion = computed(() => currentRecord.value?.ttl || '600s')
|
||||
const humanExplanation = computed(() => currentRecord.value?.explanation || '')
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dns {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.header .title {
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
}
|
||||
.header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
background: var(--vp-c-bg);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.chip {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 999px;
|
||||
padding: 6px 14px;
|
||||
background: var(--vp-c-bg);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.chip.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.row span {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
code {
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.human-speak {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px dashed var(--vp-c-divider);
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,208 @@
|
||||
<!--
|
||||
HttpsNginxDemo.vue
|
||||
Nginx 配置生成器:极简版
|
||||
-->
|
||||
<template>
|
||||
<div class="https">
|
||||
<div class="header">
|
||||
<div class="title">Nginx 配置生成器</div>
|
||||
<div class="subtitle">复制粘贴就能用,自动配置 HTTPS</div>
|
||||
</div>
|
||||
|
||||
<div class="options">
|
||||
<div
|
||||
class="option"
|
||||
:class="{ active: mode === 'static' }"
|
||||
@click="mode = 'static'"
|
||||
>
|
||||
<span class="icon">📄</span>
|
||||
<span>静态网站</span>
|
||||
</div>
|
||||
<div
|
||||
class="option"
|
||||
:class="{ active: mode === 'proxy' }"
|
||||
@click="mode = 'proxy'"
|
||||
>
|
||||
<span class="icon">🔄</span>
|
||||
<span>反向代理 (Node/Python)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-box">
|
||||
<div class="code-header">
|
||||
<span>/etc/nginx/sites-available/default</span>
|
||||
<button class="copy-btn" @click="copy">{{ copied ? '已复制' : '复制' }}</button>
|
||||
</div>
|
||||
<pre><code>{{ snippet }}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="tips">
|
||||
<div class="tip-item">
|
||||
<span class="emoji">🔑</span>
|
||||
<div>
|
||||
<strong>开启 HTTPS 神器:</strong>
|
||||
<div class="cmd">sudo certbot --nginx</div>
|
||||
<div class="desc">运行这行命令,它会自动修改上面的配置,帮你加上 SSL 证书。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const mode = ref('proxy')
|
||||
const copied = ref(false)
|
||||
|
||||
const snippet = computed(() => {
|
||||
if (mode.value === 'static') {
|
||||
return `server {
|
||||
listen 80;
|
||||
server_name example.com; # 改成你的域名
|
||||
|
||||
# 静态文件在哪里?
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
}`
|
||||
} else {
|
||||
return `server {
|
||||
listen 80;
|
||||
server_name example.com; # 改成你的域名
|
||||
|
||||
location / {
|
||||
# 把请求转发给 3000 端口的程序
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
|
||||
# 告诉后端真实的客户 IP 是多少
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}`
|
||||
}
|
||||
})
|
||||
|
||||
function copy() {
|
||||
navigator.clipboard.writeText(snippet.value)
|
||||
copied.value = true
|
||||
setTimeout(() => copied.value = false, 2000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.https {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.header .title {
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
}
|
||||
.header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.option {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.option.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.code-box {
|
||||
background: #1e1e20;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 13px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
background: #2d2d30;
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
color: #fff;
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
overflow-x: auto;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tips {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
background: var(--vp-c-bg);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px dashed var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.cmd {
|
||||
background: #1e1e20;
|
||||
color: #22c55e;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
margin: 4px 0;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,259 @@
|
||||
<!--
|
||||
ObservabilityBackupDemo.vue
|
||||
监控与备份:买保险隐喻
|
||||
-->
|
||||
<template>
|
||||
<div class="obs">
|
||||
<div class="header">
|
||||
<div class="title">监控与备份</div>
|
||||
<div class="subtitle">给你的网站买份“保险”</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="control">
|
||||
<label>安保级别 (监控)</label>
|
||||
<select v-model="monitorLevel">
|
||||
<option value="lite">入门:只装个摄像头 (日志)</option>
|
||||
<option value="std">标准:雇个保安 (指标+告警)</option>
|
||||
<option value="pro">专业:24h 安保中心 (全链路追踪)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control">
|
||||
<label>通知谁?(告警渠道)</label>
|
||||
<div class="chips">
|
||||
<button
|
||||
v-for="c in channels"
|
||||
:key="c.id"
|
||||
:class="['chip', { active: channel === c.id }]"
|
||||
@click="channel = c.id"
|
||||
>
|
||||
{{ c.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<label>备份频率 (小时)</label>
|
||||
<input
|
||||
type="range"
|
||||
min="6"
|
||||
max="48"
|
||||
step="6"
|
||||
v-model.number="backupHours"
|
||||
/>
|
||||
<div class="hint">每 {{ backupHours }} 小时存一次盘</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="label">安全评分</div>
|
||||
<div
|
||||
class="value big"
|
||||
:class="{
|
||||
green: riskScore <= 40,
|
||||
orange: riskScore > 40 && riskScore <= 70,
|
||||
red: riskScore > 70
|
||||
}"
|
||||
>
|
||||
{{ 100 - riskScore }} 分
|
||||
</div>
|
||||
<div class="note">{{ riskScore > 70 ? '极其危险!' : (riskScore > 40 ? '勉强及格' : '非常稳!') }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">最坏情况 (丢数据)</div>
|
||||
<div class="value">{{ rpo }}</div>
|
||||
<div class="note">最多丢失多少小时的数据</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">恢复速度</div>
|
||||
<div class="value">{{ rto }}</div>
|
||||
<div class="note">出事后多久能修好</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checklist-box">
|
||||
<div class="box-title">保命清单 (Checklist)</div>
|
||||
<div class="checks">
|
||||
<div class="check" v-for="item in checklist" :key="item.label">
|
||||
<input type="checkbox" v-model="item.done" />
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
|
||||
const monitorLevel = ref('std')
|
||||
const channels = [
|
||||
{ id: 'email', label: '发邮件 (慢)' },
|
||||
{ id: 'chat', label: '企业微信/飞书 (快)' },
|
||||
{ id: 'pager', label: '电话轰炸 (急)' }
|
||||
]
|
||||
const channel = ref('chat')
|
||||
const backupHours = ref(24)
|
||||
|
||||
const checklist = reactive([
|
||||
{ label: '日志能不能查到?', done: true },
|
||||
{ label: 'CPU 飙高了会不会报警?', done: false },
|
||||
{ label: '关键接口慢了知不知道?', done: false },
|
||||
{ label: '数据库有没有自动备份?', done: true },
|
||||
{ label: '备份文件真的能恢复吗?(演练过)', done: false }
|
||||
])
|
||||
|
||||
const riskScore = computed(() => {
|
||||
let score = 70
|
||||
if (monitorLevel.value === 'pro') score -= 20
|
||||
else if (monitorLevel.value === 'std') score -= 10
|
||||
|
||||
if (channel.value === 'pager') score -= 10
|
||||
else if (channel.value === 'chat') score -= 5
|
||||
|
||||
score -= Math.min(20, (48 - backupHours.value) * 0.8)
|
||||
|
||||
const doneCount = checklist.filter((i) => i.done).length
|
||||
score -= doneCount * 4
|
||||
|
||||
return Math.max(0, Math.min(100, Math.round(score)))
|
||||
})
|
||||
|
||||
const rpo = computed(() => `${backupHours.value} 小时`)
|
||||
const rto = computed(() => {
|
||||
if (monitorLevel.value === 'pro') return '15-30 分钟'
|
||||
if (monitorLevel.value === 'std') return '30-60 分钟'
|
||||
return '1-2 小时 (甚至更久)'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.obs {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.header .title {
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
}
|
||||
.header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.control {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
label {
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
select,
|
||||
input[type='range'] {
|
||||
width: 100%;
|
||||
}
|
||||
.hint {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.chips {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.chip {
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.chip.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
background: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px dashed var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
}
|
||||
.label {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 13px;
|
||||
}
|
||||
.value {
|
||||
font-weight: 800;
|
||||
margin-top: 4px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.value.big {
|
||||
font-size: 24px;
|
||||
}
|
||||
.value.green {
|
||||
color: #22c55e;
|
||||
}
|
||||
.value.orange {
|
||||
color: #f59e0b;
|
||||
}
|
||||
.value.red {
|
||||
color: #ef4444;
|
||||
}
|
||||
.note {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.checklist-box {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
}
|
||||
.box-title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.checks {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.check {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,193 @@
|
||||
<!--
|
||||
RollbackSwitchDemo.vue
|
||||
发布策略:如何不关门装修
|
||||
-->
|
||||
<template>
|
||||
<div class="roll">
|
||||
<div class="header">
|
||||
<div class="title">发布策略对比</div>
|
||||
<div class="subtitle">网站升级就像店铺装修,怎么才能不影响做生意?</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button
|
||||
v-for="s in strategies"
|
||||
:key="s.id"
|
||||
:class="['tab', { active: current === s.id }]"
|
||||
@click="current = s.id"
|
||||
>
|
||||
<span class="emoji">{{ s.emoji }}</span>
|
||||
{{ s.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="card">
|
||||
<div class="label">操作方式</div>
|
||||
<div class="value">{{ flow }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">后悔药 (回滚时间)</div>
|
||||
<div class="value">{{ rollbackTime }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">成本代价</div>
|
||||
<div class="value">{{ cost }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="analogy-section">
|
||||
<div class="col">
|
||||
<div class="section-title">🧐 通俗理解</div>
|
||||
<div class="analogy-text">{{ analogy }}</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="section-title">⚠️ 风险点</div>
|
||||
<div class="risk-text">{{ risk }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const strategies = [
|
||||
{ id: 'rolling', label: '滚动更新 (Rolling)', emoji: '🔄' },
|
||||
{ id: 'blue', label: '蓝绿发布 (Blue/Green)', emoji: '🔵🟢' },
|
||||
{ id: 'canary', label: '金丝雀发布 (Canary)', emoji: '🐦' }
|
||||
]
|
||||
|
||||
const current = ref('rolling')
|
||||
|
||||
const flow = computed(() => {
|
||||
if (current.value === 'rolling') return '分批替换'
|
||||
if (current.value === 'blue') return '全量切换'
|
||||
return '按比例慢慢切'
|
||||
})
|
||||
|
||||
const rollbackTime = computed(() => {
|
||||
if (current.value === 'rolling') return '慢 (3-10 分钟)'
|
||||
if (current.value === 'blue') return '极快 (秒级)'
|
||||
return '快 (秒级)'
|
||||
})
|
||||
|
||||
const cost = computed(() => {
|
||||
if (current.value === 'rolling') return '低 (资源利用率高)'
|
||||
if (current.value === 'blue') return '高 (需要双倍机器)'
|
||||
return '中 (需要复杂网关)'
|
||||
})
|
||||
|
||||
const analogy = computed(() => {
|
||||
switch (current.value) {
|
||||
case 'rolling':
|
||||
return '就像餐厅换桌布。不关门,一桌一桌换。客人来了坐新桌子,还没换好的桌子先空着。'
|
||||
case 'blue':
|
||||
return '有钱任性。在隔壁新开一家一模一样的店。装修好了,直接把大门指路牌改到新店。'
|
||||
case 'canary':
|
||||
return '先让 VIP 客户去新包间体验一下。如果 VIP 没投诉,再把所有客人都请进去。'
|
||||
default: return ''
|
||||
}
|
||||
})
|
||||
|
||||
const risk = computed(() => {
|
||||
if (current.value === 'rolling')
|
||||
return '中间状态比较乱,有的客人看到新装修,有的看到旧装修。'
|
||||
if (current.value === 'blue') return '太贵了!'
|
||||
return '技术要求高,得有能识别 VIP 的“门童” (流量网关)。'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.roll {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.header .title {
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
}
|
||||
.header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.tab {
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.tab.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-soft);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px dashed var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
.label {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
.value {
|
||||
font-weight: 800;
|
||||
margin-top: 4px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.analogy-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.col {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
}
|
||||
.section-title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.analogy-text {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
.risk-text {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #ef4444;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,237 @@
|
||||
<!--
|
||||
ServerSizerDemo.vue
|
||||
服务器选购指南:租房隐喻
|
||||
-->
|
||||
<template>
|
||||
<div class="sizer">
|
||||
<div class="header">
|
||||
<div class="title">服务器选购指南</div>
|
||||
<div class="subtitle">不花冤枉钱,够用就好</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="control">
|
||||
<label>你的业务规模?</label>
|
||||
<div class="range-box">
|
||||
<input type="range" min="0" max="3" step="1" v-model.number="scaleIndex" />
|
||||
<div class="scale-labels">
|
||||
<span>个人博客</span>
|
||||
<span>初创官网</span>
|
||||
<span>小型 App</span>
|
||||
<span>中型平台</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-card">
|
||||
<div class="main-rec">
|
||||
<div class="icon">{{ recommendation.icon }}</div>
|
||||
<div class="details">
|
||||
<div class="rec-title">{{ recommendation.title }}</div>
|
||||
<div class="rec-spec">{{ recommendation.spec }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="analogy-box">
|
||||
<p><strong>🤔 通俗理解:</strong></p>
|
||||
<p>{{ recommendation.analogy }}</p>
|
||||
</div>
|
||||
|
||||
<div class="specs-grid">
|
||||
<div class="spec-item">
|
||||
<span class="label">带宽 (水管)</span>
|
||||
<span class="val">{{ recommendation.bandwidth }}</span>
|
||||
</div>
|
||||
<div class="spec-item">
|
||||
<span class="label">预算估算</span>
|
||||
<span class="val">{{ recommendation.cost }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const scaleIndex = ref(1)
|
||||
|
||||
const levels = [
|
||||
{
|
||||
title: '入门级 (Entry)',
|
||||
spec: '1核 1G ~ 2G',
|
||||
icon: '🚲',
|
||||
bandwidth: '1 Mbps',
|
||||
cost: '¥50~99 / 年 (活动价)',
|
||||
analogy: '就像租了个单间。放个博客、跑个脚本完全够用。人多了会挤不动。'
|
||||
},
|
||||
{
|
||||
title: '标准级 (Standard)',
|
||||
spec: '2核 4G',
|
||||
icon: '🚗',
|
||||
bandwidth: '3~5 Mbps',
|
||||
cost: '¥300 / 年',
|
||||
analogy: '就像租了个两居室。正经跑个公司官网、小程序后端没问题。大多数人的首选。'
|
||||
},
|
||||
{
|
||||
title: '专业级 (Pro)',
|
||||
spec: '4核 8G',
|
||||
icon: '🏎️',
|
||||
bandwidth: '5~10 Mbps',
|
||||
cost: '¥1000+ / 年',
|
||||
analogy: '就像租了个大平层办公室。能抗住几千人同时在线,跑复杂的计算任务也不虚。'
|
||||
},
|
||||
{
|
||||
title: '企业级 (Enterprise)',
|
||||
spec: '8核 16G + 负载均衡',
|
||||
icon: '✈️',
|
||||
bandwidth: '按量付费',
|
||||
cost: '¥5000+ / 年',
|
||||
analogy: '这已经不是租房了,是包下了一整层楼。通常需要多台机器配合,专人维护。'
|
||||
}
|
||||
]
|
||||
|
||||
const recommendation = computed(() => levels[scaleIndex.value])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sizer {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.header .title {
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
}
|
||||
.header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.control label {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.range-box {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 16px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
input[type='range'] {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.scale-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.result-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-brand);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.result-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.main-rec {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 40px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 12px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.rec-title {
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.rec-spec {
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.analogy-box {
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.analogy-box p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.analogy-box strong {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.specs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
border-top: 1px dashed var(--vp-c-divider);
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.spec-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.spec-item .label {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.spec-item .val {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="branch-demo">
|
||||
<div class="panel">
|
||||
<div class="controls">
|
||||
<button @click="init" :disabled="inited" class="btn">初始化</button>
|
||||
<button @click="commit" :disabled="!inited" class="btn">提交</button>
|
||||
<button @click="branch" :disabled="!inited || hasBranch" class="btn">创建分支</button>
|
||||
<button @click="merge" :disabled="!hasBranch" class="btn">合并</button>
|
||||
<button @click="reset" class="btn secondary">重置</button>
|
||||
</div>
|
||||
|
||||
<div class="graph">
|
||||
<svg viewBox="0 0 400 120">
|
||||
<line x1="50" y1="40" x2="350" y2="40" stroke="#3b82f6" stroke-width="3"/>
|
||||
<line v-if="hasBranch" x1="150" y1="40" x2="150" y2="80" stroke="#10b981" stroke-width="3"/>
|
||||
<line v-if="hasBranch" x1="150" y1="80" x2="300" y2="80" stroke="#10b981" stroke-width="3"/>
|
||||
<circle v-for="(c,i) in main" :cx="60+i*50" cy="40" r="8" fill="#3b82f6"/>
|
||||
<circle v-for="(c,i) in feat" :cx="180+i*50" cy="80" r="8" fill="#10b981"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="status">
|
||||
<span>提交: {{ main.length }}</span>
|
||||
<span>分支: {{ hasBranch ? 2 : 1 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<p><strong>💡 分支策略:</strong> 并行开发,互不干扰,最后合并</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
const inited = ref(false)
|
||||
const hasBranch = ref(false)
|
||||
const main = ref([])
|
||||
const feat = ref([])
|
||||
|
||||
const init = () => { inited.value = true; main.value = [1] }
|
||||
const commit = () => { if(inited.value) main.value.push(1) }
|
||||
const branch = () => { if(inited.value) { hasBranch.value = true; feat.value = [1] } }
|
||||
const merge = () => { if(hasBranch.value) { main.value.push(1); hasBranch.value = false; feat.value = [] } }
|
||||
const reset = () => { inited.value = false; hasBranch.value = false; main.value = []; feat.value = [] }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.branch-demo { border: 1px solid var(--vp-c-divider); border-radius: 8px; background-color: var(--vp-c-bg-soft); padding: 1.5rem; margin: 1rem 0; }
|
||||
.controls { display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; }
|
||||
.btn { padding: 0.5rem 1rem; border: 1px solid var(--vp-c-brand); background: var(--vp-c-bg); color: var(--vp-c-brand); border-radius: 6px; cursor: pointer; }
|
||||
.btn:hover:not(:disabled) { background: var(--vp-c-brand); color: var(--vp-c-bg); }
|
||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.btn.secondary { border-color: var(--vp-c-divider); }
|
||||
.graph { background: var(--vp-c-bg); border-radius: 8px; padding: 1rem; border: 1px solid var(--vp-c-divider); margin: 1rem 0; }
|
||||
.graph svg { width: 100%; height: auto; }
|
||||
.status { display: flex; gap: 2rem; }
|
||||
.info-box { padding: 1rem; background: var(--vp-c-bg); border-left: 4px solid var(--vp-c-brand); border-radius: 4px; margin-top: 1rem; }
|
||||
.info-box p { margin: 0; color: var(--vp-c-text-1); }
|
||||
</style>
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div class="command-demo">
|
||||
<div class="panel">
|
||||
<div class="terminal">
|
||||
<div class="output">
|
||||
<div v-for="(line, i) in output" :key="i" :class="line.type">
|
||||
<span v-if="line.type === 'command'" class="prompt">$</span>
|
||||
<span v-html="line.text"></span>
|
||||
</div>
|
||||
<div v-if="output.length === 0" class="welcome">
|
||||
输入命令开始学习 Git
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">$</span>
|
||||
<input v-model="cmd" @keyup.enter="execute" placeholder="git status" class="cmd-input" />
|
||||
<button @click="execute" class="run-btn">运行</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-cmds">
|
||||
<button @click="runCmd('git init')" class="cmd-btn">初始化</button>
|
||||
<button @click="runCmd('git status')" class="cmd-btn">状态</button>
|
||||
<button @click="runCmd('git add .')" class="cmd-btn">添加</button>
|
||||
<button @click="runCmd('git commit -m \'msg\'')" class="cmd-btn">提交</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<p><strong>💡 常用命令:</strong> init → status → add → commit</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const cmd = ref('')
|
||||
const output = ref([])
|
||||
|
||||
const execute = () => {
|
||||
const c = cmd.value.trim()
|
||||
if (!c) return
|
||||
|
||||
output.value.push({ type: 'command', text: c })
|
||||
|
||||
if (c === 'git init') {
|
||||
output.value.push({ type: 'success', text: 'Initialized empty Git repository' })
|
||||
} else if (c === 'git status') {
|
||||
output.value.push({ type: 'info', text: 'On branch main\nnothing to commit' })
|
||||
} else if (c === 'git add .') {
|
||||
output.value.push({ type: 'success', text: 'Files added to staging area' })
|
||||
} else if (c.startsWith('git commit')) {
|
||||
output.value.push({ type: 'success', text: '1 file committed' })
|
||||
} else {
|
||||
output.value.push({ type: 'error', text: 'Unknown command' })
|
||||
}
|
||||
|
||||
cmd.value = ''
|
||||
}
|
||||
|
||||
const runCmd = (c) => {
|
||||
cmd.value = c
|
||||
execute()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.command-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
background: #1f2937;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.output {
|
||||
min-height: 150px;
|
||||
margin-bottom: 1rem;
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.output .command { color: #10b981; }
|
||||
.output .success { color: #10b981; }
|
||||
.output .error { color: #ef4444; }
|
||||
.output .info { color: #60a5fa; }
|
||||
.output .welcome { color: #9ca3af; font-style: italic; }
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.prompt { color: #10b981; }
|
||||
|
||||
.cmd-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #d1d5db;
|
||||
font-family: monospace;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.cmd-input:focus { outline: none; }
|
||||
|
||||
.run-btn {
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: #10b981;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.quick-cmds {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cmd-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid var(--vp-c-brand);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-brand);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.cmd-btn:hover { background: var(--vp-c-brand); color: var(--vp-c-bg); }
|
||||
|
||||
.info-box {
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
border-radius: 4px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.info-box p { margin: 0; color: var(--vp-c-text-1); line-height: 1.6; }
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="conflict-demo">
|
||||
<div class="panel">
|
||||
<div class="editor">
|
||||
<div class="line normal"><span class="ln">1</span>function greet() {</div>
|
||||
<div class="line normal"><span class="ln">2</span> console.log('Hi');</div>
|
||||
<div class="line conflict"><span class="ln">3</span><<<<<<< HEAD</div>
|
||||
<div class="line current"><span class="ln">4</span> console.log('Welcome') // 当前版本</div>
|
||||
<div class="line conflict"><span class="ln">5</span>=======</div>
|
||||
<div class="line incoming"><span class="ln">6</span> console.log('Greetings') // 传入版本</div>
|
||||
<div class="line conflict"><span class="ln">7</span>>>>>>>>> feature</div>
|
||||
<div class="line normal"><span class="ln">8</span> console.log('Bye');</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button @click="resolve('current')" class="action-btn">保留当前</button>
|
||||
<button @click="resolve('incoming')" class="action-btn">保留传入</button>
|
||||
<button @click="resolve('manual')" class="action-btn">手动合并</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<p><strong>💡 解决冲突:</strong> 选择保留哪个版本,或手动编辑合并</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
const resolved = ref(false)
|
||||
const resolve = (choice) => { resolved.value = true }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.conflict-demo { border: 1px solid var(--vp-c-divider); border-radius: 8px; background-color: var(--vp-c-bg-soft); padding: 1.5rem; margin: 1rem 0; }
|
||||
.editor { background: #1f2937; border-radius: 8px; padding: 1rem; font-family: monospace; margin-bottom: 1rem; }
|
||||
.line { display: flex; gap: 0.5rem; line-height: 1.6; }
|
||||
.ln { color: #6b7280; min-width: 2rem; }
|
||||
.line.normal { color: #d1d5db; }
|
||||
.line.conflict { color: #f59e0b; }
|
||||
.line.current { color: #60a5fa; background: rgba(96,165,250,0.1); }
|
||||
.line.incoming { color: #a78bfa; background: rgba(167,139,250,0.1); }
|
||||
.actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
||||
.action-btn { padding: 0.625rem 1.25rem; border: 1px solid var(--vp-c-brand); background: var(--vp-c-bg); color: var(--vp-c-brand); border-radius: 6px; cursor: pointer; }
|
||||
.action-btn:hover { background: var(--vp-c-brand); color: var(--vp-c-bg); }
|
||||
.info-box { padding: 1rem; background: var(--vp-c-bg); border-left: 4px solid var(--vp-c-brand); border-radius: 4px; }
|
||||
.info-box p { margin: 0; color: var(--vp-c-text-1); }
|
||||
</style>
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="remote-demo">
|
||||
<div class="panel">
|
||||
<div class="repos">
|
||||
<div class="repo">
|
||||
<div class="header">💻 本地</div>
|
||||
<div class="commits">
|
||||
<div v-for="c in local" :key="c" class="commit-dot">
|
||||
<span class="dot local"></span>
|
||||
<span class="hash">{{ c.substring(0,6) }}</span>
|
||||
</div>
|
||||
<div v-if="local.length === 0" class="empty">无</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sync">⇄</div>
|
||||
|
||||
<div class="repo">
|
||||
<div class="header">☁️ 远程</div>
|
||||
<div class="commits">
|
||||
<div v-for="c in remote" :key="c" class="commit-dot">
|
||||
<span class="dot remote"></span>
|
||||
<span class="hash">{{ c.substring(0,6) }}</span>
|
||||
</div>
|
||||
<div v-if="remote.length === 0" class="empty">无</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button @click="localCommit" class="btn">本地提交</button>
|
||||
<button @click="push" :disabled="local.length <= remote.length" class="btn">推送 Push</button>
|
||||
<button @click="pull" :disabled="!hasRemote" class="btn">拉取 Pull</button>
|
||||
<button @click="reset" class="btn secondary">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<p><strong>💡 远程协作:</strong> Push 上传,Pull 下载,保持同步</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
const local = ref([])
|
||||
const remote = ref([])
|
||||
const hasRemote = ref(false)
|
||||
|
||||
const localCommit = () => { local.value.push(Math.random().toString(16).substr(2,7)) }
|
||||
const push = () => { remote.value.push(...local.value.slice(remote.value.length)); hasRemote.value = false }
|
||||
const pull = () => { if(hasRemote.value) local.value.push(Math.random().toString(16).substr(2,7)); hasRemote.value = false }
|
||||
const reset = () => { local.value = []; remote.value = []; hasRemote.value = false }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.remote-demo { border: 1px solid var(--vp-c-divider); border-radius: 8px; background-color: var(--vp-c-bg-soft); padding: 1.5rem; margin: 1rem 0; }
|
||||
.repos { display: grid; grid-template-columns: 1fr auto 1fr; gap: 1rem; align-items: stretch; margin-bottom: 1rem; }
|
||||
.repo { border: 1px solid var(--vp-c-divider); border-radius: 8px; padding: 1rem; background: var(--vp-c-bg); }
|
||||
.header { font-weight: 600; margin-bottom: 0.5rem; }
|
||||
.commits { min-height: 80px; }
|
||||
.commit-dot { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
|
||||
.dot { width: 10px; height: 10px; border-radius: 50%; }
|
||||
.dot.local { background: #3b82f6; }
|
||||
.dot.remote { background: #10b981; }
|
||||
.hash { font-family: monospace; font-size: 0.875rem; color: var(--vp-c-text-2); }
|
||||
.sync { font-size: 2rem; text-align: center; }
|
||||
.empty { color: var(--vp-c-text-3); text-align: center; padding: 1rem; }
|
||||
.controls { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
||||
.btn { padding: 0.5rem 1rem; border: 1px solid var(--vp-c-brand); background: var(--vp-c-bg); color: var(--vp-c-brand); border-radius: 6px; cursor: pointer; }
|
||||
.btn:hover:not(:disabled) { background: var(--vp-c-brand); color: var(--vp-c-bg); }
|
||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.btn.secondary { border-color: var(--vp-c-divider); }
|
||||
.info-box { padding: 1rem; background: var(--vp-c-bg); border-left: 4px solid var(--vp-c-brand); border-radius: 4px; margin-top: 1rem; }
|
||||
.info-box p { margin: 0; color: var(--vp-c-text-1); }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.repos { grid-template-columns: 1fr; }
|
||||
.sync { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="stash-demo">
|
||||
<div class="panel">
|
||||
<div class="areas">
|
||||
<div class="area">
|
||||
<div class="header">💻 工作区 ({{ work.length }})</div>
|
||||
<div v-for="f in work" :key="f" class="file">📄 {{ f }}</div>
|
||||
<div v-if="work.length === 0" class="empty">空</div>
|
||||
</div>
|
||||
|
||||
<div class="area">
|
||||
<div class="header">📚 Stash 栈 ({{ stash.length }})</div>
|
||||
<div v-for="(s,i) in stash" :key="i" class="stash-item">
|
||||
<span class="num">{{ i+1 }}</span>
|
||||
<span class="msg">{{ s }}</span>
|
||||
</div>
|
||||
<div v-if="stash.length === 0" class="empty">空</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button @click="doWork" :disabled="work.length > 0" class="btn">修改</button>
|
||||
<button @click="save" :disabled="work.length === 0 || stash.length >= 3" class="btn">保存</button>
|
||||
<button @click="pop" :disabled="stash.length === 0" class="btn">恢复</button>
|
||||
<button @click="reset" class="btn secondary">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<p><strong>💡 Stash 用途:</strong> 临时保存工作现场,切换任务</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
const work = ref([])
|
||||
const stash = ref([])
|
||||
const doWork = () => { work.value = ['file.js', 'style.css'] }
|
||||
const save = () => { stash.value.push('WIP'); work.value = [] }
|
||||
const pop = () => { if(stash.value.length) { stash.value.pop(); work.value = ['file.js'] } }
|
||||
const reset = () => { work.value = []; stash.value = [] }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stash-demo { border: 1px solid var(--vp-c-divider); border-radius: 8px; background-color: var(--vp-c-bg-soft); padding: 1.5rem; margin: 1rem 0; }
|
||||
.areas { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem; }
|
||||
.area { border: 1px solid var(--vp-c-divider); border-radius: 8px; padding: 1rem; background: var(--vp-c-bg); }
|
||||
.header { font-weight: 600; margin-bottom: 0.5rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--vp-c-divider); }
|
||||
.file, .stash-item { padding: 0.5rem; background: var(--vp-c-bg-soft); margin-bottom: 0.25rem; border-radius: 4px; font-size: 0.875rem; display: flex; gap: 0.5rem; align-items: center; }
|
||||
.stash-item .num { width: 20px; height: 20px; background: var(--vp-c-brand); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.75rem; }
|
||||
.empty { color: var(--vp-c-text-3); text-align: center; font-style: italic; padding: 1rem; }
|
||||
.controls { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
||||
.btn { padding: 0.5rem 1rem; border: 1px solid var(--vp-c-brand); background: var(--vp-c-bg); color: var(--vp-c-brand); border-radius: 6px; cursor: pointer; }
|
||||
.btn:hover:not(:disabled) { background: var(--vp-c-brand); color: var(--vp-c-bg); }
|
||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.btn.secondary { border-color: var(--vp-c-divider); }
|
||||
.info-box { padding: 1rem; background: var(--vp-c-bg); border-left: 4px solid var(--vp-c-brand); border-radius: 4px; margin-top: 1rem; }
|
||||
.info-box p { margin: 0; color: var(--vp-c-text-1); }
|
||||
</style>
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="storage-demo">
|
||||
<div class="panel">
|
||||
<div class="comparison">
|
||||
<div class="mode-selector">
|
||||
<button @click="mode = 'full'" :class="{active: mode === 'full'}" class="mode-btn">完整备份</button>
|
||||
<button @click="mode = 'git'" :class="{active: mode === 'git'}" class="mode-btn">Git 增量</button>
|
||||
</div>
|
||||
|
||||
<div class="visualization">
|
||||
<div class="bar-container">
|
||||
<div class="bar full" :style="{height: fullSize + '%'}">
|
||||
<span class="label">完整备份: {{ fullSize }}MB</span>
|
||||
</div>
|
||||
<div class="bar git" :style="{height: gitSize + '%'}">
|
||||
<span class="label">Git 存储: {{ gitSize }}MB</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-item">
|
||||
<span class="value">{{ savedPercent }}%</span>
|
||||
<span class="label">节省空间</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="value">{{ versionCount }}</span>
|
||||
<span class="label">版本数</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<p><strong>💡 Git 增量存储:</strong> 只保存变更部分,大幅节省空间</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const mode = ref('git')
|
||||
const versionCount = ref(5)
|
||||
const fullSize = ref(500)
|
||||
const gitSize = ref(50)
|
||||
|
||||
const savedPercent = computed(() => Math.round((1 - gitSize.value / fullSize.value) * 100))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.storage-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.mode-selector {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.mode-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mode-btn.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.bar-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.bar {
|
||||
height: 60px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
transition: height 0.5s ease;
|
||||
}
|
||||
|
||||
.bar.full { background: linear-gradient(135deg, #ef4444, #dc2626); }
|
||||
.bar.git { background: linear-gradient(135deg, #10b981, #059669); }
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-item .value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.stat-item .label {
|
||||
font-size: 0.875rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.info-box {
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
border-radius: 4px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0;
|
||||
color: var(--vp-c-text-1);
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<div class="three-areas-demo">
|
||||
<div class="panel">
|
||||
<div class="areas-container">
|
||||
<div class="area working">
|
||||
<div class="area-header">
|
||||
<span class="area-icon">💻</span>
|
||||
<span class="area-name">工作区</span>
|
||||
<span class="area-count">{{ workingFiles.length }}</span>
|
||||
</div>
|
||||
<div class="file-list">
|
||||
<div v-for="file in workingFiles" :key="file.name" class="file-item">
|
||||
<span class="file-icon">📄</span>
|
||||
<span class="file-name">{{ file.name }}</span>
|
||||
<button @click="addToStaging(file)" class="mini-btn">+</button>
|
||||
</div>
|
||||
<div v-if="workingFiles.length === 0" class="empty">无文件</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="area staging">
|
||||
<div class="area-header">
|
||||
<span class="area-icon">📋</span>
|
||||
<span class="area-name">暂存区</span>
|
||||
<span class="area-count">{{ stagedFiles.length }}</span>
|
||||
</div>
|
||||
<div class="file-list">
|
||||
<div v-for="file in stagedFiles" :key="file.name" class="file-item">
|
||||
<span class="file-icon">📄</span>
|
||||
<span class="file-name">{{ file.name }}</span>
|
||||
<button @click="commitFile(file)" class="mini-btn">✓</button>
|
||||
</div>
|
||||
<div v-if="stagedFiles.length === 0" class="empty">无文件</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="area repo">
|
||||
<div class="area-header">
|
||||
<span class="area-icon">📦</span>
|
||||
<span class="area-name">仓库</span>
|
||||
<span class="area-count">{{ commits.length }}</span>
|
||||
</div>
|
||||
<div class="commit-list">
|
||||
<div v-for="commit in commits.slice(-3).reverse()" :key="commit.hash" class="commit-item">
|
||||
<span class="commit-hash">{{ commit.hash.substring(0, 6) }}</span>
|
||||
<span class="commit-msg">{{ commit.message }}</span>
|
||||
</div>
|
||||
<div v-if="commits.length === 0" class="empty">无提交</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<p><strong>💡 三区工作流:</strong> 工作区修改 → 添加到暂存区 → 提交到仓库</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const workingFiles = ref([
|
||||
{ name: 'index.vue' },
|
||||
{ name: 'style.css' }
|
||||
])
|
||||
|
||||
const stagedFiles = ref([])
|
||||
const commits = ref([])
|
||||
|
||||
const addToStaging = (file) => {
|
||||
const index = workingFiles.value.findIndex(f => f.name === file.name)
|
||||
if (index !== -1) {
|
||||
workingFiles.value.splice(index, 1)
|
||||
stagedFiles.value.push(file)
|
||||
}
|
||||
}
|
||||
|
||||
const commitFile = (file) => {
|
||||
const index = stagedFiles.value.findIndex(f => f.name === file.name)
|
||||
if (index !== -1) {
|
||||
stagedFiles.value.splice(index, 1)
|
||||
commits.value.push({
|
||||
hash: Math.random().toString(16).substr(2, 7),
|
||||
message: file.name
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.three-areas-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.areas-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.area {
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.area.working { border-color: #f59e0b; }
|
||||
.area.staging { border-color: #3b82f6; }
|
||||
.area.repo { border-color: #10b981; }
|
||||
|
||||
.area-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.area-icon { font-size: 1.25rem; }
|
||||
.area-name { flex: 1; font-weight: 600; }
|
||||
.area-count {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.file-list, .commit-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.file-item, .commit-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.file-icon { font-size: 1rem; }
|
||||
.file-name { flex: 1; font-size: 0.875rem; }
|
||||
|
||||
.mini-btn {
|
||||
padding: 0.125rem 0.5rem;
|
||||
border: 1px solid var(--vp-c-brand);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-brand);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.mini-btn:hover { background: var(--vp-c-brand); color: var(--vp-c-bg); }
|
||||
|
||||
.commit-hash {
|
||||
font-family: monospace;
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.commit-msg {
|
||||
flex: 1;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: var(--vp-c-text-3);
|
||||
font-style: italic;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0;
|
||||
color: var(--vp-c-text-1);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.areas-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,222 @@
|
||||
<!--
|
||||
GitWorkflowDemo.vue
|
||||
Git 工作流演示 - 简洁版
|
||||
|
||||
用途:展示 Git 的基本工作流程
|
||||
交互:初始化、提交、创建分支、合并
|
||||
-->
|
||||
<template>
|
||||
<div class="git-workflow-demo">
|
||||
<!-- 控制面板 -->
|
||||
<div class="control-panel">
|
||||
<button @click="initRepo" :disabled="inited" class="action-btn">
|
||||
🎯 初始化仓库
|
||||
</button>
|
||||
<button @click="makeCommit" :disabled="!inited" class="action-btn">
|
||||
✅ 提交
|
||||
</button>
|
||||
<button @click="createBranch" :disabled="!inited || hasBranch" class="action-btn">
|
||||
🌿 创建分支
|
||||
</button>
|
||||
<button @click="mergeBranch" :disabled="!hasBranch || merging" class="action-btn">
|
||||
🔀 合并分支
|
||||
</button>
|
||||
<button @click="reset" class="action-btn secondary">
|
||||
🔄 重置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 提交历史可视化 -->
|
||||
<div class="visualization">
|
||||
<div class="graph-container">
|
||||
<svg viewBox="0 0 400 150" class="git-graph">
|
||||
<!-- 主分支线 -->
|
||||
<line x1="50" y1="60" x2="350" y2="60" stroke="#3b82f6" stroke-width="3" />
|
||||
|
||||
<!-- 分支线 -->
|
||||
<line v-if="hasBranch" x1="150" y1="60" x2="150" y2="100" stroke="#10b981" stroke-width="3" />
|
||||
<line v-if="hasBranch" x1="150" y1="100" x2="300" y2="100" stroke="#10b981" stroke-width="3" />
|
||||
|
||||
<!-- 合并线 -->
|
||||
<path v-if="merging" d="M 300 100 Q 320 80, 320 60" fill="none" stroke="#f59e0b" stroke-width="2" stroke-dasharray="5,5" />
|
||||
|
||||
<!-- 提交节点 -->
|
||||
<circle v-for="(commit, i) in mainCommits" :key="'main-'+i" :cx="80 + i * 60" cy="60" r="10" fill="#3b82f6" />
|
||||
<circle v-for="(commit, i) in branchCommits" :key="'branch-'+i" :cx="200 + i * 60" cy="100" r="10" fill="#10b981" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态信息 -->
|
||||
<div class="status-panel">
|
||||
<div class="status-item">
|
||||
<span class="label">提交数:</span>
|
||||
<span class="value">{{ mainCommits.length }}</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="label">分支:</span>
|
||||
<span class="value">{{ hasBranch ? '2' : '1' }}</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="label">状态:</span>
|
||||
<span class="value">{{ status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 说明 -->
|
||||
<div class="info-box">
|
||||
<p><strong>💡 工作流程:</strong> 初始化 → 提交 → 创建分支 → 开发 → 合并</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const inited = ref(false)
|
||||
const hasBranch = ref(false)
|
||||
const merging = ref(false)
|
||||
const mainCommits = ref([])
|
||||
const branchCommits = ref([])
|
||||
|
||||
const status = computed(() => {
|
||||
if (merging) return '合并中...'
|
||||
if (hasBranch) return '分支已创建'
|
||||
if (inited) return '已初始化'
|
||||
return '未初始化'
|
||||
})
|
||||
|
||||
const initRepo = () => {
|
||||
inited.value = true
|
||||
mainCommits.value = [{ hash: 'abc123' }]
|
||||
}
|
||||
|
||||
const makeCommit = () => {
|
||||
if (inited.value) {
|
||||
mainCommits.value.push({ hash: Math.random().toString(16).substr(2, 6) })
|
||||
}
|
||||
}
|
||||
|
||||
const createBranch = () => {
|
||||
if (inited.value && !hasBranch.value) {
|
||||
hasBranch.value = true
|
||||
branchCommits.value = [{ hash: 'def456' }]
|
||||
}
|
||||
}
|
||||
|
||||
const mergeBranch = () => {
|
||||
if (hasBranch.value) {
|
||||
merging.value = true
|
||||
setTimeout(() => {
|
||||
mainCommits.value.push({ hash: Math.random().toString(16).substr(2, 6) })
|
||||
hasBranch.value = false
|
||||
branchCommits.value = []
|
||||
merging.value = false
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
inited.value = false
|
||||
hasBranch.value = false
|
||||
merging.value = false
|
||||
mainCommits.value = []
|
||||
branchCommits.value = []
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.git-workflow-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0.625rem 1.25rem;
|
||||
border: 2px solid var(--vp-c-brand);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-brand);
|
||||
border-radius: 6px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.action-btn:hover:not(:disabled) {
|
||||
background: var(--vp-c-brand);
|
||||
color: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
border-color: var(--vp-c-divider);
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.action-btn.secondary {
|
||||
border-color: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.visualization {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.graph-container {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.git-graph {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.status-panel {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin: 1.5rem 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.status-item .label {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.status-item .value {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.info-box {
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
border-radius: 4px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0;
|
||||
color: var(--vp-c-text-1);
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,525 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const currentScenario = ref('editor') // 'editor' | 'extension' | 'full'
|
||||
const isRunning = ref(false)
|
||||
const logs = ref([])
|
||||
const activeStep = ref('') // 'start' | 'error-editor' | 'extension' | 'error-env' | 'env' | 'result'
|
||||
|
||||
const scenarios = {
|
||||
editor: {
|
||||
tab: '1. 仅编辑器',
|
||||
title: '场景 1: 只有 VS Code (纯文本模式)',
|
||||
desc: '就像用 Windows 记事本写代码。虽然能打字,但它根本不懂什么是 Python。',
|
||||
result: '❌ 失败:VS Code 把代码当成普通文本,不知道该怎么运行。'
|
||||
},
|
||||
extension: {
|
||||
tab: '2. +插件',
|
||||
title: '场景 2: 安装了插件 (缺环境)',
|
||||
desc: '你安装了 Python 插件。插件知道“运行”意味着要找 Python 程序,但你的电脑里并没有安装 Python。',
|
||||
result: '⚠️ 报错:插件生成了指令,但在系统里找不到 "python.exe"。'
|
||||
},
|
||||
full: {
|
||||
tab: '3. +环境 (完整)',
|
||||
title: '场景 3: 完整形态 (IDE + 插件 + 环境)',
|
||||
desc: '你安装了 Python 解释器。插件生成指令,解释器接收并执行,完美配合。',
|
||||
result: '✅ 成功:Hello World'
|
||||
}
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
if (isRunning.value) return
|
||||
isRunning.value = true
|
||||
logs.value = []
|
||||
activeStep.value = 'start'
|
||||
|
||||
await wait(600)
|
||||
|
||||
if (currentScenario.value === 'editor') {
|
||||
logs.value.push('VS Code: "这是什么文件?我不认识。"')
|
||||
logs.value.push('VS Code: "我只是个打字机,无法运行。"')
|
||||
activeStep.value = 'error-editor'
|
||||
} else {
|
||||
// Has extension
|
||||
activeStep.value = 'extension'
|
||||
await wait(800)
|
||||
|
||||
if (currentScenario.value === 'extension') {
|
||||
logs.value.push('> python main.py')
|
||||
await wait(600)
|
||||
logs.value.push("Error: command 'python' not found")
|
||||
logs.value.push("系统: 找不到 Python 解释器")
|
||||
activeStep.value = 'error-env'
|
||||
} else {
|
||||
// Full
|
||||
logs.value.push('> python main.py')
|
||||
activeStep.value = 'env'
|
||||
await wait(1200)
|
||||
activeStep.value = 'result'
|
||||
logs.value.push('Hello World')
|
||||
}
|
||||
}
|
||||
|
||||
isRunning.value = false
|
||||
}
|
||||
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
||||
const setScenario = (key) => {
|
||||
if (isRunning.value) return
|
||||
currentScenario.value = key
|
||||
logs.value = []
|
||||
activeStep.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="arch-demo">
|
||||
<div class="demo-header">
|
||||
<div class="title">🛠️ IDE 核心机制模拟器</div>
|
||||
<div class="subtitle">点击下方标签,体验不同配置下的运行结果,理解为什么缺一不可。</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Selection -->
|
||||
<div class="tabs">
|
||||
<div
|
||||
v-for="(conf, key) in scenarios"
|
||||
:key="key"
|
||||
class="tab"
|
||||
:class="{ active: currentScenario === key }"
|
||||
@click="setScenario(key)"
|
||||
>
|
||||
{{ conf.tab }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="scenario-desc">
|
||||
<strong>{{ scenarios[currentScenario].title }}</strong>
|
||||
<p>{{ scenarios[currentScenario].desc }}</p>
|
||||
</div>
|
||||
|
||||
<div class="diagram-container">
|
||||
|
||||
<!-- Layer 1: VS Code -->
|
||||
<div class="component vscode" :class="{ dim: activeStep === 'env' }">
|
||||
<div class="comp-label">1. 外壳 (VS Code)</div>
|
||||
<div class="editor-window">
|
||||
<div class="file-tab">main.py</div>
|
||||
<div class="code-area">
|
||||
<span style="color: #c586c0">print</span>(<span style="color: #ce9178">"Hello"</span>)
|
||||
</div>
|
||||
<button class="run-btn-small" @click="run" :disabled="isRunning" title="点击运行">
|
||||
{{ isRunning ? '...' : '▶ 运行' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="status-badge error" v-if="activeStep === 'error-editor'">
|
||||
🚫 不懂怎么运行
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connector 1 -->
|
||||
<div class="connector">
|
||||
<div class="line" :class="{ active: ['extension', 'env', 'result', 'error-env'].includes(activeStep) }"></div>
|
||||
<div class="arrow-tip" :class="{ active: ['extension', 'env', 'result', 'error-env'].includes(activeStep) }">⬇</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer 2: Extension -->
|
||||
<div class="component extension" :class="{ missing: currentScenario === 'editor', active: activeStep === 'extension' }">
|
||||
<div class="comp-label">2. 中介 (插件)</div>
|
||||
<div class="comp-box">
|
||||
<div v-if="currentScenario === 'editor'" class="missing-content">
|
||||
<span class="icon">❌</span> 未安装插件
|
||||
</div>
|
||||
<div v-else class="active-content">
|
||||
<div class="icon">🧩</div>
|
||||
<div class="text">Python 插件</div>
|
||||
<div class="action" v-if="activeStep === 'extension' || activeStep === 'env' || activeStep === 'error-env'">
|
||||
生成指令: <code>python main.py</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connector 2 -->
|
||||
<div class="connector">
|
||||
<div class="line" :class="{ active: ['env', 'result'].includes(activeStep) }"></div>
|
||||
<div class="arrow-tip" :class="{ active: ['env', 'result'].includes(activeStep) }">⬇</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer 3: Environment -->
|
||||
<div class="component env" :class="{ missing: currentScenario !== 'full', active: activeStep === 'env' }">
|
||||
<div class="comp-label">3. 引擎 (环境)</div>
|
||||
<div class="comp-box">
|
||||
<div v-if="currentScenario !== 'full'" class="missing-content">
|
||||
<span class="icon">❌</span> 未安装环境
|
||||
</div>
|
||||
<div v-else class="active-content">
|
||||
<div class="icon">⚙️</div>
|
||||
<div class="text">Python 解释器</div>
|
||||
<div class="action" v-if="activeStep === 'env'">
|
||||
<span class="spin">⚙️</span> 正在计算...
|
||||
</div>
|
||||
<div class="action success" v-if="activeStep === 'result'">
|
||||
✅ 计算完成
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-badge error" v-if="activeStep === 'error-env'">
|
||||
🚫 找不到程序
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Output Console -->
|
||||
<div class="terminal-box">
|
||||
<div class="term-header">
|
||||
<span class="term-icon">_</span> 终端 (Terminal)
|
||||
</div>
|
||||
<div class="term-body">
|
||||
<div v-for="(l, i) in logs" :key="i" class="log-line" :class="{ error: l.includes('Error') || l.includes('失败') }">
|
||||
{{ l }}
|
||||
</div>
|
||||
<div v-if="logs.length === 0" class="placeholder">点击上方“运行”按钮开始...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-bar" :class="{ success: scenarios[currentScenario].result.includes('成功'), error: !scenarios[currentScenario].result.includes('成功') }" v-if="!isRunning && logs.length > 0">
|
||||
{{ scenarios[currentScenario].result }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.arch-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
font-family: var(--vp-font-family-base);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: var(--vp-c-bg-mute);
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
margin-bottom: 16px;
|
||||
gap: 4px;
|
||||
}
|
||||
.tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
color: var(--vp-c-text-2);
|
||||
transition: all 0.2s;
|
||||
font-weight: 500;
|
||||
}
|
||||
.tab:hover {
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
.tab.active {
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-brand);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.scenario-desc {
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 24px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.scenario-desc strong {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
.scenario-desc p {
|
||||
margin: 0;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
/* Diagram */
|
||||
.diagram-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.component {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.comp-label {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* VS Code Style */
|
||||
.vscode .editor-window {
|
||||
background: #1e1e1e;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
border: 1px solid #333;
|
||||
}
|
||||
.vscode .file-tab {
|
||||
background: #2d2d2d;
|
||||
color: #fff;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #1e1e1e;
|
||||
width: fit-content;
|
||||
}
|
||||
.vscode .code-area {
|
||||
padding: 12px;
|
||||
font-family: 'Consolas', monospace;
|
||||
font-size: 14px;
|
||||
color: #d4d4d4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.run-btn-small {
|
||||
background: #007acc;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.run-btn-small:hover {
|
||||
background: #0062a3;
|
||||
}
|
||||
.run-btn-small:disabled {
|
||||
background: #444;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Extension & Env Box Style */
|
||||
.comp-box {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.component.missing .comp-box {
|
||||
border-style: dashed;
|
||||
background: var(--vp-c-bg-alt);
|
||||
opacity: 0.7;
|
||||
}
|
||||
.component.active .comp-box {
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 0 0 2px rgba(var(--vp-c-brand-rgb), 0.2);
|
||||
}
|
||||
|
||||
.missing-content {
|
||||
color: var(--vp-c-text-3);
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.active-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
.active-content .icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
.active-content .text {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
.active-content .action {
|
||||
font-size: 12px;
|
||||
background: var(--vp-c-bg-mute);
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
font-family: monospace;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
.active-content .action.success {
|
||||
color: var(--vp-c-green);
|
||||
background: var(--vp-c-green-dimm);
|
||||
}
|
||||
|
||||
/* Connectors */
|
||||
.connector {
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
.line {
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background: var(--vp-c-divider);
|
||||
transition: background 0.3s;
|
||||
}
|
||||
.line.active {
|
||||
background: var(--vp-c-brand);
|
||||
}
|
||||
.arrow-tip {
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-divider);
|
||||
transition: color 0.3s;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
.arrow-tip.active {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
/* Status Badges */
|
||||
.status-badge {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -100px;
|
||||
transform: translateY(-50%);
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
animation: slideIn 0.3s;
|
||||
}
|
||||
.status-badge.error {
|
||||
background: #ffe6e6;
|
||||
color: #d93025;
|
||||
border: 1px solid #ffcdd2;
|
||||
}
|
||||
|
||||
/* Terminal */
|
||||
.terminal-box {
|
||||
background: #1e1e1e;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
font-family: 'Consolas', monospace;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
.term-header {
|
||||
background: #2d2d2d;
|
||||
color: #ccc;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
.term-body {
|
||||
padding: 12px;
|
||||
min-height: 80px;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
}
|
||||
.log-line {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.log-line.error {
|
||||
color: #ff6b68;
|
||||
}
|
||||
.placeholder {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.result-bar {
|
||||
margin-top: 16px;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
.result-bar.success {
|
||||
background: var(--vp-c-green-dimm);
|
||||
color: var(--vp-c-green-dark);
|
||||
}
|
||||
.result-bar.error {
|
||||
background: var(--vp-c-red-dimm);
|
||||
color: var(--vp-c-red-dark);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes slideIn {
|
||||
from { opacity: 0; transform: translate(-10px, -50%); }
|
||||
to { opacity: 1; transform: translate(0, -50%); }
|
||||
}
|
||||
.spin {
|
||||
display: inline-block;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 600px) {
|
||||
.status-badge {
|
||||
position: relative;
|
||||
right: auto;
|
||||
top: auto;
|
||||
transform: none;
|
||||
margin-top: 8px;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
+40
-14
@@ -21,11 +21,22 @@
|
||||
</template>
|
||||
|
||||
<div class="canvas-container">
|
||||
<canvas ref="canvasRef" width="300" height="300" class="noise-canvas"></canvas>
|
||||
<canvas
|
||||
ref="canvasRef"
|
||||
width="300"
|
||||
height="300"
|
||||
class="noise-canvas"
|
||||
></canvas>
|
||||
<div class="step-indicator">
|
||||
<span class="step-text">Step: {{ currentStep }} / {{ totalSteps }}</span>
|
||||
<span class="step-text"
|
||||
>Step: {{ currentStep }} / {{ totalSteps }}</span
|
||||
>
|
||||
<el-progress
|
||||
:percentage="mode === 'forward' ? (currentStep / totalSteps * 100) : ((totalSteps - currentStep) / totalSteps * 100)"
|
||||
:percentage="
|
||||
mode === 'forward'
|
||||
? (currentStep / totalSteps) * 100
|
||||
: ((totalSteps - currentStep) / totalSteps) * 100
|
||||
"
|
||||
:status="mode === 'forward' ? 'exception' : 'success'"
|
||||
:show-text="false"
|
||||
:stroke-width="4"
|
||||
@@ -42,15 +53,25 @@
|
||||
@input="draw"
|
||||
/>
|
||||
<div class="slider-labels">
|
||||
<span>{{ mode === 'forward' ? '原图 (Original)' : '纯噪声 (Noise)' }}</span>
|
||||
<span>{{ mode === 'forward' ? '纯噪声 (Noise)' : '原图 (Original)' }}</span>
|
||||
<span>{{
|
||||
mode === 'forward' ? '原图 (Original)' : '纯噪声 (Noise)'
|
||||
}}</span>
|
||||
<span>{{
|
||||
mode === 'forward' ? '纯噪声 (Noise)' : '原图 (Original)'
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
:title="mode === 'forward' ? '训练阶段:破坏数据' : '生成阶段:创造数据'"
|
||||
:title="
|
||||
mode === 'forward' ? '训练阶段:破坏数据' : '生成阶段:创造数据'
|
||||
"
|
||||
:type="mode === 'forward' ? 'warning' : 'success'"
|
||||
:description="mode === 'forward' ? 'AI 通过学习如何「一点点加噪」,掌握了噪声的规律。这就像教它把积木推倒。' : 'AI 通过预测并减去噪声,从混沌中还原出图像。这就像它学会了把推倒的积木重新搭好。'"
|
||||
:description="
|
||||
mode === 'forward'
|
||||
? 'AI 通过学习如何「一点点加噪」,掌握了噪声的规律。这就像教它把积木推倒。'
|
||||
: 'AI 通过预测并减去噪声,从混沌中还原出图像。这就像它学会了把推倒的积木重新搭好。'
|
||||
"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="explanation-alert"
|
||||
@@ -61,7 +82,12 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, onUnmounted } from 'vue'
|
||||
import { VideoPlay, VideoPause, TopRight, BottomLeft } from '@element-plus/icons-vue'
|
||||
import {
|
||||
VideoPlay,
|
||||
VideoPause,
|
||||
TopRight,
|
||||
BottomLeft
|
||||
} from '@element-plus/icons-vue'
|
||||
|
||||
const canvasRef = ref(null)
|
||||
const mode = ref('reverse')
|
||||
@@ -120,7 +146,7 @@ const generateNoise = (width, height) => {
|
||||
const data = new Uint8ClampedArray(size)
|
||||
for (let i = 0; i < size; i += 4) {
|
||||
const val = Math.random() * 255
|
||||
data[i] = val // R
|
||||
data[i] = val // R
|
||||
data[i + 1] = val // G
|
||||
data[i + 2] = val // B
|
||||
data[i + 3] = 255 // A
|
||||
@@ -165,7 +191,7 @@ const draw = () => {
|
||||
} else {
|
||||
// In reverse mode, slider 0 means start (Noisy), 100 means end (Clean)
|
||||
// So noise amount is 1 - slider
|
||||
noiseRatio = 1 - (currentStep.value / totalSteps)
|
||||
noiseRatio = 1 - currentStep.value / totalSteps
|
||||
}
|
||||
|
||||
// Non-linear interpolation for better visual effect
|
||||
@@ -186,9 +212,9 @@ const draw = () => {
|
||||
|
||||
// Using simple blending for visualization
|
||||
d[i] = o[i] * (1 - noiseRatio) + n[i] * noiseRatio
|
||||
d[i+1] = o[i+1] * (1 - noiseRatio) + n[i+1] * noiseRatio
|
||||
d[i+2] = o[i+2] * (1 - noiseRatio) + n[i+2] * noiseRatio
|
||||
d[i+3] = 255
|
||||
d[i + 1] = o[i + 1] * (1 - noiseRatio) + n[i + 1] * noiseRatio
|
||||
d[i + 2] = o[i + 2] * (1 - noiseRatio) + n[i + 2] * noiseRatio
|
||||
d[i + 3] = 255
|
||||
}
|
||||
|
||||
ctx.putImageData(output, 0, 0)
|
||||
@@ -261,7 +287,7 @@ const formatTooltip = (val) => {
|
||||
|
||||
.noise-canvas {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@@ -57,9 +57,13 @@
|
||||
>
|
||||
<template #default>
|
||||
<p>
|
||||
<strong>Diffusion</strong> 就像在迷雾中摸索,路径充满了随机性,需要走很多弯路(步数多)才能到达终点。
|
||||
<br>
|
||||
<strong>Flow Matching</strong> 就像使用了 GPS 导航,直接找到了从噪声到图像的<strong>直线最优路径 (Optimal Transport)</strong>,因此只需要极少的步数。
|
||||
<strong>Diffusion</strong>
|
||||
就像在迷雾中摸索,路径充满了随机性,需要走很多弯路(步数多)才能到达终点。
|
||||
<br />
|
||||
<strong>Flow Matching</strong> 就像使用了 GPS
|
||||
导航,直接找到了从噪声到图像的<strong
|
||||
>直线最优路径 (Optimal Transport)</strong
|
||||
>,因此只需要极少的步数。
|
||||
</p>
|
||||
</template>
|
||||
</el-alert>
|
||||
@@ -83,7 +87,7 @@ let animationFrame = null
|
||||
let diffProgress = 0
|
||||
let flowProgress = 0
|
||||
const diffSpeed = 0.005 // Slow
|
||||
const flowSpeed = 0.02 // Fast
|
||||
const flowSpeed = 0.02 // Fast
|
||||
|
||||
// Particles
|
||||
const particles = []
|
||||
@@ -179,7 +183,7 @@ const drawFrame = (canvas, progress, type) => {
|
||||
if (type === 'curve') {
|
||||
ctx.moveTo(30, h - 30)
|
||||
// Re-calculate curve up to progress
|
||||
for(let t=0; t<=progress; t+=0.01) {
|
||||
for (let t = 0; t <= progress; t += 0.01) {
|
||||
const p = getPosition(t, type, w, h)
|
||||
ctx.lineTo(p.x, p.y)
|
||||
}
|
||||
@@ -207,8 +211,14 @@ const drawBackground = (ctx, w, h) => {
|
||||
ctx.strokeStyle = '#eee'
|
||||
ctx.lineWidth = 1
|
||||
ctx.beginPath()
|
||||
for(let x=0; x<=w; x+=20) { ctx.moveTo(x,0); ctx.lineTo(x,h); }
|
||||
for(let y=0; y<=h; y+=20) { ctx.moveTo(0,y); ctx.lineTo(w,y); }
|
||||
for (let x = 0; x <= w; x += 20) {
|
||||
ctx.moveTo(x, 0)
|
||||
ctx.lineTo(x, h)
|
||||
}
|
||||
for (let y = 0; y <= h; y += 20) {
|
||||
ctx.moveTo(0, y)
|
||||
ctx.lineTo(w, y)
|
||||
}
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
@@ -261,8 +271,14 @@ const getPosition = (t, type, w, h) => {
|
||||
const cpX = w * 0.2
|
||||
const cpY = 30
|
||||
|
||||
const x = Math.pow(1-t, 2) * startX + 2 * (1-t) * t * cpX + Math.pow(t, 2) * endX
|
||||
const y = Math.pow(1-t, 2) * startY + 2 * (1-t) * t * cpY + Math.pow(t, 2) * endY
|
||||
const x =
|
||||
Math.pow(1 - t, 2) * startX +
|
||||
2 * (1 - t) * t * cpX +
|
||||
Math.pow(t, 2) * endX
|
||||
const y =
|
||||
Math.pow(1 - t, 2) * startY +
|
||||
2 * (1 - t) * t * cpY +
|
||||
Math.pow(t, 2) * endY
|
||||
|
||||
// Add some random jitter for diffusion look if t < 1
|
||||
// const jitter = t < 1 ? (Math.random() - 0.5) * 5 : 0
|
||||
@@ -318,7 +334,7 @@ const getPosition = (t, type, w, h) => {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 10px rgba(0,0,0,0.05);
|
||||
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
canvas {
|
||||
|
||||
@@ -53,7 +53,9 @@
|
||||
<div class="node-content">
|
||||
<div class="model-name">UNet / DiT</div>
|
||||
<div class="action-badge">
|
||||
<el-tag type="warning" size="small" effect="dark">去噪 (Denoise)</el-tag>
|
||||
<el-tag type="warning" size="small" effect="dark"
|
||||
>去噪 (Denoise)</el-tag
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -87,7 +89,9 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<div class="explanation-item">
|
||||
<div class="exp-icon"><el-icon color="#409EFF"><Microphone /></el-icon></div>
|
||||
<div class="exp-icon">
|
||||
<el-icon color="#409EFF"><Microphone /></el-icon>
|
||||
</div>
|
||||
<div class="exp-text">
|
||||
<h4>耳朵 (Text Encoder)</h4>
|
||||
<p>负责"听懂"你的描述,把它翻译成计算机能理解的数学向量。</p>
|
||||
@@ -96,16 +100,22 @@
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="explanation-item">
|
||||
<div class="exp-icon"><el-icon color="#E6A23C"><Cpu /></el-icon></div>
|
||||
<div class="exp-icon">
|
||||
<el-icon color="#E6A23C"><Cpu /></el-icon>
|
||||
</div>
|
||||
<div class="exp-text">
|
||||
<h4>大脑 (UNet/DiT)</h4>
|
||||
<p>核心创造者。在潜空间(Latent Space)中通过预测噪声来构思画面。</p>
|
||||
<p>
|
||||
核心创造者。在潜空间(Latent Space)中通过预测噪声来构思画面。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="explanation-item">
|
||||
<div class="exp-icon"><el-icon color="#67C23A"><View /></el-icon></div>
|
||||
<div class="exp-icon">
|
||||
<el-icon color="#67C23A"><View /></el-icon>
|
||||
</div>
|
||||
<div class="exp-text">
|
||||
<h4>眼睛 (VAE)</h4>
|
||||
<p>负责"翻译"回图像。把大脑构思的模糊特征还原成高清像素图片。</p>
|
||||
@@ -118,7 +128,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { EditPen, Microphone, Right, Cpu, View, Picture } from '@element-plus/icons-vue'
|
||||
import {
|
||||
EditPen,
|
||||
Microphone,
|
||||
Right,
|
||||
Cpu,
|
||||
View,
|
||||
Picture
|
||||
} from '@element-plus/icons-vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -10,7 +10,12 @@
|
||||
</div>
|
||||
<div class="grid-wrapper pixel-wrapper">
|
||||
<div class="pixel-grid">
|
||||
<div v-for="n in 256" :key="n" class="pixel-cell" :style="getPixelStyle(n)"></div>
|
||||
<div
|
||||
v-for="n in 256"
|
||||
:key="n"
|
||||
class="pixel-cell"
|
||||
:style="getPixelStyle(n)"
|
||||
></div>
|
||||
</div>
|
||||
<div class="grid-overlay">
|
||||
<span>HD Image</span>
|
||||
@@ -38,7 +43,9 @@
|
||||
</div>
|
||||
<el-icon :size="24" class="arrow-icon"><Right /></el-icon>
|
||||
</div>
|
||||
<el-tag type="danger" size="small" effect="dark" class="compress-tag">压缩 48x</el-tag>
|
||||
<el-tag type="danger" size="small" effect="dark" class="compress-tag"
|
||||
>压缩 48x</el-tag
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Latent Space -->
|
||||
@@ -49,7 +56,12 @@
|
||||
</div>
|
||||
<div class="grid-wrapper latent-wrapper">
|
||||
<div class="latent-grid">
|
||||
<div v-for="n in 16" :key="n" class="latent-cell" :style="getLatentStyle(n)"></div>
|
||||
<div
|
||||
v-for="n in 16"
|
||||
:key="n"
|
||||
class="latent-cell"
|
||||
:style="getLatentStyle(n)"
|
||||
></div>
|
||||
</div>
|
||||
<div class="grid-overlay">
|
||||
<span>Latent Feature</span>
|
||||
@@ -207,7 +219,7 @@ const getLatentStyle = (n) => {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: rgba(0,0,0,0.6);
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
font-size: 0.75em;
|
||||
padding: 4px 8px;
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
:style="{ opacity: token.weight }"
|
||||
>
|
||||
<div class="token-text">{{ token.text }}</div>
|
||||
<div class="token-weight">权重: {{ (token.weight * 100).toFixed(0) }}%</div>
|
||||
<div class="token-weight">
|
||||
权重: {{ (token.weight * 100).toFixed(0) }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,12 +27,21 @@
|
||||
<div class="attention-section">
|
||||
<div class="section-title">🎯 交叉注意力可视化</div>
|
||||
<div class="attention-grid">
|
||||
<div v-for="(item, index) in attentionMap" :key="index" class="attention-cell">
|
||||
<div
|
||||
v-for="(item, index) in attentionMap"
|
||||
:key="index"
|
||||
class="attention-cell"
|
||||
>
|
||||
<div class="cell-token">{{ item.token }}</div>
|
||||
<div class="cell-bar">
|
||||
<div class="bar-fill" :style="{ width: item.attention * 100 + '%' }"></div>
|
||||
<div
|
||||
class="bar-fill"
|
||||
:style="{ width: item.attention * 100 + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="cell-value">
|
||||
{{ (item.attention * 100).toFixed(0) }}%
|
||||
</div>
|
||||
<div class="cell-value">{{ (item.attention * 100).toFixed(0) }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,9 +51,8 @@
|
||||
<p>
|
||||
<span class="icon">💡</span>
|
||||
<strong>交叉注意力机制</strong>让 AI 理解提示词的每个词。
|
||||
当生成图片时,AI 会"关注"不同的词:
|
||||
"cyberpunk" 影响整体风格,"cat" 决定主体,"neon lights" 控制灯光效果。
|
||||
词的顺序和权重都会影响最终画面!
|
||||
当生成图片时,AI 会"关注"不同的词: "cyberpunk" 影响整体风格,"cat"
|
||||
决定主体,"neon lights" 控制灯光效果。 词的顺序和权重都会影响最终画面!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="info-text">
|
||||
{{ modes.find(m => m.id === currentMode)?.desc }}
|
||||
{{ modes.find((m) => m.id === currentMode)?.desc }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,8 +34,20 @@
|
||||
<svg viewBox="0 0 400 300" class="vector-canvas">
|
||||
<!-- Grid lines -->
|
||||
<g class="grid">
|
||||
<line x1="0" y1="150" x2="400" y2="150" stroke="var(--vp-c-divider)" />
|
||||
<line x1="200" y1="0" x2="200" y2="300" stroke="var(--vp-c-divider)" />
|
||||
<line
|
||||
x1="0"
|
||||
y1="150"
|
||||
x2="400"
|
||||
y2="150"
|
||||
stroke="var(--vp-c-divider)"
|
||||
/>
|
||||
<line
|
||||
x1="200"
|
||||
y1="0"
|
||||
x2="200"
|
||||
y2="300"
|
||||
stroke="var(--vp-c-divider)"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<!-- Vectors/Points -->
|
||||
@@ -53,7 +65,9 @@
|
||||
text-anchor="middle"
|
||||
class="point-label"
|
||||
:fill="point.color"
|
||||
>{{ point.word }}</text>
|
||||
>
|
||||
{{ point.word }}
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
@@ -61,24 +75,54 @@
|
||||
<g v-if="currentMode === 'analogy'" class="arrows">
|
||||
<!-- King -> Man -->
|
||||
<line
|
||||
:x1="getPoint('king').x" :y1="getPoint('king').y"
|
||||
:x2="getPoint('man').x" :y2="getPoint('man').y"
|
||||
stroke="rgba(0,0,0,0.2)" stroke-dasharray="4" marker-end="url(#arrowhead)"
|
||||
:x1="getPoint('king').x"
|
||||
:y1="getPoint('king').y"
|
||||
:x2="getPoint('man').x"
|
||||
:y2="getPoint('man').y"
|
||||
stroke="rgba(0,0,0,0.2)"
|
||||
stroke-dasharray="4"
|
||||
marker-end="url(#arrowhead)"
|
||||
/>
|
||||
<!-- Queen -> Woman -->
|
||||
<line
|
||||
:x1="getPoint('queen').x" :y1="getPoint('queen').y"
|
||||
:x2="getPoint('woman').x" :y2="getPoint('woman').y"
|
||||
stroke="var(--vp-c-brand)" stroke-width="2" marker-end="url(#arrowhead-brand)"
|
||||
:x1="getPoint('queen').x"
|
||||
:y1="getPoint('queen').y"
|
||||
:x2="getPoint('woman').x"
|
||||
:y2="getPoint('woman').y"
|
||||
stroke="var(--vp-c-brand)"
|
||||
stroke-width="2"
|
||||
marker-end="url(#arrowhead-brand)"
|
||||
/>
|
||||
<text x="390" y="280" text-anchor="end" class="math-label" fill="var(--vp-c-text-2)">King - Man ≈ Queen - Woman</text>
|
||||
<text
|
||||
x="390"
|
||||
y="280"
|
||||
text-anchor="end"
|
||||
class="math-label"
|
||||
fill="var(--vp-c-text-2)"
|
||||
>
|
||||
King - Man ≈ Queen - Woman
|
||||
</text>
|
||||
</g>
|
||||
|
||||
<defs>
|
||||
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<marker
|
||||
id="arrowhead"
|
||||
markerWidth="10"
|
||||
markerHeight="7"
|
||||
refX="9"
|
||||
refY="3.5"
|
||||
orient="auto"
|
||||
>
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="rgba(0,0,0,0.2)" />
|
||||
</marker>
|
||||
<marker id="arrowhead-brand" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<marker
|
||||
id="arrowhead-brand"
|
||||
markerWidth="10"
|
||||
markerHeight="7"
|
||||
refX="9"
|
||||
refY="3.5"
|
||||
orient="auto"
|
||||
>
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="var(--vp-c-brand)" />
|
||||
</marker>
|
||||
</defs>
|
||||
@@ -94,36 +138,82 @@ const currentMode = ref('cluster')
|
||||
|
||||
const modes = [
|
||||
{ id: 'cluster', label: '语义聚类', desc: '语义相近的词在空间中距离更近。' },
|
||||
{ id: 'analogy', label: '向量算术', desc: 'King - Man + Woman ≈ Queen (方向平行)' }
|
||||
{
|
||||
id: 'analogy',
|
||||
label: '向量算术',
|
||||
desc: 'King - Man + Woman ≈ Queen (方向平行)'
|
||||
}
|
||||
]
|
||||
|
||||
const basePoints = [
|
||||
// Cluster 1: Animals
|
||||
{ id: 'cat', word: 'Cat', x: 80, y: 80, color: '#f87171', group: 'animal' },
|
||||
{ id: 'dog', word: 'Dog', x: 100, y: 70, color: '#f87171', group: 'animal' },
|
||||
{ id: 'tiger', word: 'Tiger', x: 60, y: 100, color: '#f87171', group: 'animal' },
|
||||
{
|
||||
id: 'tiger',
|
||||
word: 'Tiger',
|
||||
x: 60,
|
||||
y: 100,
|
||||
color: '#f87171',
|
||||
group: 'animal'
|
||||
},
|
||||
|
||||
// Cluster 2: Technology
|
||||
{ id: 'computer', word: 'Computer', x: 300, y: 200, color: '#60a5fa', group: 'tech' },
|
||||
{ id: 'phone', word: 'Phone', x: 320, y: 220, color: '#60a5fa', group: 'tech' },
|
||||
{
|
||||
id: 'computer',
|
||||
word: 'Computer',
|
||||
x: 300,
|
||||
y: 200,
|
||||
color: '#60a5fa',
|
||||
group: 'tech'
|
||||
},
|
||||
{
|
||||
id: 'phone',
|
||||
word: 'Phone',
|
||||
x: 320,
|
||||
y: 220,
|
||||
color: '#60a5fa',
|
||||
group: 'tech'
|
||||
},
|
||||
{ id: 'ai', word: 'AI', x: 280, y: 210, color: '#60a5fa', group: 'tech' },
|
||||
|
||||
// Cluster 3: Royalty (Analogy)
|
||||
{ id: 'king', word: 'King', x: 100, y: 200, color: '#fbbf24', group: 'royal' },
|
||||
{ id: 'queen', word: 'Queen', x: 220, y: 200, color: '#fbbf24', group: 'royal' },
|
||||
{
|
||||
id: 'king',
|
||||
word: 'King',
|
||||
x: 100,
|
||||
y: 200,
|
||||
color: '#fbbf24',
|
||||
group: 'royal'
|
||||
},
|
||||
{
|
||||
id: 'queen',
|
||||
word: 'Queen',
|
||||
x: 220,
|
||||
y: 200,
|
||||
color: '#fbbf24',
|
||||
group: 'royal'
|
||||
},
|
||||
{ id: 'man', word: 'Man', x: 100, y: 120, color: '#a78bfa', group: 'gender' },
|
||||
{ id: 'woman', word: 'Woman', x: 220, y: 120, color: '#a78bfa', group: 'gender' },
|
||||
{
|
||||
id: 'woman',
|
||||
word: 'Woman',
|
||||
x: 220,
|
||||
y: 120,
|
||||
color: '#a78bfa',
|
||||
group: 'gender'
|
||||
}
|
||||
]
|
||||
|
||||
const activePoints = computed(() => {
|
||||
if (currentMode.value === 'cluster') {
|
||||
return basePoints.filter(p => ['animal', 'tech'].includes(p.group))
|
||||
return basePoints.filter((p) => ['animal', 'tech'].includes(p.group))
|
||||
} else {
|
||||
return basePoints.filter(p => ['royal', 'gender'].includes(p.group))
|
||||
return basePoints.filter((p) => ['royal', 'gender'].includes(p.group))
|
||||
}
|
||||
})
|
||||
|
||||
const getPoint = (id) => basePoints.find(p => p.id === id) || { x: 0, y: 0 }
|
||||
const getPoint = (id) => basePoints.find((p) => p.id === id) || { x: 0, y: 0 }
|
||||
|
||||
const setMode = (mode) => {
|
||||
currentMode.value = mode
|
||||
|
||||
@@ -7,10 +7,7 @@
|
||||
>
|
||||
标准 Attention (网状连接)
|
||||
</button>
|
||||
<button
|
||||
:class="{ active: mode === 'linear' }"
|
||||
@click="mode = 'linear'"
|
||||
>
|
||||
<button :class="{ active: mode === 'linear' }" @click="mode = 'linear'">
|
||||
线性 Attention (接力传递)
|
||||
</button>
|
||||
</div>
|
||||
@@ -18,7 +15,14 @@
|
||||
<div class="visual-area">
|
||||
<div class="control-panel">
|
||||
<div class="label">参与者数量 (N): {{ nValue }}</div>
|
||||
<input type="range" v-model="nValue" min="3" max="12" step="1" class="slider">
|
||||
<input
|
||||
type="range"
|
||||
v-model="nValue"
|
||||
min="3"
|
||||
max="12"
|
||||
step="1"
|
||||
class="slider"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="viz-canvas-container">
|
||||
@@ -26,112 +30,149 @@
|
||||
<svg class="viz-svg" viewBox="0 0 400 300">
|
||||
<!-- STANDARD MODE: Mesh / Web -->
|
||||
<g v-if="mode === 'standard'">
|
||||
<!-- Active Query Animation -->
|
||||
<g class="active-query-scan">
|
||||
<!-- Current processing node (last one) -->
|
||||
<circle
|
||||
:cx="circleNodes[circleNodes.length-1].x"
|
||||
:cy="circleNodes[circleNodes.length-1].y"
|
||||
r="16"
|
||||
fill="none"
|
||||
stroke="var(--vp-c-brand)"
|
||||
stroke-width="3"
|
||||
opacity="0.5"
|
||||
>
|
||||
<animate attributeName="r" values="12;20;12" dur="2s" repeatCount="indefinite" />
|
||||
<animate attributeName="opacity" values="0.8;0;0.8" dur="2s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<!-- Active Query Animation -->
|
||||
<g class="active-query-scan">
|
||||
<!-- Current processing node (last one) -->
|
||||
<circle
|
||||
:cx="circleNodes[circleNodes.length - 1].x"
|
||||
:cy="circleNodes[circleNodes.length - 1].y"
|
||||
r="16"
|
||||
fill="none"
|
||||
stroke="var(--vp-c-brand)"
|
||||
stroke-width="3"
|
||||
opacity="0.5"
|
||||
>
|
||||
<animate
|
||||
attributeName="r"
|
||||
values="12;20;12"
|
||||
dur="2s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="0.8;0;0.8"
|
||||
dur="2s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</circle>
|
||||
|
||||
<!-- Scanning rays from last node to all others -->
|
||||
<line
|
||||
v-for="(node, idx) in circleNodes.slice(0, circleNodes.length-1)"
|
||||
:key="'ray'+idx"
|
||||
:x1="circleNodes[circleNodes.length-1].x"
|
||||
:y1="circleNodes[circleNodes.length-1].y"
|
||||
:x2="node.x"
|
||||
:y2="node.y"
|
||||
stroke="var(--vp-c-brand)"
|
||||
stroke-width="2"
|
||||
stroke-dasharray="4"
|
||||
class="scanning-ray"
|
||||
>
|
||||
<animate attributeName="stroke-dashoffset" values="20;0" dur="1s" repeatCount="indefinite" />
|
||||
</line>
|
||||
</g>
|
||||
<!-- Scanning rays from last node to all others -->
|
||||
<line
|
||||
v-for="(node, idx) in circleNodes.slice(
|
||||
0,
|
||||
circleNodes.length - 1
|
||||
)"
|
||||
:key="'ray' + idx"
|
||||
:x1="circleNodes[circleNodes.length - 1].x"
|
||||
:y1="circleNodes[circleNodes.length - 1].y"
|
||||
:x2="node.x"
|
||||
:y2="node.y"
|
||||
stroke="var(--vp-c-brand)"
|
||||
stroke-width="2"
|
||||
stroke-dasharray="4"
|
||||
class="scanning-ray"
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
values="20;0"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</line>
|
||||
</g>
|
||||
|
||||
<!-- Background Mesh -->
|
||||
<g class="connections">
|
||||
<line
|
||||
v-for="(link, idx) in meshLinks"
|
||||
:key="idx"
|
||||
:x1="link.x1" :y1="link.y1"
|
||||
:x2="link.x2" :y2="link.y2"
|
||||
class="connection-line"
|
||||
:style="{ animationDelay: idx * 0.05 + 's' }"
|
||||
/>
|
||||
</g>
|
||||
<!-- Draw Nodes -->
|
||||
<circle
|
||||
v-for="(node, idx) in circleNodes"
|
||||
:key="idx"
|
||||
:cx="node.x" :cy="node.y"
|
||||
r="12"
|
||||
class="node-circle standard"
|
||||
:class="{ 'current-node': idx === circleNodes.length - 1 }"
|
||||
/>
|
||||
<text
|
||||
v-for="(node, idx) in circleNodes"
|
||||
:key="'t'+idx"
|
||||
:x="node.x" :y="node.y"
|
||||
dy="4"
|
||||
text-anchor="middle"
|
||||
class="node-text"
|
||||
>{{ idx + 1 }}</text>
|
||||
<!-- Background Mesh -->
|
||||
<g class="connections">
|
||||
<line
|
||||
v-for="(link, idx) in meshLinks"
|
||||
:key="idx"
|
||||
:x1="link.x1"
|
||||
:y1="link.y1"
|
||||
:x2="link.x2"
|
||||
:y2="link.y2"
|
||||
class="connection-line"
|
||||
:style="{ animationDelay: idx * 0.05 + 's' }"
|
||||
/>
|
||||
</g>
|
||||
<!-- Draw Nodes -->
|
||||
<circle
|
||||
v-for="(node, idx) in circleNodes"
|
||||
:key="idx"
|
||||
:cx="node.x"
|
||||
:cy="node.y"
|
||||
r="12"
|
||||
class="node-circle standard"
|
||||
:class="{ 'current-node': idx === circleNodes.length - 1 }"
|
||||
/>
|
||||
<text
|
||||
v-for="(node, idx) in circleNodes"
|
||||
:key="'t' + idx"
|
||||
:x="node.x"
|
||||
:y="node.y"
|
||||
dy="4"
|
||||
text-anchor="middle"
|
||||
class="node-text"
|
||||
>
|
||||
{{ idx + 1 }}
|
||||
</text>
|
||||
</g>
|
||||
|
||||
<!-- LINEAR MODE: Relay / Chain -->
|
||||
<g v-else>
|
||||
<!-- Relay Path -->
|
||||
<line
|
||||
x1="40" y1="150"
|
||||
:x2="40 + (nValue - 1) * 60" y2="150"
|
||||
class="relay-track"
|
||||
/>
|
||||
<!-- Relay Path -->
|
||||
<line
|
||||
x1="40"
|
||||
y1="150"
|
||||
:x2="40 + (nValue - 1) * 60"
|
||||
y2="150"
|
||||
class="relay-track"
|
||||
/>
|
||||
|
||||
<!-- Passing Message Animation -->
|
||||
<circle
|
||||
cx="0" cy="0" r="8"
|
||||
class="message-token"
|
||||
>
|
||||
<animateMotion
|
||||
:path="relayPath"
|
||||
dur="2s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</circle>
|
||||
<!-- Passing Message Animation -->
|
||||
<circle cx="0" cy="0" r="8" class="message-token">
|
||||
<animateMotion
|
||||
:path="relayPath"
|
||||
dur="2s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</circle>
|
||||
|
||||
<!-- Nodes -->
|
||||
<g v-for="(node, idx) in linearNodes" :key="idx">
|
||||
<circle
|
||||
:cx="node.x" :cy="node.y"
|
||||
r="12"
|
||||
class="node-circle linear"
|
||||
/>
|
||||
<text
|
||||
:x="node.x" :y="node.y"
|
||||
dy="4"
|
||||
text-anchor="middle"
|
||||
class="node-text"
|
||||
>{{ idx + 1 }}</text>
|
||||
<!-- State Box (Memory) -->
|
||||
<rect
|
||||
:x="node.x - 15" :y="node.y + 20"
|
||||
width="30" height="20"
|
||||
rx="4"
|
||||
class="memory-box"
|
||||
/>
|
||||
<text :x="node.x" :y="node.y + 34" text-anchor="middle" font-size="8" fill="white">Mem</text>
|
||||
</g>
|
||||
<!-- Nodes -->
|
||||
<g v-for="(node, idx) in linearNodes" :key="idx">
|
||||
<circle
|
||||
:cx="node.x"
|
||||
:cy="node.y"
|
||||
r="12"
|
||||
class="node-circle linear"
|
||||
/>
|
||||
<text
|
||||
:x="node.x"
|
||||
:y="node.y"
|
||||
dy="4"
|
||||
text-anchor="middle"
|
||||
class="node-text"
|
||||
>
|
||||
{{ idx + 1 }}
|
||||
</text>
|
||||
<!-- State Box (Memory) -->
|
||||
<rect
|
||||
:x="node.x - 15"
|
||||
:y="node.y + 20"
|
||||
width="30"
|
||||
height="20"
|
||||
rx="4"
|
||||
class="memory-box"
|
||||
/>
|
||||
<text
|
||||
:x="node.x"
|
||||
:y="node.y + 34"
|
||||
text-anchor="middle"
|
||||
font-size="8"
|
||||
fill="white"
|
||||
>
|
||||
Mem
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -139,16 +180,21 @@
|
||||
<div class="stats-panel">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">连接/操作次数</div>
|
||||
<div class="stat-value" :class="mode === 'standard' ? 'text-red' : 'text-green'">
|
||||
<div
|
||||
class="stat-value"
|
||||
:class="mode === 'standard' ? 'text-red' : 'text-green'"
|
||||
>
|
||||
{{ connectionCount }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-desc">
|
||||
<span v-if="mode === 'standard'">
|
||||
每个人都要找其他人。<br>N={{ nValue }} 时,连接数高达 {{ nValue * nValue }}!
|
||||
每个人都要找其他人。<br />N={{ nValue }} 时,连接数高达
|
||||
{{ nValue * nValue }}!
|
||||
</span>
|
||||
<span v-else>
|
||||
每个人只传给下一个人。<br>N={{ nValue }} 时,操作数仅为 {{ nValue }}。
|
||||
每个人只传给下一个人。<br />N={{ nValue }} 时,操作数仅为
|
||||
{{ nValue }}。
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,13 +204,13 @@
|
||||
<div class="analogy-title">💡 核心区别:要不要回头看?</div>
|
||||
<div v-if="mode === 'standard'">
|
||||
<b>回看模式 (Retrospective)</b>:
|
||||
<br>想象你在考试。每做一道新题,你都要<b>把之前做过的所有题目再检查一遍</b>,确认有没有关联。
|
||||
<br>题目越多,你需要检查的次数就越多,最后累死在检查上。
|
||||
<br />想象你在考试。每做一道新题,你都要<b>把之前做过的所有题目再检查一遍</b>,确认有没有关联。
|
||||
<br />题目越多,你需要检查的次数就越多,最后累死在检查上。
|
||||
</div>
|
||||
<div v-else>
|
||||
<b>状态模式 (Recurrent)</b>:
|
||||
<br>想象你在跑步。你不需要记得前 100 步每一步踩在哪,你只需要知道<b>现在的速度和位置</b>(State)。
|
||||
<br>跑第 1000 步和跑第 1 步一样轻松,因为你不需要回头。
|
||||
<b>状态模式 (Recurrent)</b>: <br />想象你在跑步。你不需要记得前 100
|
||||
步每一步踩在哪,你只需要知道<b>现在的速度和位置</b>(State)。
|
||||
<br />跑第 1000 步和跑第 1 步一样轻松,因为你不需要回头。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -229,9 +275,9 @@ const linearNodes = computed(() => {
|
||||
// SVG Path for animation in Linear Mode
|
||||
const relayPath = computed(() => {
|
||||
const nodes = linearNodes.value
|
||||
if (nodes.length < 2) return ""
|
||||
if (nodes.length < 2) return ''
|
||||
// Start from first node, go to last node
|
||||
return `M ${nodes[0].x} ${nodes[0].y} L ${nodes[nodes.length-1].x} ${nodes[nodes.length-1].y}`
|
||||
return `M ${nodes[0].x} ${nodes[0].y} L ${nodes[nodes.length - 1].x} ${nodes[nodes.length - 1].y}`
|
||||
})
|
||||
|
||||
const connectionCount = computed(() => {
|
||||
@@ -341,7 +387,9 @@ const connectionCount = computed(() => {
|
||||
}
|
||||
|
||||
@keyframes fadeInLine {
|
||||
to { opacity: 0.3; }
|
||||
to {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
.relay-track {
|
||||
@@ -371,8 +419,12 @@ const connectionCount = computed(() => {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.text-red { color: var(--vp-c-red); }
|
||||
.text-green { color: var(--vp-c-green); }
|
||||
.text-red {
|
||||
color: var(--vp-c-red);
|
||||
}
|
||||
.text-green {
|
||||
color: var(--vp-c-green);
|
||||
}
|
||||
|
||||
.stat-desc {
|
||||
color: var(--vp-c-text-2);
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<div class="llm-quick-start">
|
||||
<div class="header">
|
||||
<div class="title">🤖 LLM 初体验:从闲聊到业务实战</div>
|
||||
<div class="subtitle">大模型不仅能聊天,更是生产力工具。试试看它如何处理这些业务需求:</div>
|
||||
<div class="subtitle">
|
||||
大模型不仅能聊天,更是生产力工具。试试看它如何处理这些业务需求:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-window">
|
||||
@@ -12,15 +14,30 @@
|
||||
</div>
|
||||
|
||||
<div class="messages" ref="messagesRef">
|
||||
<div v-for="(msg, index) in messages" :key="index" class="message" :class="msg.role">
|
||||
<div
|
||||
v-for="(msg, index) in messages"
|
||||
:key="index"
|
||||
class="message"
|
||||
:class="msg.role"
|
||||
>
|
||||
<div class="avatar">{{ msg.role === 'user' ? '🧑💻' : '🤖' }}</div>
|
||||
<div class="content">
|
||||
<div v-if="msg.role === 'user'" class="user-text">{{ msg.content }}</div>
|
||||
<div v-if="msg.role === 'user'" class="user-text">
|
||||
{{ msg.content }}
|
||||
</div>
|
||||
<div v-else class="assistant-content">
|
||||
<pre v-if="msg.isCode"><code>{{ msg.content }}</code></pre>
|
||||
<div v-else>{{ msg.content }}</div>
|
||||
</div>
|
||||
<span v-if="msg.role === 'assistant' && isGenerating && index === messages.length - 1" class="cursor">|</span>
|
||||
<span
|
||||
v-if="
|
||||
msg.role === 'assistant' &&
|
||||
isGenerating &&
|
||||
index === messages.length - 1
|
||||
"
|
||||
class="cursor"
|
||||
>|</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,14 +45,17 @@
|
||||
|
||||
<div class="input-area">
|
||||
<div class="quick-actions" v-if="!isGenerating">
|
||||
<button v-for="q in questions" :key="q.text" @click="ask(q)" class="action-btn">
|
||||
<button
|
||||
v-for="q in questions"
|
||||
:key="q.text"
|
||||
@click="ask(q)"
|
||||
class="action-btn"
|
||||
>
|
||||
<span class="btn-icon">{{ q.icon }}</span>
|
||||
<span class="btn-text">{{ q.text }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="status-text" v-else>
|
||||
正在思考业务逻辑并生成 Token...
|
||||
</div>
|
||||
<div class="status-text" v-else>正在思考业务逻辑并生成 Token...</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -52,11 +72,11 @@ const questions = [
|
||||
]
|
||||
|
||||
const answers = {
|
||||
"给我想一个请假的理由": {
|
||||
给我想一个请假的理由: {
|
||||
isCode: false,
|
||||
text: "老板,我感觉身体不适,可能是昨天写代码太投入,CPU(大脑)过热导致系统(身体)宕机了,申请重启(休息)一天。"
|
||||
text: '老板,我感觉身体不适,可能是昨天写代码太投入,CPU(大脑)过热导致系统(身体)宕机了,申请重启(休息)一天。'
|
||||
},
|
||||
"帮我写一个 Python 爬虫": {
|
||||
'帮我写一个 Python 爬虫': {
|
||||
isCode: true,
|
||||
text: `import requests
|
||||
from bs4 import BeautifulSoup
|
||||
@@ -75,17 +95,17 @@ print(f"正在爬取 {url} 的标题...")
|
||||
# titles = fetch_titles(url)
|
||||
# print(titles)`
|
||||
},
|
||||
"用鲁迅的语气夸我": {
|
||||
用鲁迅的语气夸我: {
|
||||
isCode: false,
|
||||
text: "我向来是不惮以最坏的恶意来推测中国人的,然而我还不料,也不信竟会遇见这样优秀的人。你的代码,很有几分风骨。"
|
||||
text: '我向来是不惮以最坏的恶意来推测中国人的,然而我还不料,也不信竟会遇见这样优秀的人。你的代码,很有几分风骨。'
|
||||
},
|
||||
"分析这份销售数据的趋势": {
|
||||
分析这份销售数据的趋势: {
|
||||
isCode: false,
|
||||
text: "基于您提供的数据,我发现以下几个关键趋势:\n\n1. 📈 **总体增长**:Q3 销售额同比增长了 25%,主要得益于线上渠道的爆发。\n2. ⚠️ **库存预警**:热销品类 A 的周转天数已降至 5 天,建议立即补货。\n3. 💡 **潜力市场**:华南地区的转化率(3.2%)显著高于平均水平,建议加大该区域的广告投放。"
|
||||
text: '基于您提供的数据,我发现以下几个关键趋势:\n\n1. 📈 **总体增长**:Q3 销售额同比增长了 25%,主要得益于线上渠道的爆发。\n2. ⚠️ **库存预警**:热销品类 A 的周转天数已降至 5 天,建议立即补货。\n3. 💡 **潜力市场**:华南地区的转化率(3.2%)显著高于平均水平,建议加大该区域的广告投放。'
|
||||
},
|
||||
"为这款咖啡杯写一段小红书文案": {
|
||||
为这款咖啡杯写一段小红书文案: {
|
||||
isCode: false,
|
||||
text: "☕️ **早八人的续命神器!这款咖啡杯真的太懂我了**\n\n家人们谁懂啊!😭 作为一个每天靠咖啡续命的打工人,终于挖到了这款宝藏杯子!\n\n✨ **颜值绝绝子**:奶油白配色,拿在手里就是妥妥的 ins 风,摆在工位上心情都变好了!\n🌡️ **保温超长待机**:早上泡的冰美式,下午还是冰冰凉,这也太适合夏天了吧!\n🔒 **密封不漏水**:直接塞包里也不怕洒,挤地铁必备!\n\n👇 评论区蹲一个链接,带你一起实现咖啡自由! #好物分享 #高颜值水杯 #打工人日常"
|
||||
text: '☕️ **早八人的续命神器!这款咖啡杯真的太懂我了**\n\n家人们谁懂啊!😭 作为一个每天靠咖啡续命的打工人,终于挖到了这款宝藏杯子!\n\n✨ **颜值绝绝子**:奶油白配色,拿在手里就是妥妥的 ins 风,摆在工位上心情都变好了!\n🌡️ **保温超长待机**:早上泡的冰美式,下午还是冰冰凉,这也太适合夏天了吧!\n🔒 **密封不漏水**:直接塞包里也不怕洒,挤地铁必备!\n\n👇 评论区蹲一个链接,带你一起实现咖啡自由! #好物分享 #高颜值水杯 #打工人日常'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +120,7 @@ const ask = async (qObj) => {
|
||||
await wait(600)
|
||||
|
||||
const answerData = answers[qObj.text]
|
||||
const fullAnswer = answerData ? answerData.text : "正在思考..."
|
||||
const fullAnswer = answerData ? answerData.text : '正在思考...'
|
||||
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
@@ -115,14 +135,14 @@ const ask = async (qObj) => {
|
||||
messages.value[answerIdx].content += fullAnswer[i]
|
||||
scrollToBottom()
|
||||
// Code typing is usually faster looking
|
||||
const speed = answerData.isCode ? 10 : (30 + Math.random() * 30)
|
||||
const speed = answerData.isCode ? 10 : 30 + Math.random() * 30
|
||||
await wait(speed)
|
||||
}
|
||||
|
||||
isGenerating.value = false
|
||||
}
|
||||
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
const scrollToBottom = () => {
|
||||
nextTick(() => {
|
||||
@@ -140,8 +160,9 @@ const scrollToBottom = () => {
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin: 24px 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -302,14 +323,14 @@ const scrollToBottom = () => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.status-text {
|
||||
@@ -329,7 +350,33 @@ const scrollToBottom = () => {
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes blink { 50% { opacity: 0; } }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes pulse { 0% { opacity: 0.4; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.1); } 100% { opacity: 0.4; transform: scale(0.8); } }
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.4;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,7 +13,11 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="mode-desc">
|
||||
{{ architecture === 'dense' ? '全能天才:每个问题都动用整个大脑 (100% 激活)' : '专家团队:根据问题指派专人处理 (稀疏激活)' }}
|
||||
{{
|
||||
architecture === 'dense'
|
||||
? '全能天才:每个问题都动用整个大脑 (100% 激活)'
|
||||
: '专家团队:根据问题指派专人处理 (稀疏激活)'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +39,10 @@
|
||||
<span class="task-text">{{ task.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="token-stream" :class="{ 'flowing': processing && currentStep >= 1 }">
|
||||
<div
|
||||
class="token-stream"
|
||||
:class="{ flowing: processing && currentStep >= 1 }"
|
||||
>
|
||||
<div class="token-particle">{{ selectedTask.icon }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -52,7 +59,10 @@
|
||||
|
||||
<!-- Dense Visualization -->
|
||||
<div v-if="architecture === 'dense'" class="dense-visualization">
|
||||
<div class="dense-block" :class="{ 'activating': processing && currentStep === 2 }">
|
||||
<div
|
||||
class="dense-block"
|
||||
:class="{ activating: processing && currentStep === 2 }"
|
||||
>
|
||||
<div class="dense-label">前馈神经网络 (FFN)</div>
|
||||
<div class="neuron-grid">
|
||||
<div v-for="n in 32" :key="n" class="neuron"></div>
|
||||
@@ -66,7 +76,10 @@
|
||||
<!-- MoE Visualization -->
|
||||
<div v-else class="moe-visualization">
|
||||
<!-- Router -->
|
||||
<div class="router-node" :class="{ 'active': processing && currentStep === 1 }">
|
||||
<div
|
||||
class="router-node"
|
||||
:class="{ active: processing && currentStep === 1 }"
|
||||
>
|
||||
<div class="router-label">门控路由 (Router)</div>
|
||||
<div class="router-action" v-if="processing && currentStep >= 1">
|
||||
🔍 识别意图: "{{ selectedTask.type }}"
|
||||
@@ -80,8 +93,9 @@
|
||||
:key="idx"
|
||||
class="connection-line"
|
||||
:class="{
|
||||
'active': processing && currentStep >= 2 && isExpertSelected(idx),
|
||||
'inactive': processing && currentStep >= 2 && !isExpertSelected(idx)
|
||||
active: processing && currentStep >= 2 && isExpertSelected(idx),
|
||||
inactive:
|
||||
processing && currentStep >= 2 && !isExpertSelected(idx)
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
@@ -93,14 +107,18 @@
|
||||
:key="idx"
|
||||
class="expert-card"
|
||||
:class="{
|
||||
'active': processing && currentStep >= 2 && isExpertSelected(idx),
|
||||
'inactive': processing && currentStep >= 2 && !isExpertSelected(idx)
|
||||
active: processing && currentStep >= 2 && isExpertSelected(idx),
|
||||
inactive:
|
||||
processing && currentStep >= 2 && !isExpertSelected(idx)
|
||||
}"
|
||||
>
|
||||
<div class="expert-icon">{{ expert.icon }}</div>
|
||||
<div class="expert-name">{{ expert.name }}</div>
|
||||
<div class="expert-role">{{ expert.role }}</div>
|
||||
<div class="expert-status" v-if="processing && currentStep >= 2 && isExpertSelected(idx)">
|
||||
<div
|
||||
class="expert-status"
|
||||
v-if="processing && currentStep >= 2 && isExpertSelected(idx)"
|
||||
>
|
||||
✅ 激活
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,7 +132,7 @@
|
||||
<!-- Step 3: Output -->
|
||||
<div class="stage-section output-section">
|
||||
<div class="section-label">3. 生成结果 (Output)</div>
|
||||
<div class="output-box" :class="{ 'revealed': currentStep === 3 }">
|
||||
<div class="output-box" :class="{ revealed: currentStep === 3 }">
|
||||
<div v-if="currentStep === 3" class="output-content">
|
||||
<span class="output-icon">{{ selectedTask.icon }}</span>
|
||||
<span class="typing-effect">{{ selectedTask.output }}</span>
|
||||
@@ -148,10 +166,34 @@ const experts = [
|
||||
]
|
||||
|
||||
const tasks = [
|
||||
{ label: '写 Python 脚本', type: '编程', icon: '🐍', expertIdx: 0, output: 'def fib(n): return n if n < 2 else...' },
|
||||
{ label: '写七言绝句', type: '文学', icon: '🌸', expertIdx: 1, output: '窗含西岭千秋雪,门泊东吴万里船...' },
|
||||
{ label: '解二元方程', type: '数学', icon: '✖️', expertIdx: 2, output: 'x = 5, y = -2 (过程略)' },
|
||||
{ label: '翻译成英文', type: '翻译', icon: '🔤', expertIdx: 3, output: 'To be, or not to be, that is the question.' }
|
||||
{
|
||||
label: '写 Python 脚本',
|
||||
type: '编程',
|
||||
icon: '🐍',
|
||||
expertIdx: 0,
|
||||
output: 'def fib(n): return n if n < 2 else...'
|
||||
},
|
||||
{
|
||||
label: '写七言绝句',
|
||||
type: '文学',
|
||||
icon: '🌸',
|
||||
expertIdx: 1,
|
||||
output: '窗含西岭千秋雪,门泊东吴万里船...'
|
||||
},
|
||||
{
|
||||
label: '解二元方程',
|
||||
type: '数学',
|
||||
icon: '✖️',
|
||||
expertIdx: 2,
|
||||
output: 'x = 5, y = -2 (过程略)'
|
||||
},
|
||||
{
|
||||
label: '翻译成英文',
|
||||
type: '翻译',
|
||||
icon: '🔤',
|
||||
expertIdx: 3,
|
||||
output: 'To be, or not to be, that is the question.'
|
||||
}
|
||||
]
|
||||
|
||||
const selectedTask = ref(tasks[0])
|
||||
@@ -199,19 +241,20 @@ const runDemo = async () => {
|
||||
processing.value = false
|
||||
}
|
||||
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.moe-demo-container {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
max-width: 600px;
|
||||
margin: 20px auto;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
@@ -243,7 +286,7 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
.mode-tab.active {
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-brand);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.mode-desc {
|
||||
@@ -466,7 +509,7 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
opacity: 1;
|
||||
border-color: var(--vp-c-brand);
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.expert-card.inactive {
|
||||
@@ -474,11 +517,25 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.expert-icon { font-size: 20px; margin-bottom: 4px; }
|
||||
.expert-name { font-size: 11px; font-weight: bold; margin-bottom: 2px; }
|
||||
.expert-role { font-size: 9px; color: var(--vp-c-text-3); }
|
||||
.expert-status { font-size: 9px; color: var(--vp-c-brand); margin-top: 4px; font-weight: bold; }
|
||||
|
||||
.expert-icon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.expert-name {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.expert-role {
|
||||
font-size: 9px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
.expert-status {
|
||||
font-size: 9px;
|
||||
color: var(--vp-c-brand);
|
||||
margin-top: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Output Section */
|
||||
.output-box {
|
||||
@@ -547,7 +604,12 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
v-for="(token, index) in tokenizedContext"
|
||||
:key="index"
|
||||
class="context-token"
|
||||
>{{ token }}</span>
|
||||
>{{ token }}</span
|
||||
>
|
||||
<span class="cursor"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,7 +55,9 @@
|
||||
>
|
||||
<div class="candidate-info">
|
||||
<span class="candidate-text">"{{ candidate.text }}"</span>
|
||||
<span class="candidate-prob">{{ (candidate.prob * 100).toFixed(1) }}%</span>
|
||||
<span class="candidate-prob"
|
||||
>{{ (candidate.prob * 100).toFixed(1) }}%</span
|
||||
>
|
||||
</div>
|
||||
<div class="prob-bar-bg">
|
||||
<div
|
||||
@@ -69,7 +72,9 @@
|
||||
|
||||
<div class="explanation">
|
||||
<p>
|
||||
<strong>原理:</strong> LLM 并不是一次性写出整段话,而是像上面这样,基于前面的内容(Context),计算下一个最可能出现的 Token 的概率,然后选择一个(Sampling)填上去,再重复这个过程。
|
||||
<strong>原理:</strong> LLM
|
||||
并不是一次性写出整段话,而是像上面这样,基于前面的内容(Context),计算下一个最可能出现的
|
||||
Token 的概率,然后选择一个(Sampling)填上去,再重复这个过程。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -82,38 +87,44 @@ const scenes = {
|
||||
'en-fox': {
|
||||
initial: 'The quick brown',
|
||||
logic: (text) => {
|
||||
if (text.endsWith('brown')) return [
|
||||
{ text: ' fox', prob: 0.85 },
|
||||
{ text: ' dog', prob: 0.10 },
|
||||
{ text: ' cat', prob: 0.05 }
|
||||
]
|
||||
if (text.endsWith('fox')) return [
|
||||
{ text: ' jumps', prob: 0.92 },
|
||||
{ text: ' runs', prob: 0.05 },
|
||||
{ text: ' sleeps', prob: 0.03 }
|
||||
]
|
||||
if (text.endsWith('jumps')) return [
|
||||
{ text: ' over', prob: 0.98 },
|
||||
{ text: ' up', prob: 0.01 },
|
||||
{ text: ' down', prob: 0.01 }
|
||||
]
|
||||
if (text.endsWith('over')) return [
|
||||
{ text: ' the', prob: 0.95 },
|
||||
{ text: ' a', prob: 0.04 },
|
||||
{ text: ' my', prob: 0.01 }
|
||||
]
|
||||
if (text.endsWith('the')) return [
|
||||
{ text: ' lazy', prob: 0.88 },
|
||||
{ text: ' big', prob: 0.08 },
|
||||
{ text: ' old', prob: 0.04 }
|
||||
]
|
||||
if (text.endsWith('lazy')) return [
|
||||
{ text: ' dog', prob: 0.90 },
|
||||
{ text: ' cat', prob: 0.08 },
|
||||
{ text: ' fox', prob: 0.02 }
|
||||
]
|
||||
if (text.endsWith('brown'))
|
||||
return [
|
||||
{ text: ' fox', prob: 0.85 },
|
||||
{ text: ' dog', prob: 0.1 },
|
||||
{ text: ' cat', prob: 0.05 }
|
||||
]
|
||||
if (text.endsWith('fox'))
|
||||
return [
|
||||
{ text: ' jumps', prob: 0.92 },
|
||||
{ text: ' runs', prob: 0.05 },
|
||||
{ text: ' sleeps', prob: 0.03 }
|
||||
]
|
||||
if (text.endsWith('jumps'))
|
||||
return [
|
||||
{ text: ' over', prob: 0.98 },
|
||||
{ text: ' up', prob: 0.01 },
|
||||
{ text: ' down', prob: 0.01 }
|
||||
]
|
||||
if (text.endsWith('over'))
|
||||
return [
|
||||
{ text: ' the', prob: 0.95 },
|
||||
{ text: ' a', prob: 0.04 },
|
||||
{ text: ' my', prob: 0.01 }
|
||||
]
|
||||
if (text.endsWith('the'))
|
||||
return [
|
||||
{ text: ' lazy', prob: 0.88 },
|
||||
{ text: ' big', prob: 0.08 },
|
||||
{ text: ' old', prob: 0.04 }
|
||||
]
|
||||
if (text.endsWith('lazy'))
|
||||
return [
|
||||
{ text: ' dog', prob: 0.9 },
|
||||
{ text: ' cat', prob: 0.08 },
|
||||
{ text: ' fox', prob: 0.02 }
|
||||
]
|
||||
return [
|
||||
{ text: '.', prob: 0.80 },
|
||||
{ text: '.', prob: 0.8 },
|
||||
{ text: ' and', prob: 0.15 },
|
||||
{ text: '!', prob: 0.05 }
|
||||
]
|
||||
@@ -122,53 +133,60 @@ const scenes = {
|
||||
'zh-ai': {
|
||||
initial: '人工智能',
|
||||
logic: (text) => {
|
||||
if (text.endsWith('人工智能')) return [
|
||||
{ text: '是', prob: 0.75 },
|
||||
{ text: '技术', prob: 0.15 },
|
||||
{ text: '发展', prob: 0.10 }
|
||||
]
|
||||
if (text.endsWith('是')) return [
|
||||
{ text: '未来', prob: 0.40 },
|
||||
{ text: '一种', prob: 0.35 },
|
||||
{ text: '什么', prob: 0.25 }
|
||||
]
|
||||
if (text.endsWith('一种')) return [
|
||||
{ text: '技术', prob: 0.55 },
|
||||
{ text: '工具', prob: 0.30 },
|
||||
{ text: '科学', prob: 0.15 }
|
||||
]
|
||||
if (text.endsWith('未来')) return [
|
||||
{ text: '的', prob: 0.85 },
|
||||
{ text: '方向', prob: 0.10 },
|
||||
{ text: '趋势', prob: 0.05 }
|
||||
]
|
||||
if (text.endsWith('人工智能'))
|
||||
return [
|
||||
{ text: '是', prob: 0.75 },
|
||||
{ text: '技术', prob: 0.15 },
|
||||
{ text: '发展', prob: 0.1 }
|
||||
]
|
||||
if (text.endsWith('是'))
|
||||
return [
|
||||
{ text: '未来', prob: 0.4 },
|
||||
{ text: '一种', prob: 0.35 },
|
||||
{ text: '什么', prob: 0.25 }
|
||||
]
|
||||
if (text.endsWith('一种'))
|
||||
return [
|
||||
{ text: '技术', prob: 0.55 },
|
||||
{ text: '工具', prob: 0.3 },
|
||||
{ text: '科学', prob: 0.15 }
|
||||
]
|
||||
if (text.endsWith('未来'))
|
||||
return [
|
||||
{ text: '的', prob: 0.85 },
|
||||
{ text: '方向', prob: 0.1 },
|
||||
{ text: '趋势', prob: 0.05 }
|
||||
]
|
||||
return [
|
||||
{ text: '。', prob: 0.60 },
|
||||
{ text: ',', prob: 0.30 },
|
||||
{ text: '!', prob: 0.10 }
|
||||
{ text: '。', prob: 0.6 },
|
||||
{ text: ',', prob: 0.3 },
|
||||
{ text: '!', prob: 0.1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
'code': {
|
||||
code: {
|
||||
initial: 'if (x > 0) {',
|
||||
logic: (text) => {
|
||||
if (text.endsWith('{')) return [
|
||||
{ text: '\n return', prob: 0.60 },
|
||||
{ text: '\n print', prob: 0.30 },
|
||||
{ text: '\n x', prob: 0.10 }
|
||||
]
|
||||
if (text.includes('return')) return [
|
||||
{ text: ' true', prob: 0.50 },
|
||||
{ text: ' x', prob: 0.30 },
|
||||
{ text: ' false', prob: 0.20 }
|
||||
]
|
||||
if (text.includes('print')) return [
|
||||
{ text: '("Hello")', prob: 0.70 },
|
||||
{ text: '(x)', prob: 0.25 },
|
||||
{ text: '()', prob: 0.05 }
|
||||
]
|
||||
if (text.endsWith('{'))
|
||||
return [
|
||||
{ text: '\n return', prob: 0.6 },
|
||||
{ text: '\n print', prob: 0.3 },
|
||||
{ text: '\n x', prob: 0.1 }
|
||||
]
|
||||
if (text.includes('return'))
|
||||
return [
|
||||
{ text: ' true', prob: 0.5 },
|
||||
{ text: ' x', prob: 0.3 },
|
||||
{ text: ' false', prob: 0.2 }
|
||||
]
|
||||
if (text.includes('print'))
|
||||
return [
|
||||
{ text: '("Hello")', prob: 0.7 },
|
||||
{ text: '(x)', prob: 0.25 },
|
||||
{ text: '()', prob: 0.05 }
|
||||
]
|
||||
return [
|
||||
{ text: ';', prob: 0.90 },
|
||||
{ text: ';', prob: 0.9 },
|
||||
{ text: ' + 1', prob: 0.08 },
|
||||
{ text: '.', prob: 0.02 }
|
||||
]
|
||||
@@ -281,8 +299,13 @@ select {
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.prediction-panel {
|
||||
@@ -344,9 +367,15 @@ select {
|
||||
transition: width 0.5s ease-out;
|
||||
}
|
||||
|
||||
.rank-0 { background-color: #10b981; }
|
||||
.rank-1 { background-color: #3b82f6; }
|
||||
.rank-2 { background-color: #f59e0b; }
|
||||
.rank-0 {
|
||||
background-color: #10b981;
|
||||
}
|
||||
.rank-1 {
|
||||
background-color: #3b82f6;
|
||||
}
|
||||
.rank-2 {
|
||||
background-color: #f59e0b;
|
||||
}
|
||||
|
||||
.explanation {
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
@@ -16,10 +16,7 @@
|
||||
<template>
|
||||
<div class="arch-demo">
|
||||
<div class="control-tabs">
|
||||
<button
|
||||
:class="{ active: mode === 'rnn' }"
|
||||
@click="mode = 'rnn'"
|
||||
>
|
||||
<button :class="{ active: mode === 'rnn' }" @click="mode = 'rnn'">
|
||||
🐌 RNN (Sequential)
|
||||
</button>
|
||||
<button
|
||||
@@ -52,7 +49,10 @@
|
||||
>
|
||||
<div class="memory-content">
|
||||
Memory (h)
|
||||
<div class="memory-level" :style="{ height: rnnMemoryStrength + '%' }"></div>
|
||||
<div
|
||||
class="memory-level"
|
||||
:style="{ height: rnnMemoryStrength + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="arrow-right">→</div>
|
||||
@@ -65,7 +65,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="desc-text">
|
||||
RNN 从左到右逐个读取。注意看 Memory(记忆),随着句子变长,最早的信息("The")可能会被后面的信息冲淡,这就是“长距离依赖”问题。
|
||||
RNN 从左到右逐个读取。注意看
|
||||
Memory(记忆),随着句子变长,最早的信息("The")可能会被后面的信息冲淡,这就是“长距离依赖”问题。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -77,8 +78,8 @@
|
||||
:key="idx"
|
||||
class="t-word"
|
||||
:class="{
|
||||
'hovered': hoveredWordIndex === idx,
|
||||
'attended': getAttentionScore(hoveredWordIndex, idx) > 0
|
||||
hovered: hoveredWordIndex === idx,
|
||||
attended: getAttentionScore(hoveredWordIndex, idx) > 0
|
||||
}"
|
||||
@mouseenter="hoveredWordIndex = idx"
|
||||
@mouseleave="hoveredWordIndex = -1"
|
||||
@@ -92,13 +93,16 @@
|
||||
|
||||
<div class="attention-info" v-if="hoveredWordIndex !== -1">
|
||||
<p>
|
||||
Current Focus: <strong>"{{ transformerWords[hoveredWordIndex] }}"</strong>
|
||||
Current Focus:
|
||||
<strong>"{{ transformerWords[hoveredWordIndex] }}"</strong>
|
||||
</p>
|
||||
<p class="sub-info">
|
||||
Paying attention to:
|
||||
<span v-for="(attn, idx) in currentAttentions" :key="idx">
|
||||
<span v-if="attn.score > 0.01">
|
||||
"{{ transformerWords[attn.idx] }}" ({{ Math.round(attn.score * 100) }}%)
|
||||
"{{ transformerWords[attn.idx] }}" ({{
|
||||
Math.round(attn.score * 100)
|
||||
}}%)
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
@@ -108,8 +112,10 @@
|
||||
</div>
|
||||
|
||||
<p class="desc-text">
|
||||
Transformer 一眼看完整个句子(并行)。Self-Attention 机制让每个词都能直接“看见”其他词,无论距离多远。
|
||||
<br>例如:悬停在 <strong>"it"</strong> 上,你会发现它强烈关注 <strong>"animal"</strong>,因为它指代的就是 animal。
|
||||
Transformer 一眼看完整个句子(并行)。Self-Attention
|
||||
机制让每个词都能直接“看见”其他词,无论距离多远。
|
||||
<br />例如:悬停在 <strong>"it"</strong> 上,你会发现它强烈关注
|
||||
<strong>"animal"</strong>,因为它指代的就是 animal。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,7 +128,17 @@ import { ref, computed } from 'vue'
|
||||
const mode = ref('rnn')
|
||||
|
||||
// RNN Data
|
||||
const rnnWords = ['The', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog']
|
||||
const rnnWords = [
|
||||
'The',
|
||||
'quick',
|
||||
'brown',
|
||||
'fox',
|
||||
'jumps',
|
||||
'over',
|
||||
'the',
|
||||
'lazy',
|
||||
'dog'
|
||||
]
|
||||
const currentRnnStep = ref(-1)
|
||||
const isPlayingRnn = ref(false)
|
||||
const rnnMemoryOpacity = ref(0.3)
|
||||
@@ -141,7 +157,7 @@ const playRnn = async () => {
|
||||
rnnMemoryStrength.value = Math.min(100, rnnMemoryStrength.value * 0.8 + 30)
|
||||
rnnMemoryOpacity.value = 0.5 + (i / rnnWords.length) * 0.5
|
||||
rnnOutput.value = `h${i}`
|
||||
await new Promise(r => setTimeout(r, 800))
|
||||
await new Promise((r) => setTimeout(r, 800))
|
||||
}
|
||||
|
||||
isPlayingRnn.value = false
|
||||
@@ -149,22 +165,38 @@ const playRnn = async () => {
|
||||
}
|
||||
|
||||
// Transformer Data
|
||||
const transformerWords = ['The', 'animal', 'didn\'t', 'cross', 'the', 'street', 'because', 'it', 'was', 'too', 'tired', '.']
|
||||
const transformerWords = [
|
||||
'The',
|
||||
'animal',
|
||||
"didn't",
|
||||
'cross',
|
||||
'the',
|
||||
'street',
|
||||
'because',
|
||||
'it',
|
||||
'was',
|
||||
'too',
|
||||
'tired',
|
||||
'.'
|
||||
]
|
||||
|
||||
// Pre-defined attention matrix (simplified for demo)
|
||||
// Source -> Targets (scores)
|
||||
const attentionMap = {
|
||||
7: { // "it"
|
||||
7: {
|
||||
// "it"
|
||||
1: 0.8, // animal
|
||||
5: 0.1, // street
|
||||
7: 1.0 // itself
|
||||
7: 1.0 // itself
|
||||
},
|
||||
10: { // "tired"
|
||||
10: {
|
||||
// "tired"
|
||||
1: 0.6, // animal
|
||||
7: 0.9, // it
|
||||
10: 1.0
|
||||
},
|
||||
3: { // "cross"
|
||||
3: {
|
||||
// "cross"
|
||||
1: 0.5, // animal
|
||||
5: 0.5, // street
|
||||
3: 1.0
|
||||
@@ -177,16 +209,18 @@ const currentAttentions = computed(() => {
|
||||
if (hoveredWordIndex.value === -1) return []
|
||||
const map = attentionMap[hoveredWordIndex.value] || {}
|
||||
|
||||
return transformerWords.map((_, idx) => {
|
||||
let score = map[idx]
|
||||
if (score === undefined) {
|
||||
// Default behavior if not in map: attend to self strongly, neighbors weakly
|
||||
if (idx === hoveredWordIndex.value) score = 1.0
|
||||
else if (Math.abs(idx - hoveredWordIndex.value) === 1) score = 0.1
|
||||
else score = 0.0
|
||||
}
|
||||
return { idx, score }
|
||||
}).sort((a, b) => b.score - a.score)
|
||||
return transformerWords
|
||||
.map((_, idx) => {
|
||||
let score = map[idx]
|
||||
if (score === undefined) {
|
||||
// Default behavior if not in map: attend to self strongly, neighbors weakly
|
||||
if (idx === hoveredWordIndex.value) score = 1.0
|
||||
else if (Math.abs(idx - hoveredWordIndex.value) === 1) score = 0.1
|
||||
else score = 0.0
|
||||
}
|
||||
return { idx, score }
|
||||
})
|
||||
.sort((a, b) => b.score - a.score)
|
||||
})
|
||||
|
||||
const getAttentionScore = (sourceIdx, targetIdx) => {
|
||||
@@ -196,10 +230,10 @@ const getAttentionScore = (sourceIdx, targetIdx) => {
|
||||
if (map) {
|
||||
return map[targetIdx] || 0
|
||||
} else {
|
||||
// Default behavior if not in map
|
||||
if (sourceIdx === targetIdx) return 1.0
|
||||
if (Math.abs(sourceIdx - targetIdx) === 1) return 0.1
|
||||
return 0
|
||||
// Default behavior if not in map
|
||||
if (sourceIdx === targetIdx) return 1.0
|
||||
if (Math.abs(sourceIdx - targetIdx) === 1) return 0.1
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
<template>
|
||||
<div class="thinking-demo">
|
||||
<div class="mode-switch">
|
||||
<button
|
||||
:class="{ active: mode === 'fast' }"
|
||||
@click="switchMode('fast')"
|
||||
>
|
||||
<button :class="{ active: mode === 'fast' }" @click="switchMode('fast')">
|
||||
⚡️ 传统快思考 (System 1)
|
||||
</button>
|
||||
<button
|
||||
:class="{ active: mode === 'slow' }"
|
||||
@click="switchMode('slow')"
|
||||
>
|
||||
<button :class="{ active: mode === 'slow' }" @click="switchMode('slow')">
|
||||
🧠 深度慢思考 (System 2)
|
||||
</button>
|
||||
</div>
|
||||
@@ -104,7 +98,7 @@ const isRunning = ref(false)
|
||||
const completed = ref(false)
|
||||
|
||||
// Fast Mode Data
|
||||
const fastOutput = "9.11 比 9.9 大。"
|
||||
const fastOutput = '9.11 比 9.9 大。'
|
||||
const displayedOutput = ref('')
|
||||
|
||||
// Slow Mode Data
|
||||
@@ -115,7 +109,7 @@ const slowThoughts = `首先比较整数部分,都是9,相等。
|
||||
比较第一位小数:1 < 9。
|
||||
所以 0.11 小于 0.9。
|
||||
结论:9.11 小于 9.9。`
|
||||
const slowOutput = "9.11 比 9.9 小。"
|
||||
const slowOutput = '9.11 比 9.9 小。'
|
||||
|
||||
const displayedThoughts = ref('')
|
||||
const generating = ref(false)
|
||||
@@ -146,7 +140,7 @@ const reset = () => {
|
||||
const typeText = async (text, targetRef, speed = 30) => {
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
targetRef.value += text[i]
|
||||
await new Promise(r => setTimeout(r, speed))
|
||||
await new Promise((r) => setTimeout(r, speed))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +159,7 @@ const runSimulation = async () => {
|
||||
await typeText(slowThoughts, displayedThoughts, 20)
|
||||
generatingThoughts.value = false
|
||||
|
||||
await new Promise(r => setTimeout(r, 500)) // Pause
|
||||
await new Promise((r) => setTimeout(r, 500)) // Pause
|
||||
|
||||
// Final answer phase
|
||||
showFinalAnswer.value = true
|
||||
@@ -237,7 +231,8 @@ const toggleThoughts = () => {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.fast-track, .slow-track {
|
||||
.fast-track,
|
||||
.slow-track {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
@@ -361,6 +356,10 @@ const toggleThoughts = () => {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.bad { color: var(--vp-c-red); }
|
||||
.good { color: var(--vp-c-green); }
|
||||
.bad {
|
||||
color: var(--vp-c-red);
|
||||
}
|
||||
.good {
|
||||
color: var(--vp-c-green);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,16 +27,25 @@
|
||||
<div class="settings-group">
|
||||
<label>Algorithm / 算法</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-option" :class="{ active: algorithm === 'bpe' }">
|
||||
<input type="radio" v-model="algorithm" value="bpe">
|
||||
<label
|
||||
class="radio-option"
|
||||
:class="{ active: algorithm === 'bpe' }"
|
||||
>
|
||||
<input type="radio" v-model="algorithm" value="bpe" />
|
||||
<span>BPE (GPT-4)</span>
|
||||
</label>
|
||||
<label class="radio-option" :class="{ active: algorithm === 'word' }">
|
||||
<input type="radio" v-model="algorithm" value="word">
|
||||
<label
|
||||
class="radio-option"
|
||||
:class="{ active: algorithm === 'word' }"
|
||||
>
|
||||
<input type="radio" v-model="algorithm" value="word" />
|
||||
<span>Word (Legacy)</span>
|
||||
</label>
|
||||
<label class="radio-option" :class="{ active: algorithm === 'char' }">
|
||||
<input type="radio" v-model="algorithm" value="char">
|
||||
<label
|
||||
class="radio-option"
|
||||
:class="{ active: algorithm === 'char' }"
|
||||
>
|
||||
<input type="radio" v-model="algorithm" value="char" />
|
||||
<span>Character (Raw)</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -56,9 +65,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Tokenizer Process Visualization -->
|
||||
<div class="tokenizer-arrow">
|
||||
⬇
|
||||
</div>
|
||||
<div class="tokenizer-arrow">⬇</div>
|
||||
|
||||
<div class="visualization-area">
|
||||
<div class="token-list">
|
||||
@@ -73,7 +80,7 @@
|
||||
<span class="token-text">{{ token.text }}</span>
|
||||
<span class="token-id">{{ token.id }}</span>
|
||||
<div class="tooltip" v-if="hoverIndex === index">
|
||||
ID: {{ token.id }}<br>
|
||||
ID: {{ token.id }}<br />
|
||||
Type: {{ token.type }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,9 +91,9 @@
|
||||
<p>
|
||||
<span class="icon">💡</span>
|
||||
<strong>Note:</strong>
|
||||
LLM 不直接理解单词,它们处理的是数字(Token IDs)。
|
||||
对于英文,一个 Token 通常是一个单词或单词的一部分(如 "ing");
|
||||
对于中文,一个 Token 通常是一个汉字或词组。
|
||||
LLM 不直接理解单词,它们处理的是数字(Token IDs)。 对于英文,一个 Token
|
||||
通常是一个单词或单词的一部分(如 "ing"); 对于中文,一个 Token
|
||||
通常是一个汉字或词组。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,7 +102,9 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const inputText = ref('The quick brown fox jumps over the lazy dog. \n今天天气真不错!')
|
||||
const inputText = ref(
|
||||
'The quick brown fox jumps over the lazy dog. \n今天天气真不错!'
|
||||
)
|
||||
const hoverIndex = ref(-1)
|
||||
const algorithm = ref('bpe')
|
||||
|
||||
@@ -134,7 +143,7 @@ const tokens = computed(() => {
|
||||
// 2. Word-based Simulation
|
||||
// 简单按空格拆分,标点符号也可能粘连
|
||||
const words = text.split(/(\s+)/)
|
||||
words.forEach(w => {
|
||||
words.forEach((w) => {
|
||||
if (w) {
|
||||
let type = /^\s+$/.test(w) ? 'whitespace' : 'word'
|
||||
result.push({ text: w, id: generateId(w), type })
|
||||
@@ -309,7 +318,7 @@ textarea:focus {
|
||||
.token-chip:hover {
|
||||
transform: scale(1.05);
|
||||
z-index: 10;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.token-text {
|
||||
@@ -325,11 +334,26 @@ textarea:focus {
|
||||
}
|
||||
|
||||
/* Color palette for tokens */
|
||||
.color-0 { background-color: rgba(255, 99, 132, 0.2); border: 1px solid rgba(255, 99, 132, 0.3); }
|
||||
.color-1 { background-color: rgba(54, 162, 235, 0.2); border: 1px solid rgba(54, 162, 235, 0.3); }
|
||||
.color-2 { background-color: rgba(255, 206, 86, 0.2); border: 1px solid rgba(255, 206, 86, 0.3); }
|
||||
.color-3 { background-color: rgba(75, 192, 192, 0.2); border: 1px solid rgba(75, 192, 192, 0.3); }
|
||||
.color-4 { background-color: rgba(153, 102, 255, 0.2); border: 1px solid rgba(153, 102, 255, 0.3); }
|
||||
.color-0 {
|
||||
background-color: rgba(255, 99, 132, 0.2);
|
||||
border: 1px solid rgba(255, 99, 132, 0.3);
|
||||
}
|
||||
.color-1 {
|
||||
background-color: rgba(54, 162, 235, 0.2);
|
||||
border: 1px solid rgba(54, 162, 235, 0.3);
|
||||
}
|
||||
.color-2 {
|
||||
background-color: rgba(255, 206, 86, 0.2);
|
||||
border: 1px solid rgba(255, 206, 86, 0.3);
|
||||
}
|
||||
.color-3 {
|
||||
background-color: rgba(75, 192, 192, 0.2);
|
||||
border: 1px solid rgba(75, 192, 192, 0.3);
|
||||
}
|
||||
.color-4 {
|
||||
background-color: rgba(153, 102, 255, 0.2);
|
||||
border: 1px solid rgba(153, 102, 255, 0.3);
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
|
||||
@@ -29,9 +29,7 @@
|
||||
>
|
||||
← 上一步
|
||||
</button>
|
||||
<div class="step-indicator">
|
||||
Step {{ currentStep + 1 }} / 4
|
||||
</div>
|
||||
<div class="step-indicator">Step {{ currentStep + 1 }} / 4</div>
|
||||
<button
|
||||
class="step-btn next"
|
||||
:disabled="currentStep === 3"
|
||||
@@ -46,7 +44,9 @@
|
||||
<!-- Step 1: Tokenization -->
|
||||
<div class="stage-content" v-if="currentStep === 0">
|
||||
<h3 class="stage-title">Step 1: Tokenization (分词)</h3>
|
||||
<p class="stage-desc">计算机首先将文本切分为最小的语义单位(Token)。</p>
|
||||
<p class="stage-desc">
|
||||
计算机首先将文本切分为最小的语义单位(Token)。
|
||||
</p>
|
||||
<div class="token-container">
|
||||
<div
|
||||
v-for="(token, idx) in tokens"
|
||||
@@ -62,10 +62,15 @@
|
||||
<!-- Step 2: ID Mapping -->
|
||||
<div class="stage-content" v-if="currentStep === 1">
|
||||
<h3 class="stage-title">Step 2: ID Mapping (索引映射)</h3>
|
||||
<p class="stage-desc">在词表(Vocabulary)中查找每个 Token 对应的唯一数字 ID。</p>
|
||||
<p class="stage-desc">
|
||||
在词表(Vocabulary)中查找每个 Token 对应的唯一数字 ID。
|
||||
</p>
|
||||
<div class="mapping-container">
|
||||
<div v-for="(token, idx) in tokens" :key="idx" class="mapping-row">
|
||||
<div class="token-box sm" :style="{ borderColor: getTokenColor(idx) }">
|
||||
<div
|
||||
class="token-box sm"
|
||||
:style="{ borderColor: getTokenColor(idx) }"
|
||||
>
|
||||
{{ token.text }}
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
@@ -83,14 +88,20 @@
|
||||
<!-- Step 3: Embedding Lookup -->
|
||||
<div class="stage-content" v-if="currentStep === 2">
|
||||
<h3 class="stage-title">Step 3: Embedding Lookup (向量查表)</h3>
|
||||
<p class="stage-desc">每个 ID 对应一个预训练好的高维向量(这里简化为 4 维)。</p>
|
||||
<p class="stage-desc">
|
||||
每个 ID 对应一个预训练好的高维向量(这里简化为 4 维)。
|
||||
</p>
|
||||
<div class="lookup-container">
|
||||
<div v-for="(token, idx) in tokens" :key="idx" class="lookup-row">
|
||||
<div class="id-box">{{ token.id }}</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="vector-row">
|
||||
<span class="bracket">[</span>
|
||||
<span v-for="(val, vIdx) in token.vector" :key="vIdx" class="vector-val">
|
||||
<span
|
||||
v-for="(val, vIdx) in token.vector"
|
||||
:key="vIdx"
|
||||
class="vector-val"
|
||||
>
|
||||
{{ val.toFixed(2) }}
|
||||
</span>
|
||||
<span class="bracket">]</span>
|
||||
@@ -102,7 +113,10 @@
|
||||
<!-- Step 4: Input Matrix -->
|
||||
<div class="stage-content" v-if="currentStep === 3">
|
||||
<h3 class="stage-title">Step 4: Matrix Construction (构建矩阵)</h3>
|
||||
<p class="stage-desc">所有向量堆叠在一起,形成了输入矩阵(Shape: [Batch, Seq_Len, Dim])。这就是 LLM 真正“看见”的东西。</p>
|
||||
<p class="stage-desc">
|
||||
所有向量堆叠在一起,形成了输入矩阵(Shape: [Batch, Seq_Len,
|
||||
Dim])。这就是 LLM 真正“看见”的东西。
|
||||
</p>
|
||||
<div class="matrix-container">
|
||||
<div class="matrix-bracket left"></div>
|
||||
<div class="matrix-grid">
|
||||
@@ -119,9 +133,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="matrix-bracket right"></div>
|
||||
<div class="matrix-label">
|
||||
Shape: ({{ tokens.length }}, 4)
|
||||
</div>
|
||||
<div class="matrix-label">Shape: ({{ tokens.length }}, 4)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,13 +157,14 @@ const tokens = computed(() => {
|
||||
return rawTokens.map((t, i) => {
|
||||
// 确定性伪随机生成 ID 和 Vector
|
||||
let hash = 0
|
||||
for (let j = 0; j < t.length; j++) hash = t.charCodeAt(j) + ((hash << 5) - hash)
|
||||
for (let j = 0; j < t.length; j++)
|
||||
hash = t.charCodeAt(j) + ((hash << 5) - hash)
|
||||
const id = Math.abs(hash) % 10000
|
||||
|
||||
// 生成 4 维向量
|
||||
const vector = []
|
||||
for(let k=0; k<4; k++) {
|
||||
const val = Math.sin(id * (k+1)) // 伪随机值 -1 ~ 1
|
||||
for (let k = 0; k < 4; k++) {
|
||||
const val = Math.sin(id * (k + 1)) // 伪随机值 -1 ~ 1
|
||||
vector.push(val)
|
||||
}
|
||||
|
||||
@@ -350,7 +363,7 @@ const getHeatmapColor = (val) => {
|
||||
justify-content: center;
|
||||
font-size: 0.7rem;
|
||||
color: #fff; /* text always white for contrast on colored bg */
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.matrix-bracket {
|
||||
|
||||
@@ -18,18 +18,29 @@
|
||||
</div>
|
||||
|
||||
<div class="demo-content">
|
||||
|
||||
<!-- Tab 1: 基础能力 - 文本续写 -->
|
||||
<div v-if="currentTab === 'completion'" class="mode-view">
|
||||
<div class="desc-box">
|
||||
<p><strong>LLM 的本能是“续写”</strong>:它并不懂对话,只是根据上文猜下一个词。</p>
|
||||
<p>
|
||||
<strong>LLM 的本能是“续写”</strong
|
||||
>:它并不懂对话,只是根据上文猜下一个词。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="interactive-area">
|
||||
<div class="input-row">
|
||||
<span class="prompt-label">Prompt (提示词):</span>
|
||||
<input type="text" v-model="completionInput" placeholder="Enter text..." :disabled="isGenerating">
|
||||
<button class="primary-btn" @click="runCompletion" :disabled="isGenerating || !completionInput">
|
||||
<input
|
||||
type="text"
|
||||
v-model="completionInput"
|
||||
placeholder="Enter text..."
|
||||
:disabled="isGenerating"
|
||||
/>
|
||||
<button
|
||||
class="primary-btn"
|
||||
@click="runCompletion"
|
||||
:disabled="isGenerating || !completionInput"
|
||||
>
|
||||
✨ Generate
|
||||
</button>
|
||||
</div>
|
||||
@@ -49,7 +60,10 @@
|
||||
<!-- Tab 2: 技巧 - 对话原理 (Template) -->
|
||||
<div v-if="currentTab === 'chat'" class="mode-view">
|
||||
<div class="desc-box">
|
||||
<p><strong>如何让它对话?</strong> 我们用“剧本”包装输入,让模型以为自己在续写一段对话。</p>
|
||||
<p>
|
||||
<strong>如何让它对话?</strong>
|
||||
我们用“剧本”包装输入,让模型以为自己在续写一段对话。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="chat-container">
|
||||
@@ -61,7 +75,11 @@
|
||||
<div class="msg bot" v-if="chatOutput">{{ chatOutput }}</div>
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<input v-model="chatInput" placeholder="Say hello..." @keyup.enter="runChat">
|
||||
<input
|
||||
v-model="chatInput"
|
||||
placeholder="Say hello..."
|
||||
@keyup.enter="runChat"
|
||||
/>
|
||||
<button @click="runChat" :disabled="isGenerating">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,13 +89,13 @@
|
||||
<div class="model-view-half">
|
||||
<div class="half-label">模型看到的 (Raw Prompt)</div>
|
||||
<div class="raw-prompt">
|
||||
<span class="sys-tag"><|system|></span><br>
|
||||
You are a helpful assistant.<br>
|
||||
<span class="bot-tag"><|assistant|></span><br>
|
||||
我是 AI 助手,你好!<br>
|
||||
<span class="user-tag"><|user|></span><br>
|
||||
{{ chatInput || '...' }}<br>
|
||||
<span class="bot-tag"><|assistant|></span><br>
|
||||
<span class="sys-tag"><|system|></span><br />
|
||||
You are a helpful assistant.<br />
|
||||
<span class="bot-tag"><|assistant|></span><br />
|
||||
我是 AI 助手,你好!<br />
|
||||
<span class="user-tag"><|user|></span><br />
|
||||
{{ chatInput || '...' }}<br />
|
||||
<span class="bot-tag"><|assistant|></span><br />
|
||||
<span class="ai-text typing">{{ chatOutput }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,14 +105,20 @@
|
||||
<!-- Tab 3: 原理 - 训练 (Training) -->
|
||||
<div v-if="currentTab === 'train'" class="mode-view">
|
||||
<div class="desc-box">
|
||||
<p><strong>Training (训练原理)</strong>: 模型通过大量数据的“填空题”训练。计算预测结果与真实结果的差异(Loss),并不断调整参数以降低 Loss。</p>
|
||||
<p>
|
||||
<strong>Training (训练原理)</strong>:
|
||||
模型通过大量数据的“填空题”训练。计算预测结果与真实结果的差异(Loss),并不断调整参数以降低
|
||||
Loss。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="training-dashboard">
|
||||
<!-- 左侧:训练过程可视化 -->
|
||||
<div class="train-process-panel card-panel">
|
||||
<div class="panel-header">
|
||||
<span class="step-badge">Step {{ currentStep }}/{{ totalSteps }}</span>
|
||||
<span class="step-badge"
|
||||
>Step {{ currentStep }}/{{ totalSteps }}</span
|
||||
>
|
||||
<span class="panel-title">Training Process</span>
|
||||
</div>
|
||||
|
||||
@@ -102,16 +126,29 @@
|
||||
<!-- Input Section -->
|
||||
<div class="flow-stage input-stage">
|
||||
<div class="stage-label">1. Input (输入)</div>
|
||||
<div v-if="currentStep === 0" class="content-box input placeholder">
|
||||
<div
|
||||
v-if="currentStep === 0"
|
||||
class="content-box input placeholder"
|
||||
>
|
||||
<span class="text-content">点击下方按钮开始训练</span>
|
||||
</div>
|
||||
<div v-else class="content-box input">
|
||||
<span class="text-content">"{{ currentTrainData.input }}"</span>
|
||||
<span class="text-content"
|
||||
>"{{ currentTrainData.input }}"</span
|
||||
>
|
||||
</div>
|
||||
<div class="matrix-viz">
|
||||
<span class="matrix-label">Embedding:</span>
|
||||
<div class="matrix-row">
|
||||
<span v-for="n in 5" :key="n" class="matrix-cell" :style="{ opacity: inputEmbeddingOpacities[n - 1] ?? 0.6, transform: `scaleY(${inputEmbeddingOpacities[n - 1] ?? 1})` }"></span>
|
||||
<span
|
||||
v-for="n in 5"
|
||||
:key="n"
|
||||
class="matrix-cell"
|
||||
:style="{
|
||||
opacity: inputEmbeddingOpacities[n - 1] ?? 0.6,
|
||||
transform: `scaleY(${inputEmbeddingOpacities[n - 1] ?? 1})`
|
||||
}"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,12 +166,22 @@
|
||||
<div class="compare-row">
|
||||
<div class="compare-item">
|
||||
<span class="sub-label">Prediction</span>
|
||||
<div class="content-box pred" :class="{ correct: isPredictionCorrect }">
|
||||
<div
|
||||
class="content-box pred"
|
||||
:class="{ correct: isPredictionCorrect }"
|
||||
>
|
||||
"{{ currentPrediction || '...' }}"
|
||||
</div>
|
||||
<div class="matrix-viz small">
|
||||
<div class="matrix-row">
|
||||
<span v-for="n in 5" :key="n" class="matrix-cell pred-cell" :style="{ opacity: predEmbeddingOpacities[n - 1] ?? 0.6 }"></span>
|
||||
<span
|
||||
v-for="n in 5"
|
||||
:key="n"
|
||||
class="matrix-cell pred-cell"
|
||||
:style="{
|
||||
opacity: predEmbeddingOpacities[n - 1] ?? 0.6
|
||||
}"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -148,7 +195,14 @@
|
||||
</div>
|
||||
<div class="matrix-viz small">
|
||||
<div class="matrix-row">
|
||||
<span v-for="n in 5" :key="n" class="matrix-cell target-cell" :style="{ opacity: targetEmbeddingOpacities[n - 1] ?? 0.9 }"></span>
|
||||
<span
|
||||
v-for="n in 5"
|
||||
:key="n"
|
||||
class="matrix-cell target-cell"
|
||||
:style="{
|
||||
opacity: targetEmbeddingOpacities[n - 1] ?? 0.9
|
||||
}"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,14 +213,34 @@
|
||||
<div v-if="currentStep > 0" class="flow-stage loss-stage">
|
||||
<div class="stage-header">
|
||||
<span class="stage-label">3. Loss Calculation</span>
|
||||
<span class="loss-val-badge" :style="{ backgroundColor: getLossColor(currentLoss) }">Loss: {{ currentLoss.toFixed(4) }}</span>
|
||||
<span
|
||||
class="loss-val-badge"
|
||||
:style="{ backgroundColor: getLossColor(currentLoss) }"
|
||||
>Loss: {{ currentLoss.toFixed(4) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="loss-bar-container">
|
||||
<div class="loss-bar-bg">
|
||||
<div class="loss-bar-fill" :style="{ width: Math.min((currentLoss / 3) * 100, 100) + '%', backgroundColor: getLossColor(currentLoss) }"></div>
|
||||
<div
|
||||
class="loss-bar-fill"
|
||||
:style="{
|
||||
width: Math.min((currentLoss / 3) * 100, 100) + '%',
|
||||
backgroundColor: getLossColor(currentLoss)
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<div class="loss-feedback" :class="{ success: isPredictionCorrect, error: !isPredictionCorrect }">
|
||||
{{ isPredictionCorrect ? '✅ Parameters Good' : '❌ Update Weights' }}
|
||||
<div
|
||||
class="loss-feedback"
|
||||
:class="{
|
||||
success: isPredictionCorrect,
|
||||
error: !isPredictionCorrect
|
||||
}"
|
||||
>
|
||||
{{
|
||||
isPredictionCorrect
|
||||
? '✅ Parameters Good'
|
||||
: '❌ Update Weights'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -182,19 +256,58 @@
|
||||
<svg viewBox="0 0 300 150" class="loss-chart">
|
||||
<!-- Background Grid -->
|
||||
<defs>
|
||||
<pattern id="grid" width="30" height="30" patternUnits="userSpaceOnUse">
|
||||
<path d="M 30 0 L 0 0 0 30" fill="none" stroke="var(--vp-c-divider)" stroke-width="0.5" stroke-opacity="0.3"/>
|
||||
<pattern
|
||||
id="grid"
|
||||
width="30"
|
||||
height="30"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path
|
||||
d="M 30 0 L 0 0 0 30"
|
||||
fill="none"
|
||||
stroke="var(--vp-c-divider)"
|
||||
stroke-width="0.5"
|
||||
stroke-opacity="0.3"
|
||||
/>
|
||||
</pattern>
|
||||
<linearGradient id="chartGradient" x1="0" x2="0" y1="0" y2="1">
|
||||
<stop offset="0%" stop-color="var(--vp-c-brand)" stop-opacity="0.2"/>
|
||||
<stop offset="100%" stop-color="var(--vp-c-brand)" stop-opacity="0"/>
|
||||
<linearGradient
|
||||
id="chartGradient"
|
||||
x1="0"
|
||||
x2="0"
|
||||
y1="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stop-color="var(--vp-c-brand)"
|
||||
stop-opacity="0.2"
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
stop-color="var(--vp-c-brand)"
|
||||
stop-opacity="0"
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
|
||||
<!-- Axes -->
|
||||
<line x1="20" y1="130" x2="290" y2="130" stroke="var(--vp-c-text-3)" stroke-width="1" />
|
||||
<line x1="20" y1="10" x2="20" y2="130" stroke="var(--vp-c-text-3)" stroke-width="1" />
|
||||
<line
|
||||
x1="20"
|
||||
y1="130"
|
||||
x2="290"
|
||||
y2="130"
|
||||
stroke="var(--vp-c-text-3)"
|
||||
stroke-width="1"
|
||||
/>
|
||||
<line
|
||||
x1="20"
|
||||
y1="10"
|
||||
x2="20"
|
||||
y2="130"
|
||||
stroke="var(--vp-c-text-3)"
|
||||
stroke-width="1"
|
||||
/>
|
||||
|
||||
<!-- Fill Area -->
|
||||
<polygon
|
||||
@@ -230,11 +343,32 @@
|
||||
<span class="console-title">training_log.txt</span>
|
||||
</div>
|
||||
<div class="log-console">
|
||||
<div v-if="trainingLogs.length === 0" class="log-placeholder">Waiting for training to start...</div>
|
||||
<div v-for="(log, idx) in trainingLogs" :key="idx" class="log-item">
|
||||
<span class="log-step">[Step {{ String(log.step).padStart(2, '0') }}]</span>
|
||||
<span class="log-loss" :style="{ color: getLossColor(log.loss) }">Loss={{ log.loss.toFixed(2) }}</span>
|
||||
<span class="log-detail">{{ log.input }} -> <span :class="{ 'text-green': log.pred === log.target, 'text-red': log.pred !== log.target }">{{ log.pred }}</span></span>
|
||||
<div v-if="trainingLogs.length === 0" class="log-placeholder">
|
||||
Waiting for training to start...
|
||||
</div>
|
||||
<div
|
||||
v-for="(log, idx) in trainingLogs"
|
||||
:key="idx"
|
||||
class="log-item"
|
||||
>
|
||||
<span class="log-step"
|
||||
>[Step {{ String(log.step).padStart(2, '0') }}]</span
|
||||
>
|
||||
<span
|
||||
class="log-loss"
|
||||
:style="{ color: getLossColor(log.loss) }"
|
||||
>Loss={{ log.loss.toFixed(2) }}</span
|
||||
>
|
||||
<span class="log-detail"
|
||||
>{{ log.input }} ->
|
||||
<span
|
||||
:class="{
|
||||
'text-green': log.pred === log.target,
|
||||
'text-red': log.pred !== log.target
|
||||
}"
|
||||
>{{ log.pred }}</span
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,9 +376,15 @@
|
||||
</div>
|
||||
|
||||
<div class="action-bar">
|
||||
<button class="train-btn" @click="handleTrainClick" :class="{ 'is-restart': currentStep >= totalSteps }">
|
||||
<button
|
||||
class="train-btn"
|
||||
@click="handleTrainClick"
|
||||
:class="{ 'is-restart': currentStep >= totalSteps }"
|
||||
>
|
||||
<span class="btn-icon" v-if="currentStep === 0">🚀</span>
|
||||
<span class="btn-icon" v-else-if="currentStep >= totalSteps">🔄</span>
|
||||
<span class="btn-icon" v-else-if="currentStep >= totalSteps"
|
||||
>🔄</span
|
||||
>
|
||||
<span class="btn-icon" v-else>▶️</span>
|
||||
{{ trainButtonText }}
|
||||
</button>
|
||||
@@ -254,19 +394,28 @@
|
||||
<!-- Tab 4: 进阶 - 微调与对齐 (RLHF) -->
|
||||
<div v-if="currentTab === 'rlhf'" class="mode-view">
|
||||
<div class="desc-box">
|
||||
<p><strong>从“胡说”到“好助手”</strong>:通过 RLHF (人类反馈) 让模型学会礼貌和安全。</p>
|
||||
<p>
|
||||
<strong>从“胡说”到“好助手”</strong>:通过 RLHF (人类反馈)
|
||||
让模型学会礼貌和安全。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="alignment-demo">
|
||||
<div class="controls">
|
||||
<div class="radio-group">
|
||||
<span class="group-label">模型状态:</span>
|
||||
<label class="radio-option" :class="{ active: alignmentState === 'base' }">
|
||||
<input type="radio" v-model="alignmentState" value="base">
|
||||
<label
|
||||
class="radio-option"
|
||||
:class="{ active: alignmentState === 'base' }"
|
||||
>
|
||||
<input type="radio" v-model="alignmentState" value="base" />
|
||||
Base Model (未对齐)
|
||||
</label>
|
||||
<label class="radio-option" :class="{ active: alignmentState === 'aligned' }">
|
||||
<input type="radio" v-model="alignmentState" value="aligned">
|
||||
<label
|
||||
class="radio-option"
|
||||
:class="{ active: alignmentState === 'aligned' }"
|
||||
>
|
||||
<input type="radio" v-model="alignmentState" value="aligned" />
|
||||
Aligned Model (已对齐)
|
||||
</label>
|
||||
</div>
|
||||
@@ -276,7 +425,9 @@
|
||||
<div class="user-query">User: "如何制造混乱?"</div>
|
||||
|
||||
<div class="model-response" :class="alignmentState">
|
||||
<div class="avatar">{{ alignmentState === 'base' ? '🤪' : '🤖' }}</div>
|
||||
<div class="avatar">
|
||||
{{ alignmentState === 'base' ? '🤪' : '🤖' }}
|
||||
</div>
|
||||
<div class="bubble">
|
||||
<div v-if="alignmentState === 'base'">
|
||||
哈哈!制造混乱很简单!你可以去大街上大喊大叫,或者...(此处省略1000字胡言乱语)...这太好玩了!
|
||||
@@ -288,13 +439,14 @@
|
||||
</div>
|
||||
|
||||
<div class="analysis">
|
||||
<span v-if="alignmentState === 'base'" class="bad-tag">⚠️ Unsafe / Not Helpful</span>
|
||||
<span v-if="alignmentState === 'base'" class="bad-tag"
|
||||
>⚠️ Unsafe / Not Helpful</span
|
||||
>
|
||||
<span v-else class="good-tag">✅ Safe & Helpful</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -322,7 +474,7 @@ const runCompletion = async () => {
|
||||
|
||||
const target = ' blue and beautiful.'
|
||||
for (const char of target) {
|
||||
await new Promise(r => setTimeout(r, 50))
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
completionOutput.value += char
|
||||
}
|
||||
isGenerating.value = false
|
||||
@@ -337,11 +489,15 @@ const runChat = async () => {
|
||||
isGenerating.value = true
|
||||
chatOutput.value = ''
|
||||
|
||||
const responses = ['Hi there! How can I help?', 'Hello! Nice to meet you.', 'Greetings!']
|
||||
const responses = [
|
||||
'Hi there! How can I help?',
|
||||
'Hello! Nice to meet you.',
|
||||
'Greetings!'
|
||||
]
|
||||
const target = responses[Math.floor(Math.random() * responses.length)]
|
||||
|
||||
for (const char of target) {
|
||||
await new Promise(r => setTimeout(r, 50))
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
chatOutput.value += char
|
||||
}
|
||||
isGenerating.value = false
|
||||
@@ -383,9 +539,18 @@ const resetTrainingState = () => {
|
||||
}
|
||||
|
||||
const seedOpacities = () => {
|
||||
inputEmbeddingOpacities.value = Array.from({ length: 5 }, () => Math.random() * 0.5 + 0.5)
|
||||
predEmbeddingOpacities.value = Array.from({ length: 5 }, () => Math.random() * 0.5 + 0.5)
|
||||
targetEmbeddingOpacities.value = Array.from({ length: 5 }, () => Math.random() * 0.2 + 0.8)
|
||||
inputEmbeddingOpacities.value = Array.from(
|
||||
{ length: 5 },
|
||||
() => Math.random() * 0.5 + 0.5
|
||||
)
|
||||
predEmbeddingOpacities.value = Array.from(
|
||||
{ length: 5 },
|
||||
() => Math.random() * 0.5 + 0.5
|
||||
)
|
||||
targetEmbeddingOpacities.value = Array.from(
|
||||
{ length: 5 },
|
||||
() => Math.random() * 0.2 + 0.8
|
||||
)
|
||||
}
|
||||
|
||||
const handleTrainClick = () => {
|
||||
@@ -394,7 +559,8 @@ const handleTrainClick = () => {
|
||||
}
|
||||
|
||||
if (!activeTrainData.value) {
|
||||
activeTrainData.value = trainDataset[Math.floor(Math.random() * trainDataset.length)]
|
||||
activeTrainData.value =
|
||||
trainDataset[Math.floor(Math.random() * trainDataset.length)]
|
||||
}
|
||||
|
||||
currentStep.value += 1
|
||||
@@ -404,49 +570,49 @@ const handleTrainClick = () => {
|
||||
currentTrainData.value = data
|
||||
|
||||
// Define a volatile loss curve for 10 steps to simulate real training instability
|
||||
// High -> Low -> Spike (Wrong) -> Low (Correct) -> Spike (Wrong) -> Stable Low
|
||||
const targetLossCurve = [
|
||||
2.8, // 1. Start high (Wrong)
|
||||
2.3, // 2. Dropping (Wrong)
|
||||
2.6, // 3. SPIKE! (Wrong)
|
||||
1.8, // 4. Recovering (Wrong)
|
||||
0.5, // 5. Good! (CORRECT!) -> Loss drops significantly because prediction matches
|
||||
1.5, // 6. SPIKE! (Wrong) -> Loss jumps up because prediction is wrong again
|
||||
0.4, // 7. Converging (Correct)
|
||||
0.3, // 8. Good (Correct)
|
||||
0.4, // 9. Small fluctuation (Correct)
|
||||
0.1 // 10. Converged (Correct)
|
||||
]
|
||||
const baseLoss = targetLossCurve[i - 1] || 0.1
|
||||
// High -> Low -> Spike (Wrong) -> Low (Correct) -> Spike (Wrong) -> Stable Low
|
||||
const targetLossCurve = [
|
||||
2.8, // 1. Start high (Wrong)
|
||||
2.3, // 2. Dropping (Wrong)
|
||||
2.6, // 3. SPIKE! (Wrong)
|
||||
1.8, // 4. Recovering (Wrong)
|
||||
0.5, // 5. Good! (CORRECT!) -> Loss drops significantly because prediction matches
|
||||
1.5, // 6. SPIKE! (Wrong) -> Loss jumps up because prediction is wrong again
|
||||
0.4, // 7. Converging (Correct)
|
||||
0.3, // 8. Good (Correct)
|
||||
0.4, // 9. Small fluctuation (Correct)
|
||||
0.1 // 10. Converged (Correct)
|
||||
]
|
||||
const baseLoss = targetLossCurve[i - 1] || 0.1
|
||||
|
||||
// Add small randomness (+/- 0.05) to make it feel organic
|
||||
let noise = (Math.random() * 0.1) - 0.05
|
||||
let finalLoss = baseLoss + noise
|
||||
// Add small randomness (+/- 0.05) to make it feel organic
|
||||
let noise = Math.random() * 0.1 - 0.05
|
||||
let finalLoss = baseLoss + noise
|
||||
|
||||
// Boundary checks
|
||||
if (finalLoss < 0.01) finalLoss = 0.01
|
||||
// Boundary checks
|
||||
if (finalLoss < 0.01) finalLoss = 0.01
|
||||
|
||||
// IMPORTANT: Ensure consistency between Loss and Prediction
|
||||
// Threshold logic:
|
||||
// Loss <= 0.8: Prediction is CORRECT (Low loss)
|
||||
// Loss > 0.8: Prediction is WRONG (High loss)
|
||||
// This ensures that when Loss spikes to 1.5 (Step 6), prediction MUST be wrong.
|
||||
// When Loss drops to 0.5 (Step 5), prediction MUST be correct.
|
||||
// IMPORTANT: Ensure consistency between Loss and Prediction
|
||||
// Threshold logic:
|
||||
// Loss <= 0.8: Prediction is CORRECT (Low loss)
|
||||
// Loss > 0.8: Prediction is WRONG (High loss)
|
||||
// This ensures that when Loss spikes to 1.5 (Step 6), prediction MUST be wrong.
|
||||
// When Loss drops to 0.5 (Step 5), prediction MUST be correct.
|
||||
|
||||
let pred
|
||||
const threshold = 0.8
|
||||
let pred
|
||||
const threshold = 0.8
|
||||
|
||||
if (finalLoss > threshold) {
|
||||
pred = getRandomWord()
|
||||
// Safety: ensure random word is not the target
|
||||
while (pred === data.target) {
|
||||
pred = getRandomWord()
|
||||
}
|
||||
} else {
|
||||
pred = data.target
|
||||
// Optional: clamp loss if it accidentally went above threshold due to noise
|
||||
if (finalLoss > threshold - 0.01) finalLoss = threshold - 0.01
|
||||
}
|
||||
if (finalLoss > threshold) {
|
||||
pred = getRandomWord()
|
||||
// Safety: ensure random word is not the target
|
||||
while (pred === data.target) {
|
||||
pred = getRandomWord()
|
||||
}
|
||||
} else {
|
||||
pred = data.target
|
||||
// Optional: clamp loss if it accidentally went above threshold due to noise
|
||||
if (finalLoss > threshold - 0.01) finalLoss = threshold - 0.01
|
||||
}
|
||||
|
||||
currentLoss.value = finalLoss
|
||||
currentPrediction.value = pred
|
||||
@@ -471,7 +637,18 @@ const trainButtonText = computed(() => {
|
||||
})
|
||||
|
||||
const getRandomWord = () => {
|
||||
const words = ['cat', 'fly', 'run', 'red', 'table', 'what', 'bad', '未知', '乱码', '错误']
|
||||
const words = [
|
||||
'cat',
|
||||
'fly',
|
||||
'run',
|
||||
'red',
|
||||
'table',
|
||||
'what',
|
||||
'bad',
|
||||
'未知',
|
||||
'乱码',
|
||||
'错误'
|
||||
]
|
||||
return words[Math.floor(Math.random() * words.length)]
|
||||
}
|
||||
|
||||
@@ -483,7 +660,7 @@ const lossPolylinePoints = computed(() => {
|
||||
const startX = 20
|
||||
const endX = 290
|
||||
const startY = 130 // Bottom (Loss = 0)
|
||||
const endY = 10 // Top (Loss = maxLoss)
|
||||
const endY = 10 // Top (Loss = maxLoss)
|
||||
|
||||
const width = endX - startX
|
||||
const height = startY - endY
|
||||
@@ -500,17 +677,19 @@ const lossPolylinePoints = computed(() => {
|
||||
// To keep the chart stable (points appearing from left to right),
|
||||
// we should map based on totalSteps
|
||||
|
||||
return lossHistory.value.map((loss, idx) => {
|
||||
// idx 0 corresponds to Step 1
|
||||
// We want Step 1 to be at x=0? Or spread out?
|
||||
// Let's spread out based on current progress or fixed totalSteps?
|
||||
// Fixed totalSteps is better for visualization "filling up"
|
||||
return lossHistory.value
|
||||
.map((loss, idx) => {
|
||||
// idx 0 corresponds to Step 1
|
||||
// We want Step 1 to be at x=0? Or spread out?
|
||||
// Let's spread out based on current progress or fixed totalSteps?
|
||||
// Fixed totalSteps is better for visualization "filling up"
|
||||
|
||||
const stepIndex = idx // 0 to 9
|
||||
const x = startX + (stepIndex / (totalSteps - 1)) * width
|
||||
const y = startY - (loss / maxLoss) * height
|
||||
return `${x},${y}`
|
||||
}).join(' ')
|
||||
const stepIndex = idx // 0 to 9
|
||||
const x = startX + (stepIndex / (totalSteps - 1)) * width
|
||||
const y = startY - (loss / maxLoss) * height
|
||||
return `${x},${y}`
|
||||
})
|
||||
.join(' ')
|
||||
})
|
||||
|
||||
const getLossColor = (loss) => {
|
||||
@@ -523,7 +702,6 @@ seedOpacities()
|
||||
|
||||
// Tab 4 Logic
|
||||
const alignmentState = ref('base')
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -633,7 +811,8 @@ const alignmentState = ref('base')
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.chat-ui-half, .model-view-half {
|
||||
.chat-ui-half,
|
||||
.model-view-half {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -717,9 +896,15 @@ const alignmentState = ref('base')
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.sys-tag { color: #569cd6; }
|
||||
.user-tag { color: #ce9178; }
|
||||
.bot-tag { color: #4ec9b0; }
|
||||
.sys-tag {
|
||||
color: #569cd6;
|
||||
}
|
||||
.user-tag {
|
||||
color: #ce9178;
|
||||
}
|
||||
.bot-tag {
|
||||
color: #4ec9b0;
|
||||
}
|
||||
|
||||
/* Tab 3 Styles (New) */
|
||||
.training-dashboard {
|
||||
@@ -815,7 +1000,7 @@ const alignmentState = ref('base')
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: inset 0 2px 4px rgba(0,0,0,0.03);
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.content-box.input.placeholder {
|
||||
@@ -868,8 +1053,12 @@ const alignmentState = ref('base')
|
||||
transform-origin: bottom;
|
||||
}
|
||||
|
||||
.matrix-cell.pred-cell { background-color: #f59e0b; }
|
||||
.matrix-cell.target-cell { background-color: #10b981; }
|
||||
.matrix-cell.pred-cell {
|
||||
background-color: #f59e0b;
|
||||
}
|
||||
.matrix-cell.target-cell {
|
||||
background-color: #10b981;
|
||||
}
|
||||
|
||||
/* Arrows */
|
||||
.process-arrow {
|
||||
@@ -952,7 +1141,9 @@ const alignmentState = ref('base')
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.stage-header .stage-label { margin-bottom: 0; }
|
||||
.stage-header .stage-label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.loss-val-badge {
|
||||
font-size: 0.75rem;
|
||||
@@ -981,7 +1172,9 @@ const alignmentState = ref('base')
|
||||
.loss-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
transition: width 0.4s ease, background-color 0.3s;
|
||||
transition:
|
||||
width 0.4s ease,
|
||||
background-color 0.3s;
|
||||
}
|
||||
|
||||
.loss-feedback {
|
||||
@@ -993,8 +1186,14 @@ const alignmentState = ref('base')
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.loss-feedback.success { color: #10b981; background: rgba(16, 185, 129, 0.1); }
|
||||
.loss-feedback.error { color: #ef4444; background: rgba(239, 68, 68, 0.1); }
|
||||
.loss-feedback.success {
|
||||
color: #10b981;
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
}
|
||||
.loss-feedback.error {
|
||||
color: #ef4444;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
/* Chart & Logs */
|
||||
.chart-container {
|
||||
@@ -1027,7 +1226,7 @@ const alignmentState = ref('base')
|
||||
background: #1e1e1e;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.console-header {
|
||||
@@ -1044,10 +1243,20 @@ const alignmentState = ref('base')
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.dot { width: 10px; height: 10px; border-radius: 50%; }
|
||||
.dot.red { background: #ff5f56; }
|
||||
.dot.yellow { background: #ffbd2e; }
|
||||
.dot.green { background: #27c93f; }
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dot.red {
|
||||
background: #ff5f56;
|
||||
}
|
||||
.dot.yellow {
|
||||
background: #ffbd2e;
|
||||
}
|
||||
.dot.green {
|
||||
background: #27c93f;
|
||||
}
|
||||
|
||||
.console-title {
|
||||
color: #888;
|
||||
@@ -1079,11 +1288,28 @@ const alignmentState = ref('base')
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.log-step { color: #569cd6; flex-shrink: 0; }
|
||||
.log-loss { font-weight: bold; flex-shrink: 0; }
|
||||
.log-detail { color: #9cdcfe; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.text-green { color: #4ec9b0; font-weight: bold; }
|
||||
.text-red { color: #ce9178; font-weight: bold; }
|
||||
.log-step {
|
||||
color: #569cd6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.log-loss {
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.log-detail {
|
||||
color: #9cdcfe;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.text-green {
|
||||
color: #4ec9b0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.text-red {
|
||||
color: #ce9178;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Action Bar */
|
||||
.action-bar {
|
||||
@@ -1223,40 +1449,40 @@ const alignmentState = ref('base')
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
padding: 6px 12px;
|
||||
background-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
button {
|
||||
cursor: pointer;
|
||||
padding: 6px 12px;
|
||||
background-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background-color: var(--vp-c-brand-dark);
|
||||
}
|
||||
button:hover:not(:disabled) {
|
||||
background-color: var(--vp-c-brand-dark);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
padding: 8px 20px;
|
||||
font-size: 1rem;
|
||||
box-shadow: 0 2px 8px rgba(var(--vp-c-brand-rgb), 0.25);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.primary-btn {
|
||||
padding: 8px 20px;
|
||||
font-size: 1rem;
|
||||
box-shadow: 0 2px 8px rgba(var(--vp-c-brand-rgb), 0.25);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.primary-btn:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(var(--vp-c-brand-rgb), 0.35);
|
||||
}
|
||||
.primary-btn:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(var(--vp-c-brand-rgb), 0.35);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+107
-231
@@ -1,256 +1,132 @@
|
||||
<!--
|
||||
ChainOfThoughtDemo.vue
|
||||
“先计划再输出”演示(更易懂版本)。
|
||||
|
||||
注意:这里不强调让模型展示冗长推理,而是用“先列计划/检查点”来降低跑偏概率。
|
||||
-->
|
||||
<template>
|
||||
<div class="chain-of-thought-demo">
|
||||
<div class="comparison-container">
|
||||
<!-- Direct Answer -->
|
||||
<div class="method-card">
|
||||
<div class="method-header">
|
||||
<span class="method-icon">⚡</span>
|
||||
<span class="method-title">直接回答</span>
|
||||
</div>
|
||||
<div class="method-body">
|
||||
<div class="prompt-box">
|
||||
<div class="prompt-label">提示词</div>
|
||||
<div class="prompt-text">
|
||||
罗杰有 5 个网球。他又买了 2 罐网球,每罐有 3 个球。
|
||||
现在他总共有多少个网球?
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="result-box">
|
||||
<div class="result-label">AI 可能的输出</div>
|
||||
<div class="result-content bad">
|
||||
"11 个球。"
|
||||
<br><br>
|
||||
<span class="badge">错误 ❌</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cot">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">复杂任务:先“列计划”,再“交付结果”</div>
|
||||
<div class="subtitle">你要的是:不漏步骤 + 可检查 + 不跑题。</div>
|
||||
</div>
|
||||
|
||||
<!-- Chain of Thought -->
|
||||
<div class="method-card highlight">
|
||||
<div class="method-header">
|
||||
<span class="method-icon">🧠</span>
|
||||
<span class="method-title">思维链 (CoT)</span>
|
||||
</div>
|
||||
<div class="method-body">
|
||||
<div class="prompt-box">
|
||||
<div class="prompt-label">提示词</div>
|
||||
<div class="prompt-text">
|
||||
罗杰有 5 个网球。他又买了 2 罐网球,每罐有 3 个球。
|
||||
现在他总共有多少个网球?
|
||||
<br><br>
|
||||
<span class="instruction">请一步步思考:</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="result-box">
|
||||
<div class="result-label">AI 输出</div>
|
||||
<div class="result-content good">
|
||||
<div class="thinking-process">
|
||||
① 罗杰原本有 5 个球<br>
|
||||
② 他买了 2 罐,每罐 3 个:2 × 3 = 6 个<br>
|
||||
③ 总共:5 + 6 = 11 个
|
||||
</div>
|
||||
<div class="final-answer">
|
||||
<strong>答案:11 个球</strong>
|
||||
</div>
|
||||
<span class="badge success">正确 ✅</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<select v-model="task">
|
||||
<option value="debug">代码审查</option>
|
||||
<option value="plan">行程规划</option>
|
||||
</select>
|
||||
<button
|
||||
v-for="m in modes"
|
||||
:key="m.id"
|
||||
:class="['mode', { active: mode === m.id }]"
|
||||
@click="mode = m.id"
|
||||
>
|
||||
{{ m.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation">
|
||||
<div class="exp-item">
|
||||
<span class="exp-icon">🔍</span>
|
||||
<span><strong>思维链</strong>:让 AI "展示思考过程",一步步推理问题。
|
||||
对于数学、逻辑、推理类问题特别有效!</span>
|
||||
<div class="grid">
|
||||
<div class="panel">
|
||||
<div class="panel-title">提示词 / Prompt</div>
|
||||
<pre><code>{{ prompt }}</code></pre>
|
||||
</div>
|
||||
<div class="exp-item">
|
||||
<span class="exp-icon">📝</span>
|
||||
<span><strong>触发词</strong>:使用"请一步步思考"、"详细说明推理过程"等提示语可以激活 CoT</span>
|
||||
<div class="panel">
|
||||
<div class="panel-title">输出(示意)</div>
|
||||
<div class="output">{{ output }}</div>
|
||||
</div>
|
||||
<div class="exp-item">
|
||||
<span class="exp-icon">🎯</span>
|
||||
<span><strong>适用场景</strong>:数学计算、逻辑推理、复杂问题拆解、多步骤任务</span>
|
||||
</div>
|
||||
|
||||
<div class="why">
|
||||
<div class="why-title">为什么这样更稳?</div>
|
||||
<div class="why-grid">
|
||||
<div class="why-card">
|
||||
<div class="k">✅ 不漏步骤</div>
|
||||
<div class="v">计划就是清单,能一项项对照。</div>
|
||||
</div>
|
||||
<div class="why-card">
|
||||
<div class="k">✅ 更好验收</div>
|
||||
<div class="v">你知道该检查什么,而不是只看“像不像”。</div>
|
||||
</div>
|
||||
<div class="why-card">
|
||||
<div class="k">✅ 更少返工</div>
|
||||
<div class="v">先对齐方向,再生成结果,减少来回修。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.chain-of-thought-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 20px 0;
|
||||
}
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
.comparison-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
const task = ref('debug')
|
||||
const mode = ref('plan-first')
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.comparison-container {
|
||||
grid-template-columns: 1fr;
|
||||
const modes = [
|
||||
{ id: 'direct', label: '直接输出' },
|
||||
{ id: 'plan-first', label: '先列计划再输出' }
|
||||
]
|
||||
|
||||
const prompt = computed(() => {
|
||||
if (task.value === 'debug') {
|
||||
if (mode.value === 'direct') {
|
||||
return '帮我看看这段代码有什么问题,并给修复建议。'
|
||||
}
|
||||
return `你是资深前端工程师。\n任务:代码审查。\n要求:\n1) 先列“检查清单”(3-5 项),说明你将检查什么\n2) 再输出问题列表(每条包含:现象/原因/修复)\n3) 最后给一段修复后的代码(仅关键片段)`
|
||||
}
|
||||
}
|
||||
// plan
|
||||
if (mode.value === 'direct') return '帮我做一个上海三日游行程,越详细越好。'
|
||||
return `你是旅行规划师。\n任务:上海三日游。\n要求:\n1) 先列“规划原则”(交通/节奏/预算)\n2) 再给 Day1-Day3 行程(每段 3-5 个地点)\n3) 每天最后给一句“备选方案”\n输出:Markdown`
|
||||
})
|
||||
|
||||
.method-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
const output = computed(() => {
|
||||
if (task.value === 'debug') {
|
||||
if (mode.value === 'direct') {
|
||||
return '代码可能有一些问题,比如命名不规范、性能不佳……(容易泛泛而谈/漏点)'
|
||||
}
|
||||
return `检查清单:\n- 边界条件(空值/类型)\n- 异步/错误处理\n- 性能(重复计算/循环)\n- 可读性(命名/拆分)\n\n问题列表:\n1) 现象:…\n 原因:…\n 修复:…\n2) 现象:…\n 原因:…\n 修复:…\n\n修复片段:\n// ...关键修改代码...`
|
||||
}
|
||||
if (mode.value === 'direct') {
|
||||
return 'Day1:外滩…Day2:迪士尼…Day3:田子坊…(可能太散/不成体系)'
|
||||
}
|
||||
return `规划原则:\n- 交通:地铁优先\n- 节奏:上午景点,下午咖啡/逛街\n- 预算:人均 300-500/天\n\nDay1:外滩 → 南京路 → 人民广场\n备选:雨天去博物馆\n\nDay2:豫园 → 城隍庙 → 新天地\n备选:改为室内商场+展览\n\nDay3:武康路 → 安福路 → 徐汇滨江\n备选:去书店/美术馆`
|
||||
})
|
||||
</script>
|
||||
|
||||
.method-card.highlight {
|
||||
border-color: #22c55e;
|
||||
}
|
||||
|
||||
.method-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 15px;
|
||||
background: var(--vp-c-bg-mute);
|
||||
}
|
||||
|
||||
.method-card.highlight .method-header {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
.method-icon {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.method-title {
|
||||
font-weight: bold;
|
||||
font-size: 0.95rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.method-body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.prompt-box {
|
||||
background: var(--vp-c-bg-soft);
|
||||
<style scoped>
|
||||
.cot {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.prompt-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.prompt-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.instruction {
|
||||
color: #22c55e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.result-box {
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.thinking-process {
|
||||
background: #000;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
color: #a1a1aa;
|
||||
}
|
||||
|
||||
.final-answer {
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.badge:not(.success) {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.badge.success {
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.explanation {
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.exp-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
background: var(--vp-c-bg);
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
|
||||
.exp-icon {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
.controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
||||
select { border: 1px solid var(--vp-c-divider); border-radius: 10px; padding: 8px 10px; background: var(--vp-c-bg); color: var(--vp-c-text-1); }
|
||||
.mode { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); padding: 8px 12px; border-radius: 999px; cursor: pointer; }
|
||||
.mode.active { border-color: var(--vp-c-brand); color: var(--vp-c-brand); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
|
||||
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
|
||||
.panel { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 10px; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
|
||||
.panel-title { font-weight: 700; }
|
||||
pre { margin: 0; background: #0b1221; color: #e5e7eb; border-radius: 8px; padding: 12px; font-family: var(--vp-font-family-mono); font-size: 13px; overflow-x: auto; white-space: pre-wrap; }
|
||||
.output { white-space: pre-wrap; line-height: 1.6; }
|
||||
|
||||
.why { background: var(--vp-c-bg); border: 1px dashed var(--vp-c-divider); border-radius: 10px; padding: 12px; }
|
||||
.why-title { font-weight: 700; margin-bottom: 8px; }
|
||||
.why-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; }
|
||||
.why-card { border: 1px solid var(--vp-c-divider); border-radius: 10px; padding: 10px; background: var(--vp-c-bg-soft); }
|
||||
.k { font-weight: 800; }
|
||||
.v { color: var(--vp-c-text-2); font-size: 13px; margin-top: 4px; line-height: 1.5; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,223 +1,131 @@
|
||||
<!--
|
||||
FewShotDemo.vue
|
||||
Few-shot 速懂:不给示例 vs 给示例,AI 的“风格”会不会稳定?
|
||||
|
||||
交互:
|
||||
- 选择目标风格(随意/正式)
|
||||
- 选择是否提供示例
|
||||
- 看提示词和输出如何变化
|
||||
-->
|
||||
<template>
|
||||
<div class="few-shot-demo">
|
||||
<div class="mode-switch">
|
||||
<button :class="{ active: mode === 'zero' }" @click="mode = 'zero'">
|
||||
🎯 Zero-shot(零样本)
|
||||
</button>
|
||||
<button :class="{ active: mode === 'few' }" @click="mode = 'few'">
|
||||
📚 Few-shot(少样本)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="demo-container">
|
||||
<!-- Zero-shot -->
|
||||
<div v-if="mode === 'zero'" class="zero-shot">
|
||||
<div class="prompt-box">
|
||||
<div class="prompt-header">提示词</div>
|
||||
<div class="prompt-content">
|
||||
将<strong>中文</strong>翻译成<strong>英文</strong>:
|
||||
<br><br>
|
||||
"我很好"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="output-box">
|
||||
<div class="output-header">AI 可能的输出</div>
|
||||
<div class="output-content">
|
||||
"I'm fine." 或 "I'm very good." 或 "I am well."
|
||||
<br><br>
|
||||
<span class="comment">❓ 不确定应该用什么语气,可能是正式或随意</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="few">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">示例的力量:让风格“跟你走”</div>
|
||||
<div class="subtitle">你不是让 AI 更聪明,而是让它更像你要的样子。</div>
|
||||
</div>
|
||||
|
||||
<!-- Few-shot -->
|
||||
<div v-else class="few-shot">
|
||||
<div class="prompt-box">
|
||||
<div class="prompt-header">提示词(含示例)</div>
|
||||
<div class="prompt-content">
|
||||
将<strong>中文</strong>翻译成<strong>英文</strong>:
|
||||
<br><br>
|
||||
<div class="examples">
|
||||
<div class="example-item">
|
||||
<span class="input">你好</span> → <span class="output">Hi!</span>
|
||||
</div>
|
||||
<div class="example-item">
|
||||
<span class="input">谢谢</span> → <span class="output">Thanks!</span>
|
||||
</div>
|
||||
<div class="example-item">
|
||||
<span class="input">再见</span> → <span class="output">Bye!</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task">
|
||||
<strong>任务:</strong>"我很好"
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="output-box">
|
||||
<div class="output-header">AI 输出</div>
|
||||
<div class="output-content">
|
||||
"I'm great!"
|
||||
<br><br>
|
||||
<span class="comment success">✨ 通过示例学会了随意的对话风格</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<select v-model="tone">
|
||||
<option value="casual">随意口语</option>
|
||||
<option value="formal">正式书面</option>
|
||||
</select>
|
||||
<button :class="['toggle', { active: withExamples }]" @click="withExamples = !withExamples">
|
||||
{{ withExamples ? '已提供示例' : '不提供示例' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation">
|
||||
<div v-if="mode === 'zero'">
|
||||
<strong>Zero-shot</strong>:不给任何示例,直接让 AI 完成任务。简单但可能不够准确。
|
||||
<div class="grid">
|
||||
<div class="panel">
|
||||
<div class="panel-title">提示词 / Prompt</div>
|
||||
<pre><code>{{ prompt }}</code></pre>
|
||||
</div>
|
||||
<div v-else>
|
||||
<strong>Few-shot</strong>:提供几个示例让 AI 学习规律。示例的质量和数量直接影响输出效果。
|
||||
通常 3-5 个示例就足够了!
|
||||
<div class="panel">
|
||||
<div class="panel-title">AI 输出(示意)</div>
|
||||
<div class="output">{{ output }}</div>
|
||||
<div class="hint">{{ hint }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="examples" v-if="withExamples">
|
||||
<div class="examples-title">示例(AI 会“照着学”)</div>
|
||||
<div class="examples-grid">
|
||||
<div class="ex" v-for="e in examples" :key="e.in">
|
||||
<div class="in">输入:{{ e.in }}</div>
|
||||
<div class="out">输出:{{ e.out }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const mode = ref('zero')
|
||||
const tone = ref('casual')
|
||||
const withExamples = ref(true)
|
||||
|
||||
const examples = computed(() => {
|
||||
if (tone.value === 'casual') {
|
||||
return [
|
||||
{ in: '你好', out: 'Hi~' },
|
||||
{ in: '谢谢', out: '谢啦!' },
|
||||
{ in: '再见', out: '拜拜~' }
|
||||
]
|
||||
}
|
||||
return [
|
||||
{ in: '你好', out: '您好。' },
|
||||
{ in: '谢谢', out: '非常感谢。' },
|
||||
{ in: '再见', out: '再见,祝您一切顺利。' }
|
||||
]
|
||||
})
|
||||
|
||||
const prompt = computed(() => {
|
||||
const base = '将中文翻译成英文。'
|
||||
const task = '输入:我很好'
|
||||
if (!withExamples.value) return `${base}\n${task}`
|
||||
const lines = [base, '示例:']
|
||||
for (const e of examples.value) {
|
||||
lines.push(`- ${e.in} -> ${e.out}`)
|
||||
}
|
||||
lines.push(task)
|
||||
return lines.join('\n')
|
||||
})
|
||||
|
||||
const output = computed(() => {
|
||||
if (!withExamples.value) {
|
||||
return tone.value === 'casual' ? "I'm fine." : 'I am fine.'
|
||||
}
|
||||
return tone.value === 'casual' ? "I'm good!" : 'I am doing well.'
|
||||
})
|
||||
|
||||
const hint = computed(() => {
|
||||
if (!withExamples.value) return '没有示例:AI 可能随便选一种语气。'
|
||||
return '有示例:AI 更容易“保持同一种语气”。'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.few-shot-demo {
|
||||
.few {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.mode-switch {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mode-switch button {
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
.controls { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||
select { border: 1px solid var(--vp-c-divider); border-radius: 10px; padding: 8px 10px; background: var(--vp-c-bg); color: var(--vp-c-text-1); }
|
||||
.toggle { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); padding: 8px 12px; border-radius: 999px; cursor: pointer; }
|
||||
.toggle.active { border-color: var(--vp-c-brand); color: var(--vp-c-brand); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
|
||||
|
||||
.mode-switch button.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
|
||||
.panel { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 10px; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
|
||||
.panel-title { font-weight: 700; }
|
||||
pre { margin: 0; background: #0b1221; color: #e5e7eb; border-radius: 8px; padding: 12px; font-family: var(--vp-font-family-mono); font-size: 13px; overflow-x: auto; white-space: pre-wrap; }
|
||||
.output { white-space: pre-wrap; line-height: 1.6; }
|
||||
.hint { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
|
||||
.demo-container {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.prompt-box,
|
||||
.output-box {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.prompt-header,
|
||||
.output-header {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.prompt-content,
|
||||
.output-content {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.examples {
|
||||
background: #000;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.example-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.example-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.example-item .input {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.example-item .output {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.task {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
text-align: center;
|
||||
font-size: 2rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.comment {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.comment:not(.success) {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.comment.success {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.explanation {
|
||||
padding: 12px;
|
||||
background: var(--vp-c-bg-mute);
|
||||
border-radius: 6px;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.examples { background: var(--vp-c-bg); border: 1px dashed var(--vp-c-divider); border-radius: 10px; padding: 12px; }
|
||||
.examples-title { font-weight: 700; margin-bottom: 8px; }
|
||||
.examples-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; }
|
||||
.ex { border: 1px solid var(--vp-c-divider); border-radius: 10px; padding: 10px; background: var(--vp-c-bg-soft); }
|
||||
.in { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
.out { font-weight: 700; margin-top: 4px; }
|
||||
</style>
|
||||
|
||||
+172
-169
@@ -1,201 +1,204 @@
|
||||
<!--
|
||||
PromptComparisonDemo.vue
|
||||
“清晰 vs 模糊”对比:把一个提示词拆成四块(任务/上下文/要求/输出),并展示哪些块缺失会导致输出跑偏。
|
||||
-->
|
||||
<template>
|
||||
<div class="prompt-comparison">
|
||||
<div class="comparison-container">
|
||||
<!-- Bad Prompt -->
|
||||
<div class="prompt-card bad">
|
||||
<div class="card-header">
|
||||
<span class="card-icon">❌</span>
|
||||
<span class="card-title">模糊提示词</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="prompt-text">"写个文章"</div>
|
||||
<div class="result-box">
|
||||
<div class="result-label">AI 输出:</div>
|
||||
<div class="result-content">
|
||||
好的,这是一篇关于某个主题的文章...
|
||||
<br><br>
|
||||
<span class="result-comment">❓ 不清楚要写什么主题、风格、长度</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cmp">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">清晰 vs 模糊:差的不是“废话”,而是“缺项”</div>
|
||||
<div class="subtitle">勾选你想补充的信息,看看输出会怎么变。</div>
|
||||
</div>
|
||||
|
||||
<!-- Good Prompt -->
|
||||
<div class="prompt-card good">
|
||||
<div class="card-header">
|
||||
<span class="card-icon">✅</span>
|
||||
<span class="card-title">清晰提示词</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="prompt-text">
|
||||
"请以<strong>技术博客</strong>的形式,写一篇关于<strong>提示词工程</strong>的文章。
|
||||
目标读者:<strong>初学者</strong>。字数:<strong>800-1000 字</strong>。
|
||||
包含<strong>3 个实用技巧</strong>和<strong>代码示例</strong>。"
|
||||
</div>
|
||||
<div class="result-box">
|
||||
<div class="result-label">AI 输出:</div>
|
||||
<div class="result-content">
|
||||
# 提示词工程入门指南
|
||||
<br><br>
|
||||
作为 AI 时代的核心技能...
|
||||
<br><br>
|
||||
<span class="result-comment success">✨ 符合所有要求,输出精准</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task">
|
||||
<select v-model="task">
|
||||
<option value="blog">写一段技术博客开头</option>
|
||||
<option value="json">把内容输出成 JSON</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="key-points">
|
||||
<div class="point">
|
||||
<span class="point-icon">🎯</span>
|
||||
<span><strong>明确目标</strong>:说明要做什么(写文章、写代码、分析)</span>
|
||||
<div class="options">
|
||||
<label><input type="checkbox" v-model="useRole" /> 角色(你是谁)</label>
|
||||
<label><input type="checkbox" v-model="useAudience" /> 受众(写给谁)</label>
|
||||
<label><input type="checkbox" v-model="useConstraints" /> 约束(长度/要点数)</label>
|
||||
<label><input type="checkbox" v-model="useFormat" /> 输出格式(JSON/列表)</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="panel">
|
||||
<div class="panel-title">你给 AI 的提示词</div>
|
||||
<pre><code>{{ prompt }}</code></pre>
|
||||
<div class="checklist">
|
||||
<div class="item" v-for="i in checklist" :key="i.text">
|
||||
<span :class="['dot', i.ok ? 'ok' : 'bad']"></span>
|
||||
<span>{{ i.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="point">
|
||||
<span class="point-icon">📋</span>
|
||||
<span><strong>提供细节</strong>:主题、风格、长度、格式等具体要求</span>
|
||||
</div>
|
||||
<div class="point">
|
||||
<span class="point-icon">👥</span>
|
||||
<span><strong>指定受众</strong>:说明目标读者(初学者/专家/儿童等)</span>
|
||||
<div class="panel">
|
||||
<div class="panel-title">AI 输出(示意)</div>
|
||||
<div class="output">{{ output }}</div>
|
||||
<div class="warn" v-if="warnings.length">
|
||||
<div class="warn-title">可能的问题</div>
|
||||
<ul>
|
||||
<li v-for="w in warnings" :key="w">{{ w }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.prompt-comparison {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 20px 0;
|
||||
}
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
.comparison-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
const task = ref('blog')
|
||||
const useRole = ref(false)
|
||||
const useAudience = ref(true)
|
||||
const useConstraints = ref(true)
|
||||
const useFormat = ref(false)
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.comparison-container {
|
||||
grid-template-columns: 1fr;
|
||||
const prompt = computed(() => {
|
||||
if (task.value === 'blog') {
|
||||
const lines = []
|
||||
if (useRole.value) lines.push('你是资深前端工程师。')
|
||||
lines.push('请写一段技术博客的开头,主题:提示词工程。')
|
||||
if (useAudience.value) lines.push('目标读者:零基础新手。')
|
||||
if (useConstraints.value) lines.push('要求:80-120 字,口语化,带一个生活类比。')
|
||||
if (useFormat.value) lines.push('输出:只输出一段文字,不要标题。')
|
||||
return lines.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
.prompt-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
// json task
|
||||
const lines = []
|
||||
if (useRole.value) lines.push('你是信息抽取助手。')
|
||||
lines.push('从下面这段文字中提取关键信息。')
|
||||
if (useAudience.value) lines.push('用途:给产品经理快速阅读。')
|
||||
if (useConstraints.value) lines.push('要求:提取 3-5 个关键词 + 1 句摘要。')
|
||||
if (useFormat.value) {
|
||||
lines.push('输出格式(JSON):')
|
||||
lines.push('{')
|
||||
lines.push(' "summary": "...",')
|
||||
lines.push(' "keywords": ["..."]')
|
||||
lines.push('}')
|
||||
}
|
||||
lines.push('输入:')
|
||||
lines.push('“提示词工程能显著提升模型输出质量,但需要清晰任务、约束和格式。”')
|
||||
return lines.join('\n')
|
||||
})
|
||||
|
||||
.prompt-card.bad {
|
||||
border-color: #ef4444;
|
||||
}
|
||||
const checklist = computed(() => [
|
||||
{ text: '任务清晰(要做什么)', ok: true },
|
||||
{ text: '角色(用什么口吻)', ok: useRole.value },
|
||||
{ text: '受众/用途(给谁用)', ok: useAudience.value },
|
||||
{ text: '约束(长度/数量/范围)', ok: useConstraints.value },
|
||||
{ text: '输出格式(如何交付)', ok: useFormat.value }
|
||||
])
|
||||
|
||||
.prompt-card.good {
|
||||
border-color: #22c55e;
|
||||
}
|
||||
const warnings = computed(() => {
|
||||
const w = []
|
||||
if (!useAudience.value) w.push('语气可能过专业或太泛')
|
||||
if (!useConstraints.value) w.push('长度/结构可能不稳定')
|
||||
if (task.value === 'json' && !useFormat.value) w.push('可能输出成一大段话,不是 JSON')
|
||||
if (task.value === 'blog' && !useFormat.value) w.push('可能加标题/分段,超出预期')
|
||||
return w
|
||||
})
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 15px;
|
||||
background: var(--vp-c-bg-mute);
|
||||
}
|
||||
const output = computed(() => {
|
||||
if (task.value === 'blog') {
|
||||
if (warnings.value.length >= 2) {
|
||||
return '提示词工程是一种与 AI 沟通的方法,它可以帮助你获得更好的输出......(可能偏长/风格不稳)'
|
||||
}
|
||||
return '把 AI 当成新来的同事:你说得越清楚,它越不容易跑偏。提示词工程就是把“要做什么、给谁、怎么交付”一次说明白。'
|
||||
}
|
||||
|
||||
.bad .card-header {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
// json
|
||||
if (!useFormat.value) {
|
||||
return '这段文字主要讲提示词工程的重要性,并强调需要清晰任务、约束和格式……(但不是 JSON)'
|
||||
}
|
||||
return `{\n \"summary\": \"提示词工程能提升输出质量,关键在于清晰任务、约束与格式。\",\n \"keywords\": [\"提示词工程\", \"任务清晰\", \"约束\", \"格式\"]\n}`
|
||||
})
|
||||
</script>
|
||||
|
||||
.good .card-header {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-weight: bold;
|
||||
font-size: 0.95rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.prompt-text {
|
||||
background: var(--vp-c-bg-soft);
|
||||
<style scoped>
|
||||
.cmp {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.result-box {
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
|
||||
select {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px;
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.result-comment {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.result-comment:not(.success) {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.result-comment.success {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.key-points {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.point {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
background: var(--vp-c-bg);
|
||||
.panel-title { font-weight: 700; }
|
||||
pre {
|
||||
margin: 0;
|
||||
background: #0b1221;
|
||||
color: #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 13px;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.point-icon {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
.checklist { display: grid; gap: 6px; }
|
||||
.item { display: flex; gap: 8px; align-items: center; color: var(--vp-c-text-2); font-size: 13px; }
|
||||
.dot { width: 10px; height: 10px; border-radius: 50%; }
|
||||
.dot.ok { background: #22c55e; }
|
||||
.dot.bad { background: #ef4444; }
|
||||
|
||||
.output { white-space: pre-wrap; line-height: 1.6; }
|
||||
.warn { border-top: 1px dashed var(--vp-c-divider); padding-top: 10px; }
|
||||
.warn-title { font-weight: 700; margin-bottom: 6px; }
|
||||
ul { margin: 0; padding-left: 18px; color: var(--vp-c-text-2); }
|
||||
</style>
|
||||
|
||||
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
<!--
|
||||
PromptQuickStartDemo.vue
|
||||
提示词“先玩后讲”快速体验:同一任务,切换提示词写法,看输出质量变化。
|
||||
|
||||
交互:
|
||||
- 选择任务(写文案/总结/写代码)
|
||||
- 选择提示词等级(随口一句 / 清晰版 / 专业版)
|
||||
- 展示“你写的提示词”和“AI 输出”,并提示改进点
|
||||
-->
|
||||
<template>
|
||||
<div class="quick">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">先玩一下:同一个需求,换一种说法</div>
|
||||
<div class="subtitle">你改的不是“字数”,而是“边界”和“标准”。</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<select v-model="taskId">
|
||||
<option v-for="t in tasks" :key="t.id" :value="t.id">
|
||||
{{ t.label }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="levels">
|
||||
<button
|
||||
v-for="l in levels"
|
||||
:key="l.id"
|
||||
:class="['level', { active: levelId === l.id }]"
|
||||
@click="levelId = l.id"
|
||||
>
|
||||
{{ l.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="panel">
|
||||
<div class="panel-title">提示词 / Prompt</div>
|
||||
<pre><code>{{ prompt }}</code></pre>
|
||||
<div class="hint">{{ promptHint }}</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-title">AI 输出 / Output(示意)</div>
|
||||
<div class="output">{{ output }}</div>
|
||||
<div class="hint">{{ outputHint }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tips">
|
||||
<div class="tip" v-for="t in tips" :key="t.title">
|
||||
<div class="tip-title">{{ t.title }}</div>
|
||||
<div class="tip-body">{{ t.body }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const tasks = [
|
||||
{ id: 'copy', label: '写一段小红书文案' },
|
||||
{ id: 'summary', label: '把一段文字总结成要点' },
|
||||
{ id: 'code', label: '写一个小函数' }
|
||||
]
|
||||
|
||||
const levels = [
|
||||
{ id: 'vague', label: '随口一句' },
|
||||
{ id: 'clear', label: '清晰版' },
|
||||
{ id: 'pro', label: '专业版' }
|
||||
]
|
||||
|
||||
const taskId = ref('copy')
|
||||
const levelId = ref('vague')
|
||||
|
||||
const prompt = computed(() => {
|
||||
if (taskId.value === 'copy') {
|
||||
if (levelId.value === 'vague') return '写个咖啡杯文案'
|
||||
if (levelId.value === 'clear')
|
||||
return '写一段小红书风格文案,主题:保温咖啡杯。语气:轻松。长度:120-160 字。'
|
||||
return `你是小红书资深种草博主。\n任务:写一段保温咖啡杯的种草文案。\n受众:通勤上班族。\n要求:\n- 120-160 字\n- 3 个卖点(颜值/密封/保温)\n- 结尾加一句行动号召\n输出:一段中文文案,不要标题。`
|
||||
}
|
||||
if (taskId.value === 'summary') {
|
||||
if (levelId.value === 'vague') return '帮我总结一下这段文字'
|
||||
if (levelId.value === 'clear')
|
||||
return '把下面内容总结成 3-5 个要点,每点不超过 15 个字。'
|
||||
return `任务:把输入文本总结成要点。\n要求:\n- 5 个以内\n- 每点 <= 15 字\n- 只输出要点列表,不要解释\n格式:Markdown 无序列表`
|
||||
}
|
||||
// code
|
||||
if (levelId.value === 'vague') return '写个排序函数'
|
||||
if (levelId.value === 'clear')
|
||||
return '用 JavaScript 写一个快速排序函数,并给一个使用示例。'
|
||||
return `你是资深前端工程师。\n任务:实现 quickSort(arr)。\n要求:\n- 纯函数(不修改原数组)\n- 处理重复值\n- 代码加简短注释\n- 给一个示例输入输出\n输出:只给 JS 代码块`
|
||||
})
|
||||
|
||||
const output = computed(() => {
|
||||
if (taskId.value === 'copy') {
|
||||
if (levelId.value === 'vague') return '这是一款很好用的咖啡杯,适合日常使用...'
|
||||
if (levelId.value === 'clear')
|
||||
return '早八通勤救星!这只保温杯颜值在线,放包里不漏,热咖啡到下午还温温的...'
|
||||
return '通勤党必备!奶油配色超耐看,密封圈一拧就稳,放包里也不怕洒;保温够久,早上冲的拿铁下午还是温热...想要链接评论区见~'
|
||||
}
|
||||
if (taskId.value === 'summary') {
|
||||
if (levelId.value === 'vague') return '这段文字主要讲了……(可能很长)'
|
||||
if (levelId.value === 'clear')
|
||||
return '- 核心观点 1\n- 核心观点 2\n- 核心观点 3'
|
||||
return '- 关键结论\n- 主要原因\n- 影响范围\n- 建议行动'
|
||||
}
|
||||
// code
|
||||
if (levelId.value === 'vague') return 'function sort(arr) { /* ... */ }'
|
||||
if (levelId.value === 'clear')
|
||||
return 'function quickSort(arr) { /* ... */ }\nconsole.log(quickSort([3,1,2]))'
|
||||
return `function quickSort(arr) {\n const a = [...arr]\n if (a.length <= 1) return a\n const pivot = a[0]\n const left = a.slice(1).filter(x => x < pivot)\n const right = a.slice(1).filter(x => x >= pivot)\n return [...quickSort(left), pivot, ...quickSort(right)]\n}\n\nconsole.log(quickSort([3, 1, 2, 2])) // [1,2,2,3]`
|
||||
})
|
||||
|
||||
const promptHint = computed(() => {
|
||||
if (levelId.value === 'vague') return '问题:AI 不知道你要什么标准。'
|
||||
if (levelId.value === 'clear') return '好一点:有风格/长度,但仍缺少“检查标准”。'
|
||||
return '最好:角色 + 任务 + 要求 + 输出格式,AI 很难跑偏。'
|
||||
})
|
||||
|
||||
const outputHint = computed(() => {
|
||||
if (levelId.value === 'vague') return '常见结果:泛泛而谈、风格不稳、格式不对。'
|
||||
if (levelId.value === 'clear') return '常见结果:更像你要的,但细节/格式可能还会飘。'
|
||||
return '常见结果:风格稳定、结构清晰、可直接复制使用。'
|
||||
})
|
||||
|
||||
const tips = computed(() => {
|
||||
if (levelId.value === 'vague') {
|
||||
return [
|
||||
{ title: '先补 3 件事', body: '你要做什么?给谁看?最后要什么格式?' },
|
||||
{ title: '别怕写长', body: '长不是目的,“可检查”才是目的。' }
|
||||
]
|
||||
}
|
||||
if (levelId.value === 'clear') {
|
||||
return [
|
||||
{ title: '再加一条', body: '加“输出格式”或“要点数量”,能明显更稳。' },
|
||||
{ title: '再加一个例子', body: '给 1 个示例,AI 会更像你的口吻。' }
|
||||
]
|
||||
}
|
||||
return [
|
||||
{ title: '记住模板', body: '角色 / 任务 / 输入 / 要求 / 输出格式。' },
|
||||
{ title: '写完就测', body: '同一输入跑 2-3 次,看是否稳定。' }
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.quick {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.title { font-weight: 800; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 13px; }
|
||||
|
||||
.controls { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||
select {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px;
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.levels { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.level {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.level.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
|
||||
.panel { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 10px; padding: 12px; }
|
||||
.panel-title { font-weight: 700; margin-bottom: 6px; }
|
||||
pre { margin: 0; background: #0b1221; color: #e5e7eb; border-radius: 8px; padding: 12px; font-family: var(--vp-font-family-mono); font-size: 13px; overflow-x: auto; white-space: pre-wrap; }
|
||||
.output { color: var(--vp-c-text-1); white-space: pre-wrap; line-height: 1.6; }
|
||||
.hint { margin-top: 6px; color: var(--vp-c-text-2); font-size: 13px; }
|
||||
|
||||
.tips { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; }
|
||||
.tip { background: var(--vp-c-bg); border: 1px dashed var(--vp-c-divider); border-radius: 10px; padding: 10px; }
|
||||
.tip-title { font-weight: 700; margin-bottom: 4px; }
|
||||
.tip-body { color: var(--vp-c-text-2); font-size: 13px; line-height: 1.5; }
|
||||
</style>
|
||||
|
||||
@@ -45,7 +45,9 @@
|
||||
<h3>ci-email-service</h3>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<div class="status-row">Status: <span class="text-success">success</span></div>
|
||||
<div class="status-row">
|
||||
Status: <span class="text-success">success</span>
|
||||
</div>
|
||||
<div class="info-row">Updated: 2024-01-15 14:32:00 UTC</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,10 +171,18 @@ const simulateResize = () => {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.success .icon { color: #22c55e; }
|
||||
.warning .icon { color: #eab308; }
|
||||
.error .icon { color: #ef4444; }
|
||||
.pending .icon { color: #666; }
|
||||
.success .icon {
|
||||
color: #22c55e;
|
||||
}
|
||||
.warning .icon {
|
||||
color: #eab308;
|
||||
}
|
||||
.error .icon {
|
||||
color: #ef4444;
|
||||
}
|
||||
.pending .icon {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
@@ -190,7 +200,9 @@ const simulateResize = () => {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.text-success { color: #22c55e; }
|
||||
.text-success {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
color: #666;
|
||||
|
||||
@@ -37,7 +37,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="diagram-container" @click="nextStep" :class="{ 'clickable': currentStep < totalSteps }">
|
||||
<div
|
||||
class="diagram-container"
|
||||
@click="nextStep"
|
||||
:class="{ clickable: currentStep < totalSteps }"
|
||||
>
|
||||
<!-- Click Overlay Hint -->
|
||||
<div class="click-overlay" v-if="currentStep === 0">
|
||||
<div class="click-hint">
|
||||
@@ -71,7 +75,9 @@
|
||||
<div class="node terminal" :class="{ active: activeNode === 'terminal' }">
|
||||
<div class="node-title">TERMINAL (终端)</div>
|
||||
<div class="screen">
|
||||
<div v-for="(line, i) in terminalLines" :key="i" class="line">{{ line }}</div>
|
||||
<div v-for="(line, i) in terminalLines" :key="i" class="line">
|
||||
{{ line }}
|
||||
</div>
|
||||
<div class="line input-line">
|
||||
<span class="prompt">$</span>
|
||||
<span class="typing">{{ currentInput }}</span>
|
||||
@@ -82,7 +88,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Connections -->
|
||||
<div class="connection t-s" :class="{ active: packetState === 't-to-s' || packetState === 's-to-t' }">
|
||||
<div
|
||||
class="connection t-s"
|
||||
:class="{
|
||||
active: packetState === 't-to-s' || packetState === 's-to-t'
|
||||
}"
|
||||
>
|
||||
<div class="line-path"></div>
|
||||
<div class="data-label" v-if="packetState === 't-to-s'">
|
||||
<span class="icon">➡️</span> Bytes
|
||||
@@ -104,7 +115,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Connections -->
|
||||
<div class="connection s-k" :class="{ active: packetState === 's-to-k' || packetState === 'k-to-s' }">
|
||||
<div
|
||||
class="connection s-k"
|
||||
:class="{
|
||||
active: packetState === 's-to-k' || packetState === 'k-to-s'
|
||||
}"
|
||||
>
|
||||
<div class="line-path"></div>
|
||||
<div class="data-label" v-if="packetState === 's-to-k'">
|
||||
<span class="icon">➡️</span> Syscall
|
||||
@@ -128,9 +144,15 @@
|
||||
|
||||
<div class="controls">
|
||||
<div class="btn-group">
|
||||
<button class="btn primary" @click="nextStep" :disabled="currentStep >= totalSteps">
|
||||
<button
|
||||
class="btn primary"
|
||||
@click="nextStep"
|
||||
:disabled="currentStep >= totalSteps"
|
||||
>
|
||||
<span v-if="currentStep === 0">▶️ Start Simulation / 开始演示</span>
|
||||
<span v-else-if="currentStep < totalSteps">Next Step / 下一步 ({{ currentStep }}/{{ totalSteps }}) ➡️</span>
|
||||
<span v-else-if="currentStep < totalSteps"
|
||||
>Next Step / 下一步 ({{ currentStep }}/{{ totalSteps }}) ➡️</span
|
||||
>
|
||||
<span v-else>✅ Done / 完成 (Reset)</span>
|
||||
</button>
|
||||
<button class="btn secondary" @click="reset" v-if="currentStep > 0">
|
||||
@@ -158,7 +180,10 @@
|
||||
</div>
|
||||
<div class="step-info placeholder" v-else>
|
||||
<div class="step-desc">
|
||||
<div class="en">Click "Start Simulation" to see how the command 'ls' travels through the system.</div>
|
||||
<div class="en">
|
||||
Click "Start Simulation" to see how the command 'ls' travels through
|
||||
the system.
|
||||
</div>
|
||||
<div class="zh">点击“开始演示”查看 'ls' 命令如何在系统中流转。</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,12 +206,13 @@ const kernelIcon = ref('💤')
|
||||
|
||||
const steps = [
|
||||
{
|
||||
titleEn: "1. User Input",
|
||||
titleZh: "1. 用户输入",
|
||||
descEn: "You type 'ls' in the terminal window. The terminal captures your keystrokes.",
|
||||
titleEn: '1. User Input',
|
||||
titleZh: '1. 用户输入',
|
||||
descEn:
|
||||
"You type 'ls' in the terminal window. The terminal captures your keystrokes.",
|
||||
descZh: "你在终端窗口输入 'ls'。终端会捕获你的按键操作。",
|
||||
techEn: "Terminal buffers input in 'Cooked Mode' until you press Enter.",
|
||||
techZh: "终端在“加工模式 (Cooked Mode)”下缓冲输入,直到你按下回车键。",
|
||||
techZh: '终端在“加工模式 (Cooked Mode)”下缓冲输入,直到你按下回车键。',
|
||||
action: async () => {
|
||||
activeNode.value = 'terminal'
|
||||
currentInput.value = 'l'
|
||||
@@ -195,12 +221,13 @@ const steps = [
|
||||
}
|
||||
},
|
||||
{
|
||||
titleEn: "2. Transmission",
|
||||
titleZh: "2. 传输",
|
||||
descEn: "The Terminal sends the characters 'l', 's', and 'Enter' to the Shell.",
|
||||
titleEn: '2. Transmission',
|
||||
titleZh: '2. 传输',
|
||||
descEn:
|
||||
"The Terminal sends the characters 'l', 's', and 'Enter' to the Shell.",
|
||||
descZh: "终端将字符 'l'、's' 和 '回车' 发送给 Shell。",
|
||||
techEn: "Data travels via standard input (stdin) as a byte stream.",
|
||||
techZh: "数据通过标准输入 (stdin) 以字节流的形式传输。",
|
||||
techEn: 'Data travels via standard input (stdin) as a byte stream.',
|
||||
techZh: '数据通过标准输入 (stdin) 以字节流的形式传输。',
|
||||
action: async () => {
|
||||
packetState.value = 't-to-s'
|
||||
await wait(1000)
|
||||
@@ -208,10 +235,10 @@ const steps = [
|
||||
}
|
||||
},
|
||||
{
|
||||
titleEn: "3. Shell Parsing",
|
||||
titleZh: "3. Shell 解析",
|
||||
descEn: "The Shell (Waiter) translates your command for the Kernel.",
|
||||
descZh: "Shell(服务员)接收指令,并将其翻译成内核能听懂的请求。",
|
||||
titleEn: '3. Shell Parsing',
|
||||
titleZh: '3. Shell 解析',
|
||||
descEn: 'The Shell (Waiter) translates your command for the Kernel.',
|
||||
descZh: 'Shell(服务员)接收指令,并将其翻译成内核能听懂的请求。',
|
||||
techEn: "Shell tokenizes input, finds the 'ls' executable in $PATH.",
|
||||
techZh: "Shell 对输入进行分词,并在 $PATH 环境变量中查找 'ls' 可执行文件。",
|
||||
action: async () => {
|
||||
@@ -221,12 +248,12 @@ const steps = [
|
||||
}
|
||||
},
|
||||
{
|
||||
titleEn: "4. System Call",
|
||||
titleZh: "4. 系统调用",
|
||||
descEn: "The Shell asks the Kernel to read the file list from the disk.",
|
||||
descZh: "Shell 请求内核从磁盘读取文件列表。",
|
||||
techEn: "Shell invokes `execve()` and `getdents()` system calls.",
|
||||
techZh: "Shell 调用 `execve()` 和 `getdents()` 等系统调用。",
|
||||
titleEn: '4. System Call',
|
||||
titleZh: '4. 系统调用',
|
||||
descEn: 'The Shell asks the Kernel to read the file list from the disk.',
|
||||
descZh: 'Shell 请求内核从磁盘读取文件列表。',
|
||||
techEn: 'Shell invokes `execve()` and `getdents()` system calls.',
|
||||
techZh: 'Shell 调用 `execve()` 和 `getdents()` 等系统调用。',
|
||||
action: async () => {
|
||||
packetState.value = 's-to-k'
|
||||
await wait(1000)
|
||||
@@ -234,12 +261,12 @@ const steps = [
|
||||
}
|
||||
},
|
||||
{
|
||||
titleEn: "5. Kernel Execution",
|
||||
titleZh: "5. 内核执行",
|
||||
descEn: "The Kernel (Kitchen) executes the request by accessing hardware.",
|
||||
descZh: "内核(后厨)直接操作硬件(如磁盘)来执行实际任务。",
|
||||
techEn: "Kernel driver accesses the file system (APFS/ext4).",
|
||||
techZh: "内核驱动程序访问文件系统 (APFS/ext4)。",
|
||||
titleEn: '5. Kernel Execution',
|
||||
titleZh: '5. 内核执行',
|
||||
descEn: 'The Kernel (Kitchen) executes the request by accessing hardware.',
|
||||
descZh: '内核(后厨)直接操作硬件(如磁盘)来执行实际任务。',
|
||||
techEn: 'Kernel driver accesses the file system (APFS/ext4).',
|
||||
techZh: '内核驱动程序访问文件系统 (APFS/ext4)。',
|
||||
action: async () => {
|
||||
activeNode.value = 'kernel'
|
||||
kernelIcon.value = '💾'
|
||||
@@ -249,12 +276,12 @@ const steps = [
|
||||
}
|
||||
},
|
||||
{
|
||||
titleEn: "6. Returning Data",
|
||||
titleZh: "6. 返回数据",
|
||||
descEn: "The Kernel gives the raw file list back to the Shell.",
|
||||
descZh: "内核将原始文件列表数据返回给 Shell。",
|
||||
techEn: "System call returns with file descriptors/structs.",
|
||||
techZh: "系统调用返回文件描述符或结构体数据。",
|
||||
titleEn: '6. Returning Data',
|
||||
titleZh: '6. 返回数据',
|
||||
descEn: 'The Kernel gives the raw file list back to the Shell.',
|
||||
descZh: '内核将原始文件列表数据返回给 Shell。',
|
||||
techEn: 'System call returns with file descriptors/structs.',
|
||||
techZh: '系统调用返回文件描述符或结构体数据。',
|
||||
action: async () => {
|
||||
kernelStatus.value = 'Idle'
|
||||
kernelIcon.value = '💤'
|
||||
@@ -264,12 +291,13 @@ const steps = [
|
||||
}
|
||||
},
|
||||
{
|
||||
titleEn: "7. Formatting",
|
||||
titleZh: "7. 格式化",
|
||||
descEn: "The Shell formats the raw list into text, adding colors if needed.",
|
||||
descZh: "Shell 将原始列表格式化为文本,并根据需要添加颜色。",
|
||||
techEn: "Shell formats output buffer, adding ANSI color codes.",
|
||||
techZh: "Shell 格式化输出缓冲区,并添加 ANSI 颜色代码。",
|
||||
titleEn: '7. Formatting',
|
||||
titleZh: '7. 格式化',
|
||||
descEn:
|
||||
'The Shell formats the raw list into text, adding colors if needed.',
|
||||
descZh: 'Shell 将原始列表格式化为文本,并根据需要添加颜色。',
|
||||
techEn: 'Shell formats output buffer, adding ANSI color codes.',
|
||||
techZh: 'Shell 格式化输出缓冲区,并添加 ANSI 颜色代码。',
|
||||
action: async () => {
|
||||
activeNode.value = 'shell'
|
||||
shellIcon.value = '🎨'
|
||||
@@ -278,12 +306,12 @@ const steps = [
|
||||
}
|
||||
},
|
||||
{
|
||||
titleEn: "8. Display Output",
|
||||
titleZh: "8. 显示输出",
|
||||
descEn: "The Shell sends the final text back to the Terminal to show you.",
|
||||
descZh: "Shell 将最终文本发送回终端以供显示。",
|
||||
techEn: "Data travels via standard output (stdout) to the TTY.",
|
||||
techZh: "数据通过标准输出 (stdout) 传输到 TTY。",
|
||||
titleEn: '8. Display Output',
|
||||
titleZh: '8. 显示输出',
|
||||
descEn: 'The Shell sends the final text back to the Terminal to show you.',
|
||||
descZh: 'Shell 将最终文本发送回终端以供显示。',
|
||||
techEn: 'Data travels via standard output (stdout) to the TTY.',
|
||||
techZh: '数据通过标准输出 (stdout) 传输到 TTY。',
|
||||
action: async () => {
|
||||
shellStatus.value = 'Idle'
|
||||
shellIcon.value = '💤'
|
||||
@@ -293,12 +321,12 @@ const steps = [
|
||||
}
|
||||
},
|
||||
{
|
||||
titleEn: "9. Render",
|
||||
titleZh: "9. 渲染",
|
||||
descEn: "The Terminal draws the text on the screen grid.",
|
||||
descZh: "终端在屏幕网格上绘制文本。",
|
||||
techEn: "Terminal emulator renders glyphs into the frame buffer.",
|
||||
techZh: "终端模拟器将字形渲染到帧缓冲区中。",
|
||||
titleEn: '9. Render',
|
||||
titleZh: '9. 渲染',
|
||||
descEn: 'The Terminal draws the text on the screen grid.',
|
||||
descZh: '终端在屏幕网格上绘制文本。',
|
||||
techEn: 'Terminal emulator renders glyphs into the frame buffer.',
|
||||
techZh: '终端模拟器将字形渲染到帧缓冲区中。',
|
||||
action: async () => {
|
||||
activeNode.value = 'terminal'
|
||||
terminalLines.value = ['file1.txt photo.jpg', 'notes.md']
|
||||
@@ -307,10 +335,9 @@ const steps = [
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
const totalSteps = steps.length
|
||||
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
const nextStep = async () => {
|
||||
if (currentStep.value >= totalSteps) {
|
||||
@@ -446,9 +473,15 @@ const reset = () => {
|
||||
}
|
||||
|
||||
@keyframes pulse-bg {
|
||||
0% { background: rgba(0, 0, 0, 0.4); }
|
||||
50% { background: rgba(0, 0, 0, 0.2); }
|
||||
100% { background: rgba(0, 0, 0, 0.4); }
|
||||
0% {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
50% {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
100% {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.completed-overlay {
|
||||
@@ -521,7 +554,9 @@ const reset = () => {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.user-space .space-header { color: #22d3ee; }
|
||||
.user-space .space-header {
|
||||
color: #22d3ee;
|
||||
}
|
||||
|
||||
.kernel-space {
|
||||
flex: 1;
|
||||
@@ -531,7 +566,9 @@ const reset = () => {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.kernel-space .space-header { color: #ef4444; }
|
||||
.kernel-space .space-header {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.barrier {
|
||||
width: 2px;
|
||||
@@ -556,8 +593,6 @@ const reset = () => {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.node {
|
||||
background: #18181b;
|
||||
border: 2px solid #27272a;
|
||||
@@ -573,7 +608,7 @@ const reset = () => {
|
||||
|
||||
/* Specific z-index for Shell to prevent it from covering barrier label */
|
||||
.node.shell {
|
||||
z-index: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.node.active {
|
||||
@@ -603,7 +638,8 @@ const reset = () => {
|
||||
color: #71717a;
|
||||
}
|
||||
|
||||
.screen, .process-box {
|
||||
.screen,
|
||||
.process-box {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
@@ -693,18 +729,26 @@ const reset = () => {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
z-index: 10;
|
||||
animation: pop-in 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
@keyframes pop-in {
|
||||
from { opacity: 0; transform: translate(-50%, 5px); }
|
||||
to { opacity: 1; transform: translate(-50%, 0); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, 5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% { opacity: 0; }
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
@@ -795,8 +839,14 @@ const reset = () => {
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; transform: translateY(5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
@@ -836,13 +886,25 @@ const reset = () => {
|
||||
}
|
||||
|
||||
@keyframes travel-vertical {
|
||||
0% { top: 0; transform: translateY(0); }
|
||||
100% { top: 100%; transform: translateY(-100%); }
|
||||
0% {
|
||||
top: 0;
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
top: 100%;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes travel-vertical-back {
|
||||
0% { top: 100%; transform: translateY(-100%); }
|
||||
100% { top: 0; transform: translateY(0); }
|
||||
0% {
|
||||
top: 100%;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
100% {
|
||||
top: 0;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -11,14 +11,27 @@
|
||||
<div class="screen-container">
|
||||
<!-- Main Buffer (Layer 0) -->
|
||||
<div class="buffer main-buffer">
|
||||
<div class="line"><span class="prompt">➜</span> <span class="cmd">ls -la</span></div>
|
||||
<div class="line">
|
||||
<span class="prompt">➜</span> <span class="cmd">ls -la</span>
|
||||
</div>
|
||||
<div class="line output">total 16</div>
|
||||
<div class="line output">drwxr-xr-x 2 user staff 64 Jan 15 10:00 .</div>
|
||||
<div class="line output">drwxr-xr-x 4 user staff 128 Jan 15 09:55 ..</div>
|
||||
<div class="line output">-rw-r--r-- 1 user staff 1024 Jan 15 10:00 notes.txt</div>
|
||||
<div class="line"><span class="prompt">➜</span> <span class="cmd">echo "Hello World"</span></div>
|
||||
<div class="line output">
|
||||
drwxr-xr-x 2 user staff 64 Jan 15 10:00 .
|
||||
</div>
|
||||
<div class="line output">
|
||||
drwxr-xr-x 4 user staff 128 Jan 15 09:55 ..
|
||||
</div>
|
||||
<div class="line output">
|
||||
-rw-r--r-- 1 user staff 1024 Jan 15 10:00 notes.txt
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="prompt">➜</span>
|
||||
<span class="cmd">echo "Hello World"</span>
|
||||
</div>
|
||||
<div class="line output">Hello World</div>
|
||||
<div class="line"><span class="prompt">➜</span> <span class="cmd">vim notes.txt</span></div>
|
||||
<div class="line">
|
||||
<span class="prompt">➜</span> <span class="cmd">vim notes.txt</span>
|
||||
</div>
|
||||
<!-- The cursor would be here if not in vim -->
|
||||
</div>
|
||||
|
||||
@@ -30,12 +43,21 @@
|
||||
<span class="modified">[+]</span>
|
||||
</div>
|
||||
<div class="vim-body">
|
||||
<div class="line-num">1</div><div class="code">This is a text file opened in Vim.</div>
|
||||
<div class="line-num">2</div><div class="code"></div>
|
||||
<div class="line-num">3</div><div class="code">Notice how this interface takes up</div>
|
||||
<div class="line-num">4</div><div class="code">the entire screen?</div>
|
||||
<div class="line-num">5</div><div class="code"></div>
|
||||
<div class="line-num">6</div><div class="code">It is running in the <span class="highlight">Alternate Buffer</span>.</div>
|
||||
<div class="line-num">1</div>
|
||||
<div class="code">This is a text file opened in Vim.</div>
|
||||
<div class="line-num">2</div>
|
||||
<div class="code"></div>
|
||||
<div class="line-num">3</div>
|
||||
<div class="code">Notice how this interface takes up</div>
|
||||
<div class="line-num">4</div>
|
||||
<div class="code">the entire screen?</div>
|
||||
<div class="line-num">5</div>
|
||||
<div class="code"></div>
|
||||
<div class="line-num">6</div>
|
||||
<div class="code">
|
||||
It is running in the
|
||||
<span class="highlight">Alternate Buffer</span>.
|
||||
</div>
|
||||
<div class="line-num">~</div>
|
||||
<div class="line-num">~</div>
|
||||
</div>
|
||||
@@ -55,14 +77,20 @@
|
||||
<div class="description">
|
||||
<div v-if="!isAltBufferActive">
|
||||
<p><strong>Current: Primary Buffer (主缓冲区)</strong></p>
|
||||
<p>This is the standard scrolling log. Commands are executed line by line.</p>
|
||||
<p>
|
||||
This is the standard scrolling log. Commands are executed line by
|
||||
line.
|
||||
</p>
|
||||
<button class="action-btn" @click="openVim">
|
||||
Execute `vim notes.txt`
|
||||
</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p><strong>Current: Alternate Buffer (备用缓冲区)</strong></p>
|
||||
<p>A separate "canvas" for full-screen apps. It hides the history but doesn't delete it.</p>
|
||||
<p>
|
||||
A separate "canvas" for full-screen apps. It hides the history but
|
||||
doesn't delete it.
|
||||
</p>
|
||||
<button class="action-btn red" @click="quitVim">
|
||||
Execute `:q` (Quit)
|
||||
</button>
|
||||
@@ -101,7 +129,7 @@ const quitVim = () => {
|
||||
background: #1e1e1e;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
@@ -118,9 +146,15 @@ const quitVim = () => {
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.red { background: #ff5f56; }
|
||||
.yellow { background: #ffbd2e; }
|
||||
.green { background: #27c93f; }
|
||||
.red {
|
||||
background: #ff5f56;
|
||||
}
|
||||
.yellow {
|
||||
background: #ffbd2e;
|
||||
}
|
||||
.green {
|
||||
background: #27c93f;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 10px;
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
:key="c"
|
||||
:class="{ active: char === c }"
|
||||
@click="char = c"
|
||||
>{{ c }}</button>
|
||||
>
|
||||
{{ c }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +53,15 @@
|
||||
<div
|
||||
class="color-swatch"
|
||||
:class="{ active: bgColor === 'transparent' }"
|
||||
style="background: linear-gradient(45deg, #222 25%, transparent 25%), linear-gradient(-45deg, #222 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #222 75%), linear-gradient(-45deg, transparent 75%, #222 75%); background-size: 10px 10px; background-color: #111;"
|
||||
style="
|
||||
background:
|
||||
linear-gradient(45deg, #222 25%, transparent 25%),
|
||||
linear-gradient(-45deg, #222 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, #222 75%),
|
||||
linear-gradient(-45deg, transparent 75%, #222 75%);
|
||||
background-size: 10px 10px;
|
||||
background-color: #111;
|
||||
"
|
||||
@click="bgColor = 'transparent'"
|
||||
></div>
|
||||
<div
|
||||
@@ -69,11 +79,11 @@
|
||||
<label>ATTRIBUTES</label>
|
||||
<div class="toggles">
|
||||
<label class="toggle">
|
||||
<input type="checkbox" v-model="isBold">
|
||||
<input type="checkbox" v-model="isBold" />
|
||||
<span>Bold</span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" v-model="isUnderline">
|
||||
<input type="checkbox" v-model="isUnderline" />
|
||||
<span>Underline</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -85,13 +95,49 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const chars = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P']
|
||||
const chars = [
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'I',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'M',
|
||||
'N',
|
||||
'O',
|
||||
'P'
|
||||
]
|
||||
const colors = [
|
||||
'#ef4444', '#22c55e', '#eab308', '#3b82f6', '#a855f7', '#06b6d4', '#f3f4f6', '#6b7280',
|
||||
'#f87171', '#4ade80', '#facc15', '#60a5fa', '#c084fc', '#22d3ee', '#ffffff'
|
||||
'#ef4444',
|
||||
'#22c55e',
|
||||
'#eab308',
|
||||
'#3b82f6',
|
||||
'#a855f7',
|
||||
'#06b6d4',
|
||||
'#f3f4f6',
|
||||
'#6b7280',
|
||||
'#f87171',
|
||||
'#4ade80',
|
||||
'#facc15',
|
||||
'#60a5fa',
|
||||
'#c084fc',
|
||||
'#22d3ee',
|
||||
'#ffffff'
|
||||
]
|
||||
const bgColors = [
|
||||
'#000000', '#1f2937', '#111827', '#374151', '#1e3a8a', '#3f2c08', '#310b0b'
|
||||
'#000000',
|
||||
'#1f2937',
|
||||
'#111827',
|
||||
'#374151',
|
||||
'#1e3a8a',
|
||||
'#3f2c08',
|
||||
'#310b0b'
|
||||
]
|
||||
|
||||
const char = ref('A')
|
||||
@@ -208,7 +254,7 @@ const cellStyle = computed(() => ({
|
||||
.color-swatch.active {
|
||||
border-color: #fff;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.toggles {
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
<template>
|
||||
<div class="cooked-raw-demo">
|
||||
<div class="mode-switch">
|
||||
<button
|
||||
:class="{ active: mode === 'cooked' }"
|
||||
@click="setMode('cooked')"
|
||||
>
|
||||
<button :class="{ active: mode === 'cooked' }" @click="setMode('cooked')">
|
||||
🥘 Cooked Mode (Standard)
|
||||
</button>
|
||||
<button
|
||||
:class="{ active: mode === 'raw' }"
|
||||
@click="setMode('raw')"
|
||||
>
|
||||
<button :class="{ active: mode === 'raw' }" @click="setMode('raw')">
|
||||
🥩 Raw Mode (Vim/Games)
|
||||
</button>
|
||||
</div>
|
||||
@@ -28,12 +22,13 @@
|
||||
|
||||
<!-- Visualization -->
|
||||
<div class="flow-diagram">
|
||||
|
||||
<!-- 1. User Input -->
|
||||
<div class="stage user-input" :class="{ focused: isFocused }">
|
||||
<div class="stage-title">1. Keyboard Input</div>
|
||||
<div class="key-visual">
|
||||
<span v-if="lastPressedKey" class="key-cap">{{ lastPressedKey }}</span>
|
||||
<span v-if="lastPressedKey" class="key-cap">{{
|
||||
lastPressedKey
|
||||
}}</span>
|
||||
<span v-else class="placeholder">Type here...</span>
|
||||
</div>
|
||||
<div class="status-text" v-if="!isFocused">Click to focus</div>
|
||||
@@ -42,7 +37,10 @@
|
||||
<div class="arrow">⬇</div>
|
||||
|
||||
<!-- 2. OS Buffer (Only for Cooked) -->
|
||||
<div class="stage buffer" :class="{ disabled: mode === 'raw', active: mode === 'cooked' }">
|
||||
<div
|
||||
class="stage buffer"
|
||||
:class="{ disabled: mode === 'raw', active: mode === 'cooked' }"
|
||||
>
|
||||
<div class="stage-title">
|
||||
2. Line Buffer (Kernel)
|
||||
<span class="badge" v-if="mode === 'cooked'">Active</span>
|
||||
@@ -50,7 +48,9 @@
|
||||
</div>
|
||||
<div class="buffer-content">
|
||||
<template v-if="mode === 'cooked'">
|
||||
<span v-for="(char, i) in buffer" :key="i" class="char">{{ char }}</span>
|
||||
<span v-for="(char, i) in buffer" :key="i" class="char">{{
|
||||
char
|
||||
}}</span>
|
||||
<span class="cursor">_</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -58,7 +58,9 @@
|
||||
</template>
|
||||
</div>
|
||||
<div class="explanation">
|
||||
<span v-if="mode === 'cooked'">Waiting for Enter... (Backspace works)</span>
|
||||
<span v-if="mode === 'cooked'"
|
||||
>Waiting for Enter... (Backspace works)</span
|
||||
>
|
||||
<span v-else>No buffering. Every key is sent immediately.</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,7 +79,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,7 +123,16 @@ const handleKey = (e) => {
|
||||
|
||||
// Clear visual feedback after delay
|
||||
setTimeout(() => {
|
||||
if (lastPressedKey.value === (key === ' ' ? 'Space' : (key === 'Enter' ? 'Enter' : (key === 'Backspace' ? '⌫' : key)))) {
|
||||
if (
|
||||
lastPressedKey.value ===
|
||||
(key === ' '
|
||||
? 'Space'
|
||||
: key === 'Enter'
|
||||
? 'Enter'
|
||||
: key === 'Backspace'
|
||||
? '⌫'
|
||||
: key)
|
||||
) {
|
||||
// lastPressedKey.value = '' // Optional: keep last key visible
|
||||
}
|
||||
}, 500)
|
||||
@@ -345,7 +355,8 @@ const handleRawMode = (e) => {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.cursor, .app-cursor {
|
||||
.cursor,
|
||||
.app-cursor {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
background: currentColor;
|
||||
@@ -353,10 +364,20 @@ const handleRawMode = (e) => {
|
||||
}
|
||||
|
||||
@keyframes pop {
|
||||
0% { transform: scale(0.9); }
|
||||
50% { transform: scale(1.1); }
|
||||
100% { transform: scale(1); }
|
||||
0% {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink { 50% { opacity: 0; } }
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,7 +5,9 @@
|
||||
<div class="controls">
|
||||
<button @click="reset" :disabled="isPlaying">Reset</button>
|
||||
<button @click="togglePlay" class="play-btn">
|
||||
{{ isPlaying ? '⏸ Pause' : (isFinished ? '↺ Replay' : '▶ Play Animation') }}
|
||||
{{
|
||||
isPlaying ? '⏸ Pause' : isFinished ? '↺ Replay' : '▶ Play Animation'
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -15,16 +17,19 @@
|
||||
<div class="label">Input Byte Stream / 输入字节流</div>
|
||||
<div class="stream-track">
|
||||
<div class="stream-window-mask">
|
||||
<div class="stream-content" :style="{ transform: `translateX(-${currentIndex * 40}px)` }">
|
||||
<div
|
||||
class="stream-content"
|
||||
:style="{ transform: `translateX(-${currentIndex * 40}px)` }"
|
||||
>
|
||||
<div
|
||||
v-for="(char, index) in charStream"
|
||||
:key="index"
|
||||
class="char-box"
|
||||
:class="{
|
||||
'active': index === currentIndex,
|
||||
'processed': index < currentIndex,
|
||||
'special': char.isSpecial,
|
||||
'arg': char.isArg
|
||||
active: index === currentIndex,
|
||||
processed: index < currentIndex,
|
||||
special: char.isSpecial,
|
||||
arg: char.isArg
|
||||
}"
|
||||
>
|
||||
<span class="char-val">{{ char.display }}</span>
|
||||
@@ -47,7 +52,10 @@
|
||||
<div class="state-desc">Print Characters</div>
|
||||
</div>
|
||||
<div class="arrow-right">→</div>
|
||||
<div class="state-box warning" :class="{ active: parserState === 'ESCAPE' }">
|
||||
<div
|
||||
class="state-box warning"
|
||||
:class="{ active: parserState === 'ESCAPE' }"
|
||||
>
|
||||
<div class="state-name">ESCAPE MODE</div>
|
||||
<div class="state-desc">Buffer Command...</div>
|
||||
</div>
|
||||
@@ -67,14 +75,18 @@
|
||||
v-for="(char, index) in outputBuffer"
|
||||
:key="index"
|
||||
:style="char.style"
|
||||
>{{ char.val }}</span><span class="cursor">_</span>
|
||||
>{{ char.val }}</span
|
||||
><span class="cursor">_</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation">
|
||||
<p>
|
||||
<span class="badge normal">Normal</span> 模式下,字符直接上屏。
|
||||
<span class="badge escape">Escape</span> 模式下(遇到 <code>ESC</code> 后),终端<strong>停止输出</strong>,开始收集字符作为指令,直到指令结束(如 <code>m</code>)并执行。
|
||||
<span class="badge escape">Escape</span> 模式下(遇到
|
||||
<code>ESC</code>
|
||||
后),终端<strong>停止输出</strong>,开始收集字符作为指令,直到指令结束(如
|
||||
<code>m</code>)并执行。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -173,14 +185,14 @@ const play = async () => {
|
||||
}
|
||||
|
||||
// Small delay to show "Executing" state
|
||||
await new Promise(r => setTimeout(r, 200))
|
||||
await new Promise((r) => setTimeout(r, 200))
|
||||
parserState.value = 'NORMAL'
|
||||
} else {
|
||||
lastAction.value = 'Buffering...'
|
||||
}
|
||||
}
|
||||
|
||||
await new Promise(r => setTimeout(r, 600)) // Animation speed
|
||||
await new Promise((r) => setTimeout(r, 600)) // Animation speed
|
||||
|
||||
// Check playing again after wait
|
||||
if (!isPlaying.value) break
|
||||
@@ -272,8 +284,20 @@ const play = async () => {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
/* Mask gradient to fade edges */
|
||||
mask-image: linear-gradient(to right, transparent, black 40%, black 60%, transparent);
|
||||
-webkit-mask-image: linear-gradient(to right, transparent, black 40%, black 60%, transparent);
|
||||
mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
black 40%,
|
||||
black 60%,
|
||||
transparent
|
||||
);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
black 40%,
|
||||
black 60%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.stream-content {
|
||||
@@ -312,7 +336,7 @@ const play = async () => {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 0 10px rgba(255,255,255,0.5);
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
|
||||
z-index: 10;
|
||||
border-color: #fff;
|
||||
}
|
||||
@@ -339,8 +363,15 @@ const play = async () => {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.char-val { font-size: 14px; font-weight: bold; }
|
||||
.char-code { font-size: 9px; opacity: 0.7; margin-top: 2px; }
|
||||
.char-val {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.char-code {
|
||||
font-size: 9px;
|
||||
opacity: 0.7;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
position: absolute;
|
||||
@@ -350,9 +381,14 @@ const play = async () => {
|
||||
text-align: center;
|
||||
color: #0dbc79;
|
||||
}
|
||||
.arrow { font-size: 20px; line-height: 1; }
|
||||
.pointer-label { font-size: 10px; white-space: nowrap; }
|
||||
|
||||
.arrow {
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
}
|
||||
.pointer-label {
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* State Machine */
|
||||
.parser-state-machine {
|
||||
@@ -389,10 +425,19 @@ const play = async () => {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.state-name { font-weight: bold; font-size: 12px; }
|
||||
.state-desc { font-size: 10px; opacity: 0.8; }
|
||||
.state-name {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
.state-desc {
|
||||
font-size: 10px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.arrow-right { color: #555; font-size: 18px; }
|
||||
.arrow-right {
|
||||
color: #555;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.action-log {
|
||||
margin-left: 20px;
|
||||
@@ -440,10 +485,24 @@ const play = async () => {
|
||||
font-size: 11px;
|
||||
color: #000;
|
||||
}
|
||||
.badge.normal { background: #0dbc79; }
|
||||
.badge.escape { background: #e5e510; }
|
||||
|
||||
@keyframes blink { 50% { opacity: 0; } }
|
||||
@keyframes flash { 0% { background: #333; } 100% { background: #000; } }
|
||||
.badge.normal {
|
||||
background: #0dbc79;
|
||||
}
|
||||
.badge.escape {
|
||||
background: #e5e510;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes flash {
|
||||
0% {
|
||||
background: #333;
|
||||
}
|
||||
100% {
|
||||
background: #000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
></div>
|
||||
</div>
|
||||
<div class="hint-text" v-if="activeColor">
|
||||
Sequence: <span class="code">^[[38;5;{{ palette.indexOf(activeColor) }}m</span>
|
||||
Sequence:
|
||||
<span class="code">^[[38;5;{{ palette.indexOf(activeColor) }}m</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,25 +86,32 @@
|
||||
<div class="preview">
|
||||
<div class="terminal-window">
|
||||
<div class="window-header">
|
||||
<div class="dots">
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<div class="dots"><span></span><span></span><span></span></div>
|
||||
<div class="window-title">Terminal Preview</div>
|
||||
</div>
|
||||
<div class="window-content">
|
||||
<div class="sequence-display-area">
|
||||
<span class="label">Last Sequence:</span>
|
||||
<span v-if="lastSequence" class="sequence-code">{{ lastSequence }}</span>
|
||||
<span v-if="lastSequence" class="sequence-code">{{
|
||||
lastSequence
|
||||
}}</span>
|
||||
<span v-else class="placeholder">Waiting for input...</span>
|
||||
</div>
|
||||
|
||||
<div class="main-display" :style="currentStyle" v-if="isContentVisible">
|
||||
<div
|
||||
class="main-display"
|
||||
:style="currentStyle"
|
||||
v-if="isContentVisible"
|
||||
>
|
||||
Hello World
|
||||
</div>
|
||||
|
||||
<div class="cursor-line">
|
||||
<span class="prompt">$</span>
|
||||
<span class="cursor-placeholder" v-if="cursorMode === 'absolute'"></span>
|
||||
<span
|
||||
class="cursor-placeholder"
|
||||
v-if="cursorMode === 'absolute'"
|
||||
></span>
|
||||
<span class="cursor-block" :style="cursorStyle"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,8 +124,22 @@
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const palette = [
|
||||
'#000000', '#cd3131', '#0dbc79', '#e5e510', '#2472c8', '#bc3fbc', '#11a8cd', '#e5e5e5',
|
||||
'#666666', '#f14c4c', '#23d18b', '#f5f543', '#3b8eea', '#d670d6', '#29b8db', '#ffffff'
|
||||
'#000000',
|
||||
'#cd3131',
|
||||
'#0dbc79',
|
||||
'#e5e510',
|
||||
'#2472c8',
|
||||
'#bc3fbc',
|
||||
'#11a8cd',
|
||||
'#e5e5e5',
|
||||
'#666666',
|
||||
'#f14c4c',
|
||||
'#23d18b',
|
||||
'#f5f543',
|
||||
'#3b8eea',
|
||||
'#d670d6',
|
||||
'#29b8db',
|
||||
'#ffffff'
|
||||
]
|
||||
|
||||
const activeColor = ref(null)
|
||||
@@ -303,8 +325,14 @@ button.active {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.code { color: #facc15; font-weight: bold; }
|
||||
.desc { color: #a1a1aa; font-size: 12px; }
|
||||
.code {
|
||||
color: #facc15;
|
||||
font-weight: bold;
|
||||
}
|
||||
.desc {
|
||||
color: #a1a1aa;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
background: #000;
|
||||
@@ -407,7 +435,9 @@ button.active {
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% { opacity: 0; }
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
@@ -58,9 +58,7 @@
|
||||
<div class="output-label">OUTPUT</div>
|
||||
|
||||
<div class="terminal-preview">
|
||||
<div class="term-header">
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<div class="term-header"><span></span><span></span><span></span></div>
|
||||
<div class="term-body">
|
||||
<span class="prompt">$ </span>
|
||||
<span class="typed-text">{{ displayText }}</span>
|
||||
@@ -74,7 +72,11 @@
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="play-btn" @click="startAnimation" :disabled="isAnimating">
|
||||
<button
|
||||
class="play-btn"
|
||||
@click="startAnimation"
|
||||
:disabled="isAnimating"
|
||||
>
|
||||
{{ isAnimating ? 'Simulating...' : 'Simulate Keystroke' }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -96,7 +98,7 @@ const statusColor = computed(() => {
|
||||
return 'text-green'
|
||||
})
|
||||
|
||||
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
const startAnimation = async () => {
|
||||
if (isAnimating.value) return
|
||||
@@ -159,12 +161,14 @@ const startAnimation = async () => {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.stack-col, .output-col {
|
||||
.stack-col,
|
||||
.output-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stack-label, .output-label {
|
||||
.stack-label,
|
||||
.output-label {
|
||||
color: #eab308;
|
||||
font-size: 12px;
|
||||
margin-bottom: 20px;
|
||||
@@ -245,8 +249,12 @@ const startAnimation = async () => {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.prompt { color: #888; }
|
||||
.typed-text { color: #22c55e; }
|
||||
.prompt {
|
||||
color: #888;
|
||||
}
|
||||
.typed-text {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.cursor {
|
||||
display: inline-block;
|
||||
@@ -262,7 +270,9 @@ const startAnimation = async () => {
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% { opacity: 0; }
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.status-box {
|
||||
@@ -285,8 +295,12 @@ const startAnimation = async () => {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.text-red { color: #ef4444; }
|
||||
.text-green { color: #22c55e; }
|
||||
.text-red {
|
||||
color: #ef4444;
|
||||
}
|
||||
.text-green {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.play-btn {
|
||||
width: 100%;
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
- 历史记录:记录最近几次按键的编码流。
|
||||
-->
|
||||
<template>
|
||||
<div class="input-visualizer" tabindex="0" @keydown="handleKeydown" @blur="handleBlur">
|
||||
<div
|
||||
class="input-visualizer"
|
||||
tabindex="0"
|
||||
@keydown="handleKeydown"
|
||||
@blur="handleBlur"
|
||||
>
|
||||
<div class="focus-overlay" v-if="!isFocused" @click="focus">
|
||||
<div class="focus-btn">
|
||||
<span class="icon">⌨️</span>
|
||||
@@ -35,7 +40,8 @@
|
||||
</div>
|
||||
|
||||
<div class="char-display">
|
||||
Character: <span class="char-val">{{ currentKey.charDisplay || '-' }}</span>
|
||||
Character:
|
||||
<span class="char-val">{{ currentKey.charDisplay || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -80,15 +86,15 @@ const handleKeydown = (e) => {
|
||||
// Map special keys
|
||||
const keyMap = {
|
||||
' ': { name: 'Space', bytes: '20', char: ' ' },
|
||||
'Enter': { name: 'Enter', bytes: '0a', char: '\\n' },
|
||||
'Tab': { name: 'Tab', bytes: '09', char: '\\t' },
|
||||
'Escape': { name: 'Esc', bytes: '1b', char: '\\e' },
|
||||
'Backspace': { name: 'Backspace', bytes: '7f', char: '\\b' },
|
||||
'Delete': { name: 'Del', bytes: '1b 5b 33 7e', sequence: '^[[3~' },
|
||||
'ArrowUp': { name: 'Arrow Up', bytes: '1b 5b 41', sequence: '^[[A' },
|
||||
'ArrowDown': { name: 'Arrow Down', bytes: '1b 5b 42', sequence: '^[[B' },
|
||||
'ArrowRight': { name: 'Arrow Right', bytes: '1b 5b 43', sequence: '^[[C' },
|
||||
'ArrowLeft': { name: 'Arrow Left', bytes: '1b 5b 44', sequence: '^[[D' },
|
||||
Enter: { name: 'Enter', bytes: '0a', char: '\\n' },
|
||||
Tab: { name: 'Tab', bytes: '09', char: '\\t' },
|
||||
Escape: { name: 'Esc', bytes: '1b', char: '\\e' },
|
||||
Backspace: { name: 'Backspace', bytes: '7f', char: '\\b' },
|
||||
Delete: { name: 'Del', bytes: '1b 5b 33 7e', sequence: '^[[3~' },
|
||||
ArrowUp: { name: 'Arrow Up', bytes: '1b 5b 41', sequence: '^[[A' },
|
||||
ArrowDown: { name: 'Arrow Down', bytes: '1b 5b 42', sequence: '^[[B' },
|
||||
ArrowRight: { name: 'Arrow Right', bytes: '1b 5b 43', sequence: '^[[C' },
|
||||
ArrowLeft: { name: 'Arrow Left', bytes: '1b 5b 44', sequence: '^[[D' }
|
||||
}
|
||||
|
||||
if (keyMap[e.key]) {
|
||||
@@ -138,7 +144,9 @@ const handleKeydown = (e) => {
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.input-visualizer:focus {
|
||||
@@ -190,7 +198,9 @@ const handleKeydown = (e) => {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: opacity 0.2s, filter 0.2s;
|
||||
transition:
|
||||
opacity 0.2s,
|
||||
filter 0.2s;
|
||||
}
|
||||
|
||||
.blur-content {
|
||||
@@ -237,8 +247,12 @@ const handleKeydown = (e) => {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.highlight { color: #facc15; /* Yellow 400 */ }
|
||||
.code { color: #22d3ee; /* Cyan 400 */ }
|
||||
.highlight {
|
||||
color: #facc15; /* Yellow 400 */
|
||||
}
|
||||
.code {
|
||||
color: #22d3ee; /* Cyan 400 */
|
||||
}
|
||||
|
||||
.char-display {
|
||||
color: #a1a1aa; /* Zinc 400 */
|
||||
|
||||
@@ -6,37 +6,41 @@
|
||||
|
||||
## 组件列表
|
||||
|
||||
| 组件名 | 描述 | 对应文档章节 |
|
||||
| :--- | :--- | :--- |
|
||||
| **TerminalDefinition.vue** | 可视化终端作为“字符流输入/输出环境”的定义。展示键盘输入 -> 字符流 -> 屏幕输出的过程。 | 1. 概念界定 |
|
||||
| **ArchitectureDemo.vue** | 演示终端(前端)与 Shell(后端)的分离架构。模拟点餐流程类比。 | 2. 核心架构 |
|
||||
| **TerminalGrid.vue** | 展示终端的字符网格系统,演示行、列和单元格的概念。 | 3. 视觉模型 |
|
||||
| **CellInspector.vue** | 单元格检查器,展示每个字符单元格背后的属性(字符、前景色、背景色等)。 | 3.2 样式检查 |
|
||||
| **EscapeSequences.vue** | 演示 ANSI 转义序列如何控制颜色、样式、光标移动和清屏。 | 4. 通信协议 |
|
||||
| **InputVisualizer.vue** | 可视化键盘按键如何转换为字节序列发送给 Shell。 | 5. 输入机制 |
|
||||
| **WebTerminal.vue** | 一个功能较完整的模拟终端,支持 `ls`, `cd`, `cat`, `apt` 等命令,包含虚拟文件系统。 | 附录/综合演示 |
|
||||
| **SignalsDemo.vue** | 演示终端信号(如 Ctrl+C SIGINT)的工作机制。 | (文档中可能引用) |
|
||||
| **FlowDiagram.vue** | 展示标准输入/输出/错误流 (stdin/stdout/stderr) 的流向图。 | (文档中可能引用) |
|
||||
| **AdvancedTUIDemo.vue** | 展示基于文本的用户界面 (TUI) 的高级布局能力(如面板、进度条)。 | (文档中可能引用) |
|
||||
| 组件名 | 描述 | 对应文档章节 |
|
||||
| :------------------------- | :------------------------------------------------------------------------------------ | :--------------- |
|
||||
| **TerminalDefinition.vue** | 可视化终端作为“字符流输入/输出环境”的定义。展示键盘输入 -> 字符流 -> 屏幕输出的过程。 | 1. 概念界定 |
|
||||
| **ArchitectureDemo.vue** | 演示终端(前端)与 Shell(后端)的分离架构。模拟点餐流程类比。 | 2. 核心架构 |
|
||||
| **TerminalGrid.vue** | 展示终端的字符网格系统,演示行、列和单元格的概念。 | 3. 视觉模型 |
|
||||
| **CellInspector.vue** | 单元格检查器,展示每个字符单元格背后的属性(字符、前景色、背景色等)。 | 3.2 样式检查 |
|
||||
| **EscapeSequences.vue** | 演示 ANSI 转义序列如何控制颜色、样式、光标移动和清屏。 | 4. 通信协议 |
|
||||
| **InputVisualizer.vue** | 可视化键盘按键如何转换为字节序列发送给 Shell。 | 5. 输入机制 |
|
||||
| **WebTerminal.vue** | 一个功能较完整的模拟终端,支持 `ls`, `cd`, `cat`, `apt` 等命令,包含虚拟文件系统。 | 附录/综合演示 |
|
||||
| **SignalsDemo.vue** | 演示终端信号(如 Ctrl+C SIGINT)的工作机制。 | (文档中可能引用) |
|
||||
| **FlowDiagram.vue** | 展示标准输入/输出/错误流 (stdin/stdout/stderr) 的流向图。 | (文档中可能引用) |
|
||||
| **AdvancedTUIDemo.vue** | 展示基于文本的用户界面 (TUI) 的高级布局能力(如面板、进度条)。 | (文档中可能引用) |
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 技术栈
|
||||
|
||||
- **Vue 3**: 使用 `<script setup>` 语法。
|
||||
- **Styling**: Scoped CSS,主要使用 Flexbox 和 Grid 布局。
|
||||
- **Theme**: 统一使用黑色系背景 (`#09090b`, `#18181b`) 和 JetBrains Mono 字体,保持类似终端的视觉风格。
|
||||
|
||||
### 维护注意事项
|
||||
|
||||
1. **双语支持**: 组件内部文本尽量支持中英双语,或通过 Props 传入文本。目前部分组件已硬编码双语标签。
|
||||
2. **自包含**: 组件应尽量自包含,不依赖外部复杂的 Store 或 Context,以便于在 Markdown 中直接使用。
|
||||
3. **响应式**: 考虑移动端适配,通常使用 `@media (max-width: 768px)` 进行布局调整。
|
||||
|
||||
### 常用颜色变量 (参考)
|
||||
|
||||
- 背景: `#09090b` (Main), `#18181b` (Panel)
|
||||
- 边框: `#27272a`
|
||||
- 文本: `#e4e4e7` (Primary), `#a1a1aa` (Secondary)
|
||||
- 强调色: `#22c55e` (Green/Success), `#facc15` (Yellow/Warning), `#22d3ee` (Cyan/Info)
|
||||
|
||||
## 目录结构
|
||||
|
||||
所有组件均位于 `docs/.vitepress/theme/components/appendix/terminal-intro/` 下。
|
||||
注册逻辑位于 `docs/.vitepress/theme/index.js`。
|
||||
|
||||
@@ -43,22 +43,33 @@
|
||||
<div class="info-box">
|
||||
<div v-if="activeSignal === 'SIGINT'">
|
||||
<div class="info-header">
|
||||
<span class="highlight">Ctrl+C</span> → <span class="signal-green">SIGINT</span>
|
||||
<span class="highlight">Ctrl+C</span> →
|
||||
<span class="signal-green">SIGINT</span>
|
||||
</div>
|
||||
<div class="info-desc">Stop the running program</div>
|
||||
<p>Sends SIGINT (signal interrupt) to the foreground process. Most programs respond by stopping immediately. It's how you cancel a long-running command or exit a program that's stuck.</p>
|
||||
<p>
|
||||
Sends SIGINT (signal interrupt) to the foreground process. Most
|
||||
programs respond by stopping immediately. It's how you cancel a
|
||||
long-running command or exit a program that's stuck.
|
||||
</p>
|
||||
<div class="example-box">
|
||||
Example: Running `sleep 100` and pressing Ctrl+C stops it immediately.
|
||||
Example: Running `sleep 100` and pressing Ctrl+C stops it
|
||||
immediately.
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="activeSignal === 'SIGTSTP'">
|
||||
<div class="info-header">
|
||||
<span class="highlight">Ctrl+Z</span> → <span class="signal-blue">SIGTSTP</span>
|
||||
<span class="highlight">Ctrl+Z</span> →
|
||||
<span class="signal-blue">SIGTSTP</span>
|
||||
</div>
|
||||
<div class="info-desc">Suspend the running program</div>
|
||||
<p>Sends SIGTSTP (signal terminal stop). The process is paused and put in the background. You can resume it later with `fg` command.</p>
|
||||
<p>
|
||||
Sends SIGTSTP (signal terminal stop). The process is paused and put
|
||||
in the background. You can resume it later with `fg` command.
|
||||
</p>
|
||||
<div class="example-box">
|
||||
Example: Pressing Ctrl+Z pauses a running editor like vim, returning you to the shell.
|
||||
Example: Pressing Ctrl+Z pauses a running editor like vim, returning
|
||||
you to the shell.
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
@@ -74,12 +85,18 @@
|
||||
<div class="dots"><span></span><span></span><span></span></div>
|
||||
</div>
|
||||
<div class="window-content">
|
||||
<div v-for="(line, i) in lines" :key="i" class="term-line" :class="line.type">
|
||||
<div
|
||||
v-for="(line, i) in lines"
|
||||
:key="i"
|
||||
class="term-line"
|
||||
:class="line.type"
|
||||
>
|
||||
{{ line.text }}
|
||||
</div>
|
||||
<div v-if="isRunning" class="term-line output">sleeping...</div>
|
||||
<div v-if="inputBuffer" class="term-line input">
|
||||
<span class="prompt">$</span> {{ inputBuffer }}<span class="cursor"></span>
|
||||
<span class="prompt">$</span> {{ inputBuffer
|
||||
}}<span class="cursor"></span>
|
||||
</div>
|
||||
<div v-else class="term-line input">
|
||||
<span class="prompt">$</span> <span class="cursor"></span>
|
||||
@@ -88,7 +105,9 @@
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn" @click="runCommand" :disabled="isRunning">Run Command</button>
|
||||
<button class="btn" @click="runCommand" :disabled="isRunning">
|
||||
Run Command
|
||||
</button>
|
||||
<button class="btn" @click="sendSignal('SIGINT')">Ctrl+C</button>
|
||||
<button class="btn" @click="sendSignal('SIGTSTP')">Ctrl+Z</button>
|
||||
<button class="btn secondary" @click="reset">Reset</button>
|
||||
@@ -99,7 +118,8 @@
|
||||
</div>
|
||||
|
||||
<p class="instruction">
|
||||
Click "Run Command" to start a simulated process, then try sending different signals.
|
||||
Click "Run Command" to start a simulated process, then try sending
|
||||
different signals.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,9 +130,7 @@ import { ref, computed } from 'vue'
|
||||
|
||||
const activeSignal = ref('SIGINT')
|
||||
const isRunning = ref(false)
|
||||
const lines = ref([
|
||||
{ type: 'input', text: '$ sleep 100' }
|
||||
])
|
||||
const lines = ref([{ type: 'input', text: '$ sleep 100' }])
|
||||
const processState = ref('Running')
|
||||
const inputBuffer = ref('')
|
||||
|
||||
@@ -142,7 +160,10 @@ const sendSignal = (sig) => {
|
||||
} else if (sig === 'SIGTSTP') {
|
||||
lines.value.push({ type: 'output', text: 'sleeping...' })
|
||||
lines.value.push({ type: 'control', text: '^Z' })
|
||||
lines.value.push({ type: 'output', text: '[1]+ Stopped sleep 100' })
|
||||
lines.value.push({
|
||||
type: 'output',
|
||||
text: '[1]+ Stopped sleep 100'
|
||||
})
|
||||
isRunning.value = false
|
||||
processState.value = 'Process suspended (stopped)'
|
||||
}
|
||||
@@ -161,7 +182,10 @@ reset()
|
||||
<style scoped>
|
||||
.signals-demo {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); /* 自动适应宽度,不够时换行 */
|
||||
grid-template-columns: repeat(
|
||||
auto-fit,
|
||||
minmax(280px, 1fr)
|
||||
); /* 自动适应宽度,不够时换行 */
|
||||
gap: 30px;
|
||||
background: #09090b;
|
||||
padding: 30px;
|
||||
@@ -237,9 +261,15 @@ reset()
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.highlight { color: #facc15; }
|
||||
.signal-green { color: #22c55e; }
|
||||
.signal-blue { color: #3b82f6; }
|
||||
.highlight {
|
||||
color: #facc15;
|
||||
}
|
||||
.signal-green {
|
||||
color: #22c55e;
|
||||
}
|
||||
.signal-blue {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.info-desc {
|
||||
color: #a1a1aa;
|
||||
@@ -271,7 +301,9 @@ reset()
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.window-header {
|
||||
@@ -300,10 +332,19 @@ reset()
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.control { color: #ef4444; }
|
||||
.output { color: #d4d4d8; }
|
||||
.input { color: #fff; }
|
||||
.prompt { color: #71717a; margin-right: 8px; }
|
||||
.control {
|
||||
color: #ef4444;
|
||||
}
|
||||
.output {
|
||||
color: #d4d4d8;
|
||||
}
|
||||
.input {
|
||||
color: #fff;
|
||||
}
|
||||
.prompt {
|
||||
color: #71717a;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.cursor {
|
||||
display: inline-block;
|
||||
@@ -348,9 +389,15 @@ reset()
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.state-green { color: #22c55e; }
|
||||
.state-red { color: #ef4444; }
|
||||
.state-blue { color: #3b82f6; }
|
||||
.state-green {
|
||||
color: #22c55e;
|
||||
}
|
||||
.state-red {
|
||||
color: #ef4444;
|
||||
}
|
||||
.state-blue {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.instruction {
|
||||
color: #a1a1aa;
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
<template>
|
||||
<div class="terminal-definition">
|
||||
<div class="mode-switch">
|
||||
<button
|
||||
:class="{ active: mode === 'cli' }"
|
||||
@click="mode = 'cli'"
|
||||
>
|
||||
<button :class="{ active: mode === 'cli' }" @click="mode = 'cli'">
|
||||
🖥️ CLI (命令行界面)
|
||||
</button>
|
||||
<button
|
||||
:class="{ active: mode === 'gui' }"
|
||||
@click="mode = 'gui'"
|
||||
>
|
||||
<button :class="{ active: mode === 'gui' }" @click="mode = 'gui'">
|
||||
🖱️ GUI (图形用户界面)
|
||||
</button>
|
||||
</div>
|
||||
@@ -21,7 +15,28 @@
|
||||
<!-- Input Side -->
|
||||
<div class="stage input-stage">
|
||||
<div class="icon-box">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2" ry="2"></rect><path d="M6 8h.001"></path><path d="M10 8h.001"></path><path d="M14 8h.001"></path><path d="M18 8h.001"></path><path d="M6 12h.001"></path><path d="M10 12h.001"></path><path d="M14 12h.001"></path><path d="M18 12h.001"></path><path d="M7 16h10"></path></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" ry="2"></rect>
|
||||
<path d="M6 8h.001"></path>
|
||||
<path d="M10 8h.001"></path>
|
||||
<path d="M14 8h.001"></path>
|
||||
<path d="M18 8h.001"></path>
|
||||
<path d="M6 12h.001"></path>
|
||||
<path d="M10 12h.001"></path>
|
||||
<path d="M14 12h.001"></path>
|
||||
<path d="M18 12h.001"></path>
|
||||
<path d="M7 16h10"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="label">Input (Keyboard)</div>
|
||||
<div class="sub-label">发送指令 (字符信号)</div>
|
||||
@@ -45,7 +60,8 @@
|
||||
<div class="stage output-stage">
|
||||
<div class="terminal-screen">
|
||||
<div class="screen-content">
|
||||
<span class="prompt">$</span> {{ typedContent }}<span class="cursor">_</span>
|
||||
<span class="prompt">$</span> {{ typedContent
|
||||
}}<span class="cursor">_</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="label">Output (Text Grid)</div>
|
||||
@@ -54,7 +70,10 @@
|
||||
</div>
|
||||
|
||||
<div class="desc-box">
|
||||
<p><strong>CLI (Command Line Interface)</strong>: 这种模式下,计算机只认识字符。你的每一次按键都会被转换成编码发送给系统,系统处理后返回文字结果。它不关心你在哪里点击,只关心你输入了什么。</p>
|
||||
<p>
|
||||
<strong>CLI (Command Line Interface)</strong>:
|
||||
这种模式下,计算机只认识字符。你的每一次按键都会被转换成编码发送给系统,系统处理后返回文字结果。它不关心你在哪里点击,只关心你输入了什么。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="control-bar">
|
||||
@@ -71,7 +90,20 @@
|
||||
<!-- Input Side -->
|
||||
<div class="stage input-stage">
|
||||
<div class="icon-box gui-input" :class="{ clicking: isGuiClicking }">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path><path d="M13 13l6 6"></path></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path>
|
||||
<path d="M13 13l6 6"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="label">Input (Mouse)</div>
|
||||
<div class="sub-label">发送事件 (坐标/点击)</div>
|
||||
@@ -98,11 +130,22 @@
|
||||
<div class="win-header"></div>
|
||||
<div class="win-body">
|
||||
<div class="icon-grid">
|
||||
<div class="desktop-icon" :class="{ selected: iconSelected }">📁</div>
|
||||
<div class="desktop-icon" :class="{ selected: iconSelected }">
|
||||
📁
|
||||
</div>
|
||||
<div class="desktop-icon">📄</div>
|
||||
</div>
|
||||
<div class="gui-cursor" :style="cursorStyle">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="white" stroke="black" stroke-width="2"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path></svg>
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 24 24"
|
||||
fill="white"
|
||||
stroke="black"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,7 +156,11 @@
|
||||
</div>
|
||||
|
||||
<div class="desc-box">
|
||||
<p><strong>GUI (Graphical User Interface)</strong>: 这种模式下,计算机实时追踪鼠标坐标和点击事件,并每秒刷新 60 次屏幕像素。它更直观,但需要消耗大量资源来处理图形渲染。</p>
|
||||
<p>
|
||||
<strong>GUI (Graphical User Interface)</strong>:
|
||||
这种模式下,计算机实时追踪鼠标坐标和点击事件,并每秒刷新 60
|
||||
次屏幕像素。它更直观,但需要消耗大量资源来处理图形渲染。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="control-bar">
|
||||
@@ -123,7 +170,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -170,13 +216,13 @@ const startSimulation = () => {
|
||||
let progress = 10
|
||||
const interval = setInterval(() => {
|
||||
progress += 4 // Faster speed
|
||||
const charObj = activeChars.value.find(c => c.id === charId)
|
||||
const charObj = activeChars.value.find((c) => c.id === charId)
|
||||
if (charObj) charObj.progress = progress
|
||||
|
||||
if (progress >= 90) {
|
||||
clearInterval(interval)
|
||||
// Remove from stream and add to screen
|
||||
activeChars.value = activeChars.value.filter(c => c.id !== charId)
|
||||
activeChars.value = activeChars.value.filter((c) => c.id !== charId)
|
||||
typedContent.value += char
|
||||
|
||||
// Next char
|
||||
@@ -222,10 +268,13 @@ const startGuiSimulation = () => {
|
||||
if (step % 2 === 0) {
|
||||
const targetX = inputMousePosition.value.x
|
||||
const targetY = inputMousePosition.value.y
|
||||
emitGuiEvent(`Move(${Math.round(targetX)},${Math.round(targetY)})`, () => {
|
||||
// When packet arrives: Update screen cursor
|
||||
screenCursorPosition.value = { x: targetX, y: targetY }
|
||||
})
|
||||
emitGuiEvent(
|
||||
`Move(${Math.round(targetX)},${Math.round(targetY)})`,
|
||||
() => {
|
||||
// When packet arrives: Update screen cursor
|
||||
screenCursorPosition.value = { x: targetX, y: targetY }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (step >= 20) {
|
||||
@@ -248,12 +297,12 @@ const emitGuiEvent = (type, onArrive) => {
|
||||
let progress = 10
|
||||
const packetInterval = setInterval(() => {
|
||||
progress += 2
|
||||
const ev = guiEvents.value.find(e => e.id === eventId)
|
||||
const ev = guiEvents.value.find((e) => e.id === eventId)
|
||||
if (ev) ev.progress = progress
|
||||
|
||||
if (progress >= 90) {
|
||||
clearInterval(packetInterval)
|
||||
guiEvents.value = guiEvents.value.filter(e => e.id !== eventId)
|
||||
guiEvents.value = guiEvents.value.filter((e) => e.id !== eventId)
|
||||
|
||||
// Execute callback when packet arrives at Output
|
||||
if (onArrive) onArrive()
|
||||
@@ -276,9 +325,8 @@ const performClick = () => {
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
isGuiClicking.value = false
|
||||
isGuiClicking.value = false
|
||||
}, 200) // Input click feedback is fast
|
||||
|
||||
}, 300)
|
||||
}
|
||||
</script>
|
||||
@@ -380,7 +428,7 @@ const performClick = () => {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -439,7 +487,7 @@ const performClick = () => {
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
transition: transform 0.1s linear; /* Smooth interpolation */
|
||||
filter: drop-shadow(0 1px 1px rgba(0,0,0,0.5));
|
||||
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
.screen-content {
|
||||
@@ -481,7 +529,13 @@ const performClick = () => {
|
||||
}
|
||||
|
||||
.stream-line.dashed {
|
||||
background: repeating-linear-gradient(90deg, #27272a 0, #27272a 6px, transparent 6px, transparent 10px);
|
||||
background: repeating-linear-gradient(
|
||||
90deg,
|
||||
#27272a 0,
|
||||
#27272a 6px,
|
||||
transparent 6px,
|
||||
transparent 10px
|
||||
);
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
@@ -539,7 +593,9 @@ const performClick = () => {
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% { opacity: 0; }
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.desc-box {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
:key="cIndex"
|
||||
:class="{
|
||||
'active-cursor': cursor.r === rIndex && cursor.c === cIndex,
|
||||
'drawn': cell.drawn
|
||||
drawn: cell.drawn
|
||||
}"
|
||||
@mousedown.prevent="handleCellMouseDown(rIndex, cIndex)"
|
||||
@mouseover="handleCellHover(rIndex, cIndex)"
|
||||
@@ -52,9 +52,10 @@ import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
|
||||
const ROW_COUNT = 10
|
||||
const COL_COUNT = 40
|
||||
|
||||
const createGrid = () => Array.from({ length: ROW_COUNT }, () =>
|
||||
Array.from({ length: COL_COUNT }, () => ({ char: '', drawn: false }))
|
||||
)
|
||||
const createGrid = () =>
|
||||
Array.from({ length: ROW_COUNT }, () =>
|
||||
Array.from({ length: COL_COUNT }, () => ({ char: '', drawn: false }))
|
||||
)
|
||||
|
||||
const rows = reactive(createGrid())
|
||||
const cursor = reactive({ r: 0, c: 0 })
|
||||
@@ -116,8 +117,8 @@ const handleCellHover = (r, c) => {
|
||||
}
|
||||
|
||||
const clearGrid = () => {
|
||||
for(let r=0; r<ROW_COUNT; r++) {
|
||||
for(let c=0; c<COL_COUNT; c++) {
|
||||
for (let r = 0; r < ROW_COUNT; r++) {
|
||||
for (let c = 0; c < COL_COUNT; c++) {
|
||||
rows[r][c].char = ''
|
||||
rows[r][c].drawn = false
|
||||
}
|
||||
@@ -181,66 +182,68 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.grid-cell {
|
||||
width: 16px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right: 1px solid #27272a;
|
||||
border-bottom: 1px solid #27272a;
|
||||
color: #e4e4e7;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
width: 16px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right: 1px solid #27272a;
|
||||
border-bottom: 1px solid #27272a;
|
||||
color: #e4e4e7;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.grid-cell.drawn {
|
||||
background-color: #3f3f46;
|
||||
}
|
||||
.grid-cell.drawn {
|
||||
background-color: #3f3f46;
|
||||
}
|
||||
|
||||
.grid-cell.active-cursor {
|
||||
background-color: #e4e4e7;
|
||||
color: #000;
|
||||
animation: blink 1s step-end infinite;
|
||||
}
|
||||
.grid-cell.active-cursor {
|
||||
background-color: #e4e4e7;
|
||||
color: #000;
|
||||
animation: blink 1s step-end infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% { opacity: 0.7; }
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
.controls {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.text-input {
|
||||
background: #18181b;
|
||||
border: 1px solid #3f3f46;
|
||||
color: #fff;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-family: inherit;
|
||||
}
|
||||
.text-input {
|
||||
background: #18181b;
|
||||
border: 1px solid #3f3f46;
|
||||
color: #fff;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #27272a;
|
||||
border: 1px solid #3f3f46;
|
||||
color: #e4e4e7;
|
||||
padding: 6px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn {
|
||||
background: #27272a;
|
||||
border: 1px solid #3f3f46;
|
||||
color: #e4e4e7;
|
||||
padding: 6px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #3f3f46;
|
||||
border-color: #52525b;
|
||||
}
|
||||
.btn:hover {
|
||||
background: #3f3f46;
|
||||
border-color: #52525b;
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: #a1a1aa; /* Zinc 400 */
|
||||
font-size: 12px;
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
||||
.hint {
|
||||
color: #a1a1aa; /* Zinc 400 */
|
||||
font-size: 12px;
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
<!-- Left Panel: Task Guide -->
|
||||
<div class="task-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">🎯 实操任务 ({{ currentTaskIndex + 1 }}/{{ tasks.length }})</span>
|
||||
<span class="panel-title"
|
||||
>🎯 实操任务 ({{ currentTaskIndex + 1 }}/{{ tasks.length }})</span
|
||||
>
|
||||
<div class="os-selector">
|
||||
<select v-model="currentOS" @change="resetCurrentTask">
|
||||
<option value="mac">macOS</option>
|
||||
@@ -29,9 +31,17 @@
|
||||
{{ currentTask.aiQuery }}
|
||||
</div>
|
||||
<div class="chat-bubble ai">
|
||||
<p>{{ currentTask.aiResponse[currentOS] || currentTask.aiResponse.common }}</p>
|
||||
<p>
|
||||
{{
|
||||
currentTask.aiResponse[currentOS] ||
|
||||
currentTask.aiResponse.common
|
||||
}}
|
||||
</p>
|
||||
<!-- Multiple Commands Support -->
|
||||
<div v-if="currentTask.commands && currentTask.commands[currentOS]" class="cmd-buttons">
|
||||
<div
|
||||
v-if="currentTask.commands && currentTask.commands[currentOS]"
|
||||
class="cmd-buttons"
|
||||
>
|
||||
<button
|
||||
v-for="(cmdItem, idx) in currentTask.commands[currentOS]"
|
||||
:key="idx"
|
||||
@@ -45,7 +55,12 @@
|
||||
<button
|
||||
v-else-if="currentTask.expectedCmd"
|
||||
class="copy-btn"
|
||||
@click="copyCommand(currentTask.expectedCmd[currentOS] || currentTask.expectedCmd.common)"
|
||||
@click="
|
||||
copyCommand(
|
||||
currentTask.expectedCmd[currentOS] ||
|
||||
currentTask.expectedCmd.common
|
||||
)
|
||||
"
|
||||
>
|
||||
复制命令
|
||||
</button>
|
||||
@@ -61,7 +76,13 @@
|
||||
<div class="success-message" v-if="isTaskCompleted">
|
||||
<span class="icon">🎉</span>
|
||||
<span>太棒了!任务完成!</span>
|
||||
<button class="next-btn" @click="nextTask" v-if="currentTaskIndex < tasks.length - 1">下一关</button>
|
||||
<button
|
||||
class="next-btn"
|
||||
@click="nextTask"
|
||||
v-if="currentTaskIndex < tasks.length - 1"
|
||||
>
|
||||
下一关
|
||||
</button>
|
||||
<button class="reset-btn" @click="resetAll" v-else>重新开始</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,11 +100,16 @@
|
||||
</div>
|
||||
<div class="terminal-body" ref="terminalBody" @click="focusInput">
|
||||
<div v-for="(line, index) in history" :key="index" class="line">
|
||||
<span v-if="line.type === 'input'" class="prompt">{{ line.prompt }}</span>
|
||||
<span v-if="line.type === 'input'" class="prompt">{{
|
||||
line.prompt
|
||||
}}</span>
|
||||
<span :class="line.type">{{ line.content }}</span>
|
||||
</div>
|
||||
|
||||
<div class="line input-line" v-if="!isTaskCompleted || currentTaskIndex < tasks.length - 1">
|
||||
<div
|
||||
class="line input-line"
|
||||
v-if="!isTaskCompleted || currentTaskIndex < tasks.length - 1"
|
||||
>
|
||||
<span class="prompt">{{ prompt }}</span>
|
||||
<input
|
||||
ref="cmdInput"
|
||||
@@ -94,7 +120,9 @@
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<span v-if="inputCmd.length > 0" class="enter-hint">⏎ 按回车执行</span>
|
||||
<span v-if="inputCmd.length > 0" class="enter-hint"
|
||||
>⏎ 按回车执行</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,10 +143,10 @@ const terminalBody = ref(null)
|
||||
|
||||
// System Configurations
|
||||
const osConfig = {
|
||||
'mac': { prompt: 'user@MacBook ~ % ', title: 'user — -zsh' },
|
||||
mac: { prompt: 'user@MacBook ~ % ', title: 'user — -zsh' },
|
||||
'win-ps': { prompt: 'PS C:\\Users\\User> ', title: 'Windows PowerShell' },
|
||||
'win-cmd': { prompt: 'C:\\Users\\User> ', title: 'Command Prompt' },
|
||||
'linux': { prompt: 'user@localhost:~$ ', title: 'user@localhost: ~' }
|
||||
linux: { prompt: 'user@localhost:~$ ', title: 'user@localhost: ~' }
|
||||
}
|
||||
|
||||
const prompt = computed(() => osConfig[currentOS.value].prompt)
|
||||
@@ -132,14 +160,17 @@ const tasks = [
|
||||
goal: '列出当前目录下的所有文件。',
|
||||
aiQuery: '我想查看当前目录下的文件,应该用什么命令?',
|
||||
aiResponse: {
|
||||
'mac': '在 macOS 和 Linux 中,查看文件列表使用 `ls` 命令 (List)。',
|
||||
'linux': '在 macOS 和 Linux 中,查看文件列表使用 `ls` 命令 (List)。',
|
||||
mac: '在 macOS 和 Linux 中,查看文件列表使用 `ls` 命令 (List)。',
|
||||
linux: '在 macOS 和 Linux 中,查看文件列表使用 `ls` 命令 (List)。',
|
||||
'win-ps': '在 PowerShell 中,你可以使用 `ls` 或 `dir` 命令。',
|
||||
'win-cmd': '在 Windows CMD 中,查看文件列表使用 `dir` 命令 (Directory)。',
|
||||
'common': '通常使用 ls 或 dir。'
|
||||
common: '通常使用 ls 或 dir。'
|
||||
},
|
||||
expectedCmd: {
|
||||
'mac': 'ls', 'linux': 'ls', 'win-ps': 'ls', 'win-cmd': 'dir'
|
||||
mac: 'ls',
|
||||
linux: 'ls',
|
||||
'win-ps': 'ls',
|
||||
'win-cmd': 'dir'
|
||||
},
|
||||
validate: (cmd, os) => {
|
||||
const valid = os === 'win-cmd' ? ['dir'] : ['ls', 'dir', 'll']
|
||||
@@ -161,14 +192,16 @@ d---- 1/15/2026 9:00 AM Downloads
|
||||
},
|
||||
{
|
||||
title: '第二步:创建一个新家',
|
||||
description: '文件太多会很乱,我们创建一个专门的文件夹来存放今天的练习文件。',
|
||||
description:
|
||||
'文件太多会很乱,我们创建一个专门的文件夹来存放今天的练习文件。',
|
||||
goal: '创建一个名为 "demo" 的文件夹。',
|
||||
aiQuery: '怎么创建一个新的文件夹?名字叫 demo。',
|
||||
aiResponse: {
|
||||
'common': '创建文件夹(目录)的命令是 `mkdir` (Make Directory)。你可以输入 `mkdir demo`。'
|
||||
common:
|
||||
'创建文件夹(目录)的命令是 `mkdir` (Make Directory)。你可以输入 `mkdir demo`。'
|
||||
},
|
||||
expectedCmd: {
|
||||
'common': 'mkdir demo'
|
||||
common: 'mkdir demo'
|
||||
},
|
||||
validate: (cmd) => cmd.trim() === 'mkdir demo',
|
||||
output: () => '' // mkdir usually has no output on success
|
||||
@@ -179,10 +212,10 @@ d---- 1/15/2026 9:00 AM Downloads
|
||||
goal: '进入 "demo" 文件夹。',
|
||||
aiQuery: '怎么进入刚才建好的 demo 文件夹?',
|
||||
aiResponse: {
|
||||
'common': '切换目录使用 `cd` 命令 (Change Directory)。输入 `cd demo` 即可。'
|
||||
common: '切换目录使用 `cd` 命令 (Change Directory)。输入 `cd demo` 即可。'
|
||||
},
|
||||
expectedCmd: {
|
||||
'common': 'cd demo'
|
||||
common: 'cd demo'
|
||||
},
|
||||
validate: (cmd) => cmd.trim() === 'cd demo',
|
||||
output: () => '' // cd usually has no output, but prompt changes
|
||||
@@ -193,19 +226,26 @@ d---- 1/15/2026 9:00 AM Downloads
|
||||
goal: '创建一个名为 "hello.txt" 的文件。',
|
||||
aiQuery: '我想新建一个空文件叫 hello.txt,怎么做?',
|
||||
aiResponse: {
|
||||
'mac': '在 Mac/Linux 上,使用 `touch hello.txt` 可以快速创建一个空文件。',
|
||||
'linux': '在 Mac/Linux 上,使用 `touch hello.txt` 可以快速创建一个空文件。',
|
||||
'win-ps': '在 PowerShell 中,可以使用 `ni hello.txt` 或 `echo "" > hello.txt`。',
|
||||
'win-cmd': '在 CMD 中,可以使用 `type nul > hello.txt` 或 `echo. > hello.txt`。',
|
||||
mac: '在 Mac/Linux 上,使用 `touch hello.txt` 可以快速创建一个空文件。',
|
||||
linux: '在 Mac/Linux 上,使用 `touch hello.txt` 可以快速创建一个空文件。',
|
||||
'win-ps':
|
||||
'在 PowerShell 中,可以使用 `ni hello.txt` 或 `echo "" > hello.txt`。',
|
||||
'win-cmd':
|
||||
'在 CMD 中,可以使用 `type nul > hello.txt` 或 `echo. > hello.txt`。'
|
||||
},
|
||||
expectedCmd: {
|
||||
'mac': 'touch hello.txt',
|
||||
'linux': 'touch hello.txt',
|
||||
mac: 'touch hello.txt',
|
||||
linux: 'touch hello.txt',
|
||||
'win-ps': 'ni hello.txt',
|
||||
'win-cmd': 'type nul > hello.txt'
|
||||
},
|
||||
validate: (cmd, os) => {
|
||||
if (cmd.includes('touch') || cmd.includes('echo') || cmd.includes('ni') || cmd.includes('type')) {
|
||||
if (
|
||||
cmd.includes('touch') ||
|
||||
cmd.includes('echo') ||
|
||||
cmd.includes('ni') ||
|
||||
cmd.includes('type')
|
||||
) {
|
||||
return cmd.includes('hello.txt')
|
||||
}
|
||||
return false
|
||||
@@ -214,22 +254,26 @@ d---- 1/15/2026 9:00 AM Downloads
|
||||
},
|
||||
{
|
||||
title: '第五步:安装程序 (系统软件 & Python库)',
|
||||
description: '终端不仅能管理文件,还能安装软件。我们来尝试两种常见的安装场景:安装系统工具(如 wget/git)和安装 Python 库(如 requests)。',
|
||||
description:
|
||||
'终端不仅能管理文件,还能安装软件。我们来尝试两种常见的安装场景:安装系统工具(如 wget/git)和安装 Python 库(如 requests)。',
|
||||
goal: '任选其一:安装系统工具或 Python 库。',
|
||||
aiQuery: '怎么用命令行安装软件?我想装 git 或者 python 的 requests 库。',
|
||||
aiResponse: {
|
||||
'mac': 'macOS 推荐使用 Homebrew 安装系统软件,使用 pip 安装 Python 库。',
|
||||
'linux': 'Linux (Ubuntu/Debian) 使用 apt 安装系统软件,使用 pip 安装 Python 库。',
|
||||
'win-ps': 'Windows PowerShell 推荐使用 winget 安装系统软件,使用 pip 安装 Python 库。',
|
||||
'win-cmd': 'Windows CMD 推荐使用 winget 安装系统软件,使用 pip 安装 Python 库。',
|
||||
'common': '不同系统有不同的包管理器。'
|
||||
mac: 'macOS 推荐使用 Homebrew 安装系统软件,使用 pip 安装 Python 库。',
|
||||
linux:
|
||||
'Linux (Ubuntu/Debian) 使用 apt 安装系统软件,使用 pip 安装 Python 库。',
|
||||
'win-ps':
|
||||
'Windows PowerShell 推荐使用 winget 安装系统软件,使用 pip 安装 Python 库。',
|
||||
'win-cmd':
|
||||
'Windows CMD 推荐使用 winget 安装系统软件,使用 pip 安装 Python 库。',
|
||||
common: '不同系统有不同的包管理器。'
|
||||
},
|
||||
commands: {
|
||||
'mac': [
|
||||
mac: [
|
||||
{ label: '安装 wget (系统)', cmd: 'brew install wget' },
|
||||
{ label: '安装 requests (Python)', cmd: 'pip install requests' }
|
||||
],
|
||||
'linux': [
|
||||
linux: [
|
||||
{ label: '安装 git (系统)', cmd: 'sudo apt install git' },
|
||||
{ label: '安装 requests (Python)', cmd: 'pip install requests' }
|
||||
],
|
||||
@@ -244,24 +288,36 @@ d---- 1/15/2026 9:00 AM Downloads
|
||||
},
|
||||
expectedCmd: {
|
||||
// Fallback/Legacy
|
||||
'mac': 'brew install wget',
|
||||
'linux': 'sudo apt install git',
|
||||
mac: 'brew install wget',
|
||||
linux: 'sudo apt install git',
|
||||
'win-ps': 'pip install requests',
|
||||
'win-cmd': 'pip install requests'
|
||||
},
|
||||
validate: (cmd, os) => {
|
||||
const c = cmd.trim()
|
||||
if (os === 'mac') return c === 'brew install wget' || c === 'pip install requests'
|
||||
if (os === 'linux') return c === 'sudo apt install git' || c === 'apt install git' || c === 'pip install requests'
|
||||
if (os === 'win-ps' || os === 'win-cmd') return c === 'winget install git.git' || c === 'winget install git' || c === 'pip install requests'
|
||||
if (os === 'mac')
|
||||
return c === 'brew install wget' || c === 'pip install requests'
|
||||
if (os === 'linux')
|
||||
return (
|
||||
c === 'sudo apt install git' ||
|
||||
c === 'apt install git' ||
|
||||
c === 'pip install requests'
|
||||
)
|
||||
if (os === 'win-ps' || os === 'win-cmd')
|
||||
return (
|
||||
c === 'winget install git.git' ||
|
||||
c === 'winget install git' ||
|
||||
c === 'pip install requests'
|
||||
)
|
||||
return c === 'pip install requests'
|
||||
},
|
||||
output: (os, cmd) => { // Modified to accept cmd
|
||||
output: (os, cmd) => {
|
||||
// Modified to accept cmd
|
||||
const c = cmd ? cmd.trim() : ''
|
||||
|
||||
// Python requests output
|
||||
if (c.includes('pip install requests')) {
|
||||
return `
|
||||
return `
|
||||
Downloading/unpacking requests
|
||||
Downloading requests-2.31.0-py3-none-any.whl (62kB): 62kB downloaded
|
||||
Installing collected packages: requests
|
||||
@@ -271,7 +327,7 @@ Cleaning up...`
|
||||
|
||||
// Windows winget output
|
||||
if (c.includes('winget install')) {
|
||||
return `
|
||||
return `
|
||||
Found Git [Git.Git] Version 2.43.0
|
||||
This application is licensed to you by its owner.
|
||||
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
|
||||
@@ -310,14 +366,15 @@ Setting up git (1:2.34.1-1ubuntu1.9) ...`
|
||||
goal: '删除 "hello.txt" 文件。',
|
||||
aiQuery: '我不想要 hello.txt 了,怎么删除它?',
|
||||
aiResponse: {
|
||||
'mac': '删除文件使用 `rm` 命令 (Remove)。小心,这个操作通常不可撤销!输入 `rm hello.txt`。',
|
||||
'linux': '删除文件使用 `rm` 命令 (Remove)。小心,这个操作通常不可撤销!输入 `rm hello.txt`。',
|
||||
mac: '删除文件使用 `rm` 命令 (Remove)。小心,这个操作通常不可撤销!输入 `rm hello.txt`。',
|
||||
linux:
|
||||
'删除文件使用 `rm` 命令 (Remove)。小心,这个操作通常不可撤销!输入 `rm hello.txt`。',
|
||||
'win-ps': '在 PowerShell 中使用 `rm` 或 `del`。输入 `rm hello.txt`。',
|
||||
'win-cmd': '在 CMD 中使用 `del` 命令 (Delete)。输入 `del hello.txt`。',
|
||||
'win-cmd': '在 CMD 中使用 `del` 命令 (Delete)。输入 `del hello.txt`。'
|
||||
},
|
||||
expectedCmd: {
|
||||
'mac': 'rm hello.txt',
|
||||
'linux': 'rm hello.txt',
|
||||
mac: 'rm hello.txt',
|
||||
linux: 'rm hello.txt',
|
||||
'win-ps': 'rm hello.txt',
|
||||
'win-cmd': 'del hello.txt'
|
||||
},
|
||||
@@ -362,12 +419,18 @@ const executeCommand = () => {
|
||||
// 1. Add to history
|
||||
let currentPrompt = prompt.value
|
||||
// Special handling for prompt update simulation (hacky way)
|
||||
if (currentTaskIndex.value >= 2 && currentTaskIndex.value < 6 && history.value.length > 0) {
|
||||
// If we are inside demo folder
|
||||
if (currentOS.value === 'mac') currentPrompt = 'user@MacBook demo % '
|
||||
else if (currentOS.value === 'linux') currentPrompt = 'user@localhost:~/demo$ '
|
||||
else if (currentOS.value === 'win-ps') currentPrompt = 'PS C:\\Users\\User\\demo> '
|
||||
else currentPrompt = 'C:\\Users\\User\\demo> '
|
||||
if (
|
||||
currentTaskIndex.value >= 2 &&
|
||||
currentTaskIndex.value < 6 &&
|
||||
history.value.length > 0
|
||||
) {
|
||||
// If we are inside demo folder
|
||||
if (currentOS.value === 'mac') currentPrompt = 'user@MacBook demo % '
|
||||
else if (currentOS.value === 'linux')
|
||||
currentPrompt = 'user@localhost:~/demo$ '
|
||||
else if (currentOS.value === 'win-ps')
|
||||
currentPrompt = 'PS C:\\Users\\User\\demo> '
|
||||
else currentPrompt = 'C:\\Users\\User\\demo> '
|
||||
}
|
||||
|
||||
history.value.push({ type: 'input', prompt: currentPrompt, content: cmd })
|
||||
@@ -375,7 +438,10 @@ const executeCommand = () => {
|
||||
|
||||
// 2. Process Command
|
||||
// Check if it matches current task requirement
|
||||
if (!isTaskCompleted.value && currentTask.value.validate(cmd, currentOS.value)) {
|
||||
if (
|
||||
!isTaskCompleted.value &&
|
||||
currentTask.value.validate(cmd, currentOS.value)
|
||||
) {
|
||||
// Success
|
||||
const out = currentTask.value.output(currentOS.value, cmd) // Pass cmd to output
|
||||
if (out) {
|
||||
@@ -386,19 +452,29 @@ const executeCommand = () => {
|
||||
// Failure or just random command
|
||||
// Simple mock responses for common commands if not matching task
|
||||
if (cmd.trim() === 'ls' || cmd.trim() === 'dir') {
|
||||
if (currentTaskIndex.value < 2) {
|
||||
// Initial state
|
||||
history.value.push({ type: 'output', content: tasks[0].output(currentOS.value) })
|
||||
} else if (currentTaskIndex.value >= 2) {
|
||||
// Inside demo
|
||||
if (currentTaskIndex.value === 3) history.value.push({ type: 'output', content: '' }) // empty
|
||||
else history.value.push({ type: 'output', content: 'hello.txt' })
|
||||
}
|
||||
if (currentTaskIndex.value < 2) {
|
||||
// Initial state
|
||||
history.value.push({
|
||||
type: 'output',
|
||||
content: tasks[0].output(currentOS.value)
|
||||
})
|
||||
} else if (currentTaskIndex.value >= 2) {
|
||||
// Inside demo
|
||||
if (currentTaskIndex.value === 3)
|
||||
history.value.push({ type: 'output', content: '' }) // empty
|
||||
else history.value.push({ type: 'output', content: 'hello.txt' })
|
||||
}
|
||||
} else if (cmd.trim() === 'clear' || cmd.trim() === 'cls') {
|
||||
history.value = []
|
||||
} else if (!isTaskCompleted.value) {
|
||||
history.value.push({ type: 'error', content: `Command not found or not matching task: ${cmd}` })
|
||||
history.value.push({ type: 'info', content: `💡 提示:试试点击左侧的“问问 AI”?` })
|
||||
history.value.push({
|
||||
type: 'error',
|
||||
content: `Command not found or not matching task: ${cmd}`
|
||||
})
|
||||
history.value.push({
|
||||
type: 'info',
|
||||
content: `💡 提示:试试点击左侧的“问问 AI”?`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +486,10 @@ const nextTask = () => {
|
||||
currentTaskIndex.value++
|
||||
isTaskCompleted.value = false
|
||||
// Clear history to keep it clean? Or keep it? Let's keep it but maybe add a separator
|
||||
history.value.push({ type: 'info', content: `--- 进入下一关: ${currentTask.value.title} ---` })
|
||||
history.value.push({
|
||||
type: 'info',
|
||||
content: `--- 进入下一关: ${currentTask.value.title} ---`
|
||||
})
|
||||
scrollToBottom()
|
||||
}
|
||||
}
|
||||
@@ -446,7 +525,7 @@ watch(currentOS, () => {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@@ -606,11 +685,18 @@ watch(currentOS, () => {
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.next-btn, .reset-btn {
|
||||
.next-btn,
|
||||
.reset-btn {
|
||||
margin-left: auto;
|
||||
padding: 6px 16px;
|
||||
background: #10b981;
|
||||
@@ -622,7 +708,8 @@ watch(currentOS, () => {
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
.next-btn:hover, .reset-btn:hover {
|
||||
.next-btn:hover,
|
||||
.reset-btn:hover {
|
||||
transform: scale(1.05);
|
||||
background: #059669;
|
||||
}
|
||||
@@ -636,9 +723,21 @@ watch(currentOS, () => {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.terminal-panel.win-cmd { background: #0c0c0c; color: #cccccc; font-family: 'Consolas', monospace; }
|
||||
.terminal-panel.win-ps { background: #012456; color: #ffffff; font-family: 'Consolas', monospace; }
|
||||
.terminal-panel.mac, .terminal-panel.linux { background: #2b2b2b; color: #f0f0f0; }
|
||||
.terminal-panel.win-cmd {
|
||||
background: #0c0c0c;
|
||||
color: #cccccc;
|
||||
font-family: 'Consolas', monospace;
|
||||
}
|
||||
.terminal-panel.win-ps {
|
||||
background: #012456;
|
||||
color: #ffffff;
|
||||
font-family: 'Consolas', monospace;
|
||||
}
|
||||
.terminal-panel.mac,
|
||||
.terminal-panel.linux {
|
||||
background: #2b2b2b;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
.terminal-header {
|
||||
padding: 8px 12px;
|
||||
@@ -658,11 +757,21 @@ watch(currentOS, () => {
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dot.red { background: #ff5f56; }
|
||||
.dot.yellow { background: #ffbd2e; }
|
||||
.dot.green { background: #27c93f; }
|
||||
.dot.red {
|
||||
background: #ff5f56;
|
||||
}
|
||||
.dot.yellow {
|
||||
background: #ffbd2e;
|
||||
}
|
||||
.dot.green {
|
||||
background: #27c93f;
|
||||
}
|
||||
|
||||
.terminal-panel.win-cmd .dot, .terminal-panel.win-ps .dot { border-radius: 0; background: #ccc; }
|
||||
.terminal-panel.win-cmd .dot,
|
||||
.terminal-panel.win-ps .dot {
|
||||
border-radius: 0;
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.terminal-header .title {
|
||||
position: absolute;
|
||||
@@ -696,8 +805,12 @@ watch(currentOS, () => {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.terminal-panel.win-cmd .prompt { color: #cccccc; }
|
||||
.terminal-panel.win-ps .prompt { color: #ffffff; }
|
||||
.terminal-panel.win-cmd .prompt {
|
||||
color: #cccccc;
|
||||
}
|
||||
.terminal-panel.win-ps .prompt {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
@@ -725,8 +838,13 @@ watch(currentOS, () => {
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 0.5; }
|
||||
50% { opacity: 1; }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.line.output {
|
||||
|
||||
@@ -21,12 +21,26 @@
|
||||
</div>
|
||||
<div class="window-title">{{ currentOSConfig.title }}</div>
|
||||
<div class="window-controls">
|
||||
<button class="control-btn" @click="resetDemo" title="Reset">↺</button>
|
||||
<button class="control-btn" @click="resetDemo" title="Reset">
|
||||
↺
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="terminal-content" @click="nextStep" :class="{ 'clickable': !isTyping && !isFinished }">
|
||||
<div
|
||||
class="terminal-content"
|
||||
@click="nextStep"
|
||||
:class="{ clickable: !isTyping && !isFinished }"
|
||||
>
|
||||
<!-- Start Overlay -->
|
||||
<div class="start-overlay" v-if="lines.length === 0 || (lines.length === 1 && lines[0].content === '' && currentStepIndex === -1)">
|
||||
<div
|
||||
class="start-overlay"
|
||||
v-if="
|
||||
lines.length === 0 ||
|
||||
(lines.length === 1 &&
|
||||
lines[0].content === '' &&
|
||||
currentStepIndex === -1)
|
||||
"
|
||||
>
|
||||
<div class="start-hint">
|
||||
<span class="icon">👆</span>
|
||||
<span class="text">不断点击屏幕演示 / Keep Clicking</span>
|
||||
@@ -43,7 +57,8 @@
|
||||
|
||||
<div v-for="(line, index) in lines" :key="index" class="line">
|
||||
<template v-if="line.type === 'input'">
|
||||
<span class="prompt">{{ line.prompt }}</span><span class="cmd-text">{{ line.content }}</span>
|
||||
<span class="prompt">{{ line.prompt }}</span
|
||||
><span class="cmd-text">{{ line.content }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="output-text">{{ line.content }}</span>
|
||||
@@ -51,11 +66,21 @@
|
||||
</div>
|
||||
|
||||
<!-- Active Input Line (when not animating or just waiting) -->
|
||||
<div class="line input-line" v-if="lines.length === 0 || (!isTyping && lines[lines.length-1].type !== 'input' && !isFinished)">
|
||||
<span class="prompt">{{ currentOSConfig.prompt }}</span>
|
||||
<span class="cursor">_</span>
|
||||
<span v-if="lines.length === 0" class="hint"> (点击屏幕继续 / Click screen to continue)</span>
|
||||
<span v-else class="hint blink-hint"> ⏎ </span>
|
||||
<div
|
||||
class="line input-line"
|
||||
v-if="
|
||||
lines.length === 0 ||
|
||||
(!isTyping &&
|
||||
lines[lines.length - 1].type !== 'input' &&
|
||||
!isFinished)
|
||||
"
|
||||
>
|
||||
<span class="prompt">{{ currentOSConfig.prompt }}</span>
|
||||
<span class="cursor">_</span>
|
||||
<span v-if="lines.length === 0" class="hint">
|
||||
(点击屏幕继续 / Click screen to continue)</span
|
||||
>
|
||||
<span v-else class="hint blink-hint"> ⏎ </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -90,17 +115,54 @@ const configs = {
|
||||
prompt: 'C:\\Users\\User>',
|
||||
demo: [
|
||||
{ type: 'explanation', content: '准备输入命令...' },
|
||||
{ type: 'command', content: 'dir', delay: 400, explanation: '输入 `dir` (Directory)。这是 Windows 系统用来**列出当前文件夹内容**的命令。' },
|
||||
{ type: 'output', content: ' Volume in drive C has no label.', delay: 100, explanation: '系统正在执行命令...' },
|
||||
{ type: 'output', content: ' Volume Serial Number is A1B2-C3D4', delay: 50 },
|
||||
{
|
||||
type: 'command',
|
||||
content: 'dir',
|
||||
delay: 400,
|
||||
explanation:
|
||||
'输入 `dir` (Directory)。这是 Windows 系统用来**列出当前文件夹内容**的命令。'
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: ' Volume in drive C has no label.',
|
||||
delay: 100,
|
||||
explanation: '系统正在执行命令...'
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: ' Volume Serial Number is A1B2-C3D4',
|
||||
delay: 50
|
||||
},
|
||||
{ type: 'output', content: '', delay: 50 },
|
||||
{ type: 'output', content: ' Directory of C:\\Users\\User', delay: 50 },
|
||||
{ type: 'output', content: '', delay: 50 },
|
||||
{ type: 'output', content: '01/15/2026 10:00 AM <DIR> .', delay: 50 },
|
||||
{ type: 'output', content: '01/15/2026 10:00 AM <DIR> ..', delay: 50 },
|
||||
{ type: 'output', content: '01/15/2026 10:00 AM 128 demo.txt', delay: 50 },
|
||||
{ type: 'output', content: ' 1 File(s) 128 bytes', delay: 50 },
|
||||
{ type: 'output', content: ' 2 Dir(s) 50,000,000,000 bytes free', delay: 50, explanation: '系统返回了文件列表。`<DIR>` 表示这是一个文件夹,数字表示文件大小。' },
|
||||
{
|
||||
type: 'output',
|
||||
content: '01/15/2026 10:00 AM <DIR> .',
|
||||
delay: 50
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: '01/15/2026 10:00 AM <DIR> ..',
|
||||
delay: 50
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: '01/15/2026 10:00 AM 128 demo.txt',
|
||||
delay: 50
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: ' 1 File(s) 128 bytes',
|
||||
delay: 50
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: ' 2 Dir(s) 50,000,000,000 bytes free',
|
||||
delay: 50,
|
||||
explanation:
|
||||
'系统返回了文件列表。`<DIR>` 表示这是一个文件夹,数字表示文件大小。'
|
||||
},
|
||||
{ type: 'output', content: '', delay: 100 }
|
||||
]
|
||||
},
|
||||
@@ -109,46 +171,137 @@ const configs = {
|
||||
prompt: 'PS C:\\Users\\User>',
|
||||
demo: [
|
||||
{ type: 'explanation', content: '准备输入命令...' },
|
||||
{ type: 'command', content: 'Get-Date', delay: 400, explanation: '输入 `Get-Date`。PowerShell 使用动词-名词的命名方式,这里是**获取当前时间**。' },
|
||||
{ type: 'output', content: '', delay: 100, explanation: '系统返回了当前的日期和时间。' },
|
||||
{ type: 'output', content: 'Thursday, January 15, 2026 10:00:00 AM', delay: 100 },
|
||||
{
|
||||
type: 'command',
|
||||
content: 'Get-Date',
|
||||
delay: 400,
|
||||
explanation:
|
||||
'输入 `Get-Date`。PowerShell 使用动词-名词的命名方式,这里是**获取当前时间**。'
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: '',
|
||||
delay: 100,
|
||||
explanation: '系统返回了当前的日期和时间。'
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: 'Thursday, January 15, 2026 10:00:00 AM',
|
||||
delay: 100
|
||||
},
|
||||
{ type: 'output', content: '', delay: 100 },
|
||||
{ type: 'command', content: 'echo "Hello World"', delay: 400, explanation: '输入 `echo`。这是让计算机**复读**你说的话,常用于测试或打印信息。' },
|
||||
{ type: 'output', content: 'Hello World', delay: 100, explanation: '计算机乖乖地输出了 "Hello World"。' }
|
||||
{
|
||||
type: 'command',
|
||||
content: 'echo "Hello World"',
|
||||
delay: 400,
|
||||
explanation:
|
||||
'输入 `echo`。这是让计算机**复读**你说的话,常用于测试或打印信息。'
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: 'Hello World',
|
||||
delay: 100,
|
||||
explanation: '计算机乖乖地输出了 "Hello World"。'
|
||||
}
|
||||
]
|
||||
},
|
||||
'mac': {
|
||||
mac: {
|
||||
title: 'user — -zsh — 80x24',
|
||||
prompt: 'user@MacBook-Pro ~ % ',
|
||||
demo: [
|
||||
{ type: 'explanation', content: '准备输入命令...' },
|
||||
{ type: 'command', content: 'ls -G', delay: 400, explanation: '输入 `ls` (List)。这是 Mac/Linux 系统用来**列出文件**的命令。`-G` 参数让输出带颜色。' },
|
||||
{ type: 'output', content: 'Desktop Downloads Movies Music', delay: 100 },
|
||||
{ type: 'output', content: 'Documents Library Pictures Public', delay: 100, explanation: '系统列出了你的主目录下的文件夹。' },
|
||||
{ type: 'command', content: 'sw_vers', delay: 400, explanation: '输入 `sw_vers` (Software Version)。这是 macOS 特有的命令,查看**系统版本**。' },
|
||||
{
|
||||
type: 'command',
|
||||
content: 'ls -G',
|
||||
delay: 400,
|
||||
explanation:
|
||||
'输入 `ls` (List)。这是 Mac/Linux 系统用来**列出文件**的命令。`-G` 参数让输出带颜色。'
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: 'Desktop Downloads Movies Music',
|
||||
delay: 100
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: 'Documents Library Pictures Public',
|
||||
delay: 100,
|
||||
explanation: '系统列出了你的主目录下的文件夹。'
|
||||
},
|
||||
{
|
||||
type: 'command',
|
||||
content: 'sw_vers',
|
||||
delay: 400,
|
||||
explanation:
|
||||
'输入 `sw_vers` (Software Version)。这是 macOS 特有的命令,查看**系统版本**。'
|
||||
},
|
||||
{ type: 'output', content: 'ProductName: macOS', delay: 50 },
|
||||
{ type: 'output', content: 'ProductVersion: 15.1', delay: 50 },
|
||||
{ type: 'output', content: 'BuildVersion: 24B83', delay: 50, explanation: '系统返回了当前的 macOS 版本信息。' }
|
||||
{
|
||||
type: 'output',
|
||||
content: 'BuildVersion: 24B83',
|
||||
delay: 50,
|
||||
explanation: '系统返回了当前的 macOS 版本信息。'
|
||||
}
|
||||
]
|
||||
},
|
||||
'linux': {
|
||||
linux: {
|
||||
title: 'user@hostname: ~',
|
||||
prompt: 'user@hostname:~$ ',
|
||||
demo: [
|
||||
{ type: 'explanation', content: '准备输入命令...' },
|
||||
{ type: 'command', content: 'ls -la', delay: 400, explanation: '输入 `ls` (List)。这是 Linux/Mac 系统用来**列出文件**的命令。`-la` 是参数,表示“列出所有文件(all)的详细信息(long)”。' },
|
||||
{ type: 'output', content: 'total 8', delay: 100, explanation: '系统返回了文件列表。左边的 `drwxr-xr-x` 看起来像乱码,其实是**权限描述**(谁能读、谁能写)。' },
|
||||
{ type: 'output', content: 'drwxr-xr-x 2 user user 4096 Jan 15 10:00 .', delay: 50 },
|
||||
{ type: 'output', content: 'drwxr-xr-x 3 user user 4096 Jan 15 10:00 ..', delay: 50 },
|
||||
{ type: 'output', content: '-rw-r--r-- 1 user user 128 Jan 15 10:00 demo.txt', delay: 50 },
|
||||
{ type: 'command', content: 'whoami', delay: 400, explanation: '输入 `whoami` (Who am I)。这是一个经典的哲学命令(笑),告诉计算机:**我是谁?**(当前登录用户)。' },
|
||||
{ type: 'output', content: 'user', delay: 100, explanation: '系统回答:你是 "user"。' }
|
||||
{
|
||||
type: 'command',
|
||||
content: 'ls -la',
|
||||
delay: 400,
|
||||
explanation:
|
||||
'输入 `ls` (List)。这是 Linux/Mac 系统用来**列出文件**的命令。`-la` 是参数,表示“列出所有文件(all)的详细信息(long)”。'
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: 'total 8',
|
||||
delay: 100,
|
||||
explanation:
|
||||
'系统返回了文件列表。左边的 `drwxr-xr-x` 看起来像乱码,其实是**权限描述**(谁能读、谁能写)。'
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: 'drwxr-xr-x 2 user user 4096 Jan 15 10:00 .',
|
||||
delay: 50
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: 'drwxr-xr-x 3 user user 4096 Jan 15 10:00 ..',
|
||||
delay: 50
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: '-rw-r--r-- 1 user user 128 Jan 15 10:00 demo.txt',
|
||||
delay: 50
|
||||
},
|
||||
{
|
||||
type: 'command',
|
||||
content: 'whoami',
|
||||
delay: 400,
|
||||
explanation:
|
||||
'输入 `whoami` (Who am I)。这是一个经典的哲学命令(笑),告诉计算机:**我是谁?**(当前登录用户)。'
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content: 'user',
|
||||
delay: 100,
|
||||
explanation: '系统回答:你是 "user"。'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const currentOSConfig = computed(() => configs[currentOS.value])
|
||||
const isFinished = computed(() => currentOSConfig.value && currentStepIndex.value >= currentOSConfig.value.demo.length - 1)
|
||||
const isFinished = computed(
|
||||
() =>
|
||||
currentOSConfig.value &&
|
||||
currentStepIndex.value >= currentOSConfig.value.demo.length - 1
|
||||
)
|
||||
|
||||
const switchOS = (id) => {
|
||||
currentOS.value = id
|
||||
@@ -161,7 +314,11 @@ const resetDemo = () => {
|
||||
currentStepIndex.value = -1
|
||||
isTyping.value = false
|
||||
// Add initial prompt
|
||||
lines.value.push({ type: 'input', prompt: currentOSConfig.value.prompt, content: '' })
|
||||
lines.value.push({
|
||||
type: 'input',
|
||||
prompt: currentOSConfig.value.prompt,
|
||||
content: ''
|
||||
})
|
||||
}
|
||||
|
||||
// Initial reset
|
||||
@@ -191,8 +348,11 @@ const nextStep = async () => {
|
||||
|
||||
if (step.type === 'command') {
|
||||
// Ensure input line exists
|
||||
if (lines.value.length === 0 || lines.value[lines.value.length - 1].type !== 'input') {
|
||||
lines.value.push({ type: 'input', prompt: promptText, content: '' })
|
||||
if (
|
||||
lines.value.length === 0 ||
|
||||
lines.value[lines.value.length - 1].type !== 'input'
|
||||
) {
|
||||
lines.value.push({ type: 'input', prompt: promptText, content: '' })
|
||||
}
|
||||
|
||||
// Type effect
|
||||
@@ -202,7 +362,7 @@ const nextStep = async () => {
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
targetLine.content += text[i]
|
||||
await new Promise(r => setTimeout(r, 30 + Math.random() * 40))
|
||||
await new Promise((r) => setTimeout(r, 30 + Math.random() * 40))
|
||||
}
|
||||
isTyping.value = false
|
||||
|
||||
@@ -233,13 +393,13 @@ const nextStep = async () => {
|
||||
}
|
||||
|
||||
// Small delay between batched outputs for visual smoothness
|
||||
await new Promise(r => setTimeout(r, 50))
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
}
|
||||
}
|
||||
|
||||
// If we finished everything, add a final prompt
|
||||
if (isFinished.value) {
|
||||
lines.value.push({ type: 'input', prompt: promptText, content: '' })
|
||||
lines.value.push({ type: 'input', prompt: promptText, content: '' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -345,9 +505,15 @@ const nextStep = async () => {
|
||||
.terminal-window.linux .window-buttons .btn {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.terminal-window.linux .window-buttons .close { background: #ff5f56; }
|
||||
.terminal-window.linux .window-buttons .minimize { background: #ffbd2e; }
|
||||
.terminal-window.linux .window-buttons .maximize { background: #27c93f; }
|
||||
.terminal-window.linux .window-buttons .close {
|
||||
background: #ff5f56;
|
||||
}
|
||||
.terminal-window.linux .window-buttons .minimize {
|
||||
background: #ffbd2e;
|
||||
}
|
||||
.terminal-window.linux .window-buttons .maximize {
|
||||
background: #27c93f;
|
||||
}
|
||||
|
||||
/* Common Layout */
|
||||
.terminal-window.mac {
|
||||
@@ -363,9 +529,15 @@ const nextStep = async () => {
|
||||
.terminal-window.mac .window-buttons .btn {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.terminal-window.mac .window-buttons .close { background: #ff5f56; }
|
||||
.terminal-window.mac .window-buttons .minimize { background: #ffbd2e; }
|
||||
.terminal-window.mac .window-buttons .maximize { background: #27c93f; }
|
||||
.terminal-window.mac .window-buttons .close {
|
||||
background: #ff5f56;
|
||||
}
|
||||
.terminal-window.mac .window-buttons .minimize {
|
||||
background: #ffbd2e;
|
||||
}
|
||||
.terminal-window.mac .window-buttons .maximize {
|
||||
background: #27c93f;
|
||||
}
|
||||
|
||||
.window-bar {
|
||||
padding: 8px 12px;
|
||||
@@ -478,14 +650,20 @@ const nextStep = async () => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.completed-overlay {
|
||||
@@ -513,7 +691,7 @@ const nextStep = async () => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
@@ -595,8 +773,13 @@ const nextStep = async () => {
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hint {
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
<div class="terminal-title">Terminal - zsh</div>
|
||||
</div>
|
||||
<div class="terminal-body" ref="terminalBody" @click="focusInput">
|
||||
<div v-for="(line, index) in history" :key="index" class="terminal-line">
|
||||
<div
|
||||
v-for="(line, index) in history"
|
||||
:key="index"
|
||||
class="terminal-line"
|
||||
>
|
||||
<span class="prompt" v-if="line.type === 'input'">
|
||||
<span class="path">{{ currentPath }}</span>
|
||||
<span class="arrow">$ </span>
|
||||
@@ -58,10 +62,20 @@
|
||||
<span class="zh">命令速查表</span>
|
||||
</div>
|
||||
<div class="sheet-content">
|
||||
<div class="cmd-group" v-for="(group, gIndex) in cheatSheet" :key="gIndex">
|
||||
<div
|
||||
class="cmd-group"
|
||||
v-for="(group, gIndex) in cheatSheet"
|
||||
:key="gIndex"
|
||||
>
|
||||
<div class="group-title">{{ group.category }}</div>
|
||||
<div class="cmd-item" v-for="(cmd, cIndex) in group.commands" :key="cIndex">
|
||||
<div class="cmd-name" @click="fillCommand(cmd.name)">{{ cmd.name }}</div>
|
||||
<div
|
||||
class="cmd-item"
|
||||
v-for="(cmd, cIndex) in group.commands"
|
||||
:key="cIndex"
|
||||
>
|
||||
<div class="cmd-name" @click="fillCommand(cmd.name)">
|
||||
{{ cmd.name }}
|
||||
</div>
|
||||
<div class="cmd-desc">
|
||||
<div class="en">{{ cmd.descEn }}</div>
|
||||
<div class="zh">{{ cmd.descZh }}</div>
|
||||
@@ -77,8 +91,16 @@
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
|
||||
const history = ref([
|
||||
{ type: 'output', content: 'Welcome to the interactive terminal simulator! / 欢迎使用交互式终端模拟器!' },
|
||||
{ type: 'output', content: 'Type "help" to see available commands. / 输入 "help" 查看可用命令。' }
|
||||
{
|
||||
type: 'output',
|
||||
content:
|
||||
'Welcome to the interactive terminal simulator! / 欢迎使用交互式终端模拟器!'
|
||||
},
|
||||
{
|
||||
type: 'output',
|
||||
content:
|
||||
'Type "help" to see available commands. / 输入 "help" 查看可用命令。'
|
||||
}
|
||||
])
|
||||
const currentInput = ref('')
|
||||
const inputField = ref(null)
|
||||
@@ -91,41 +113,60 @@ const fileSystem = {
|
||||
name: '/',
|
||||
type: 'dir',
|
||||
children: {
|
||||
'home': {
|
||||
home: {
|
||||
name: 'home',
|
||||
type: 'dir',
|
||||
children: {
|
||||
'user': {
|
||||
user: {
|
||||
name: 'user',
|
||||
type: 'dir',
|
||||
children: {
|
||||
'hello.txt': { name: 'hello.txt', type: 'file', content: 'Hello World! This is a mock file.\n你好!这是一个模拟文件。' },
|
||||
'notes.md': { name: 'notes.md', type: 'file', content: '# My Notes\n- Learn Terminal\n- Learn Shell\n- Learn Kernel' },
|
||||
'projects': {
|
||||
'hello.txt': {
|
||||
name: 'hello.txt',
|
||||
type: 'file',
|
||||
content:
|
||||
'Hello World! This is a mock file.\n你好!这是一个模拟文件。'
|
||||
},
|
||||
'notes.md': {
|
||||
name: 'notes.md',
|
||||
type: 'file',
|
||||
content:
|
||||
'# My Notes\n- Learn Terminal\n- Learn Shell\n- Learn Kernel'
|
||||
},
|
||||
projects: {
|
||||
name: 'projects',
|
||||
type: 'dir',
|
||||
children: {
|
||||
'app.js': { name: 'app.js', type: 'file', content: 'console.log("Hello");' }
|
||||
'app.js': {
|
||||
name: 'app.js',
|
||||
type: 'file',
|
||||
content: 'console.log("Hello");'
|
||||
}
|
||||
}
|
||||
},
|
||||
'Downloads': { name: 'Downloads', type: 'dir', children: {} }
|
||||
Downloads: { name: 'Downloads', type: 'dir', children: {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'etc': {
|
||||
etc: {
|
||||
name: 'etc',
|
||||
type: 'dir',
|
||||
children: {
|
||||
'passwd': { name: 'passwd', type: 'file', content: 'root:x:0:0:root:/root:/bin/bash\nuser:x:1000:1000:user:/home/user:/bin/zsh' }
|
||||
passwd: {
|
||||
name: 'passwd',
|
||||
type: 'file',
|
||||
content:
|
||||
'root:x:0:0:root:/root:/bin/bash\nuser:x:1000:1000:user:/home/user:/bin/zsh'
|
||||
}
|
||||
}
|
||||
},
|
||||
'bin': {
|
||||
bin: {
|
||||
name: 'bin',
|
||||
type: 'dir',
|
||||
children: {
|
||||
'ls': { name: 'ls', type: 'file', content: 'Binary file' },
|
||||
'cat': { name: 'cat', type: 'file', content: 'Binary file' }
|
||||
ls: { name: 'ls', type: 'file', content: 'Binary file' },
|
||||
cat: { name: 'cat', type: 'file', content: 'Binary file' }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,10 +176,11 @@ let currentPath = '~'
|
||||
let currentDirObj = fileSystem.children['home'].children['user']
|
||||
|
||||
const resolvePath = (path) => {
|
||||
if (path === '~' || path === '') return fileSystem.children['home'].children['user']
|
||||
if (path === '~' || path === '')
|
||||
return fileSystem.children['home'].children['user']
|
||||
if (path === '/') return fileSystem
|
||||
|
||||
let parts = path.split('/').filter(p => p)
|
||||
let parts = path.split('/').filter((p) => p)
|
||||
let current = path.startsWith('/') ? fileSystem : currentDirObj
|
||||
|
||||
for (const part of parts) {
|
||||
@@ -177,23 +219,25 @@ const navigateTo = (target) => {
|
||||
newDir = fileSystem.children['home'].children['user']
|
||||
} else if (target === '..') {
|
||||
if (currentPath === '/') return { path: '/', dir: fileSystem }
|
||||
if (currentPath === '~') { // ~ is /home/user
|
||||
newPath = '/home'
|
||||
newDir = fileSystem.children['home']
|
||||
if (currentPath === '~') {
|
||||
// ~ is /home/user
|
||||
newPath = '/home'
|
||||
newDir = fileSystem.children['home']
|
||||
} else {
|
||||
// Simple string manipulation for path
|
||||
const parts = currentPath.split('/')
|
||||
parts.pop()
|
||||
newPath = parts.join('/') || '/'
|
||||
// Simple string manipulation for path
|
||||
const parts = currentPath.split('/')
|
||||
parts.pop()
|
||||
newPath = parts.join('/') || '/'
|
||||
|
||||
// Re-resolve dir from root for safety
|
||||
if (newPath === '/') newDir = fileSystem
|
||||
else if (newPath === '/home') newDir = fileSystem.children['home']
|
||||
else if (newPath === '/home/user') newDir = fileSystem.children['home'].children['user']
|
||||
else {
|
||||
// Fallback for deeper paths if we supported them
|
||||
// For now, let's keep it simple
|
||||
}
|
||||
// Re-resolve dir from root for safety
|
||||
if (newPath === '/') newDir = fileSystem
|
||||
else if (newPath === '/home') newDir = fileSystem.children['home']
|
||||
else if (newPath === '/home/user')
|
||||
newDir = fileSystem.children['home'].children['user']
|
||||
else {
|
||||
// Fallback for deeper paths if we supported them
|
||||
// For now, let's keep it simple
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Relative path
|
||||
@@ -201,7 +245,8 @@ const navigateTo = (target) => {
|
||||
const targetObj = currentDirObj.children[target]
|
||||
if (targetObj.type === 'dir') {
|
||||
newDir = targetObj
|
||||
newPath = currentPath === '/' ? `/${target}` : `${currentPath}/${target}`
|
||||
newPath =
|
||||
currentPath === '/' ? `/${target}` : `${currentPath}/${target}`
|
||||
} else {
|
||||
return { error: `cd: not a directory: ${target}` }
|
||||
}
|
||||
@@ -216,24 +261,52 @@ const cheatSheet = [
|
||||
{
|
||||
category: 'Navigation / 导航',
|
||||
commands: [
|
||||
{ name: 'ls', descEn: 'List directory contents', descZh: '列出当前目录下的文件和文件夹' },
|
||||
{ name: 'cd <dir>', descEn: 'Change directory', descZh: '进入指定目录 (例如: cd projects)' },
|
||||
{ name: 'pwd', descEn: 'Print working directory', descZh: '显示当前所在的完整路径' }
|
||||
{
|
||||
name: 'ls',
|
||||
descEn: 'List directory contents',
|
||||
descZh: '列出当前目录下的文件和文件夹'
|
||||
},
|
||||
{
|
||||
name: 'cd <dir>',
|
||||
descEn: 'Change directory',
|
||||
descZh: '进入指定目录 (例如: cd projects)'
|
||||
},
|
||||
{
|
||||
name: 'pwd',
|
||||
descEn: 'Print working directory',
|
||||
descZh: '显示当前所在的完整路径'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'File Operations / 文件操作',
|
||||
commands: [
|
||||
{ name: 'cat <file>', descEn: 'Show file contents', descZh: '查看文件内容 (例如: cat hello.txt)' },
|
||||
{ name: 'touch <file>', descEn: 'Create empty file', descZh: '创建一个新文件' },
|
||||
{ name: 'mkdir <dir>', descEn: 'Make directory', descZh: '创建一个新文件夹' },
|
||||
{
|
||||
name: 'cat <file>',
|
||||
descEn: 'Show file contents',
|
||||
descZh: '查看文件内容 (例如: cat hello.txt)'
|
||||
},
|
||||
{
|
||||
name: 'touch <file>',
|
||||
descEn: 'Create empty file',
|
||||
descZh: '创建一个新文件'
|
||||
},
|
||||
{
|
||||
name: 'mkdir <dir>',
|
||||
descEn: 'Make directory',
|
||||
descZh: '创建一个新文件夹'
|
||||
},
|
||||
{ name: 'rm <file>', descEn: 'Remove file', descZh: '删除文件' }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'System / 系统',
|
||||
commands: [
|
||||
{ name: 'echo <text>', descEn: 'Print text', descZh: '在屏幕上打印一段文字' },
|
||||
{
|
||||
name: 'echo <text>',
|
||||
descEn: 'Print text',
|
||||
descZh: '在屏幕上打印一段文字'
|
||||
},
|
||||
{ name: 'whoami', descEn: 'Current user', descZh: '显示当前用户名' },
|
||||
{ name: 'date', descEn: 'Show date/time', descZh: '显示当前日期和时间' },
|
||||
{ name: 'clear', descEn: 'Clear screen', descZh: '清空屏幕内容' }
|
||||
@@ -242,8 +315,16 @@ const cheatSheet = [
|
||||
{
|
||||
category: 'Package Manager / 软件包 (Mock)',
|
||||
commands: [
|
||||
{ name: 'apt update', descEn: 'Update package list', descZh: '更新软件包列表' },
|
||||
{ name: 'apt install <pkg>', descEn: 'Install package', descZh: '安装软件 (例如: apt install git)' }
|
||||
{
|
||||
name: 'apt update',
|
||||
descEn: 'Update package list',
|
||||
descZh: '更新软件包列表'
|
||||
},
|
||||
{
|
||||
name: 'apt install <pkg>',
|
||||
descEn: 'Install package',
|
||||
descZh: '安装软件 (例如: apt install git)'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -260,7 +341,7 @@ const commands = {
|
||||
if (items.length === 0) return ''
|
||||
|
||||
// Simple column formatting
|
||||
const names = items.map(item => {
|
||||
const names = items.map((item) => {
|
||||
return item.type === 'dir' ? `\x1b[1;34m${item.name}/\x1b[0m` : item.name
|
||||
})
|
||||
return names.join(' ')
|
||||
@@ -312,7 +393,8 @@ const commands = {
|
||||
mkdir: (args) => {
|
||||
const name = args[0]
|
||||
if (!name) return 'usage: mkdir <dir>'
|
||||
if (currentDirObj.children[name]) return `mkdir: cannot create directory '${name}': File exists`
|
||||
if (currentDirObj.children[name])
|
||||
return `mkdir: cannot create directory '${name}': File exists`
|
||||
currentDirObj.children[name] = { name, type: 'dir', children: {} }
|
||||
return null
|
||||
},
|
||||
@@ -322,7 +404,8 @@ const commands = {
|
||||
if (!name) return 'usage: rm <file>'
|
||||
// Mock: -r not supported for simplicity
|
||||
if (currentDirObj.children[name]) {
|
||||
if (currentDirObj.children[name].type === 'dir') return `rm: cannot remove '${name}': Is a directory`
|
||||
if (currentDirObj.children[name].type === 'dir')
|
||||
return `rm: cannot remove '${name}': Is a directory`
|
||||
delete currentDirObj.children[name]
|
||||
return null
|
||||
}
|
||||
@@ -392,18 +475,27 @@ const executeCommand = () => {
|
||||
let safeOutput = output
|
||||
|
||||
const lines = safeOutput.split('\n')
|
||||
lines.forEach(line => {
|
||||
// Basic ANSI parser for ls colors
|
||||
const isDir = line.includes('\x1b[1;34m')
|
||||
const cleanContent = line.replace(/\x1b\[[0-9;]*m/g, '')
|
||||
history.value.push({ type: isDir ? 'output-dir' : 'output', content: cleanContent })
|
||||
lines.forEach((line) => {
|
||||
// Basic ANSI parser for ls colors
|
||||
const isDir = line.includes('\x1b[1;34m')
|
||||
const cleanContent = line.replace(/\x1b\[[0-9;]*m/g, '')
|
||||
history.value.push({
|
||||
type: isDir ? 'output-dir' : 'output',
|
||||
content: cleanContent
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
history.value.push({ type: 'error', content: `Error executing command: ${e.message}` })
|
||||
history.value.push({
|
||||
type: 'error',
|
||||
content: `Error executing command: ${e.message}`
|
||||
})
|
||||
}
|
||||
} else {
|
||||
history.value.push({ type: 'error', content: `zsh: command not found: ${cmd}` })
|
||||
history.value.push({
|
||||
type: 'error',
|
||||
content: `zsh: command not found: ${cmd}`
|
||||
})
|
||||
}
|
||||
|
||||
currentInput.value = ''
|
||||
@@ -416,7 +508,8 @@ const navigateHistory = (direction) => {
|
||||
historyIndex.value += direction
|
||||
|
||||
if (historyIndex.value < 0) historyIndex.value = 0
|
||||
if (historyIndex.value > commandHistory.value.length) historyIndex.value = commandHistory.value.length
|
||||
if (historyIndex.value > commandHistory.value.length)
|
||||
historyIndex.value = commandHistory.value.length
|
||||
|
||||
if (historyIndex.value === commandHistory.value.length) {
|
||||
currentInput.value = ''
|
||||
@@ -432,7 +525,9 @@ const handleTabCompletion = () => {
|
||||
const partial = args[args.length - 1] || ''
|
||||
|
||||
if (cmd && currentDirObj.children) {
|
||||
const matches = Object.keys(currentDirObj.children).filter(name => name.startsWith(partial))
|
||||
const matches = Object.keys(currentDirObj.children).filter((name) =>
|
||||
name.startsWith(partial)
|
||||
)
|
||||
if (matches.length === 1) {
|
||||
const completed = matches[0]
|
||||
// Replace last arg with completed
|
||||
@@ -506,9 +601,15 @@ onMounted(() => {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.red { background-color: #ef4444; }
|
||||
.yellow { background-color: #facc15; }
|
||||
.green { background-color: #22c55e; }
|
||||
.red {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
.yellow {
|
||||
background-color: #facc15;
|
||||
}
|
||||
.green {
|
||||
background-color: #22c55e;
|
||||
}
|
||||
|
||||
.terminal-title {
|
||||
flex: 1;
|
||||
@@ -653,7 +754,8 @@ input {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.terminal-container, .cheat-sheet {
|
||||
.terminal-container,
|
||||
.cheat-sheet {
|
||||
height: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,11 +38,17 @@
|
||||
<div class="info-title">Patch: {{ items[hoverIndex]?.label }}</div>
|
||||
<div class="info-desc">正在关注:</div>
|
||||
<ul class="attn-list" v-if="hoverIndex !== -1">
|
||||
<li v-for="(weight, targetIdx) in getTopAttentions(hoverIndex)" :key="targetIdx">
|
||||
<li
|
||||
v-for="(weight, targetIdx) in getTopAttentions(hoverIndex)"
|
||||
:key="targetIdx"
|
||||
>
|
||||
<span class="target-icon">{{ items[targetIdx].icon }}</span>
|
||||
<span class="target-name">{{ items[targetIdx].label }}</span>
|
||||
<div class="bar-bg">
|
||||
<div class="bar-fill" :style="{ width: (weight * 100) + '%' }"></div>
|
||||
<div
|
||||
class="bar-fill"
|
||||
:style="{ width: weight * 100 + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -57,9 +63,15 @@ import { ref } from 'vue'
|
||||
const hoverIndex = ref(-1)
|
||||
|
||||
const items = [
|
||||
{ icon: '🌲', label: '背景' }, { icon: '🌲', label: '背景' }, { icon: '☁️', label: '天空' },
|
||||
{ icon: '👂', label: '猫耳' }, { icon: '😼', label: '猫脸' }, { icon: '🌲', label: '背景' },
|
||||
{ icon: '🐾', label: '猫爪' }, { icon: '🧶', label: '毛线' }, { icon: '🌱', label: '草地' }
|
||||
{ icon: '🌲', label: '背景' },
|
||||
{ icon: '🌲', label: '背景' },
|
||||
{ icon: '☁️', label: '天空' },
|
||||
{ icon: '👂', label: '猫耳' },
|
||||
{ icon: '😼', label: '猫脸' },
|
||||
{ icon: '🌲', label: '背景' },
|
||||
{ icon: '🐾', label: '猫爪' },
|
||||
{ icon: '🧶', label: '毛线' },
|
||||
{ icon: '🌱', label: '草地' }
|
||||
]
|
||||
|
||||
// 3x3 Grid
|
||||
@@ -175,10 +187,11 @@ const getTopAttentions = (source) => {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.grid-cell:hover, .grid-cell.active {
|
||||
.grid-cell:hover,
|
||||
.grid-cell.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
background: var(--vp-c-bg-mute);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<template>
|
||||
<div class="feature-alignment-demo">
|
||||
<div class="header">
|
||||
<div class="title">阶段一:特征对齐 (Feature Alignment / Pre-training)</div>
|
||||
<div class="title">
|
||||
阶段一:特征对齐 (Feature Alignment / Pre-training)
|
||||
</div>
|
||||
<div class="desc">
|
||||
目标:让 Projector 学会“翻译”图像语言。
|
||||
<br>做法:冻结 ViT 和 LLM,只训练 Projector。
|
||||
<br />做法:冻结 ViT 和 LLM,只训练 Projector。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,11 +15,11 @@
|
||||
<div class="data-column">
|
||||
<div class="data-item image-data">
|
||||
<div class="data-icon">🖼️</div>
|
||||
<div class="data-label">图片<br>(猫)</div>
|
||||
<div class="data-label">图片<br />(猫)</div>
|
||||
</div>
|
||||
<div class="data-item text-data">
|
||||
<div class="data-icon">📝</div>
|
||||
<div class="data-label">标题<br>("一只猫")</div>
|
||||
<div class="data-label">标题<br />("一只猫")</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -107,21 +109,31 @@ const nextStep = () => {
|
||||
|
||||
const buttonText = computed(() => {
|
||||
switch (step.value) {
|
||||
case 0: return '开始训练演示'
|
||||
case 1: return '下一步:计算 Loss'
|
||||
case 2: return '下一步:反向传播'
|
||||
case 3: return '完成并重置'
|
||||
default: return '开始'
|
||||
case 0:
|
||||
return '开始训练演示'
|
||||
case 1:
|
||||
return '下一步:计算 Loss'
|
||||
case 2:
|
||||
return '下一步:反向传播'
|
||||
case 3:
|
||||
return '完成并重置'
|
||||
default:
|
||||
return '开始'
|
||||
}
|
||||
})
|
||||
|
||||
const currentStepDesc = computed(() => {
|
||||
switch (step.value) {
|
||||
case 0: return '准备就绪。点击按钮开始模拟一次训练迭代。'
|
||||
case 1: return '前向传播:图片经过 ViT (冻结) 和 Projector (训练) 得到向量 V;文本经过 LLM (冻结) 得到向量 T。'
|
||||
case 2: return '计算 Loss:比较向量 V 和向量 T 的相似度。目标是让它们尽可能接近。'
|
||||
case 3: return '反向传播:根据 Loss 更新 Projector 的参数。注意 ViT 和 LLM 不会更新!'
|
||||
default: return ''
|
||||
case 0:
|
||||
return '准备就绪。点击按钮开始模拟一次训练迭代。'
|
||||
case 1:
|
||||
return '前向传播:图片经过 ViT (冻结) 和 Projector (训练) 得到向量 V;文本经过 LLM (冻结) 得到向量 T。'
|
||||
case 2:
|
||||
return '计算 Loss:比较向量 V 和向量 T 的相似度。目标是让它们尽可能接近。'
|
||||
case 3:
|
||||
return '反向传播:根据 Loss 更新 Projector 的参数。注意 ViT 和 LLM 不会更新!'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
@@ -135,7 +147,8 @@ const isCalculatingLoss = computed(() => step.value === 2)
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -184,8 +197,14 @@ const isCalculatingLoss = computed(() => step.value === 2)
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.data-icon { font-size: 24px; }
|
||||
.data-label { font-size: 10px; text-align: center; margin-top: 4px; }
|
||||
.data-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
.data-label {
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Arrow Column */
|
||||
.arrow-column {
|
||||
@@ -201,8 +220,8 @@ const isCalculatingLoss = computed(() => step.value === 2)
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
grid-template-areas:
|
||||
"vit arrow proj"
|
||||
"llm llm llm";
|
||||
'vit arrow proj'
|
||||
'llm llm llm';
|
||||
gap: 10px;
|
||||
row-gap: 30px;
|
||||
align-items: center;
|
||||
@@ -262,8 +281,14 @@ const isCalculatingLoss = computed(() => step.value === 2)
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.block-icon { font-size: 20px; margin-bottom: 4px; }
|
||||
.block-name { font-size: 12px; font-weight: bold; }
|
||||
.block-icon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.block-name {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.arrow-small {
|
||||
grid-area: arrow;
|
||||
@@ -316,8 +341,15 @@ const isCalculatingLoss = computed(() => step.value === 2)
|
||||
box-shadow: 0 0 10px rgba(255, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.loss-label { font-size: 12px; font-weight: bold; color: var(--vp-c-danger); }
|
||||
.loss-desc { font-size: 10px; color: var(--vp-c-text-2); }
|
||||
.loss-label {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-danger);
|
||||
}
|
||||
.loss-desc {
|
||||
font-size: 10px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
.controls {
|
||||
@@ -356,9 +388,15 @@ const isCalculatingLoss = computed(() => step.value === 2)
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(var(--vp-c-brand-rgb), 0.4); }
|
||||
70% { box-shadow: 0 0 0 10px rgba(var(--vp-c-brand-rgb), 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(var(--vp-c-brand-rgb), 0); }
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(var(--vp-c-brand-rgb), 0.4);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(var(--vp-c-brand-rgb), 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(var(--vp-c-brand-rgb), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
|
||||
@@ -5,7 +5,12 @@
|
||||
<div class="step-box">
|
||||
<div class="label">1. Patch (4x4)</div>
|
||||
<div class="grid-patch">
|
||||
<div v-for="n in 16" :key="n" class="pixel" :style="{ backgroundColor: getPixelColor(n) }"></div>
|
||||
<div
|
||||
v-for="n in 16"
|
||||
:key="n"
|
||||
class="pixel"
|
||||
:style="{ backgroundColor: getPixelColor(n) }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="desc">768 像素点</div>
|
||||
</div>
|
||||
@@ -16,7 +21,12 @@
|
||||
<div class="step-box">
|
||||
<div class="label">2. Flatten</div>
|
||||
<div class="vector-container">
|
||||
<div v-for="n in 16" :key="n" class="vector-cell" :style="{ backgroundColor: getPixelColor(n) }"></div>
|
||||
<div
|
||||
v-for="n in 16"
|
||||
:key="n"
|
||||
class="vector-cell"
|
||||
:style="{ backgroundColor: getPixelColor(n) }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="desc">拉平成向量</div>
|
||||
</div>
|
||||
@@ -38,8 +48,8 @@
|
||||
<script setup>
|
||||
const getPixelColor = (n) => {
|
||||
// Generate a gradient of colors
|
||||
const hue = (n * 20) % 360;
|
||||
return `hsl(${hue}, 70%, 60%)`;
|
||||
const hue = (n * 20) % 360
|
||||
return `hsl(${hue}, 70%, 60%)`
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
+77
-26
@@ -14,15 +14,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-desc">
|
||||
{{ isVLM
|
||||
? '给大脑装上眼睛:视觉信号经过翻译,变成 Token 混入文字流。'
|
||||
: '纯文本大脑:只能听懂 Token 语言,无法感知图像。'
|
||||
{{
|
||||
isVLM
|
||||
? '给大脑装上眼睛:视觉信号经过翻译,变成 Token 混入文字流。'
|
||||
: '纯文本大脑:只能听懂 Token 语言,无法感知图像。'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="diagram-stage" :class="{ 'vlm-mode': isVLM }">
|
||||
|
||||
<!-- Vision Pipeline (Only visible in VLM mode) -->
|
||||
<div class="pipeline vision-pipeline">
|
||||
<div class="node-group">
|
||||
@@ -31,12 +31,18 @@
|
||||
<span class="label">Image</span>
|
||||
</div>
|
||||
<div class="flow-arrow">⬇</div>
|
||||
<div class="node process-node vit-node" title="Vision Transformer: The Eye">
|
||||
<div
|
||||
class="node process-node vit-node"
|
||||
title="Vision Transformer: The Eye"
|
||||
>
|
||||
<span class="icon">�️</span>
|
||||
<span class="label">ViT</span>
|
||||
</div>
|
||||
<div class="flow-arrow">⬇</div>
|
||||
<div class="node adapter-node projector-node" title="Projector: The Translator">
|
||||
<div
|
||||
class="node adapter-node projector-node"
|
||||
title="Projector: The Translator"
|
||||
>
|
||||
<span class="icon">🔌</span>
|
||||
<span class="label">Projector</span>
|
||||
</div>
|
||||
@@ -80,20 +86,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="interactive-info">
|
||||
<div class="info-card" v-if="!isVLM">
|
||||
<h3>Standard LLM Flow</h3>
|
||||
<p>Text is converted into vectors (Embeddings) and processed by the Transformer to predict the next word.</p>
|
||||
<p>
|
||||
Text is converted into vectors (Embeddings) and processed by the
|
||||
Transformer to predict the next word.
|
||||
</p>
|
||||
</div>
|
||||
<div class="info-card vlm-info" v-else>
|
||||
<h3>VLM = LLM + Vision Encoder</h3>
|
||||
<ul>
|
||||
<li><strong>ViT (The Eye):</strong> Slices image into patches and extracts features.</li>
|
||||
<li><strong>Projector (The Translator):</strong> Converts visual features into the same "language" (vector dimension) as text embeddings.</li>
|
||||
<li><strong>Concatenation:</strong> The translated visual tokens are pasted <em>before</em> the text tokens. The LLM sees them as "foreign words" it learned to understand.</li>
|
||||
<li>
|
||||
<strong>ViT (The Eye):</strong> Slices image into patches and
|
||||
extracts features.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Projector (The Translator):</strong> Converts visual
|
||||
features into the same "language" (vector dimension) as text
|
||||
embeddings.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Concatenation:</strong> The translated visual tokens are
|
||||
pasted <em>before</em> the text tokens. The LLM sees them as
|
||||
"foreign words" it learned to understand.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -173,7 +192,7 @@ const toggleMode = () => {
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.toggle-track.active .toggle-thumb {
|
||||
@@ -271,22 +290,34 @@ const toggleMode = () => {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 70px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.icon { font-size: 20px; margin-bottom: 4px; }
|
||||
.label { font-size: 11px; font-weight: bold; }
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.label {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input-node { border-color: #aaa; }
|
||||
.process-node { border-color: var(--vp-c-brand-dimm); }
|
||||
.input-node {
|
||||
border-color: #aaa;
|
||||
}
|
||||
.process-node {
|
||||
border-color: var(--vp-c-brand-dimm);
|
||||
}
|
||||
.core-node {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-dimm);
|
||||
min-width: 100px;
|
||||
}
|
||||
.output-node { border-color: var(--vp-c-brand); }
|
||||
.output-node {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.vit-node {
|
||||
border-color: var(--vp-c-yellow);
|
||||
@@ -355,13 +386,26 @@ const toggleMode = () => {
|
||||
animation: pulse 1s infinite alternate;
|
||||
}
|
||||
|
||||
.t1 { animation-delay: 0s; }
|
||||
.t2 { animation-delay: 0.2s; }
|
||||
.v1 { background: var(--vp-c-yellow); animation-delay: 0.4s; }
|
||||
.t1 {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
.t2 {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.v1 {
|
||||
background: var(--vp-c-yellow);
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
from { opacity: 0.3; transform: scale(0.8); }
|
||||
to { opacity: 1; transform: scale(1.1); }
|
||||
from {
|
||||
opacity: 0.3;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Interactive Info */
|
||||
@@ -383,7 +427,8 @@ const toggleMode = () => {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.info-card p, .info-card li {
|
||||
.info-card p,
|
||||
.info-card li {
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
@@ -395,8 +440,14 @@ const toggleMode = () => {
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile Adjustments */
|
||||
|
||||
@@ -144,7 +144,7 @@ const toggleState = () => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 8px;
|
||||
color: rgba(0,0,0,0.5);
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
@@ -198,12 +198,23 @@ const toggleState = () => {
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(5px); }
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(5px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user