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:
sanbuphy
2026-01-16 19:10:21 +08:00
parent c8567ce23f
commit 73f4788d7e
150 changed files with 19530 additions and 13401 deletions
@@ -2,70 +2,70 @@
### 1. 创建 `docs/zh-cn/appendix/vlm-intro.md` (多模态大模型:给 AI 装上眼睛) ### 1. 创建 `docs/zh-cn/appendix/vlm-intro.md` (多模态大模型:给 AI 装上眼睛)
* **0. 引言**: 从“读万卷书”到“行万里路”。VLM 的核心任务:把图像信号翻译成大模型能懂的语言信号。 - **0. 引言**: 从“读万卷书”到“行万里路”。VLM 的核心任务:把图像信号翻译成大模型能懂的语言信号。
* **1. 第一步:视觉翻译 (Visual Tokenization)**: - **1. 第一步:视觉翻译 (Visual Tokenization)**:
* **ViT (Vision Transformer)**: 计算机怎么“看”图?将图片切成 16x16 的小方块 (Patches),就像把句子切成词 (Tokens)。 - **ViT (Vision Transformer)**: 计算机怎么“看”图?将图片切成 16x16 的小方块 (Patches),就像把句子切成词 (Tokens)。
* **Qwen-VL 的创新**: 提到 **Naive Dynamic Resolution**(动态分辨率),不强制压缩图片,而是根据图片比例动态切分 Patch,像人眼一样看清细节(分辨率自适应),解决了传统模型“看不清长图”的问题。 - **Qwen-VL 的创新**: 提到 **Naive Dynamic Resolution**(动态分辨率),不强制压缩图片,而是根据图片比例动态切分 Patch,像人眼一样看清细节(分辨率自适应),解决了传统模型“看不清长图”的问题。
* **2. 核心难题:跨界沟通 (Projection)**: - **2. 核心难题:跨界沟通 (Projection)**:
* 视觉向量 vs 语言向量。我们需要一个“适配器” (Projector),把视觉特征映射到文本空间。 - 视觉向量 vs 语言向量。我们需要一个“适配器” (Projector),把视觉特征映射到文本空间。
* **架构对比**: - **架构对比**:
* **Linear (LLaVA)**: 简单粗暴的线性投影,训练快,保留信息多。 - **Linear (LLaVA)**: 简单粗暴的线性投影,训练快,保留信息多。
* **Q-Former (BLIP-2)**: 使用查询向量 (Query) 提取关键视觉信息,更轻量。 - **Q-Former (BLIP-2)**: 使用查询向量 (Query) 提取关键视觉信息,更轻量。
* **C-Abstractor (Qwen-VL)**: 结合注意力机制,更高效地压缩视觉信息。 - **C-Abstractor (Qwen-VL)**: 结合注意力机制,更高效地压缩视觉信息。
* **3. 进化之路:ViT + LLM**: - **3. 进化之路:ViT + LLM**:
* Vision Transformer (ViT) 负责“看”,LLM 负责“想”和“说”。 - Vision Transformer (ViT) 负责“看”,LLM 负责“想”和“说”。
* **M-LLM**: 像 GPT-4V 或 Qwen2-VL,已经不仅是“拼接”,而是深度的多模态融合,甚至能处理视频(视为连续的图片帧)。 - **M-LLM**: 像 GPT-4V 或 Qwen2-VL,已经不仅是“拼接”,而是深度的多模态融合,甚至能处理视频(视为连续的图片帧)。
* **4. 训练揭秘:从对齐到对话**: - **4. 训练揭秘:从对齐到对话**:
* **阶段一 (Pre-training)**: 像 CLIP 一样,在大规模图文对上预训练,学会“这张图是猫”。 - **阶段一 (Pre-training)**: 像 CLIP 一样,在大规模图文对上预训练,学会“这张图是猫”。
* **阶段二 (Instruction Tuning)**: 学会“看图说话”,使用 `<image>` 标签和对话数据,让模型能回答“这只猫在干什么?”。 - **阶段二 (Instruction Tuning)**: 学会“看图说话”,使用 `<image>` 标签和对话数据,让模型能回答“这只猫在干什么?”。
* **5. 总结**: 视觉与语言的统一。 - **5. 总结**: 视觉与语言的统一。
### 2. 创建 `docs/zh-cn/appendix/image-gen-intro.md` (AI 绘画:从噪声中重构世界) ### 2. 创建 `docs/zh-cn/appendix/image-gen-intro.md` (AI 绘画:从噪声中重构世界)
* **0. 引言**: 生成式 AI 的魔法——从混沌 (Noise) 到秩序 (Data)。 - **0. 引言**: 生成式 AI 的魔法——从混沌 (Noise) 到秩序 (Data)。
* **1. 第一步:降维打击 (VAE & Latent Space)**: - **1. 第一步:降维打击 (VAE & Latent Space)**:
* 直接画像素太累了(1024x1024 有百万像素)。我们先用 **VAE (变分自编码器)** 把图片压缩成“潜变量” (Latent),在小黑屋里作画(效率提升)。 - 直接画像素太累了(1024x1024 有百万像素)。我们先用 **VAE (变分自编码器)** 把图片压缩成“潜变量” (Latent),在小黑屋里作画(效率提升)。
* **2. 核心机制:扩散 (Diffusion)**: - **2. 核心机制:扩散 (Diffusion)**:
* **破坏 (Forward)**: 像滴入墨水一样,一步步把图片变成纯高斯噪声。 - **破坏 (Forward)**: 像滴入墨水一样,一步步把图片变成纯高斯噪声。
* **重构 (Reverse)**: 训练神经网络预测噪声 $\epsilon$,一步步把墨水“吸”出来。 - **重构 (Reverse)**: 训练神经网络预测噪声 $\epsilon$,一步步把墨水“吸”出来。
* **SDE 视角**: 理解为在概率分布上进行随机游走。 - **SDE 视角**: 理解为在概率分布上进行随机游走。
* **3. 进化之路:流匹配 (Flow Matching)**: - **3. 进化之路:流匹配 (Flow Matching)**:
* **为什么 Diffusion 慢?**: 它的去噪路径是弯弯曲曲的随机路径。 - **为什么 Diffusion 慢?**: 它的去噪路径是弯弯曲曲的随机路径。
* **Flow Matching (Flux/SD3)**: 寻找从噪声分布到图像分布的 **“直线”路径 (Optimal Transport)**。 - **Flow Matching (Flux/SD3)**: 寻找从噪声分布到图像分布的 **“直线”路径 (Optimal Transport)**。
* **向量场 (Vector Field)**: 训练模型预测“速度”而非“噪声”,采样时直接沿着直线飞奔,几步就能画出好图。 - **向量场 (Vector Field)**: 训练模型预测“速度”而非“噪声”,采样时直接沿着直线飞奔,几步就能画出好图。
* **4. 操控:文本如何指挥绘画**: - **4. 操控:文本如何指挥绘画**:
* **CLIP / T5 Encoder**: 充当“甲方”,把 Prompt 变成向量。 - **CLIP / T5 Encoder**: 充当“甲方”,把 Prompt 变成向量。
* **DiT (Diffusion Transformer)**: 像 Sora 和 SD3 一样,用 Transformer 替换掉老的 U-Net,处理能力更强,画质上限更高。 - **DiT (Diffusion Transformer)**: 像 Sora 和 SD3 一样,用 Transformer 替换掉老的 U-Net,处理能力更强,画质上限更高。
* **5. 总结**: 概率分布的搬运工。 - **5. 总结**: 概率分布的搬运工。
### 3. 创建 `docs/zh-cn/appendix/audio-intro.md` (AI 音频:声音的数字化身) ### 3. 创建 `docs/zh-cn/appendix/audio-intro.md` (AI 音频:声音的数字化身)
* **0. 引言**: 听与说的艺术。声音本质上是空气的振动,计算机如何处理? - **0. 引言**: 听与说的艺术。声音本质上是空气的振动,计算机如何处理?
* **1. 第一步:声音的“文字” (Audio Tokenization)**: - **1. 第一步:声音的“文字” (Audio Tokenization)**:
* 声音是连续的波形,语言是离散的 Token。 - 声音是连续的波形,语言是离散的 Token。
* **Neural Codec (VQ-VAE / EnCodec)**: 把连续波形切碎,通过 **量化 (Quantization)** 变成一个个数字 Token (Codebook)。 - **Neural Codec (VQ-VAE / EnCodec)**: 把连续波形切碎,通过 **量化 (Quantization)** 变成一个个数字 Token (Codebook)。
* **RVQ (Residual VQ)**: 像洋葱一样一层层剥开声音细节,保证高保真音质。 - **RVQ (Residual VQ)**: 像洋葱一样一层层剥开声音细节,保证高保真音质。
* **2. 核心表示:梅尔频谱 (Mel-Spectrogram)**: - **2. 核心表示:梅尔频谱 (Mel-Spectrogram)**:
* 把“听觉问题”转化成“视觉问题”。在频域上处理声音往往比时域更高效。 - 把“听觉问题”转化成“视觉问题”。在频域上处理声音往往比时域更高效。
* **3. 架构演进:从 GPT 到 Flow**: - **3. 架构演进:从 GPT 到 Flow**:
* **AudioLM / VALL-E**: 把声音 Token 当作文字,用 GPT **自回归 (Autoregressive)** 地狂猜下一个音。优点是能学到很好的韵律,缺点是容易“胡言乱语”或无限循环。 - **AudioLM / VALL-E**: 把声音 Token 当作文字,用 GPT **自回归 (Autoregressive)** 地狂猜下一个音。优点是能学到很好的韵律,缺点是容易“胡言乱语”或无限循环。
* **F5-TTS / CosyVoice**: 使用 **Flow Matching** 直接生成频谱。不需要复杂的 Token 预测,而是从噪声中“流”出声音频谱,速度更快,控制更稳,支持零样本克隆。 - **F5-TTS / CosyVoice**: 使用 **Flow Matching** 直接生成频谱。不需要复杂的 Token 预测,而是从噪声中“流”出声音频谱,速度更快,控制更稳,支持零样本克隆。
* **4. 总结**: 统一多模态的未来。 - **4. 总结**: 统一多模态的未来。
### 4. 更新配置 ### 4. 更新配置
* 修改 `docs/.vitepress/config.mjs`,在 `zh-cn` 侧边栏添加这三个章节。 - 修改 `docs/.vitepress/config.mjs`,在 `zh-cn` 侧边栏添加这三个章节。
+227
View File
@@ -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` 成功
---
**总结**:优秀的交互式教程 = 清晰的逻辑 + 即时的反馈 + 精致的可视化。遵循本规范,可以创建出让读者"秒懂"复杂概念的高质量内容。
+41
View File
@@ -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.
+53 -55
View File
@@ -43,12 +43,10 @@
<a href="docs-readme/de-DE/README.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-d9d9d9"></a> <a href="docs-readme/de-DE/README.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-d9d9d9"></a>
</p> </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.**
**Easy-Vibe 是一个开源的、基于项目的 AI 编程课程,教你如何从零开始构建真正的 AI 产品。** AI 编程、全栈 Web 应用开发、AI Agent、工作流和 RAG 系统
**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 转化为可部署的产品。 👉 专为初学者、产品经理和开发者设计。将 AI Demo 转化为可部署的产品。
@@ -73,79 +71,79 @@ AI 编程、全栈 Web 应用开发、AI Agent、工作流和 RAG 系统
### 零、幼儿园 ### 零、幼儿园
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :--- | :--- | :--- | | :----------------------------------------------------------------------------------------------- | :------------------------------------- | :--- |
| [前言:学习地图](docs/zh-cn/stage-0/0.1-learning-map/index.md) | 整体学习路径导览 | ✅ | | [前言:学习地图](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 时代,会说话就会编程](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 通过贪吃蛇等案例初步感受 AI 编程的能力 | ✅ |
### 一、AI 产品经理 ### 一、AI 产品经理
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :--- | :--- | :--- | | :---------------------------------------------------------------------------------------- | :------------------------------------------------ | :--- |
| [初级二:认识 AI IDE 工具](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | 学会使用 IDE,在本地制作小游戏 | ✅ | | [初级二:认识 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 生成单页面,再到生成多页面产品原型 | ✅ | | [初级三:动手做出原型](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 能力(文本、图片、视频) | ✅ | | [初级四:给原型加上 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) | 模拟真实场景、接受用户反馈迭代,完整化项目 | ✅ | | [初级五:完整项目实战](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 模拟真实场景、接受用户反馈迭代,完整化项目 | ✅ |
| [大作业:做一个完整的 Web 应用原型并展示](docs/zh-cn/stage-1/1.5-final-project/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) | 从零到一做产品需要考虑的思维框架 | ✅ | | [附录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 中的常见错误及排查方法 | ✅ | | [附录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 设计和前端开发提供素材基础 | 🚧 | | [前端零:使用 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/) | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 | | [前端一:Figma 与 MasterGo 入门](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 |
| [前端二:构建第一个现代应用程序-UI 设计](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 | | [前端二:构建第一个现代应用程序-UI 设计](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 |
| [前端三:参考 UI 设计规范与多产品 UI 设计](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 围绕统一主视觉扩展多产品界面,练习系统化设计能力 | 🚧 | | [前端三:参考 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 能力的前端应用,串联设计与开发 | ✅ | | [前端四:一起做霍格沃茨画像](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 接口与请求响应模型,为后端集成与联调做准备 | ✅ | | [后端一:什么是 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,打通数据模型与前端页面 | ✅ | | [后端二:从数据库到 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/) | 用大模型协助生成接口与数据库文档及代码,实现可读可测的后端 | 🚧 | | [后端三:大模型辅助编写接口代码与接口文档](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 工作流中管理代码,进行版本控制和协作 | ✅ | | [后端四: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 上线 | ✅ | | [后端五: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 编程工具加速开发与调试,形成个人工程化工作流 | ✅ | | [后端六:现代 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/) | 接入支付系统,完成收费链路与基础结算流程 | 🚧 | | [后端七:如何集成 Stripe 等收费系统](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 接入支付系统,完成收费链路与基础结算流程 | 🚧 |
| [大作业 1:构建第一个现代应用程序-全栈应用](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 综合前端、后端与支付模块,完成可上线的全栈 Web 应用 | 🚧 | | [大作业 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,独立完成可登录注册并支持收费的产品 | 🚧 | | [大作业 2:现代前端组件库 + Trae 实战](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 使用现代前端组件库与 Trae,独立完成可登录注册并支持收费的产品 | 🚧 |
#### AI 能力附录 #### 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 一: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 二:学会查询 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 能力,把外部服务接成工具 | 🚧 | | [高级一: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 更稳定可靠 | 🚧 | | [高级二:如何让 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.3-wechat-miniprogram/) | 了解微信小程序生态,从官方模板到上线完成一个前端小程序 | ✅ |
| [高级四:多平台开发:如何构建微信小程序-包含后端](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 在小程序中接入数据库与后端逻辑,打通完整业务闭环 | 🚧 | | [高级四:多平台开发:如何构建微信小程序-包含后端](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/原生一体化的安卓应用开发 | 🚧 | | [高级五:多平台开发:如何构建安卓程序](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 应用开发 | 🚧 | | [高级六:多平台开发:如何构建 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/) | 从选型、搭建到部署,构建展示个人项目与学术成果的长久在线主页 | 🚧 | | [高级七:如何构建属于自己的个人网页与学术博客](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 从选型、搭建到部署,构建展示个人项目与学术成果的长久在线主页 | 🚧 |
#### AI 能力附录 #### 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 以及它如何工作](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 与工作流编排:以 LangGraph 为例](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | 使用 LangGraph 等工具设计多步工作流与中高级 RAG 系统 | 🚧 |
## 🛠️ 如何学习 ## 🛠️ 如何学习
+52 -51
View File
@@ -11,7 +11,7 @@
# Easy-Vibe: Learn Vibe Coding from 0 to 1 # Easy-Vibe: Learn Vibe Coding from 0 to 1
### *من الصفر، تعلم قائم على المشاريع، بناء أول منتج ذكاء اصطناعي لك* ### _من الصفر، تعلم قائم على المشاريع، بناء أول منتج ذكاء اصطناعي لك_
<p align="center"> <p align="center">
📌 <a href="https://datawhalechina.github.io/easy-vibe/">ابدأ القراءة أونلاين (Start Reading Online)</a> 📌 <a href="https://datawhalechina.github.io/easy-vibe/">ابدأ القراءة أونلاين (Start Reading Online)</a>
@@ -78,79 +78,79 @@
### 0. الروضة ### 0. الروضة
| الفصل | المحتوى الرئيسي | الحالة | | الفصل | المحتوى الرئيسي | الحالة |
| :--- | :--- | :--- | | :------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------- | :----- |
| [المقدمة: خريطة التعلم](docs/stage-0/0.1-learning-map/index.md) | دليل مسار التعلم الشامل | ✅ | | [المقدمة: خريطة التعلم](docs/stage-0/0.1-learning-map/index.md) | دليل مسار التعلم الشامل | ✅ |
| [المبتدئ 1: عصر الذكاء الاصطناعي، إذا كنت تستطيع التحدث يمكنك البرمجة](docs/stage-0/0.2-ai-capabilities-through-games/index.md) | تجربة قدرات البرمجة بالذكاء الاصطناعي لأول مرة من خلال حالات مثل Snake | ✅ | | [المبتدئ 1: عصر الذكاء الاصطناعي، إذا كنت تستطيع التحدث يمكنك البرمجة](docs/stage-0/0.2-ai-capabilities-through-games/index.md) | تجربة قدرات البرمجة بالذكاء الاصطناعي لأول مرة من خلال حالات مثل Snake | ✅ |
### 1. مدير منتج الذكاء الاصطناعي ### 1. مدير منتج الذكاء الاصطناعي
| الفصل | المحتوى الرئيسي | الحالة | | الفصل | المحتوى الرئيسي | الحالة |
| :--- | :--- | :--- | | :------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------- | :----- |
| [المبتدئ 2: معرفة أدوات IDE الذكاء الاصطناعي](docs/stage-1/1.1-introduction-to-ai-ide/index.md) | تعلم استخدام IDE، إنشاء ألعاب مصغرة محليًا | ✅ | | [المبتدئ 2: معرفة أدوات IDE الذكاء الاصطناعي](docs/stage-1/1.1-introduction-to-ai-ide/index.md) | تعلم استخدام IDE، إنشاء ألعاب مصغرة محليًا | ✅ |
| [المبتدئ 3: بناء نموذج أولي بنفسك](docs/stage-1/1.2-building-prototype/index.md) | تحليل المتطلبات، توليد صفحة واحدة بالذكاء الاصطناعي، حتى توليد نماذج منتجات متعددة الصفحات | ✅ | | [المبتدئ 3: بناء نموذج أولي بنفسك](docs/stage-1/1.2-building-prototype/index.md) | تحليل المتطلبات، توليد صفحة واحدة بالذكاء الاصطناعي، حتى توليد نماذج منتجات متعددة الصفحات | ✅ |
| [المبتدئ 4: إضافة قدرات الذكاء الاصطناعي إلى النموذج الأولي](docs/stage-1/1.3-integrating-ai-capabilities/index.md) | تعلم توصيل القدرات الشائعة للذكاء الاصطناعي (النص، الصورة، الفيديو) | ✅ | | [المبتدئ 4: إضافة قدرات الذكاء الاصطناعي إلى النموذج الأولي](docs/stage-1/1.3-integrating-ai-capabilities/index.md) | تعلم توصيل القدرات الشائعة للذكاء الاصطناعي (النص، الصورة، الفيديو) | ✅ |
| [المبتدئ 5: مشروع كامل عملي](docs/stage-1/1.4-complete-project-practice/index.md) | محاكاة سيناريوهات حقيقية، قبول ملاحظات المستخدمين للتحسين، إكمال المشروع | ✅ | | [المبتدئ 5: مشروع كامل عملي](docs/stage-1/1.4-complete-project-practice/index.md) | محاكاة سيناريوهات حقيقية، قبول ملاحظات المستخدمين للتحسين، إكمال المشروع | ✅ |
| [المشروع الكبير: إنشاء نموذج أولي كامل لتطبيق الويب وعرضه](docs/stage-1/1.5-final-project/index.md) | تنفيذ التطبيق بالكامل، عرض آثار التطبيق | ✅ | | [المشروع الكبير: إنشاء نموذج أولي كامل لتطبيق الويب وعرضه](docs/stage-1/1.5-final-project/index.md) | تنفيذ التطبيق بالكامل، عرض آثار التطبيق | ✅ |
#### الملاحق #### الملاحق
| الفصل | المحتوى الرئيسي | الحالة | | الفصل | المحتوى الرئيسي | الحالة |
| :--- | :--- | :--- | | :---------------------------------------------------------------------------------- | :-------------------------------------------------- | :----- |
| [الملحق A: مكمل تفكير المنتج](docs/stage-1/appendix-a-product-thinking/index.md) | أطر التفكير اللازمة لبناء منتج من الصفر إلى واحد | ✅ | | [الملحق A: مكمل تفكير المنتج](docs/stage-1/appendix-a-product-thinking/index.md) | أطر التفكير اللازمة لبناء منتج من الصفر إلى واحد | ✅ |
| [الملحق B: الأخطاء الشائعة والحلول](docs/stage-1/appendix-b-common-errors/index.md) | الأخطاء الشائعة في Vibe Coding وطرق استكشاف الأخطاء | ✅ | | [الملحق B: الأخطاء الشائعة والحلول](docs/stage-1/appendix-b-common-errors/index.md) | الأخطاء الشائعة في Vibe Coding وطرق استكشاف الأخطاء | ✅ |
### 2. مهندس تطوير مستوى المبتدئ-المتوسط ### 2. مهندس تطوير مستوى المبتدئ-المتوسط
#### قسم الواجهة الأمامية #### قسم الواجهة الأمامية
| الفصل | المحتوى الرئيسي | الحالة | | الفصل | المحتوى الرئيسي | الحالة |
| :--- | :--- | :--- | | :----------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | :----- |
| [الواجهة الأمامية 0: استخدام lovart لتوليد الأصول](docs/stage-2/frontend/2.0-lovart-assets/) | استخدام lovart لتوليد الأصول المرئية مثل الشخصيات والمشاهد دفعة واحدة، توفير أساس الأصول لتصميم الواجهة وتطوير الواجهة الأمامية | 🚧 | | [الواجهة الأمامية 0: استخدام lovart لتوليد الأصول](docs/stage-2/frontend/2.0-lovart-assets/) | استخدام lovart لتوليد الأصول المرئية مثل الشخصيات والمشاهد دفعة واحدة، توفير أساس الأصول لتصميم الواجهة وتطوير الواجهة الأمامية | 🚧 |
| [الواجهة الأمامية 1: مقدمة إلى Figma و MasterGo](docs/stage-2/frontend/2.1-figma-mastergo/) | استخدام أدوات التصميم لتنظيم هيكل المعلومات وهيكل الصفحة، وإعداد الأساس لتنفيذ الواجهة الأمامية | 🚧 | | [الواجهة الأمامية 1: مقدمة إلى Figma و MasterGo](docs/stage-2/frontend/2.1-figma-mastergo/) | استخدام أدوات التصميم لتنظيم هيكل المعلومات وهيكل الصفحة، وإعداد الأساس لتنفيذ الواجهة الأمامية | 🚧 |
| [الواجهة الأمامية 2: بناء أول تطبيق حديث - تصميم الواجهة](docs/stage-2/frontend/2.2-ui-design/) | إكمال واجهة قائمة على المكونات بناءً على التصاميم، تحقيق المسار الأول من التصميم إلى الكود | 🚧 | | [الواجهة الأمامية 2: بناء أول تطبيق حديث - تصميم الواجهة](docs/stage-2/frontend/2.2-ui-design/) | إكمال واجهة قائمة على المكونات بناءً على التصاميم، تحقيق المسار الأول من التصميم إلى الكود | 🚧 |
| [الواجهة الأمامية 3: مرجع مواصفات تصميم الواجهة وتصميم واجهة متعدد المنتجات](docs/stage-2/frontend/2.3-multi-product-ui/) | توسيع واجهات متعددة المنتجات حول بصري رئيسي موحد، ممارسة قدرات التصميم المنهجي | 🚧 | | [الواجهة الأمامية 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، ربط التصميم والتطوير | ✅ | | [الواجهة الأمامية 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 ونماذج الطلب-الاستجابة، الاستعداد للتكامل الخلفي والتنسيق | ✅ | | [الواجهة الخلفية 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، ربط نماذج البيانات بصفحات الواجهة الأمامية | ✅ | | [الواجهة الخلفية 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 للمساعدة في توليد التوثيق والكود للواجهات وقواعد البيانات، تحقيق خلفي قابل للقراءة والاختبار | 🚧 | | [الواجهة الخلفية 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، إجراء التحكم في الإصدار والتعاون | ✅ | | [الواجهة الخلفية 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 لجعله أونلاين | ✅ | | [الواجهة الخلفية 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 لتسريع التطوير وتصحيح الأخطاء، تشكيل سير عمل هندسي شخصي | ✅ | | [الواجهة الخلفية 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/) : توصيل أنظمة الدفع، إكمال تدفق الدفع وعملية التسوية الأساسية | 🚧 | | [الواجهة الخلفية 7: كيفية دمج أنظمة الدفع مثل Stripe](docs/stage-2/backend/2.7-stripe-payment/) : توصيل أنظمة الدفع، إكمال تدفق الدفع وعملية التسوية الأساسية | 🚧 |
| [المشروع الكبير 1: بناء أول تطبيق حديث - تطبيق كامل](docs/stage-2/assignments/2.1-fullstack-app/) : دمج الواجهة الأمامية والواجهة الخلفية ووحدات الدفع، إكمال تطبيق ويب كامل جاهز للإنتاج | 🚧 | | [المشروع الكبير 1: بناء أول تطبيق حديث - تطبيق كامل](docs/stage-2/assignments/2.1-fullstack-app/) : دمج الواجهة الأمامية والواجهة الخلفية ووحدات الدفع، إكمال تطبيق ويب كامل جاهز للإنتاج | 🚧 |
| [المشروع الكبير 2: مكتبة مكونات الواجهة الأمامية الحديثة + Trae عملية](docs/stage-2/assignments/2.2-modern-frontend-trae/) : استخدام مكتبة مكونات الواجهة الأمامية الحديثة و Trae، إكمال منتج بشكل مستقل مع تسجيل/دعم الدفع | 🚧 | | [المشروع الكبير 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 الأساسي لبناء منتجات نوع الأدوات، إنشاء مثال لترقيات التطبيقات المستقبلية | ✅ | | [الذكاء الاصطناعي 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 مناسبة، دمج القدرات متعددة الوسائط مثل النص والصورة في المنتجات | 🚧 | | [الذكاء الاصطناعي 2: تعلم البحث في قاموس الذكاء الاصطناعي ودمج واجهات API متعددة الوسائط](docs/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) : تعلم البحث عن نماذج وواجهات API مناسبة، دمج القدرات متعددة الوسائط مثل النص والصورة في المنتجات | 🚧 |
### 3. مهندس تطوير متقدم ### 3. مهندس تطوير متقدم
| الفصل | المحتوى الرئيسي | الحالة | | الفصل | المحتوى الرئيسي | الحالة |
| :--- | :--- | :--- | | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------- | :----- |
| [المتقدم 1: MCP ومهارات ClaudeCode](docs/stage-3/core-skills/3.1-mcp-claudecode-skills/) : توسيع قدرات IDE من خلال MCP والمهارات، توصيل الخدمات الخارجية كأدوات | 🚧 | | [المتقدم 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 أكثر استقرارًا وموثوقية | 🚧 | | [المتقدم 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، إكمال برنامج مصغر للواجهة الأمامية من القالب الرسمي إلى الإطلاق | ✅ | | [المتقدم 3: التطوير متعدد المنصات: كيفية بناء برامج WeChat المصغرة](docs/stage-3/cross-platform/3.3-wechat-miniprogram/) : فهم نظام البرامج المصغرة WeChat، إكمال برنامج مصغر للواجهة الأمامية من القالب الرسمي إلى الإطلاق | ✅ |
| [المتقدم 4: التطوير متعدد المنصات: كيفية بناء برامج WeChat المصغرة - بما في ذلك الخلفية](docs/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : دمج قاعدة البيانات ومنطق الواجهة الخلفية في البرامج المصغرة، تحقيق دورة نشاط كاملة | 🚧 | | [المتقدم 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 المتكاملة للويب/الأصلي | 🚧 | | [المتقدم 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 المتكاملة للويب/الأصلي | 🚧 | | [المتقدم 6: التطوير متعدد المنصات: كيفية بناء تطبيقات iOS](docs/stage-3/cross-platform/3.6-ios-app/) : استخدام أدوات مثل Expo، إكمال تطوير تطبيقات iOS المتكاملة للويب/الأصلي | 🚧 |
| [المتقدم 7: كيفية بناء موقعك الشخصي ومブログ الأكاديمي](docs/stage-3/personal-brand/3.7-personal-website-blog/) : من الاختيار، البناء إلى النشر، بناء صفحة رئيسية أونلاين طويلة الأجل تعرض المشاريع الشخصية والنتائج الأكاديمية | 🚧 | | [المتقدم 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 والهياكل الشائعة، توفير أساس استرجاع المعرفة للتطبيقات المعقدة | ✅ | | [الذكاء الاصطناعي المتقدم 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 متوسطة-متقدمة | 🚧 | | [الذكاء الاصطناعي المتقدم 2: RAG متوسط-متقدم وتنظيف سير العمل: مثال LangGraph](docs/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) : استخدام أدوات مثل LangGraph لتصميم سير العمل متعدد الخطوات وأنظمة RAG متوسطة-متقدمة | 🚧 |
## 🛠️ كيفية التعلم ## 🛠️ كيفية التعلم
@@ -206,6 +206,7 @@
<pامسح رمز QR أدناه لمتابعة الحساب الرسمي: Datawhale</p> <pامسح رمز QR أدناه لمتابعة الحساب الرسمي: Datawhale</p>
<img src="https://raw.githubusercontent.com/datawhalechina/pumpkin-book/master/res/qrcode.jpeg" width="180" height="180"> <img src="https://raw.githubusercontent.com/datawhalechina/pumpkin-book/master/res/qrcode.jpeg" width="180" height="180">
</div> </div>
## 📄 الترخيص ## 📄 الترخيص
+52 -52
View File
@@ -11,7 +11,7 @@
# Easy-Vibe: Learn Vibe Coding from 0 to 1 # 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"> <p align="center">
📌 <a href="https://datawhalechina.github.io/easy-vibe/">Online lesen starten (Start Reading Online)</a> 📌 <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 😢. 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 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 - **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 ### 0. Kindergarten
| Kapitel | Schlüsselinhalt | Status | | Kapitel | Schlüsselinhalt | Status |
| :--- | :--- | :--- | | :------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------ | :----- |
| [Vorwort: Lernkarte](docs/zh-cn/stage-0/0.1-learning-map/index.md) | Gesamtführung durch den Lernpfad | ✅ | | [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 | ✅ | | [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 ### 1. KI-Produktmanager
| Kapitel | Schlüsselinhalt | Status | | 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 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 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 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 | ✅ | | [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 | ✅ | | [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 #### Anhänge
| Kapitel | Schlüsselinhalt | Status | | 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 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 | ✅ | | [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 ### 2. Entwicklungsingenieur auf Anfänger-Fortgeschrittenen-Niveau
#### Frontend-Teil #### Frontend-Teil
| Kapitel | Schlüsselinhalt | Status | | 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 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 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 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 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 | ✅ | | [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 #### Backend- und Full-Stack-Teil
| Kapitel | Schlüsselinhalt | Status | | 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 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 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 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 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 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 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 | 🚧 | | [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 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 | 🚧 | | [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 #### KI-Fähigkeiten-Anhang
| Kapitel | Schlüsselinhalt | Status | | 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 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 | 🚧 | | [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 ### 3. Fortgeschrittener Entwicklungsingenieur
| Kapitel | Schlüsselinhalt | Status | | 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 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 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 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 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 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 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 | 🚧 | | [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 #### KI-Fähigkeiten-Anhang
| Kapitel | Schlüsselinhalt | Status | | 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 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 | 🚧 | | [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 ## 🛠️ Lernmethode
+51 -51
View File
@@ -11,7 +11,7 @@
# Easy-Vibe: Learn Vibe Coding from 0 to 1 # 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"> <p align="center">
📌 <a href="https://datawhalechina.github.io/easy-vibe/">Start Reading Online</a> 📌 <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 ### Stage 0: Kindergarten
| Chapter | Key Content | Status | | Chapter | Key Content | Status |
| :--- | :--- | :--- | | :---------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------- | :----- |
| [Preface: Learning Map](docs/zh-cn/stage-0/0.1-learning-map/index.md) | Overall learning path overview | ✅ | | [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 | ✅ | | [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 ### Stage 1: AI Product Manager
| Chapter | Key Content | Status | | 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 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 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 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 | ✅ | | [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 | ✅ | | [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 #### Appendix
| Chapter | Key Content | Status | | 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 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 | ✅ | | [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 ### Stage 2: Intermediate Developer
#### Frontend Part #### Frontend Part
| Chapter | Key Content | Status | | 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 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 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 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 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 | ✅ | | [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 #### Backend and Full Stack Part
| Chapter | Key Content | Status | | 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 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 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 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 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 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 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 | 🚧 | | [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 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 | 🚧 | | [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 #### AI Capabilities Appendix
| Chapter | Key Content | Status | | 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 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 | 🚧 | | [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 ### Stage 3: Advanced Developer
| Chapter | Key Content | Status | | 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 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 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 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 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 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 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 | 🚧 | | [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 #### AI Capabilities Appendix
| Chapter | Key Content | Status | | 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 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 | 🚧 | | [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 ## 🛠️ How to Learn
+51 -51
View File
@@ -11,7 +11,7 @@
# Easy-Vibe: Learn Vibe Coding from 0 to 1 # 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"> <p align="center">
📌 <a href="https://datawhalechina.github.io/easy-vibe/">Comenzar a leer en línea (Start Reading Online)</a> 📌 <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 ### 0. Jardín de Infancia
| Capítulo | Contenido clave | Estado | | 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 | ✅ | | [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 | ✅ | | [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 ### 1. Product Manager de IA
| Capítulo | Contenido clave | Estado | | 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 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 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 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 | ✅ | | [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 | ✅ | | [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 #### Apéndices
| Capítulo | Contenido clave | Estado | | 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 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 | ✅ | | [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 ### 2. Ingeniero de Desarrollo de Nivel Principiante-Intermedio
#### Parte Frontend #### Parte Frontend
| Capítulo | Contenido clave | Estado | | 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 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 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 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 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 | ✅ | | [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 #### Parte Backend y Full Stack
| Capítulo | Contenido clave | Estado | | 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 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 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 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 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 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 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 | 🚧 | | [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 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 | 🚧 | | [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 #### Apéndice de Capacidades de IA
| Capítulo | Contenido clave | Estado | | 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 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 | 🚧 | | [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 ### 3. Ingeniero de Desarrollo Avanzado
| Capítulo | Contenido clave | Estado | | 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 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 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 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 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 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 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 | 🚧 | | [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 #### Apéndice de Capacidades de IA
| Capítulo | Contenido clave | Estado | | 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 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 | 🚧 | | [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 ## 🛠️ Cómo Aprender
+51 -51
View File
@@ -11,7 +11,7 @@
# Easy-Vibe: Learn Vibe Coding from 0 to 1 # 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"> <p align="center">
📌 <a href="https://datawhalechina.github.io/easy-vibe/">Commencer la lecture en ligne (Start Reading Online)</a> 📌 <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 ### 0. Maternelle
| Chapitre | Contenu clé | Statut | | 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 | ✅ | | [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 | ✅ | | [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 ### 1. Product Manager IA
| Chapitre | Contenu clé | Statut | | 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 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 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 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 | ✅ | | [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 | ✅ | | [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 #### Annexes
| Chapitre | Contenu clé | Statut | | 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 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 | ✅ | | [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 ### 2. Ingénieur de Développement Niveau Débutant-Intermédiaire
#### Partie Frontend #### Partie Frontend
| Chapitre | Contenu clé | Statut | | 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 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 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 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 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 | ✅ | | [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 #### Partie Backend et Full Stack
| Chapitre | Contenu clé | Statut | | 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 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 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 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 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 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 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 | 🚧 | | [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 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 | 🚧 | | [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 #### Annexe des Capacités IA
| Chapitre | Contenu clé | Statut | | 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 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 | 🚧 | | [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é ### 3. Ingénieur de Développement Avancé
| Chapitre | Contenu clé | Statut | | 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é 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é 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é 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é 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é 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é 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 | 🚧 | | [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 #### Annexe des Capacités IA
| Chapitre | Contenu clé | Statut | | 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 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 | 🚧 | | [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 ## 🛠️ Comment Apprendre
+51 -51
View File
@@ -11,7 +11,7 @@
# Easy-Vibe: Learn Vibe Coding from 0 to 1 # Easy-Vibe: Learn Vibe Coding from 0 to 1
### *ゼロから、プロジェクトベース学習、最初の AI 製品を構築* ### _ゼロから、プロジェクトベース学習、最初の AI 製品を構築_
<p align="center"> <p align="center">
📌 <a href="https://datawhalechina.github.io/easy-vibe/">オンラインで読み始める (Start Reading Online)</a> 📌 <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) | 全体学習パスのガイド | ✅ | | [前書:学習マップ](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 時代、話せるだけでプログラミングできる](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | スネークゲームなどの事例を通じて AI プログラミングの能力を初めて体験 | ✅ |
### 一、AI プロダクトマネージャー ### 一、AI プロダクトマネージャー
| 章 | 主要内容 | 状態 | | 章 | 主要内容 | 状態 |
| :--- | :--- | :--- | | :----------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | :--- |
| [初級二:AI IDE ツールを知る](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | IDE の使用方法を学び、ローカルでミニゲームを作成 | ✅ | | [初級二: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 による単一ページ生成、複数ページプロダクトプロトタイプの生成へ | ✅ | | [初級三:手を動かしてプロトタイプを作る](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 能力(テキスト、画像、動画)の接続方法を習得 | ✅ | | [初級四:プロトタイプに 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) | 実際のシーンをシミュレート、ユーザーフィードバックを受け反復改良、プロジェクトを完全に | ✅ | | [初級五:完全プロジェクト実戦](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 実際のシーンをシミュレート、ユーザーフィードバックを受け反復改良、プロジェクトを完全に | ✅ |
| [大課題:完全な Web アプリケーションプロトタイプを作成し展示](docs/zh-cn/stage-1/1.5-final-project/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) | ゼロからイチまでプロダクトを作る際に必要な思考フレームワーク | ✅ | | [付録 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 における一般的なエラーとトラブルシューティング方法 | ✅ | | [付録 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 デザインとフロントエンド開発に素材基盤を提供 | 🚧 | | [フロントエンド零: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/) | デザインツールを使って情報アーキテクチャとページ構造を整理し、フロントエンド実装の基礎を築く | 🚧 | | [フロントエンド一:Figma と MasterGo 入門](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | デザインツールを使って情報アーキテクチャとページ構造を整理し、フロントエンド実装の基礎を築く | 🚧 |
| [フロントエンド二:最初のモダンアプリケーションを構築 - UI デザイン](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | デザイン画に基づいてコンポーネント化インターフェースを完成させ、デザインからコードへの最初のパスを実現 | 🚧 | | [フロントエンド二:最初のモダンアプリケーションを構築 - UI デザイン](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | デザイン画に基づいてコンポーネント化インターフェースを完成させ、デザインからコードへの最初のパスを実現 | 🚧 |
| [フロントエンド三:UI デザイン仕様とマルチプロダクト UI デザインを参照](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 統一されたメインビジュアルを中心にマルチプロダクトインターフェースを拡張し、体系的デザイン能力を練習 | 🚧 | | [フロントエンド三: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 能力を統合したフロントエンドアプリケーションを作成し、デザインと開発を接続 | ✅ | | [フロントエンド四:一緒にハリー・ポッターの肖像画を作ろう](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 インターフェースとリクエスト・レスポンスモデルを理解し、バックエンド統合と連携調整の準備 | ✅ | | [バックエンド一: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 を実装し、データモデルとフロントエンドページを接続 | ✅ | | [バックエンド二:データベースから 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/) | 大規模言語モデルを使ってインターフェースとデータベースのドキュメントおよびコードの生成を支援し、読みやすくテスト可能なバックエンドを実現 | 🚧 | | [バックエンド三:大規模言語モデル支援によるインターフェースコードとドキュメント作成](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 ワークフローでコードを管理し、バージョン管理とコラボレーションを行う | ✅ | | [バックエンド四: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 にデプロイしてオンライン化 | ✅ | | [バックエンド五: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 プログラミングツールを使用して開発とデバッグを加速し、個人のエンジニアリングワークフローを形成 | ✅ | | [バックエンド六:モダン 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/) | 決済システムを接続し、課金パスと基本決済フローを完成 | 🚧 | | [バックエンド七:Stripe などの課金システムの統合方法](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 決済システムを接続し、課金パスと基本決済フローを完成 | 🚧 |
| [大課題 1:最初のモダンアプリケーションを構築 - フルスタックアプリ](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | フロントエンド、バックエンド、決済モジュールを統合し、オンライン可能なフルスタック Web アプリケーションを完成 | 🚧 | | [大課題 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 を使用し、ログイン・登録可能で課金対応のプロダクトを独立完成 | 🚧 | | [大課題 2:モダンフロントエンドコンポーネントライブラリ + Trae 実戦](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | モダンフロントエンドコンポーネントライブラリと Trae を使用し、ログイン・登録可能で課金対応のプロダクトを独立完成 | 🚧 |
#### AI 能力付録 #### 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 一: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 二: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 能力を拡張し、外部サービスをツールとして接続 | 🚧 | | [上級一: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 をより安定して信頼性の高いものに | 🚧 | | [上級二: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.3-wechat-miniprogram/) | WeChat ミニプログラムのエコシステムを理解し、公式テンプレートからリリースまでフロントエンドミニプログラムを完成 | ✅ |
| [上級四:マルチプラットフォーム開発:WeChat ミニプログラムの構築方法 - バックエンド含む](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | ミニプログラムにデータベースとバックエンドロジックを統合し、完全な業務クローズドループを実現 | 🚧 | | [上級四:マルチプラットフォーム開発: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 アプリ開発を完成 | 🚧 | | [上級五:マルチプラットフォーム開発: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 アプリ開発を完成 | 🚧 | | [上級六:マルチプラットフォーム開発: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/) | 選定、構築からデプロイまで、個人プロジェクトと学術成果を展示する長期的なオンラインホームページを構築 | 🚧 | | [上級七:自分専用の個人ウェブページと学術ブログの構築方法](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 選定、構築からデプロイまで、個人プロジェクトと学術成果を展示する長期的なオンラインホームページを構築 | 🚧 |
#### AI 能力付録 #### 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 とは、それがどのように動作するか](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 とワークフロー編成:LangGraph を例に](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | LangGraph などのツールを使用してマルチステップワークフローと中上級 RAG システムを設計 | 🚧 |
## 🛠️ 学習方法 ## 🛠️ 学習方法
+51 -51
View File
@@ -11,7 +11,7 @@
# Easy-Vibe: Learn Vibe Coding from 0 to 1 # Easy-Vibe: Learn Vibe Coding from 0 to 1
### *0부터, 프로젝트 기반 학습, 첫 번째 AI 제품 구축* ### _0부터, 프로젝트 기반 학습, 첫 번째 AI 제품 구축_
<p align="center"> <p align="center">
📌 <a href="https://datawhalechina.github.io/easy-vibe/">온라인으로 읽기 시작하기 (Start Reading Online)</a> 📌 <a href="https://datawhalechina.github.io/easy-vibe/">온라인으로 읽기 시작하기 (Start Reading Online)</a>
@@ -78,79 +78,79 @@ AI로 코드를 작성하려고 할 때 오류가 계속 발생하고, 포기하
### 0. 유치원 ### 0. 유치원
| 장 | 주요 내용 | 상태 | | 장 | 주요 내용 | 상태 |
| :--- | :--- | :--- | | :--------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------- | :--- |
| [서론: 학습 맵](docs/zh-cn/stage-0/0.1-learning-map/index.md) | 전체 학습 경로 가이드 | ✅ | | [서론: 학습 맵](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 시대, 말할 수 있으면 프로그래밍 가능](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 스네이크 게임 등 사례를 통해 AI 프로그래밍 능력을 처음 체험 | ✅ |
### 1. AI 프로덕트 매니저 ### 1. AI 프로덕트 매니저
| 장 | 주요 내용 | 상태 | | 장 | 주요 내용 | 상태 |
| :--- | :--- | :--- | | :--------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :--- |
| [초급 2: AI IDE 도구 이해](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | IDE 사용법 배우기, 로컬에서 미니게임 제작 | ✅ | | [초급 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 단일 페이지 생성, 다중 페이지 프로덕트 프로토타입 생성으로 | ✅ | | [초급 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 능력(텍스트, 이미지, 비디오) 연결 방법 습득 | ✅ | | [초급 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) | 실제 시나리오 시뮬레이션, 사용자 피드백 수용 반복 개선, 프로젝트 완성 | ✅ | | [초급 5: 완전 프로젝트 실전](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 실제 시나리오 시뮬레이션, 사용자 피드백 수용 반복 개선, 프로젝트 완성 | ✅ |
| [대 과제: 완전한 웹 애플리케이션 프로토타입 만들기 및 전시](docs/zh-cn/stage-1/1.5-final-project/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까지 프로덕트 만들 때 필요한 사고 프레임워크 | ✅ | | [부록 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의 일반적인 오류 및 문제 해결 방법 | ✅ | | [부록 B: 일반적인 오류 및 해결 방안](docs/zh-cn/stage-1/appendix-b-common-errors/index.md) | Vibe Coding의 일반적인 오류 및 문제 해결 방법 | ✅ |
### 2. 초중급 개발 엔지니어 ### 2. 초중급 개발 엔지니어
#### 프론트엔드 부분 #### 프론트엔드 부분
| 장 | 주요 내용 | 상태 | | 장 | 주요 내용 | 상태 |
| :--- | :--- | :--- | | :---------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :--- |
| [프론트엔드 0: lovart 사용하여 소재 생성](docs/zh-cn/stage-2/frontend/2.0-lovart-assets/) | lovart를 사용하여 캐릭터, 장면 등 시각 자료 일괄 생성, UI 디자인과 프론트엔드 개발에 자료 기반 제공 | 🚧 | | [프론트엔드 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/) | 디자인 도구로 정보 아키텍처와 페이지 구조 정리, 프론트엔드 구현 기반 마련 | 🚧 | | [프론트엔드 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/) | 디자인 안을 기반으로 컴포넌트화 인터페이스 완성, 디자인에서 코드까지의 첫 번째 경로 실현 | 🚧 | | [프론트엔드 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/) | 통합된 메인 비주얼을 중심으로 다중 프로덕트 인터페이스 확장, 체계적 디자인 능력 연습 | 🚧 | | [프론트엔드 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 능력을 통합한 프론트엔드 애플리케이션 제작, 디자인과 개발 연결 | ✅ | | [프론트엔드 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 인터페이스와 요청-응답 모델 이해, 백엔드 통합과 연동 조정 준비 | ✅ | | [백엔드 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 구현, 데이터 모델과 프론트엔드 페이지 연결 | ✅ | | [백엔드 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/) | 대규모 언어 모델을 활용하여 인터페이스와 데이터베이스 문서 및 코드 생성 지원, 읽기 쉽고 테스트 가능한 백엔드 실현 | 🚧 | | [백엔드 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 워크플로우에서 코드 관리, 버전 관리 및 협업 수행 | ✅ | | [백엔드 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에 배포하여 온라인화 | ✅ | | [백엔드 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 프로그래밍 도구 사용하여 개발과 디버깅 가속화, 개인 엔지니어링 워크플로우 형성 | ✅ | | [백엔드 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/) | 결제 시스템 연결, 유료 경로와 기본 정산 흐름 완성 | 🚧 | | [백엔드 7: Stripe 등 유료 시스템 통합 방법](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 결제 시스템 연결, 유료 경로와 기본 정산 흐름 완성 | 🚧 |
| [대 과제 1: 첫 번째 현대 애플리케이션 구축 - 풀스택 앱](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 프론트엔드, 백엔드, 결제 모듈 통합, 온라인 가능한 풀스택 웹 애플리케이션 완성 | 🚧 | | [대 과제 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 사용, 로그인/등록 가능하고 유료 지원 프로덕트 독립 완성 | 🚧 | | [대 과제 2: 현대 프론트엔드 컴포넌트 라이브러리 + Trae 실전](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 현대 프론트엔드 컴포넌트 라이브러리와 Trae 사용, 로그인/등록 가능하고 유료 지원 프로덕트 독립 완성 | 🚧 |
#### AI 능력 부록 #### 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 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 2: AI 사전 검색 및 멀티모달 API 통합 학습](docs/zh-cn/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 적절한 모델과 API 검색 방법 학습, 텍스트, 이미지 등 멀티모달 능력을 프로덕트에 통합 | 🚧 |
### 3. 고급 개발 엔지니어 ### 3. 고급 개발 엔지니어
| 장 | 주요 내용 | 상태 | | 장 | 주요 내용 | 상태 |
| :--- | :--- | :--- | | :--------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | :--- |
| [고급 1: MCP와 ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | MCP와 Skills를 통해 IDE 능력 확장, 외부 서비스를 도구로 연결 | 🚧 | | [고급 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를 더 안정적이고 신뢰할 수 있게 | 🚧 | | [고급 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/) | 위챗 미니 프로그램 생태계 이해, 공식 템플릿부터 출시까지 프론트엔드 미니 프로그램 완성 | ✅ | | [고급 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/) | 미니 프로그램에 데이터베이스와 백엔드 로직 통합, 완전한 비즈니스 폐루프 실현 | 🚧 | | [고급 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/네이티브 일체화 안드로이드 앱 개발 완성 | 🚧 | | [고급 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 앱 개발 완성 | 🚧 | | [고급 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/) | 선정, 구축부터 배포까지, 개인 프로젝트와 학술 성과를 전시하는 장기 온라인 홈페이지 구축 | 🚧 | | [고급 7: 자신만의 개인 웹페이지와 학술 블로그 구축 방법](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 선정, 구축부터 배포까지, 개인 프로젝트와 학술 성과를 전시하는 장기 온라인 홈페이지 구축 | 🚧 |
#### AI 능력 부록 #### 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 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 2: 중고급 RAG와 워크플로우 편성: LangGraph 예시](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | LangGraph 등 도구 사용하여 다단계 워크플로우와 중고급 RAG 시스템 설계 | 🚧 |
## 🛠️ 학습 방법 ## 🛠️ 학습 방법
+51 -51
View File
@@ -11,7 +11,7 @@
# Easy-Vibe: Learn Vibe Coding from 0 to 1 # 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"> <p align="center">
📌 <a href="https://datawhalechina.github.io/easy-vibe/">Bắt đầu đọc online (Start Reading Online)</a> 📌 <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 ### 0. Mẫu giáo
| Chương | Nội dung chính | Trạng thái | | 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ể | ✅ | | [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 | ✅ | | [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 ### 1. Quản lý sản phẩm AI
| Chương | Nội dung chính | Trạng thái | | 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 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 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 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 | ✅ | | [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 | ✅ | | [Đề 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 #### Phụ lục
| Chương | Nội dung chính | Trạng thái | | 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 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ố | ✅ | | [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 ### 2. Kỹ sư phát triển cấp sơ-trung
#### Phần Frontend #### Phần Frontend
| Chương | Nội dung chính | Trạng thái | | 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 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 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 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 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 | ✅ | | [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 #### Phần Backend và Full Stack
| Chương | Nội dung chính | Trạng thái | | 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 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 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 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 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 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 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 | 🚧 | | [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 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 | 🚧 | | [Đề 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 #### Phụ lục khả năng AI
| Chương | Nội dung chính | Trạng thái | | 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 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 | 🚧 | | [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 ### 3. Kỹ sư phát triển cấp cao
| Chương | Nội dung chính | Trạng thái | | 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 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 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 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 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 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 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 | 🚧 | | [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 #### Phụ lục khả năng AI
| Chương | Nội dung chính | Trạng thái | | 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 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 | 🚧 | | [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 ## 🛠️ Cách học
+51 -51
View File
@@ -11,7 +11,7 @@
# Easy-Vibe: Learn Vibe Coding from 0 to 1 # Easy-Vibe: Learn Vibe Coding from 0 to 1
### *零基础、项目制学习、构建第一个 AI 产品* ### _零基础、项目制学习、构建第一个 AI 产品_
<p align="center"> <p align="center">
📌 <a href="https://datawhalechina.github.io/easy-vibe/">开始在线阅读 (Start Reading Online)</a> 📌 <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) | 整体学习路径导览 | ✅ | | [前言:学习地图](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 时代,会说话就会编程](docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 通过贪吃蛇等案例初步感受 AI 编程的能力 | ✅ |
### 一、AI 产品经理 ### 一、AI 产品经理
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :--- | :--- | :--- | | :---------------------------------------------------------------------------------------- | :------------------------------------------------ | :--- |
| [初级二:认识 AI IDE 工具](docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | 学会使用 IDE,在本地制作小游戏 | ✅ | | [初级二:认识 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 生成单页面,再到生成多页面产品原型 | ✅ | | [初级三:动手做出原型](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 能力(文本、图片、视频) | ✅ | | [初级四:给原型加上 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) | 模拟真实场景、接受用户反馈迭代,完整化项目 | ✅ | | [初级五:完整项目实战](docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 模拟真实场景、接受用户反馈迭代,完整化项目 | ✅ |
| [大作业:做一个完整的 Web 应用原型并展示](docs/zh-cn/stage-1/1.5-final-project/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) | 从零到一做产品需要考虑的思维框架 | ✅ | | [附录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 中的常见错误及排查方法 | ✅ | | [附录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 设计和前端开发提供素材基础 | 🚧 | | [前端零:使用 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/) | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 | | [前端一:Figma 与 MasterGo 入门](docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 |
| [前端二:构建第一个现代应用程序-UI 设计](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 | | [前端二:构建第一个现代应用程序-UI 设计](docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 |
| [前端三:参考 UI 设计规范与多产品 UI 设计](docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 围绕统一主视觉扩展多产品界面,练习系统化设计能力 | 🚧 | | [前端三:参考 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 能力的前端应用,串联设计与开发 | ✅ | | [前端四:一起做霍格沃茨画像](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 接口与请求响应模型,为后端集成与联调做准备 | ✅ | | [后端一:什么是 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,打通数据模型与前端页面 | ✅ | | [后端二:从数据库到 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/) | 用大模型协助生成接口与数据库文档及代码,实现可读可测的后端 | 🚧 | | [后端三:大模型辅助编写接口代码与接口文档](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 工作流中管理代码,进行版本控制和协作 | ✅ | | [后端四: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 上线 | ✅ | | [后端五: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 编程工具加速开发与调试,形成个人工程化工作流 | ✅ | | [后端六:现代 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/) | 接入支付系统,完成收费链路与基础结算流程 | 🚧 | | [后端七:如何集成 Stripe 等收费系统](docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 接入支付系统,完成收费链路与基础结算流程 | 🚧 |
| [大作业 1:构建第一个现代应用程序-全栈应用](docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 综合前端、后端与支付模块,完成可上线的全栈 Web 应用 | 🚧 | | [大作业 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,独立完成可登录注册并支持收费的产品 | 🚧 | | [大作业 2:现代前端组件库 + Trae 实战](docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 使用现代前端组件库与 Trae,独立完成可登录注册并支持收费的产品 | 🚧 |
#### AI 能力附录 #### 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 一: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 二:学会查询 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 能力,把外部服务接成工具 | 🚧 | | [高级一: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 更稳定可靠 | 🚧 | | [高级二:如何让 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.3-wechat-miniprogram/) | 了解微信小程序生态,从官方模板到上线完成一个前端小程序 | ✅ |
| [高级四:多平台开发:如何构建微信小程序-包含后端](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 在小程序中接入数据库与后端逻辑,打通完整业务闭环 | 🚧 | | [高级四:多平台开发:如何构建微信小程序-包含后端](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/原生一体化的安卓应用开发 | 🚧 | | [高级五:多平台开发:如何构建安卓程序](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 应用开发 | 🚧 | | [高级六:多平台开发:如何构建 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/) | 从选型、搭建到部署,构建展示个人项目与学术成果的长久在线主页 | 🚧 | | [高级七:如何构建属于自己的个人网页与学术博客](docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 从选型、搭建到部署,构建展示个人项目与学术成果的长久在线主页 | 🚧 |
#### AI 能力附录 #### 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 以及它如何工作](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 与工作流编排:以 LangGraph 为例](docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | 使用 LangGraph 等工具设计多步工作流与中高级 RAG 系统 | 🚧 |
## 🛠️ 如何学习 ## 🛠️ 如何学习
+51 -51
View File
@@ -11,7 +11,7 @@
# Easy-Vibe: Learn Vibe Coding from 0 to 1 # Easy-Vibe: Learn Vibe Coding from 0 to 1
### *零基礎、專案制學習、建構第一個 AI 產品* ### _零基礎、專案制學習、建構第一個 AI 產品_
<p align="center"> <p align="center">
📌 <a href="https://datawhalechina.github.io/easy-vibe/">開始線上閱讀 (Start Reading Online)</a> 📌 <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) | 整體學習路徑導覽 | ✅ | | [前言:學習地圖](../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 時代,會說話就會編程](../docs/zh-cn/stage-0/0.2-ai-capabilities-through-games/index.md) | 通過貪吃蛇等案例初步感受 AI 編程的能力 | ✅ |
### 一、AI 產品經理 ### 一、AI 產品經理
| 章节 | 關鍵內容 | 狀態 | | 章节 | 關鍵內容 | 狀態 |
| :--- | :--- | :--- | | :------------------------------------------------------------------------------------------- | :------------------------------------------------ | :--- |
| [初級二:認識 AI IDE 工具](../docs/zh-cn/stage-1/1.1-introduction-to-ai-ide/index.md) | 學會使用 IDE,在本地製作小遊戲 | ✅ | | [初級二:認識 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 生成單頁面,再到生成多頁面產品原型 | ✅ | | [初級三:動手做出原型](../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 能力(文本、圖片、影片) | ✅ | | [初級四:給原型加上 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) | 模擬真實場景、接受用戶反饋迭代,完整化專案 | ✅ | | [初級五:完整專案實戰](../docs/zh-cn/stage-1/1.4-complete-project-practice/index.md) | 模擬真實場景、接受用戶反饋迭代,完整化專案 | ✅ |
| [大作業:做一個完整的 Web 應用原型並展示](../docs/zh-cn/stage-1/1.5-final-project/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) | 從零到一做產品需要考慮的思維框架 | ✅ | | [附錄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 中的常見錯誤及排查方法 | ✅ | | [附錄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 設計和前端開發提供素材基礎 | 🚧 | | [前端零:使用 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/) | 用設計工具梳理信息架構和頁面結構,為前端實現打基礎 | 🚧 | | [前端一:Figma 與 MasterGo 入門](../docs/zh-cn/stage-2/frontend/2.1-figma-mastergo/) | 用設計工具梳理信息架構和頁面結構,為前端實現打基礎 | 🚧 |
| [前端二:建構第一個現代應用程式-UI 設計](../docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基於設計稿完成組件化界面,實現從設計到代碼的第一條鏈路 | 🚧 | | [前端二:建構第一個現代應用程式-UI 設計](../docs/zh-cn/stage-2/frontend/2.2-ui-design/) | 基於設計稿完成組件化界面,實現從設計到代碼的第一條鏈路 | 🚧 |
| [前端三:參考 UI 設計規範與多產品 UI 設計](../docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/) | 圍繞統一主視覺擴展多產品界面,練習系統化設計能力 | 🚧 | | [前端三:參考 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 能力的前端應用,串聯設計與開發 | ✅ | | [前端四:一起做霍格沃茨肖像](../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 接口與請求響應模型,為後端整合與聯調做準備 | ✅ | | [後端一:什麼是 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,打通數據模型與前端頁面 | ✅ | | [後端二:從數據庫到 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/) | 用大模型協助生成接口與數據庫文檔及代碼,實現可讀可測的後端 | 🚧 | | [後端三:大模型輔助編寫接口代碼與接口文檔](../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 工作流中管理代碼,進行版本控制和協作 | ✅ | | [後端四: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 上線 | ✅ | | [後端五: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 編程工具加速開發與調試,形成個人工程化工作流 | ✅ | | [後端六:現代 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/) | 接入支付系統,完成收費鏈路與基礎結算流程 | 🚧 | | [後端七:如何整合 Stripe 等收費系統](../docs/zh-cn/stage-2/backend/2.7-stripe-payment/) | 接入支付系統,完成收費鏈路與基礎結算流程 | 🚧 |
| [大作業 1:建構第一個現代應用程式-全棧應用](../docs/zh-cn/stage-2/assignments/2.1-fullstack-app/) | 綜合前端、後端與支付模塊,完成可上線的全棧 Web 應用 | 🚧 | | [大作業 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,獨立完成可登錄註冊並支持收費的產品 | 🚧 | | [大作業 2:現代前端組件庫 + Trae 實戰](../docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/) | 使用現代前端組件庫與 Trae,獨立完成可登錄註冊並支持收費的產品 | 🚧 |
#### AI 能力附錄 #### 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 一: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 二:學會查詢 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 能力,把外部服務接成工具 | 🚧 | | [高級一: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 更穩定可靠 | 🚧 | | [高級二:如何讓 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.3-wechat-miniprogram/) | 了解微信小程式生態,從官方模板到上線完成一個前端小程式 | ✅ |
| [高級四:多平台開發:如何建構微信小程式-包含後端](../docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 在小程式中接入數據庫與後端邏輯,打通完整業務閉環 | 🚧 | | [高級四:多平台開發:如何建構微信小程式-包含後端](../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/原生一體化的安卓應用開發 | 🚧 | | [高級五:多平台開發:如何建構安卓程式](../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 應用開發 | 🚧 | | [高級六:多平台開發:如何建構 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/) | 從選型、搭建到部署,建構展示個人專案與學術成果的長久在線主頁 | 🚧 | | [高級七:如何建構屬於自己的個人網頁與學術博客](../docs/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/) | 從選型、搭建到部署,建構展示個人專案與學術成果的長久在線主頁 | 🚧 |
#### AI 能力附錄 #### 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 以及它如何工作](../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 與工作流編排:以 LangGraph 為例](../docs/zh-cn/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | 使用 LangGraph 等工具設計多步工作流與中高級 RAG 系統 | 🚧 |
## 🛠️ 如何學習 ## 🛠️ 如何學習
+221 -51
View File
@@ -14,30 +14,88 @@ const base = process.env.BASE || (isVercel || isEdgeOne ? '/' : '/easy-vibe/')
// 语言映射配置 // 语言映射配置
const localeMap = { const localeMap = {
'zh-cn': { ogLocale: 'zh_CN', twitterSite: '@datawhale', lang: 'zh-CN', hreflang: 'zh-CN' }, 'zh-cn': {
'en-us': { ogLocale: 'en_US', twitterSite: '@datawhale', lang: 'en-US', hreflang: 'en' }, ogLocale: 'zh_CN',
'ja-jp': { ogLocale: 'ja_JP', twitterSite: '@datawhale', lang: 'ja-JP', hreflang: 'ja' }, twitterSite: '@datawhale',
'zh-tw': { ogLocale: 'zh_TW', twitterSite: '@datawhale', lang: 'zh-TW', hreflang: 'zh-TW' }, lang: 'zh-CN',
'ko-kr': { ogLocale: 'ko_KR', twitterSite: '@datawhale', lang: 'ko-KR', hreflang: 'ko' }, hreflang: 'zh-CN'
'es-es': { ogLocale: 'es_ES', twitterSite: '@datawhale', lang: 'es-ES', hreflang: 'es' }, },
'fr-fr': { ogLocale: 'fr_FR', twitterSite: '@datawhale', lang: 'fr-FR', hreflang: 'fr' }, 'en-us': {
'de-de': { ogLocale: 'de_DE', twitterSite: '@datawhale', lang: 'de-DE', hreflang: 'de' }, ogLocale: 'en_US',
'ar-sa': { ogLocale: 'ar_SA', twitterSite: '@datawhale', lang: 'ar-SA', hreflang: 'ar' }, twitterSite: '@datawhale',
'vi-vn': { ogLocale: 'vi_VN', twitterSite: '@datawhale', lang: 'vi-VN', hreflang: 'vi' } 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 相关配置 // SEO 相关配置
const getSeoHead = (locale, title, description, path = '') => { const getSeoHead = (locale, title, description, path = '') => {
const seoConfig = localeMap[locale] || localeMap['zh-cn'] 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 canonicalUrl = path ? `${siteUrl}${path}` : `${siteUrl}/${locale}/`
const ogImageUrl = `${siteUrl}${base}logo.png` const ogImageUrl = `${siteUrl}${base}logo.png`
const head = [ const head = [
['link', { rel: 'icon', href: `${base}logo.png`.replace('//', '/') }], ['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: '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' }], ['meta', { name: 'format-detection', content: 'telephone=no' }],
['link', { rel: 'canonical', href: canonicalUrl }], ['link', { rel: 'canonical', href: canonicalUrl }],
// Open Graph / Facebook // Open Graph / Facebook
@@ -60,7 +118,14 @@ const getSeoHead = (locale, title, description, path = '') => {
['meta', { name: 'twitter:image', content: ogImageUrl }], ['meta', { name: 'twitter:image', content: ogImageUrl }],
['meta', { name: 'twitter:image:alt', content: title }], ['meta', { name: 'twitter:image:alt', content: title }],
// Additional SEO // 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: 'author', content: 'Datawhale' }],
['meta', { name: 'robots', content: 'index,follow' }], ['meta', { name: 'robots', content: 'index,follow' }],
['meta', { name: 'googlebot', content: 'index,follow' }], ['meta', { name: 'googlebot', content: 'index,follow' }],
@@ -71,10 +136,20 @@ const getSeoHead = (locale, title, description, path = '') => {
] ]
// 添加 hreflang 标签 // 添加 hreflang 标签
Object.keys(localeMap).forEach(lang => { Object.keys(localeMap).forEach((lang) => {
head.push(['link', { rel: 'alternate', hreflang: localeMap[lang].hreflang, href: `${siteUrl}/${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 结构化数据 // 添加 JSON-LD 结构化数据
const jsonLd = { const jsonLd = {
@@ -138,7 +213,11 @@ export default defineConfig({
return items.filter((item) => { return items.filter((item) => {
const url = item.url 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 return false
} }
// 包含所有语言版本 // 包含所有语言版本
@@ -155,8 +234,13 @@ export default defineConfig({
lang: 'zh-CN', lang: 'zh-CN',
link: '/zh-cn/', link: '/zh-cn/',
title: 'Easy-Vibe 教程', title: 'Easy-Vibe 教程',
description: '从零到一学习 Vibe Coding - 零基础学会用 AI 编程,掌握 Claude Code、Cursor 等 AI IDE 工具', description:
head: getSeoHead('zh-cn', 'Easy-Vibe 教程', '从零到一学习 Vibe Coding - 零基础学会用 AI 编程,掌握 Claude Code、Cursor 等 AI IDE 工具'), '从零到一学习 Vibe Coding - 零基础学会用 AI 编程,掌握 Claude Code、Cursor 等 AI IDE 工具',
head: getSeoHead(
'zh-cn',
'Easy-Vibe 教程',
'从零到一学习 Vibe Coding - 零基础学会用 AI 编程,掌握 Claude Code、Cursor 等 AI IDE 工具'
),
themeConfig: { themeConfig: {
...commonThemeConfig, ...commonThemeConfig,
outline: { outline: {
@@ -166,7 +250,10 @@ export default defineConfig({
nav: [ nav: [
{ text: '首页', link: '/zh-cn/' }, { text: '首页', link: '/zh-cn/' },
{ text: '新手入门', link: '/zh-cn/stage-0/0.1-learning-map/' }, { 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: '初中级开发', text: '初中级开发',
link: '/zh-cn/stage-2/frontend/2.0-lovart-assets/' link: '/zh-cn/stage-2/frontend/2.0-lovart-assets/'
@@ -449,29 +536,52 @@ export default defineConfig({
items: [ items: [
{ text: '大语言模型', link: '/zh-cn/appendix/llm-intro' }, { text: '大语言模型', link: '/zh-cn/appendix/llm-intro' },
{ text: '多模态大模型', link: '/zh-cn/appendix/vlm-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: '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: '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 基础', text: 'Web 基础',
collapsed: false, collapsed: false,
items: [ 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: '开发基础', text: '开发基础',
collapsed: false, collapsed: false,
items: [ items: [
{ text: 'IDE 原理', link: '/zh-cn/appendix/ide-intro' },
{ text: '终端入门', link: '/zh-cn/appendix/terminal-intro' }, { text: '终端入门', link: '/zh-cn/appendix/terminal-intro' },
{ text: 'Git 详细介绍', link: '/zh-cn/appendix/git-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' } { text: '部署与上线', link: '/zh-cn/appendix/deployment' }
] ]
} }
@@ -486,8 +596,13 @@ export default defineConfig({
lang: 'en-US', lang: 'en-US',
link: '/en-us/', link: '/en-us/',
title: 'Easy-Vibe Tutorial', title: 'Easy-Vibe Tutorial',
description: 'Learn Vibe Coding from Zero to Advanced - Master AI programming with Claude Code, Cursor, and other AI IDE tools', description:
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'), '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: { themeConfig: {
...commonThemeConfig, ...commonThemeConfig,
outline: { outline: {
@@ -497,7 +612,10 @@ export default defineConfig({
nav: [ nav: [
{ text: 'Home', link: '/en-us/' }, { text: 'Home', link: '/en-us/' },
{ text: 'Getting Started', link: '/en-us/stage-0/0.1-learning-map/' }, { 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', text: 'Full-Stack Development',
link: '/en-us/stage-2/frontend/2.0-lovart-assets/' link: '/en-us/stage-2/frontend/2.0-lovart-assets/'
@@ -519,8 +637,13 @@ export default defineConfig({
lang: 'ja-JP', lang: 'ja-JP',
link: '/ja-jp/', link: '/ja-jp/',
title: 'Easy-Vibe チュートリアル', title: 'Easy-Vibe チュートリアル',
description: 'ゼロから学ぶ Vibe Coding - AIプログラミングを初めから体系的に学習', description:
head: getSeoHead('ja-jp', 'Easy-Vibe チュートリアル', 'ゼロから学ぶ Vibe Coding - AIプログラミングを初めから体系的に学習'), 'ゼロから学ぶ Vibe Coding - AIプログラミングを初めから体系的に学習',
head: getSeoHead(
'ja-jp',
'Easy-Vibe チュートリアル',
'ゼロから学ぶ Vibe Coding - AIプログラミングを初めから体系的に学習'
),
themeConfig: { themeConfig: {
...commonThemeConfig, ...commonThemeConfig,
outline: { outline: {
@@ -530,7 +653,10 @@ export default defineConfig({
nav: [ nav: [
{ text: 'ホーム', link: '/ja-jp/' }, { text: 'ホーム', link: '/ja-jp/' },
{ text: '入門', link: '/ja-jp/stage-0/0.1-learning-map/' }, { 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: 'フルスタック開発', text: 'フルスタック開発',
link: '/ja-jp/stage-2/frontend/2.0-lovart-assets/' link: '/ja-jp/stage-2/frontend/2.0-lovart-assets/'
@@ -550,8 +676,13 @@ export default defineConfig({
lang: 'zh-TW', lang: 'zh-TW',
link: '/zh-tw/', link: '/zh-tw/',
title: 'Easy-Vibe 教程', title: 'Easy-Vibe 教程',
description: '從零到一學習 Vibe Coding - 零基礎學會用 AI 編程,掌握 Claude Code、Cursor 等 AI IDE 工具', description:
head: getSeoHead('zh-tw', 'Easy-Vibe 教程', '從零到一學習 Vibe Coding - 零基礎學會用 AI 編程,掌握 Claude Code、Cursor 等 AI IDE 工具'), '從零到一學習 Vibe Coding - 零基礎學會用 AI 編程,掌握 Claude Code、Cursor 等 AI IDE 工具',
head: getSeoHead(
'zh-tw',
'Easy-Vibe 教程',
'從零到一學習 Vibe Coding - 零基礎學會用 AI 編程,掌握 Claude Code、Cursor 等 AI IDE 工具'
),
themeConfig: { themeConfig: {
...commonThemeConfig, ...commonThemeConfig,
outline: { outline: {
@@ -561,7 +692,10 @@ export default defineConfig({
nav: [ nav: [
{ text: '首頁', link: '/zh-tw/' }, { text: '首頁', link: '/zh-tw/' },
{ text: '新手入門', link: '/zh-tw/stage-0/0.1-learning-map/' }, { 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: '初中級開發', text: '初中級開發',
link: '/zh-tw/stage-2/frontend/2.0-lovart-assets/' link: '/zh-tw/stage-2/frontend/2.0-lovart-assets/'
@@ -580,8 +714,13 @@ export default defineConfig({
lang: 'ko-KR', lang: 'ko-KR',
link: '/ko-kr/', link: '/ko-kr/',
title: 'Easy-Vibe 튜토리얼', title: 'Easy-Vibe 튜토리얼',
description: 'Vibe Coding을 처음부터 체계적으로 학습합니다 - AI 프로그래밍을 처음부터 고급까지', description:
head: getSeoHead('ko-kr', 'Easy-Vibe 튜토리얼', 'Vibe Coding을 처음부터 체계적으로 학습합니다 - AI 프로그래밍을 처음부터 고급까지'), 'Vibe Coding을 처음부터 체계적으로 학습합니다 - AI 프로그래밍을 처음부터 고급까지',
head: getSeoHead(
'ko-kr',
'Easy-Vibe 튜토리얼',
'Vibe Coding을 처음부터 체계적으로 학습합니다 - AI 프로그래밍을 처음부터 고급까지'
),
themeConfig: { themeConfig: {
...commonThemeConfig, ...commonThemeConfig,
outline: { outline: {
@@ -610,8 +749,13 @@ export default defineConfig({
lang: 'es-ES', lang: 'es-ES',
link: '/es-es/', link: '/es-es/',
title: 'Tutorial de Easy-Vibe', title: 'Tutorial de Easy-Vibe',
description: 'Aprende Vibe Coding desde cero hasta avanzado - Domina la programación con IA desde el principio', description:
head: getSeoHead('es-es', 'Tutorial de Easy-Vibe', 'Aprende Vibe Coding desde cero hasta avanzado - Domina la programación con IA desde el principio'), '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: { themeConfig: {
...commonThemeConfig, ...commonThemeConfig,
outline: { outline: {
@@ -621,7 +765,10 @@ export default defineConfig({
nav: [ nav: [
{ text: 'Inicio', link: '/es-es/' }, { text: 'Inicio', link: '/es-es/' },
{ text: 'Principiante', link: '/es-es/stage-0/0.1-learning-map/' }, { 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', text: 'Desarrollo Full Stack',
link: '/es-es/stage-2/frontend/2.0-lovart-assets/' link: '/es-es/stage-2/frontend/2.0-lovart-assets/'
@@ -640,8 +787,13 @@ export default defineConfig({
lang: 'fr-FR', lang: 'fr-FR',
link: '/fr-fr/', link: '/fr-fr/',
title: 'Tutoriel Easy-Vibe', title: 'Tutoriel Easy-Vibe',
description: 'Apprenez Vibe Coding de zéro à avancé - Maîtrisez la programmation IA du début au niveau avancé', description:
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é'), '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: { themeConfig: {
...commonThemeConfig, ...commonThemeConfig,
outline: { outline: {
@@ -670,8 +822,13 @@ export default defineConfig({
lang: 'de-DE', lang: 'de-DE',
link: '/de-de/', link: '/de-de/',
title: 'Easy-Vibe Tutorial', title: 'Easy-Vibe Tutorial',
description: 'Lernen Sie Vibe Coding von Null bis Fortgeschritten - Meistern Sie die KI-Programmierung von Grund auf', description:
head: getSeoHead('de-de', 'Easy-Vibe Tutorial', 'Lernen Sie Vibe Coding von Null bis Fortgeschritten - Meistern Sie die KI-Programmierung von Grund auf'), '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: { themeConfig: {
...commonThemeConfig, ...commonThemeConfig,
outline: { outline: {
@@ -700,8 +857,13 @@ export default defineConfig({
lang: 'ar-SA', lang: 'ar-SA',
link: '/ar-sa/', link: '/ar-sa/',
title: 'دروس Easy-Vibe', title: 'دروس Easy-Vibe',
description: 'تعلم Vibe Coding من الصفر إلى المتقدم - إتقان البرمجة بالذكاء الاصطناعي من البداية', description:
head: getSeoHead('ar-sa', 'دروس Easy-Vibe', 'تعلم Vibe Coding من الصفر إلى المتقدم - إتقان البرمجة بالذكاء الاصطناعي من البداية'), 'تعلم Vibe Coding من الصفر إلى المتقدم - إتقان البرمجة بالذكاء الاصطناعي من البداية',
head: getSeoHead(
'ar-sa',
'دروس Easy-Vibe',
'تعلم Vibe Coding من الصفر إلى المتقدم - إتقان البرمجة بالذكاء الاصطناعي من البداية'
),
themeConfig: { themeConfig: {
...commonThemeConfig, ...commonThemeConfig,
outline: { outline: {
@@ -711,7 +873,10 @@ export default defineConfig({
nav: [ nav: [
{ text: 'الرئيسية', link: '/ar-sa/' }, { text: 'الرئيسية', link: '/ar-sa/' },
{ text: 'المبتدئين', link: '/ar-sa/stage-0/0.1-learning-map/' }, { 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', text: 'تطوير Full Stack',
link: '/ar-sa/stage-2/frontend/2.0-lovart-assets/' link: '/ar-sa/stage-2/frontend/2.0-lovart-assets/'
@@ -730,8 +895,13 @@ export default defineConfig({
lang: 'vi-VN', lang: 'vi-VN',
link: '/vi-vn/', link: '/vi-vn/',
title: 'Hướng dẫn Easy-Vibe', 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', description:
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'), '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: { themeConfig: {
...commonThemeConfig, ...commonThemeConfig,
outline: { outline: {
+23 -7
View File
@@ -30,14 +30,16 @@ const lineHeight = ref(DEFAULT_LINE_HEIGHT)
const isHydrated = ref(false) const isHydrated = ref(false)
const clampFontSize = (value) => { 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) const numeric = Number(value)
if (!Number.isFinite(numeric)) return DEFAULT_FONT_SIZE if (!Number.isFinite(numeric)) return DEFAULT_FONT_SIZE
return Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, numeric)) return Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, numeric))
} }
const clampLineHeight = (value) => { 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) const numeric = Number(value)
if (!Number.isFinite(numeric)) return DEFAULT_LINE_HEIGHT if (!Number.isFinite(numeric)) return DEFAULT_LINE_HEIGHT
return Math.min(MAX_LINE_HEIGHT, Math.max(MIN_LINE_HEIGHT, numeric)) return Math.min(MAX_LINE_HEIGHT, Math.max(MIN_LINE_HEIGHT, numeric))
@@ -50,7 +52,10 @@ const applyFontSize = (size) => {
const applyLineHeight = (value) => { const applyLineHeight = (value) => {
if (typeof document === 'undefined') return 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 = () => { const decreaseFontSize = () => {
@@ -71,7 +76,9 @@ const resetLineHeight = () => {
onMounted(() => { onMounted(() => {
const saved = clampFontSize(localStorage.getItem(FONT_SIZE_STORAGE_KEY)) 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 fontSize.value = saved
lineHeight.value = savedLineHeight lineHeight.value = savedLineHeight
applyFontSize(saved) applyFontSize(saved)
@@ -105,7 +112,7 @@ watch(lineHeight, (next) => {
class="ev-fontsize-button" class="ev-fontsize-button"
type="button" type="button"
aria-label="阅读设置" 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> <el-icon :size="16"><Setting /></el-icon>
</button> </button>
@@ -139,7 +146,12 @@ watch(lineHeight, (next) => {
A+ A+
</button> </button>
</div> </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>
<div class="ev-setting-group"> <div class="ev-setting-group">
@@ -148,7 +160,11 @@ watch(lineHeight, (next) => {
<div class="ev-setting-value">{{ lineHeight.toFixed(2) }}</div> <div class="ev-setting-value">{{ lineHeight.toFixed(2) }}</div>
</div> </div>
<div class="ev-fontsize-actions"> <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>
<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) const hasTags = computed(() => props.tags && props.tags.length > 0)
</script> </script>
@@ -43,7 +49,7 @@ const hasTags = computed(() => props.tags && props.tags.length > 0)
{{ tag }} {{ tag }}
</span> </span>
</div> </div>
<!-- Slot content (full description) always rendered below tags if tags exist, or alone if not --> <!-- Slot content (full description) always rendered below tags if tags exist, or alone if not -->
<div class="description-text" :class="{ 'has-tags': hasTags }"> <div class="description-text" :class="{ 'has-tags': hasTags }">
<slot></slot> <slot></slot>
@@ -61,7 +67,7 @@ const hasTags = computed(() => props.tags && props.tags.length > 0)
<div class="card-value" v-html="duration"></div> <div class="card-value" v-html="duration"></div>
</div> </div>
</div> </div>
<!-- Output Card --> <!-- Output Card -->
<div v-if="expectedOutput || coreOutput" class="metric-card output-card"> <div v-if="expectedOutput || coreOutput" class="metric-card output-card">
<div class="card-icon">📦</div> <div class="card-icon">📦</div>
@@ -69,7 +75,11 @@ const hasTags = computed(() => props.tags && props.tags.length > 0)
<div class="card-label">预期产出</div> <div class="card-label">预期产出</div>
<div class="output-container"> <div class="output-container">
<div v-if="coreOutput" class="core-output">{{ coreOutput }}</div> <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> </div>
</div> </div>
@@ -98,7 +108,11 @@ const hasTags = computed(() => props.tags && props.tags.length > 0)
.objective-section { .objective-section {
padding: 24px 28px; 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); border-bottom: 1px dashed var(--vp-c-divider);
} }
@@ -258,7 +272,7 @@ const hasTags = computed(() => props.tags && props.tags.length > 0)
padding: 16px 20px; padding: 16px 20px;
flex-basis: 100%; flex-basis: 100%;
} }
.objective-section { .objective-section {
padding: 20px; padding: 20px;
} }
@@ -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) { if (res.ok) {
const data = await res.json() const data = await res.json()
stars.value = data.stargazers_count stars.value = data.stargazers_count
formattedStars.value = formatStars(stars.value) formattedStars.value = formatStars(stars.value)
localStorage.setItem(CACHE_KEY, JSON.stringify({ localStorage.setItem(
count: stars.value, CACHE_KEY,
timestamp: Date.now() JSON.stringify({
})) count: stars.value,
timestamp: Date.now()
})
)
} }
} catch (e) { } catch (e) {
console.error('Failed to fetch GitHub stars:', e) console.error('Failed to fetch GitHub stars:', e)
@@ -53,9 +58,21 @@ onMounted(async () => {
aria-label="GitHub" aria-label="GitHub"
> >
<span class="icon"> <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>
<span v-if="formattedStars" class="stars-count">{{ formattedStars }}</span> <span v-if="formattedStars" class="stars-count">{{
formattedStars
}}</span>
</a> </a>
</div> </div>
</template> </template>
@@ -1,59 +1,45 @@
<!--
AgentArchitectureDemo.vue
Agent 架构点哪看哪点击模块右侧展示它负责什么 + 典型输入输出
-->
<template> <template>
<div class="agent-architecture-demo"> <div class="arch">
<div class="architecture-diagram"> <div class="header">
<div class="diagram-center"> <div>
<div class="agent-core">🤖 Agent</div> <div class="title">Agent 由哪些模块拼起来</div>
<div class="subtitle">点一下模块看它负责什么</div>
</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>
<div class="module-details"> <div class="grid">
<div class="detail-header"> <div class="diagram">
<span class="detail-icon">{{ modules[selectedModule].icon }}</span> <button
<h3>{{ modules[selectedModule].name }}</h3> 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>
<div class="detail-content"> <div class="panel">
<p>{{ modules[selectedModule].description }}</p> <div class="panel-title">{{ current.icon }} {{ current.name }}</div>
<div class="panel-body">{{ current.desc }}</div>
<div class="code-example"> <div class="io">
<div class="code-title">💻 示例代码</div> <div class="io-title">典型输入</div>
<pre><code>{{ modules[selectedModule].code }}</code></pre> <pre><code>{{ current.input }}</code></pre>
</div> </div>
<div class="io">
<div class="key-points"> <div class="io-title">典型输出</div>
<div class="point-title">🎯 关键要点</div> <pre><code>{{ current.output }}</code></pre>
<ul>
<li v-for="point in modules[selectedModule].points" :key="point">{{ point }}</li>
</ul>
</div> </div>
</div> </div>
</div> </div>
@@ -63,346 +49,104 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
const selectedModule = ref(0)
const modules = [ const modules = [
{ {
name: 'Profile', id: 'llm',
icon: '👤',
desc: '角色设定',
description: 'Profile 定义了 Agent 的身份、角色、目标和约束条件。它决定了 Agent 的行为方式和能力范围。就像给演员设定角色一样,Profile 让 Agent 知道"我是谁"和"我应该做什么"。',
code: `profile = {
"name": "Web Researcher",
"role": "网络搜索助手",
"goal": "帮助用户搜索和总结网络信息",
"constraints": [
"只能使用公开信息",
"必须注明信息来源",
"不能访问付费内容"
],
"style": "专业、简洁、准确"
}`,
points: [
'明确定义 Agent 的职责范围',
'设定合理的目标和约束',
'塑造 Agent 的沟通风格',
'防止 Agent 超出权限范围'
]
},
{
name: 'Memory',
icon: '🧠', icon: '🧠',
desc: '记忆系统', name: 'LLM(大脑)',
description: 'Memory 是 Agent 的"大脑",用于存储和检索信息。它包括短期记忆(当前对话)、长期记忆(持久化知识)和工作记忆(当前任务状态)。好的记忆系统能让 Agent 从历史经验中学习。', desc: '负责理解目标、生成计划、选择动作、组织语言输出。',
code: `memory = { input: '用户目标 + 当前状态 + 可用工具列表',
# 短期记忆:当前对话 output: '下一步计划 / 工具调用参数 / 最终回答'
"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: 'Planning', id: 'tools',
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',
icon: '🔧', icon: '🔧',
desc: '执行模块', name: 'Tools(手脚)',
description: 'Action 模块负责执行具体的操作,包括调用工具、修改文件、发送请求等。它是 Agent 与外部环境交互的接口,将"想法"转化为"行动"。', desc: '负责真正“做事”:搜索、读写文件、调用 API、运行命令。',
code: `action = { input: 'tool_name + input_schema 参数',
"tool": "web_search", output: '工具执行结果(文本/数据/文件变更)'
"input": {
"query": "AI 技术 2024",
"max_results": 10,
"time_range": "last_month"
}, },
"output": { {
"status": "success", id: 'memory',
"results": [ icon: '💾',
{ name: 'Memory(记忆)',
"title": "...", desc: '把“已经做过什么、得到什么结果”存起来,避免重复与跑偏。',
"url": "...", input: '对话历史 / 工具结果 / 当前任务状态',
"snippet": "..." output: '可检索的上下文(短期/长期/工作记忆)'
} },
] {
} id: 'planner',
} icon: '🧩',
name: 'Planning(规划)',
# 可用工具 desc: '把大目标拆成小步骤,并在失败时改计划(计划不是一次性的)。',
tools = [ input: '目标 + 约束(预算/时间/安全) + 当前进度',
"web_search", # 搜索引擎 output: '步骤清单 / 下一步动作 / 停止条件'
"read_page", # 读取网页 },
"write_file", # 写入文件 {
"run_code" # 执行代码 id: 'guard',
]`, icon: '🛡️',
points: [ name: 'Guardrails(护栏)',
'提供丰富的工具集', desc: '限制风险:权限白名单、预算上限、敏感操作确认、沙箱执行。',
'处理工具调用的输入输出', input: '请求执行的动作 + 安全策略',
'管理工具的权限和安全', output: '允许/拒绝/要求确认 + 审计日志'
'支持自定义工具扩展'
]
} }
] ]
const getModulePosition = (index) => { const current = ref(modules[0])
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]
}
</script> </script>
<style scoped> <style scoped>
.agent-architecture-demo { .arch {
border: 1px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
border-radius: 12px; border-radius: 12px;
padding: 24px;
background: var(--vp-c-bg-soft); 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; padding: 16px;
cursor: pointer; margin: 20px 0;
transition: all 0.3s; display: flex;
min-width: 140px; flex-direction: column;
text-align: center; gap: 12px;
} }
.module-card:hover { .header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
border-color: var(--vp-c-brand); .title { font-weight: 800; }
transform: scale(1.05) !important; .subtitle { color: var(--vp-c-text-2); font-size: 13px; }
}
.module-card.active { .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
border-color: var(--vp-c-brand);
background: var(--vp-c-bg-soft);
box-shadow: 0 4px 20px rgba(66, 153, 225, 0.3);
}
.module-icon { .diagram {
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 {
background: var(--vp-c-bg); background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 12px; border-radius: 12px;
padding: 24px; padding: 12px;
display: flex;
flex-direction: column;
gap: 10px;
} }
.detail-header { .node {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 10px;
margin-bottom: 20px; padding: 10px 12px;
padding-bottom: 16px; border-radius: 12px;
border-bottom: 2px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
cursor: pointer;
text-align: left;
} }
.detail-icon { .node.active { border-color: var(--vp-c-brand); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06); }
font-size: 2rem; .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 { .pipes { margin-top: 6px; padding-top: 10px; border-top: 1px dashed var(--vp-c-divider); }
margin: 0; .pipe { color: var(--vp-c-text-2); font-size: 13px; line-height: 1.5; }
color: var(--vp-c-brand); .pipe.small { font-size: 12px; color: var(--vp-c-text-3); }
font-size: 1.5rem;
}
.detail-content > p { .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; }
color: var(--vp-c-text-2); .panel-title { font-weight: 800; }
line-height: 1.7; .panel-body { color: var(--vp-c-text-2); line-height: 1.6; }
margin-bottom: 20px; .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; }
.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;
}
</style> </style>
@@ -1,490 +1,108 @@
<!--
AgentChallengesDemo.vue
挑战不是列清单而是能感受到风险
- 开关护栏步数上限/预算/确认/沙箱
- 看风险分数怎么变化
-->
<template> <template>
<div class="agent-challenges-demo"> <div class="risk">
<div class="challenges-grid"> <div class="header">
<div <div>
v-for="(challenge, index) in challenges" <div class="title">Agent 的挑战没护栏就容易翻车</div>
:key="challenge.title" <div class="subtitle">打开这些护栏风险会明显下降</div>
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> </div>
<div class="score" :class="scoreClass">风险分数{{ score }}/100</div>
</div> </div>
<div class="challenge-detail"> <div class="controls">
<div class="detail-header"> <label class="toggle"><input type="checkbox" v-model="maxSteps" /> 最大迭代次数防死循环</label>
<span class="detail-icon">{{ challenges[selectedChallenge].icon }}</span> <label class="toggle"><input type="checkbox" v-model="budget" /> 预算上限防烧钱</label>
<h3>{{ challenges[selectedChallenge].title }}</h3> <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>
<div class="card">
<div class="detail-sections"> <div class="k">你现在开启了什么</div>
<div class="detail-section"> <div class="v">{{ enabledList }}</div>
<h4>📖 问题描述</h4> <div class="note">建议最少也要有最大步数 + 确认</div>
<p>{{ challenges[selectedChallenge].description }}</p> </div>
</div> <div class="card">
<div class="k">一句话建议</div>
<div class="detail-section"> <div class="v">{{ advice }}</div>
<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> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <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 = [ const score = computed(() => {
{ let s = 85
title: '任务规划', if (maxSteps.value) s -= 18
icon: '📋', if (budget.value) s -= 15
difficulty: 5, if (confirm.value) s -= 22
description: 'Agent 需要将复杂的用户任务分解为可执行的步骤,并动态调整计划。这要求 Agent 具备强大的推理能力和前瞻性思维。', if (sandbox.value) s -= 18
reasons: [ return Math.max(0, s)
'任务分解需要深度理解用户意图', })
'长期规划容易偏离目标',
'动态调整计划增加了复杂性', const scoreClass = computed(() => {
'缺乏反馈时难以评估进度' if (score.value <= 35) return 'good'
], if (score.value <= 60) return 'mid'
solutions: [ return 'bad'
{ })
title: '层次化规划',
description: '将大任务分解为子任务,子任务再分解为具体步骤,形成层次结构。' const enabledList = computed(() => {
}, const items = []
{ if (maxSteps.value) items.push('最大步数')
title: '反思机制', if (budget.value) items.push('预算上限')
description: '定期回顾已完成的步骤,评估计划的有效性,及时调整策略。' if (confirm.value) items.push('二次确认')
}, if (sandbox.value) items.push('沙箱')
{ return items.length ? items.join('、') : '(都没开)'
title: '外部记忆', })
description: '使用 todo.md 等文件记录计划,将目标保持在 Agent 的"视野"中。'
} const advice = computed(() => {
], if (!maxSteps.value && !confirm.value) return '先加“最大步数”和“二次确认”,这是最低成本的安全感。'
progress: 40, if (score.value <= 35) return '很稳了:可以开始做更复杂的任务,但记得加日志与监控。'
resources: [ if (score.value <= 60) return '还不错:建议再加预算或沙箱,避免极端情况。'
{ title: 'ReAct 论文', url: 'https://arxiv.org/abs/2210.03629' }, return '风险偏高:建议优先补护栏,再让 Agent 真去执行。'
{ 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: '#' }
]
}
]
</script> </script>
<style scoped> <style scoped>
.agent-challenges-demo { .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; }
border: 1px solid var(--vp-c-divider); .header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; align-items: center; }
border-radius: 12px; .title { font-weight: 800; }
padding: 24px; .subtitle { color: var(--vp-c-text-2); font-size: 13px; }
background: var(--vp-c-bg-soft); .score { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 999px; padding: 8px 12px; font-weight: 900; }
margin: 24px 0; .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 { .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; }
display: grid; .toggle { display: flex; gap: 8px; align-items: center; }
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); input { accent-color: var(--vp-c-brand); }
gap: 16px;
margin-bottom: 32px;
}
.challenge-card { .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
background: var(--vp-c-bg); .card { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; }
border: 2px solid var(--vp-c-divider); .k { font-weight: 900; margin-bottom: 6px; }
border-radius: 12px; .v { color: var(--vp-c-text-2); line-height: 1.6; }
padding: 20px; .note { margin-top: 6px; color: var(--vp-c-text-3); font-size: 12px; }
cursor: pointer; ul { margin: 0; padding-left: 18px; color: var(--vp-c-text-2); line-height: 1.6; }
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);
}
</style> </style>
@@ -1,85 +1,38 @@
<!--
AgentFutureDemo.vue
Agent 未来方向点选趋势看看会带来什么变化现在就能做的准备
-->
<template> <template>
<div class="agent-future-demo"> <div class="future">
<div class="future-intro"> <div class="header">
<h3>🚀 Agent 的未来展望</h3> <div>
<p>探索 Agent 技术的发展趋势和应用前景</p> <div class="title">Agent 的未来更稳更强更协作</div>
<div class="subtitle">点一个趋势看它意味着什么</div>
</div>
</div> </div>
<div class="timeline"> <div class="chips">
<div class="timeline-line"></div> <button
v-for="t in trends"
<div :key="t.id"
v-for="(era, index) in timeline" :class="['chip', { active: current.id === t.id }]"
:key="era.period" @click="current = t"
class="timeline-item"
:class="{ active: selectedEra === index }"
@click="selectedEra = index"
> >
<div class="timeline-dot"></div> {{ t.label }}
<div class="timeline-content"> </button>
<div class="era-period">{{ era.period }}</div>
<div class="era-title">{{ era.title }}</div>
<div class="era-description">{{ era.description }}</div>
</div>
</div>
</div> </div>
<div class="era-details"> <div class="panel">
<div class="detail-card"> <div class="p-title">{{ current.label }}</div>
<h4>{{ timeline[selectedEra].title }} ({{ timeline[selectedEra].period }})</h4> <div class="p-body">{{ current.desc }}</div>
<p class="era-detail-desc">{{ timeline[selectedEra].detailDescription }}</p> <div class="grid">
<div class="card">
<div class="era-features"> <div class="k">会带来什么</div>
<div class="features-title">🎯 关键特征</div> <div class="v">{{ current.impact }}</div>
<div class="features-list">
<div
v-for="feature in timeline[selectedEra].features"
:key="feature"
class="feature-tag"
>
{{ feature }}
</div>
</div>
</div> </div>
<div class="card">
<div class="era-applications"> <div class="k">你现在能做什么准备</div>
<div class="applications-title">💼 典型应用</div> <div class="v">{{ current.prepare }}</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> </div>
</div> </div>
</div> </div>
@@ -89,435 +42,56 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
const selectedEra = ref(3) const trends = [
const timeline = [
{ {
period: '2023-2024', id: 'planning',
title: '萌芽期', label: '更强规划',
description: '单 Agent,简单工具调用', desc: '把大目标拆成更合理的子任务,并能动态改计划。',
detailDescription: 'Agent 技术的起步阶段,主要是简单的单 Agent 系统,能够调用有限工具完成基础任务。这个阶段证明了 Agent 的可行性,但能力还比较有限。', impact: '更少跑题、更少漏步骤,复杂任务成功率更高。',
features: [ prepare: '学会写“计划/检查点”,并把任务拆成可验收小块。'
'单一 Agent 执行',
'基础工具调用',
'简单的任务规划',
'有限的记忆能力'
],
applications: [
{ icon: '💬', name: '聊天机器人', description: '增强型对话助手' },
{ icon: '🔍', name: '搜索助手', description: '信息检索和汇总' },
{ icon: '📝', name: '写作助手', description: '内容生成辅助' }
],
challenges: [
'规划能力弱',
'容易迷失目标',
'上下文管理困难',
'错误恢复能力差'
]
}, },
{ {
period: '2024-2025', id: 'memory',
title: '成长期', label: '更好记忆',
description: '多工具,复杂任务处理', desc: '长期记住偏好、事实与项目状态,跨任务复用。',
detailDescription: 'Agent 开始能够处理更复杂的任务,使用多个工具,具备基本的规划能力。框架和工具日趋成熟,开始出现实际应用。', impact: '更像长期同事:越用越懂你,重复工作更少。',
features: [ prepare: '设计记忆结构:短期/长期/工作记忆,并做好隐私与脱敏。'
'多工具协作',
'层次化任务分解',
'短期记忆管理',
'基础的反思能力'
],
applications: [
{ icon: '💻', name: '编程助手', description: '代码编写和调试' },
{ icon: '📊', name: '数据分析', description: '自动化报告生成' },
{ icon: '🌐', name: 'Web Agent', description: '网页自动化操作' }
],
challenges: [
'长期规划困难',
'记忆容量有限',
'工具选择不准确',
'安全风险增加'
]
}, },
{ {
period: '2025-2026', id: 'multi',
title: '成熟期', label: '多 Agent 协作',
description: '多 Agent 协作,专业化分工', desc: '多个角色并行处理,再由协调者合并输出。',
detailDescription: '多个专业化的 Agent 开始协作,每个 Agent 专注于特定领域。通过通信和协作完成复杂任务,形成 AI 团队。', impact: '大任务并行化,质量更稳(研究/实现/评审分工)。',
features: [ prepare: '先把“角色边界”和“交付格式”定义清楚。'
'多 Agent 协作',
'专业化分工',
'持久化记忆',
'主动学习和改进'
],
applications: [
{ icon: '👥', name: 'AI 团队', description: '协作完成复杂项目' },
{ icon: '🔬', name: '研究助手', description: '自动化科研流程' },
{ icon: '🏢', name: '企业助手', description: '业务流程自动化' }
],
challenges: [
'Agent 间通信效率',
'协作策略优化',
'资源调度复杂',
'责任归属问题'
]
}, },
{ {
period: '2026-2028', id: 'safety',
title: '进化期', label: '更强安全护栏',
description: '自主 Agent,持续学习', desc: '更细的权限、确认与审计,降低工具滥用风险。',
detailDescription: 'Agent 具备强大的自主学习和改进能力,能够从经验中学习,优化自己的行为。可以适应新环境,掌握新技能,实现真正的智能。', impact: '更容易上线到真实业务场景,减少事故。',
features: [ prepare: '默认开启:最大步数、预算上限、危险操作确认、沙箱。'
'自主学习和优化',
'跨任务知识迁移',
'多模态理解',
'情感和个性'
],
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 治理',
'存在性风险'
]
} }
] ]
const predictions = [ const current = ref(trends[0])
{
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'
}
]
</script> </script>
<style scoped> <style scoped>
.agent-future-demo { .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; }
border: 1px solid var(--vp-c-divider); .header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
border-radius: 12px; .title { font-weight: 800; }
padding: 24px; .subtitle { color: var(--vp-c-text-2); font-size: 13px; }
background: var(--vp-c-bg-soft);
margin: 24px 0;
}
.future-intro { .chips { display: flex; gap: 8px; flex-wrap: wrap; }
text-align: center; .chip { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); padding: 8px 12px; border-radius: 999px; cursor: pointer; }
margin-bottom: 32px; .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 { .panel { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; }
margin: 0 0 8px 0; .p-title { font-weight: 900; margin-bottom: 6px; }
color: var(--vp-c-brand); .p-body { color: var(--vp-c-text-2); line-height: 1.6; margin-bottom: 10px; }
font-size: 1.5rem; .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; }
.future-intro p { .v { color: var(--vp-c-text-2); line-height: 1.6; }
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;
}
</style> </style>
@@ -1,348 +1,117 @@
<!--
AgentLevelDemo.vue
Agent 分级L0-L5交互拖动等级看到能做什么/不能做什么/典型任务
-->
<template> <template>
<div class="agent-level-demo"> <div class="levels">
<div class="levels-container"> <div class="header">
<div <div>
v-for="(level, index) in levels" <div class="title">Agent 能力分级从聊天到协作</div>
:key="level.id" <div class="subtitle">拖动看看等级越高越像能独立干活的同事</div>
class="level-card" </div>
:class="{ active: selectedLevel === index }" <div class="badge">当前{{ current.name }}</div>
@click="selectedLevel = index" </div>
>
<div class="level-header">
<div class="level-badge">{{ level.id }}</div>
<div class="level-name">{{ level.name }}</div>
</div>
<div class="level-features"> <div class="slider">
<div v-for="feature in level.features" :key="feature" class="feature-item"> <input type="range" min="0" max="5" step="1" v-model.number="level" />
<span class="feature-icon"></span> <div class="ticks">
{{ feature }} <span v-for="n in 6" :key="n">{{ n - 1 }}</span>
</div>
</div>
<div class="level-example">
<div class="example-label">典型应用</div>
<div class="example-text">{{ level.example }}</div>
</div>
</div> </div>
</div> </div>
<div class="level-comparison"> <div class="grid">
<h3>📊 能力对比</h3> <div class="card">
<div class="k">能做什么</div>
<div class="comparison-grid"> <ul>
<div class="comparison-item"> <li v-for="x in current.can" :key="x">{{ x }}</li>
<div class="item-label">工具使用</div> </ul>
<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> </div>
<div class="card">
<div class="level-description"> <div class="k">容易出的问题</div>
<h4>{{ levels[selectedLevel].name }}</h4> <ul>
<p>{{ levels[selectedLevel].description }}</p> <li v-for="x in current.risk" :key="x">{{ x }}</li>
</ul>
<div class="use-cases"> </div>
<div class="use-case-title">🎯 适用场景</div> <div class="card">
<ul> <div class="k">典型任务</div>
<li v-for="use in levels[selectedLevel].useCases" :key="use">{{ use }}</li> <div class="v">{{ current.example }}</div>
</ul>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { computed, ref } from 'vue'
const selectedLevel = ref(2) const level = ref(2)
const levels = [ const levels = [
{ {
id: 'L0', name: 'L0:纯对话',
name: '无工具', can: ['回答问题', '写文本/代码(但不执行)'],
features: ['只能对话', '不能执行操作', '被动响应'], risk: ['只能“说”,不能“做”', '需要你手动分步骤'],
example: 'ChatGPT 聊天', example: '解释概念、写一段文案'
description: '最基础的 LLM 应用,只能进行对话,无法执行任何实际操作。所有的"行动"都需要人工完成。',
capabilities: { tools: 0, planning: 0, autonomy: 0, complexity: 10 },
useCases: ['问答系统', '内容生成', '语言翻译']
}, },
{ {
id: 'L1', name: 'L1:单工具',
name: '单工具', can: ['调用一个固定工具', '把结果解释给你'],
features: ['使用一个固定工具', '有限的操作能力', '简单任务执行'], risk: ['工具用错参数', '缺少复杂规划'],
example: '代码解释器', example: '只会查一次搜索/只会跑一次代码'
description: '可以使用一个特定的工具来扩展能力,但工具选择是固定的,无法自主切换。',
capabilities: { tools: 20, planning: 10, autonomy: 20, complexity: 30 },
useCases: ['代码执行', '数据计算', '文件分析']
}, },
{ {
id: 'L2', name: 'L2:多工具',
name: '多工具', can: ['在多个工具间选择', '按需要组合调用'],
features: ['可以选择多个工具', '工具切换能力', '灵活的任务处理'], risk: ['选择工具不稳', '权限与安全需要控制'],
example: 'Web Agent', example: '搜索 + 打开网页 + 摘要'
description: '可以使用多个不同的工具,并能根据任务需要自主选择合适的工具。',
capabilities: { tools: 60, planning: 30, autonomy: 40, complexity: 50 },
useCases: ['网页浏览', '数据采集', '信息检索']
}, },
{ {
id: 'L3', name: 'L3:多步骤执行',
name: '多步骤', can: ['先计划后执行', '完成一串步骤', '记录中间结果'],
features: ['复杂任务规划', '多步骤执行', '状态跟踪'], risk: ['步骤漏/顺序错', '成本上升(更多调用)'],
example: '数据分析 Agent', example: '读代码 → 改代码 → 跑测试 → 出报告'
description: '能够将复杂任务分解为多个步骤,按照计划逐步执行,并跟踪整体进度。',
capabilities: { tools: 70, planning: 60, autonomy: 60, complexity: 70 },
useCases: ['数据分析', '报告生成', '工作流自动化']
}, },
{ {
id: 'L4', name: 'L4:自我纠错',
name: '自主迭代', can: ['失败后换策略', '用检查点避免跑偏'],
features: ['主动反思和改进', '从错误中学习', '策略调整'], risk: ['可能反复尝试(需要上限)', '更依赖监控与日志'],
example: '研究 Agent', example: '测试失败后自动定位并尝试修复'
description: '不仅能执行任务,还能主动反思结果,从错误中学习,不断优化自己的策略。',
capabilities: { tools: 80, planning: 80, autonomy: 80, complexity: 85 },
useCases: ['科学研究', '复杂问题求解', '自适应系统']
}, },
{ {
id: 'L5', name: 'L5:多 Agent 协作',
name: '多 Agent 协作', can: ['多个角色分工', '并行处理任务', '合并结果'],
features: ['Agent 间通信', '分工协作', '集体智能'], risk: ['协作成本更高', '需要清晰协议与仲裁机制'],
example: '企业级系统', example: '研究员找资料 + 工程师实现 + 编辑写总结'
description: '多个专业化的 Agent 协同工作,通过通信和协作完成单个 Agent 无法完成的复杂任务。',
capabilities: { tools: 100, planning: 100, autonomy: 100, complexity: 100 },
useCases: ['企业自动化', '软件开发团队', '智能组织']
} }
] ]
const current = computed(() => levels[level.value])
</script> </script>
<style scoped> <style scoped>
.agent-level-demo { .levels {
border: 1px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
border-radius: 12px; border-radius: 12px;
padding: 24px;
background: var(--vp-c-bg-soft); 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; padding: 16px;
cursor: pointer; margin: 20px 0;
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 {
display: flex; display: flex;
align-items: center; flex-direction: column;
gap: 12px; gap: 12px;
margin-bottom: 16px;
} }
.level-badge { .header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
background: var(--vp-c-brand); .title { font-weight: 800; }
color: white; .subtitle { color: var(--vp-c-text-2); font-size: 13px; }
padding: 4px 12px; .badge { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 999px; padding: 8px 12px; font-weight: 800; }
border-radius: 6px;
font-weight: bold;
font-size: 0.9rem;
}
.level-name { .slider { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 10px 12px; }
font-weight: bold; input[type='range'] { width: 100%; }
font-size: 1.1rem; .ticks { display: flex; justify-content: space-between; color: var(--vp-c-text-2); font-size: 12px; margin-top: 6px; }
color: var(--vp-c-text-1);
}
.level-features { .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
margin-bottom: 16px; .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; }
.feature-item { ul { margin: 0; padding-left: 18px; color: var(--vp-c-text-2); line-height: 1.6; }
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;
}
</style> </style>
@@ -1,421 +1,117 @@
<!--
AgentTaskFlowDemo.vue
任务执行流像看回放一样看 Agent 一步步完成一个任务
-->
<template> <template>
<div class="agent-task-flow-demo"> <div class="flow">
<div class="task-input"> <div class="header">
<div class="input-label">🎯 用户任务</div> <div>
<div class="input-content">"搜索并总结最新的 AI 技术文章"</div> <div class="title">任务回放Agent 怎么一步步做完</div>
</div> <div class="subtitle">点步骤工具调用中间结果</div>
</div>
<div class="flow-timeline"> <div class="actions">
<div class="timeline-line"></div> <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
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> </div>
</div> </div>
<div class="flow-controls"> <div class="timeline">
<button @click="prevStep" :disabled="currentStep === 0" class="control-btn"> <button
上一步 v-for="(s, i) in steps"
</button> :key="s.title"
<button @click="togglePlay" class="control-btn primary"> :class="['t', { active: i === step }]"
{{ isPlaying ? '⏸ 暂停' : '▶ 自动演示' }} @click="step = i"
</button> >
<button @click="nextStep" :disabled="currentStep === steps.length - 1" class="control-btn"> <span class="n">{{ i + 1 }}</span>
下一步 <span class="txt">{{ s.title }}</span>
</button>
<button @click="reset" class="control-btn">
重置
</button> </button>
</div> </div>
<div class="flow-explanation"> <div class="grid">
<div class="explanation-card"> <div class="panel">
<h4>{{ steps[currentStep].title }}</h4> <div class="panel-title">当前步骤</div>
<p>{{ steps[currentStep].explanation }}</p> <div class="panel-body">{{ steps[step].desc }}</div>
</div>
<div class="tips"> <div class="panel">
<div class="tip-icon">💡</div> <div class="panel-title">工具调用示意</div>
<div>{{ steps[currentStep].tip }}</div> <pre><code>{{ steps[step].tool }}</code></pre>
</div> </div>
<div class="panel">
<div class="panel-title">结果示意</div>
<pre><code>{{ steps[step].result }}</code></pre>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onUnmounted } from 'vue' import { ref } from 'vue'
const currentStep = ref(0) const step = ref(0)
const isPlaying = ref(false)
let playInterval = null
const steps = [ const steps = [
{ {
title: '理解任务', title: '理解目标',
description: 'Agent 分析用户需求,明确目标', desc: '把用户需求拆成“可交付”的输出结构。',
code: null, tool: 'LLM: parse_goal({ task, constraints, output_format })',
result: null, result: '目标:找 3 篇文章;输出:标题 + 一句话总结(Markdown 列表)'
explanation: 'Agent 首先理解用户的意图,识别出这是一个需要搜索和总结的任务。它会分析关键词: "搜索"、"最新"、"AI 技术"、"文章"、"总结"。',
tip: '好的任务理解是成功的一半。Agent 需要识别出核心需求和约束条件。'
}, },
{ {
title: '制定计划', title: '搜索',
description: '分解任务,制定执行步骤', desc: '先用搜索工具拿到候选链接。',
code: `plan = [ tool: 'tool:web_search({ query: \"agent introduction\" })',
"搜索关键词:'AI 技术 2024'", result: '- link1\n- link2\n- link3\n- link4 ...'
"筛选前 5 篇文章",
"阅读每篇文章摘要",
"生成综合总结"
]`,
result: '✅ 计划已制定:4 个步骤',
explanation: 'Agent 将复杂任务分解为可执行的小步骤。这个计划会动态调整,比如如果搜索结果质量不高,可能会重新搜索。',
tip: '任务分解是 Agent 的核心能力。复杂任务必须拆解为可管理的步骤。'
}, },
{ {
title: '执行搜索', title: '读取页面',
description: '调用搜索工具,获取文章列表', desc: '打开前三个链接,取出核心段落。',
code: `results = web_search( tool: 'tool:read_page({ url: link1/link2/link3 })',
query="AI 技术 2024", result: '每篇文章的核心段落(已截取)'
max_results=10,
time_range="last_month"
)`,
result: '✅ 找到 15 篇相关文章',
explanation: 'Agent 调用 web_search 工具,使用合适的搜索关键词和参数。搜索结果会被保存到短期记忆中,供后续步骤使用。',
tip: '工具调用需要选择合适的参数。Agent 会根据任务需求动态调整。'
}, },
{ {
title: '筛选结果', title: '压缩与整理',
description: '根据相关性筛选最佳文章', desc: '把每篇文章压缩成“一句话总结”,统一格式。',
code: `top_articles = filter_by_relevance( tool: 'LLM: summarize_each({ paragraphs, max_len: 25 })',
results, result: '- 标题A:一句话…\n- 标题B:一句话…\n- 标题C:一句话…'
top_n=5,
criteria=["date", "citations", "source"]
)`,
result: '✅ 筛选出 5 篇高质量文章',
explanation: '不是所有搜索结果都有用。Agent 会根据日期、引用数、来源权威性等指标筛选出最有价值的文章。',
tip: '信息筛选能力决定了 Agent 的输出质量。需要多维度的评估标准。'
}, },
{ {
title: '阅读内容', title: '自检与交付',
description: '读取并理解每篇文章的内容', desc: '检查是否满足“3 条 + 一句话 + 格式正确”,再输出。',
code: `for article in top_articles: tool: 'LLM: self_check({ checklist })',
content = read_page(article.url) result: '✅ 满足要求;输出已就绪'
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: '输出质量取决于信息的整合能力。结构化输出更易读、更专业。'
} }
] ]
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> </script>
<style scoped> <style scoped>
.agent-task-flow-demo { .flow {
border: 1px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
border-radius: 12px; border-radius: 12px;
padding: 24px;
background: var(--vp-c-bg-soft); 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; padding: 16px;
background: var(--vp-c-bg-soft); margin: 20px 0;
border-radius: 8px; display: flex;
border-left: 4px solid var(--vp-c-brand); 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 { .timeline { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; }
font-size: 1.5rem; .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; }
flex-shrink: 0; .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 { .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
color: var(--vp-c-text-2); .panel { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; }
line-height: 1.6; .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> </style>
@@ -1,372 +1,163 @@
<!--
AgentWorkflowDemo.vue
Agent 核心循环更像先玩后讲的演示
- 点步骤看这一轮 Agent 在干什么
- 下一轮看它如何反复迭代直到完成
-->
<template> <template>
<div class="agent-workflow-demo"> <div class="workflow">
<div class="workflow-container"> <div class="header">
<div class="cycle-diagram"> <div>
<div class="center-label">Agent 核心循环</div> <div class="title">先玩一下Agent 不是聊天循环行动</div>
<div class="subtitle">它会反复观察 计划 用工具 检查结果</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> </div>
<div class="actions">
<div class="step-details"> <button class="btn" @click="reset">重置</button>
<div class="current-action"> <button class="btn primary" @click="nextRound">下一轮 ({{ round }}/3)</button>
<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> </div>
</div> </div>
<div class="controls"> <div class="cycle">
<button @click="prevStep" :disabled="currentStep === 0" class="control-btn"> <button
上一步 v-for="s in steps"
</button> :key="s.id"
<button @click="togglePlay" class="control-btn primary"> :class="['step', { active: currentStep === s.id }]"
{{ isPlaying ? '⏸ 暂停' : '▶ 自动播放' }} @click="currentStep = s.id"
</button> >
<button @click="nextStep" :disabled="currentStep === steps.length" class="control-btn"> <span class="icon">{{ s.icon }}</span>
下一步 <span class="name">{{ s.name }}</span>
</button>
<button @click="reset" class="control-btn">
重置
</button> </button>
</div> </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> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { computed, ref } from 'vue'
const currentStep = ref(0)
const isPlaying = ref(false)
let playInterval = null
const steps = [ 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: '感知', observe: '看到用户目标:要 3 篇入门文章 + 简短总结。',
icon: '👁️', plan: '计划:1) 搜索关键词 2) 打开前几条 3) 抽取标题与要点。',
desc: 'Perceive', act: '调用工具:web_search(query="agent introduction")。',
detail: 'Agent 从环境中接收信息,包括用户输入、文件内容、网页数据等。', check: '检查:结果里有 3 条可用链接,还缺“每条一句话总结”。'
example: '用户说:帮我搜索最新的 AI 文章'
}, },
{ {
name: '决策', observe: '拿到链接列表,准备逐条打开并提取要点。',
icon: '🤔', plan: '计划:依次 read_page 3 次,把内容压缩成一句话。',
desc: 'Reason', act: '调用工具:read_page(url=...) × 3。',
detail: '分析当前状态,制定行动计划,选择合适的工具来完成任务。', check: '检查:信息够了,但标题格式不统一,需要整理输出。'
example: '分析:需要搜索 → 应该使用 web_search 工具'
}, },
{ {
name: '行动', observe: '材料齐全:标题 + 文章要点都已提取。',
icon: '🔧', plan: '计划:统一格式,输出 Markdown 列表。',
desc: 'Act', act: '组织输出:每条“标题 - 一句话总结”。',
detail: '执行决策,调用工具,修改文件,发送请求等具体操作。', check: '完成:满足“3 条 + 一句话总结 + 可直接复制”。'
example: '执行:web_search("AI 文章 2024")'
},
{
name: '观察',
icon: '👀',
desc: 'Observe',
detail: '查看行动结果,评估是否达成目标,决定是继续还是结束。',
example: '观察:找到 10 篇相关文章 → 继续阅读'
} }
] ]
const arrowColor = computed(() => { const current = computed(() => scenarios[round.value - 1])
if (currentStep.value === 0) return 'var(--vp-c-divider)'
return 'var(--vp-c-brand)' 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 nextRound = () => {
const positions = [ if (round.value >= 3) return
{ top: '10%', left: '50%', transform: 'translateX(-50%)' }, // Top round.value++
{ right: '10%', top: '50%', transform: 'translateY(-50%)' }, // Right currentStep.value = 'observe'
{ 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 reset = () => { const reset = () => {
currentStep.value = 0 round.value = 1
isPlaying.value = false currentStep.value = 'observe'
clearInterval(playInterval)
} }
onUnmounted(() => {
clearInterval(playInterval)
})
</script> </script>
<style scoped> <style scoped>
.agent-workflow-demo { .workflow {
border: 1px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
border-radius: 12px; border-radius: 12px;
padding: 24px;
background: var(--vp-c-bg-soft); 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; padding: 16px;
background: var(--vp-c-bg-soft); margin: 20px 0;
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 {
display: flex; display: flex;
flex-direction: column;
gap: 12px; gap: 12px;
justify-content: center;
flex-wrap: wrap;
} }
.control-btn { .header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
padding: 10px 20px; .title { font-weight: 800; }
border: 2px solid var(--vp-c-divider); .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); background: var(--vp-c-bg);
color: var(--vp-c-text-1); border: 1px solid var(--vp-c-divider);
border-radius: 8px; border-radius: 12px;
padding: 10px 12px;
display: flex;
gap: 10px;
align-items: center;
cursor: pointer; cursor: pointer;
font-size: 0.9rem; text-align: left;
font-weight: 600;
transition: all 0.2s;
} }
.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) { .panels { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
border-color: var(--vp-c-brand); .panel { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; }
background: var(--vp-c-bg-soft); .panel-title { font-weight: 700; margin-bottom: 6px; }
} .panel-body { color: var(--vp-c-text-2); line-height: 1.6; }
.control-btn:disabled { .log { background: var(--vp-c-bg); border: 1px dashed var(--vp-c-divider); border-radius: 12px; padding: 12px; }
opacity: 0.5; .log-title { font-weight: 700; margin-bottom: 8px; }
cursor: not-allowed; 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; }
}
.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);
}
</style> </style>
@@ -1,529 +1,98 @@
<!--
FrameworkComparisonDemo.vue
框架对比更直观选择关注点表格高亮适配度
-->
<template> <template>
<div class="framework-comparison-demo"> <div class="cmp">
<div class="framework-tabs"> <div class="header">
<button <div>
v-for="(framework, index) in frameworks" <div class="title">主流框架对比先看适配度</div>
:key="framework.name" <div class="subtitle">先选你的关注点再看推荐</div>
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> </div>
<div class="focus">
<div class="framework-details"> <button
<div class="detail-section"> v-for="f in focuses"
<h4> 特点</h4> :key="f.id"
<ul> :class="['chip', { active: focus === f.id }]"
<li v-for="feature in frameworks[selectedFramework].features" :key="feature"> @click="focus = f.id"
{{ feature }} >
</li> {{ f.label }}
</ul> </button>
</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> </div>
</div> </div>
<div class="comparison-table"> <div class="table">
<h3>📋 快速对比</h3> <div class="row head">
<table> <div>框架</div>
<thead> <div>上手</div>
<tr> <div>可控</div>
<th>特性</th> <div> Agent</div>
<th v-for="fw in frameworks" :key="fw.name">{{ fw.name }}</th> <div>适合做什么</div>
</tr> </div>
</thead> <div v-for="fw in frameworks" :key="fw.name" :class="['row', { best: fw.name === best }]">
<tbody> <div class="name">{{ fw.name }}</div>
<tr> <div>{{ fw.learn }}</div>
<td>学习曲线</td> <div>{{ fw.control }}</div>
<td v-for="fw in frameworks" :key="fw.name">{{ fw.learningCurve }}</td> <div>{{ fw.multi }}</div>
</tr> <div class="use">{{ fw.use }}</div>
<tr> </div>
<td>社区规模</td> </div>
<td v-for="fw in frameworks" :key="fw.name">{{ fw.community }}</td>
</tr> <div class="rec">
<tr> <div class="rec-title">此刻更推荐{{ best }}</div>
<td>最佳用途</td> <div class="rec-body">{{ reason }}</div>
<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> </div>
</div> </div>
</template> </template>
<script setup> <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 = [ const frameworks = [
{ { name: 'LangChain / LangGraph', learn: '中', control: '高', multi: '中', use: '可控的工具调用、工作流、企业集成' },
name: 'LangChain', { name: 'AutoGen', learn: '中', control: '中', multi: '高', use: '多 Agent 对话协作、编程/分析助手' },
icon: '🦜', { name: 'CrewAI', learn: '', control: '中', multi: '高', use: '角色分工清晰的团队协作任务' }
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+'
}
] ]
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> </script>
<style scoped> <style scoped>
.framework-comparison-demo { .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; }
border: 1px solid var(--vp-c-divider); .header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
border-radius: 12px; .title { font-weight: 800; }
padding: 24px; .subtitle { color: var(--vp-c-text-2); font-size: 13px; }
background: var(--vp-c-bg-soft); .focus { display: flex; gap: 8px; flex-wrap: wrap; }
margin: 24px 0; .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 { .table { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; overflow: hidden; }
display: flex; .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; }
gap: 12px; .row.head { border-top: none; font-weight: 800; color: var(--vp-c-text-2); background: var(--vp-c-bg-soft); }
margin-bottom: 24px; .name { font-weight: 800; }
flex-wrap: wrap; .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 { .rec { background: var(--vp-c-bg); border: 1px dashed var(--vp-c-divider); border-radius: 12px; padding: 12px; }
padding: 12px 24px; .rec-title { font-weight: 800; margin-bottom: 6px; }
border: 2px solid var(--vp-c-divider); .rec-body { color: var(--vp-c-text-2); line-height: 1.6; }
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;
}
</style> </style>
@@ -1,407 +1,123 @@
<!--
FrameworkSelectionDemo.vue
框架选择小向导回答 3 个问题给出推荐 + 适配理由 + 你需要注意什么
-->
<template> <template>
<div class="framework-selection-demo"> <div class="sel">
<div class="selection-quiz"> <div class="header">
<h3>🤔 选择合适的 Agent 框架</h3> <div>
<p class="quiz-intro">回答几个问题帮你找到最适合的框架</p> <div class="title">三问选框架</div>
<div class="subtitle">目标先跑通一个最小 Agent再逐步增强</div>
<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> </div>
</div>
<div v-else class="recommendation"> <div class="q">
<div class="result-header"> <div class="q-title">1) 你更在乎什么</div>
<div class="result-icon">🎯</div> <div class="opts">
<h4>推荐框架{{ recommendedFramework }}</h4> <button v-for="o in q1" :key="o.id" :class="['opt', { active: a1 === o.id }]" @click="a1 = o.id">{{ o.label }}</button>
</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> </div>
</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> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue' import { computed, ref } from 'vue'
const currentQuestion = ref(0) const q1 = [
const answers = ref([]) { id: 'easy', label: '快速上手' },
{ id: 'stable', label: '可控可上线' },
const questions = [ { id: 'team', label: '团队协作' }
{ ]
question: '你的主要使用场景是什么?', const q2 = [
options: [ { id: 'workflow', label: '有明确流程(步骤/图)' },
{ text: '🤖 编程和代码开发', scores: { LangChain: 2, AutoGen: 5, CrewAI: 2, AgentScope: 2 } }, { id: 'chat', label: '偏对话与协商' },
{ text: '📝 内容创作和文案', scores: { LangChain: 3, AutoGen: 1, CrewAI: 5, AgentScope: 3 } }, { id: 'explore', label: '探索式试错' }
{ text: '🔍 数据分析和研究', scores: { LangChain: 4, AutoGen: 4, CrewAI: 3, AgentScope: 3 } }, ]
{ text: '🌐 通用应用开发', scores: { LangChain: 5, AutoGen: 2, CrewAI: 3, AgentScope: 4 } } const q3 = [
] { id: 'no', label: '不需要' },
}, { id: 'maybe', label: '可能需要' },
{ { id: 'yes', label: '必须需要' }
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 frameworkInfo = { const a1 = ref('stable')
LangChain: { const a2 = ref('workflow')
website: 'https://langchain.com', const a3 = ref('maybe')
docs: 'https://python.langchain.com',
github: 'https://github.com/langchain-ai/langchain', const rec = computed(() => {
description: 'LangChain 是最流行的 LLM 应用开发框架,拥有最完善的生态系统和社区支持。适合需要高度定制和集成的场景。', // Multi-agent first
reasons: [ if (a3.value === 'yes' || a1.value === 'team') {
'最强大的生态系统', if (a2.value === 'chat') {
'高度可定制', return {
'丰富的集成选项', name: 'AutoGen',
'活跃的社区支持' reason: '多 Agent 对话协作是强项,适合“互相讨论、分工协作”。',
] note: '先把角色边界写清楚,否则容易重复劳动或互怼。',
}, next: '从 2 个 Agent 开始:研究员 + 执行者。'
AutoGen: { }
website: 'https://microsoft.github.io/autogen', }
docs: 'https://microsoft.github.io/autogen/docs', return {
github: 'https://github.com/microsoft/autogen', name: 'CrewAI',
description: 'AutoGen 是微软开发的多 Agent 协作框架,特别擅长编程和代码相关任务。如果你需要多个 Agent 协作完成编程任务,这是最佳选择。', reason: '角色+任务模型很直观,适合“分工明确”的团队工作流。',
reasons: [ note: '先把输入/输出格式定死,避免多人输出难合并。',
'独特的协作模式', next: '先搭 2-3 个角色:Researcher/Writer/Reviewer。'
'强大的代码执行能力', }
'微软官方支持',
'适合编程辅助场景'
]
},
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 recommendedFramework = computed(() => { // Single-agent / controllable workflow
const scores = { LangChain: 0, AutoGen: 0, CrewAI: 0, AgentScope: 0 } if (a1.value === 'stable' || a2.value === 'workflow') {
return {
name: 'LangChain / LangGraph',
reason: '更适合把 Agent 写成“可控流程”,便于调试、上线、加护栏。',
note: '别一上来做大系统,先把 1 个工具调用跑通。',
next: '用 LangGraph 画一个 3-5 节点的小图。'
}
}
answers.value.forEach((answerIndex, questionIndex) => { // Easy start
const optionScores = questions[questionIndex].options[answerIndex].scores return {
Object.keys(optionScores).forEach(framework => { name: 'CrewAI',
scores[framework] += optionScores[framework] reason: '上手快、概念直观,适合先做出一个“能跑”的 demo。',
}) note: 'demo 能跑不代表可上线,后续要补安全与可观测。',
}) next: '先做一个“研究+写作”的最小团队。'
}
return Object.keys(scores).reduce((a, b) => scores[a] > scores[b] ? a : b)
}) })
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> </script>
<style scoped> <style scoped>
.framework-selection-demo { .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; }
border: 1px solid var(--vp-c-divider); .header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
border-radius: 12px; .title { font-weight: 800; }
padding: 24px; .subtitle { color: var(--vp-c-text-2); font-size: 13px; }
background: var(--vp-c-bg-soft);
margin: 24px 0;
}
.selection-quiz h3 { .q { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 12px; padding: 12px; }
margin: 0 0 8px 0; .q-title { font-weight: 800; margin-bottom: 8px; }
color: var(--vp-c-brand); .opts { display: flex; gap: 8px; flex-wrap: wrap; }
font-size: 1.5rem; .opt { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); padding: 8px 12px; border-radius: 999px; cursor: pointer; }
text-align: center; .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 { .result { background: var(--vp-c-bg); border: 1px dashed var(--vp-c-divider); border-radius: 12px; padding: 12px; }
text-align: center; .r-title { font-weight: 900; margin-bottom: 6px; }
color: var(--vp-c-text-2); .r-body { color: var(--vp-c-text-2); line-height: 1.6; margin-bottom: 6px; }
margin-bottom: 32px; .r-note, .r-next { color: var(--vp-c-text-2); line-height: 1.6; }
}
.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);
}
</style> </style>
@@ -7,7 +7,12 @@
</el-button> </el-button>
</div> </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="音频信号" description="连续波形" />
<el-step title="切片 (Chunking)" description="20ms/帧" /> <el-step title="切片 (Chunking)" description="20ms/帧" />
<el-step title="量化 (Quantization)" description="查字典" /> <el-step title="量化 (Quantization)" description="查字典" />
@@ -18,8 +23,15 @@
<!-- Stage 0: Audio --> <!-- Stage 0: Audio -->
<div v-if="activeStep === 0" class="stage-content audio-stage"> <div v-if="activeStep === 0" class="stage-content audio-stage">
<div class="waveform-viz"> <div class="waveform-viz">
<div class="wave-bar" v-for="n in 20" :key="n" <div
:style="{ height: (30 + Math.random() * 50) + '%', animationDelay: n * 0.1 + 's' }"></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>
<div class="stage-desc">原始的连续模拟信号或高采样率数字信号</div> <div class="stage-desc">原始的连续模拟信号或高采样率数字信号</div>
</div> </div>
@@ -28,26 +40,41 @@
<div v-if="activeStep === 1" class="stage-content chunks-stage"> <div v-if="activeStep === 1" class="stage-content chunks-stage">
<div class="chunks-container"> <div class="chunks-container">
<div class="chunk-item" v-for="n in 5" :key="n"> <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> </div>
<div class="stage-desc">将音频切分为固定长度的小片段例如 20ms</div> <div class="stage-desc">
将音频切分为固定长度的小片段例如 20ms
</div>
</div> </div>
<!-- Stage 2: Codebook --> <!-- Stage 2: Codebook -->
<div v-if="activeStep === 2" class="stage-content codebook-stage"> <div v-if="activeStep === 2" class="stage-content codebook-stage">
<div class="codebook-grid"> <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 }} {{ 1024 + n * 50 }}
</div> </div>
</div> </div>
<div class="stage-desc">在预训练的"声音字典"中寻找最接近的特征向量</div> <div class="stage-desc">
在预训练的"声音字典"中寻找最接近的特征向量
</div>
</div> </div>
<!-- Stage 3: Tokens --> <!-- Stage 3: Tokens -->
<div v-if="activeStep === 3" class="stage-content token-stage"> <div v-if="activeStep === 3" class="stage-content token-stage">
<div class="token-list"> <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 }} {{ token }}
</el-tag> </el-tag>
</div> </div>
@@ -83,7 +110,7 @@
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<el-alert <el-alert
title="为什么要做 Tokenization?" title="为什么要做 Tokenization?"
type="warning" type="warning"
@@ -108,29 +135,29 @@ const playDemo = async () => {
if (isPlaying.value) return if (isPlaying.value) return
isPlaying.value = true isPlaying.value = true
activeStep.value = 0 activeStep.value = 0
// Step 0 -> 1 // Step 0 -> 1
await wait(1000) await wait(1000)
activeStep.value = 1 activeStep.value = 1
// Step 1 -> 2 // Step 1 -> 2
await wait(1500) await wait(1500)
activeStep.value = 2 activeStep.value = 2
// Simulate codebook matching // Simulate codebook matching
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
currentMatch.value = Math.floor(Math.random() * 9) + 1 currentMatch.value = Math.floor(Math.random() * 9) + 1
await wait(200) await wait(200)
} }
currentMatch.value = 0 currentMatch.value = 0
// Step 2 -> 3 // Step 2 -> 3
activeStep.value = 3 activeStep.value = 3
isPlaying.value = false isPlaying.value = false
} }
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
</script> </script>
<style scoped> <style scoped>
@@ -186,8 +213,15 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
} }
@keyframes wave { @keyframes wave {
0%, 100% { height: 30%; opacity: 0.5; } 0%,
50% { height: 100%; opacity: 1; } 100% {
height: 30%;
opacity: 0.5;
}
50% {
height: 100%;
opacity: 1;
}
} }
/* Chunks Stage */ /* Chunks Stage */
@@ -34,7 +34,9 @@
<div class="step-box"> <div class="step-box">
<div class="label">🔢 数字化</div> <div class="label">🔢 数字化</div>
<div class="digital-visual"> <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>
<div class="desc">PCM 数据</div> <div class="desc">PCM 数据</div>
</div> </div>
@@ -43,8 +45,9 @@
<div class="explanation"> <div class="explanation">
<p> <p>
<span class="icon">💡</span> <span class="icon">💡</span>
计算机无法直接处理连续的声波需要把它转换成数字 计算机无法直接处理连续的声波需要把它转换成数字 这个过程叫<strong
这个过程叫<strong>模数转换 (ADC)</strong>每隔一小段时间测量一次声音的强度记录成数字 >模数转换 (ADC)</strong
>每隔一小段时间测量一次声音的强度记录成数字
</p> </p>
</div> </div>
</div> </div>
@@ -2,7 +2,12 @@
<div class="ar-comparison"> <div class="ar-comparison">
<el-card shadow="never"> <el-card shadow="never">
<div class="controls"> <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> </el-button>
</div> </div>
@@ -20,10 +25,10 @@
<div class="visual-area"> <div class="visual-area">
<div class="token-stream"> <div class="token-stream">
<transition-group name="list"> <transition-group name="list">
<el-tag <el-tag
v-for="(token, i) in displayedArTokens" v-for="(token, i) in displayedArTokens"
:key="i" :key="i"
type="danger" type="danger"
class="token-item" class="token-item"
effect="plain" effect="plain"
> >
@@ -34,7 +39,9 @@
</div> </div>
<div class="stats"> <div class="stats">
<el-descriptions :column="1" size="small" border> <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-descriptions-item label="速度">
<el-tag type="danger" size="small"> (Slow)</el-tag> <el-tag type="danger" size="small"> (Slow)</el-tag>
</el-descriptions-item> </el-descriptions-item>
@@ -54,17 +61,29 @@
<div class="method-body"> <div class="method-body">
<div class="visual-area"> <div class="visual-area">
<div class="flow-field" :style="{ opacity: flowProgress }"> <div class="flow-field" :style="{ opacity: flowProgress }">
<div v-for="n in 20" :key="n" class="flow-bar" <div
:style="{ height: flowProgress * (30 + Math.random() * 70) + '%', transitionDelay: n * 0.02 + 's' }"></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>
<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> <el-icon class="is-loading"><Loading /></el-icon>
<span>Denoising...</span> <span>Denoising...</span>
</div> </div>
</div> </div>
<div class="stats"> <div class="stats">
<el-descriptions :column="1" size="small" border> <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-descriptions-item label="速度">
<el-tag type="success" size="small">极快 (Fast)</el-tag> <el-tag type="success" size="small">极快 (Fast)</el-tag>
</el-descriptions-item> </el-descriptions-item>
@@ -76,17 +95,14 @@
<el-divider /> <el-divider />
<el-alert <el-alert title="技术演进" type="success" :closable="false" show-icon>
title="技术演进"
type="success"
:closable="false"
show-icon
>
<template #default> <template #default>
<p> <p>
<strong>自回归</strong> ( VALL-E) 像人说话一样必须说完上一个字才能说下一个字所以很慢 <strong>自回归</strong> ( VALL-E)
<br> 像人说话一样必须说完上一个字才能说下一个字所以很慢
<strong>流匹配</strong> ( F5-TTS) 像画画一样可以同时在画布的所有角落开始上色效率提升了 10-20 <br />
<strong>流匹配</strong> ( F5-TTS)
像画画一样可以同时在画布的所有角落开始上色效率提升了 10-20
</p> </p>
</template> </template>
</el-alert> </el-alert>
@@ -108,9 +124,9 @@ const playDemo = async () => {
isPlaying.value = true isPlaying.value = true
displayedArTokens.value = [] displayedArTokens.value = []
flowProgress.value = 0 flowProgress.value = 0
// Start Flow Matching (Fast) // Start Flow Matching (Fast)
const flowPromise = new Promise(resolve => { const flowPromise = new Promise((resolve) => {
let p = 0 let p = 0
const interval = setInterval(() => { const interval = setInterval(() => {
p += 0.05 p += 0.05
@@ -121,16 +137,16 @@ const playDemo = async () => {
} }
}, 50) // Total ~1s }, 50) // Total ~1s
}) })
// Start AR (Slow) // Start AR (Slow)
const arPromise = new Promise(async resolve => { const arPromise = new Promise(async (resolve) => {
for (const token of arTokensSource) { 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) displayedArTokens.value.push(token)
} }
resolve() resolve()
}) })
await Promise.all([flowPromise, arPromise]) await Promise.all([flowPromise, arPromise])
isPlaying.value = false isPlaying.value = false
} }
@@ -220,7 +236,7 @@ const playDemo = async () => {
.flow-bar { .flow-bar {
flex: 1; flex: 1;
background: linear-gradient(to top, #67C23A, #95d475); background: linear-gradient(to top, #67c23a, #95d475);
border-radius: 2px 2px 0 0; border-radius: 2px 2px 0 0;
transition: height 0.5s ease; transition: height 0.5s ease;
} }
@@ -231,7 +247,7 @@ const playDemo = async () => {
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(255,255,255,0.5); background: rgba(255, 255, 255, 0.5);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -10,8 +10,15 @@
</div> </div>
<div class="viz-content waveform-container"> <div class="viz-content waveform-container">
<div class="wave-bars"> <div class="wave-bars">
<div v-for="n in 30" :key="n" class="wave-bar" <div
:style="{ height: 20 + Math.random() * 60 + '%', animationDelay: n * 0.05 + 's' }"></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>
<div class="axis-label x-axis">时间 (Time) </div> <div class="axis-label x-axis">时间 (Time) </div>
<div class="axis-label y-axis">振幅 (Amplitude) </div> <div class="axis-label y-axis">振幅 (Amplitude) </div>
@@ -50,13 +57,18 @@
<template #default> <template #default>
<div class="legend"> <div class="legend">
<div class="legend-item"> <div class="legend-item">
<div class="color-box low"></div> 低能量 (安静) <div class="color-box low"></div>
低能量 (安静)
</div> </div>
<div class="legend-item"> <div class="legend-item">
<div class="color-box high"></div> 高能量 (响亮) <div class="color-box high"></div>
高能量 (响亮)
</div> </div>
</div> </div>
<p>频谱图将一维的声音信号变成了二维图像这样我们就可以用 <strong>CNN (卷积神经网络)</strong> 等图像模型来处理声音了</p> <p>
频谱图将一维的声音信号变成了二维图像这样我们就可以用
<strong>CNN (卷积神经网络)</strong> 等图像模型来处理声音了
</p>
</template> </template>
</el-alert> </el-alert>
</el-card> </el-card>
@@ -90,10 +102,10 @@ const drawSpectrogram = () => {
const baseEnergy = normalizedY * 0.8 const baseEnergy = normalizedY * 0.8
const noise = Math.random() * 0.2 const noise = Math.random() * 0.2
const timeVar = Math.sin(x * 0.1) * 0.2 // Time variation const timeVar = Math.sin(x * 0.1) * 0.2 // Time variation
let intensity = baseEnergy + noise + timeVar let intensity = baseEnergy + noise + timeVar
intensity = Math.max(0, Math.min(1, intensity)) intensity = Math.max(0, Math.min(1, intensity))
const hue = 240 - intensity * 240 // Blue (low) to Red (high) const hue = 240 - intensity * 240 // Blue (low) to Red (high)
ctx.fillStyle = `hsl(${hue}, 80%, 50%)` ctx.fillStyle = `hsl(${hue}, 80%, 50%)`
ctx.fillRect(x, height - y - 4, 4, 4) ctx.fillRect(x, height - y - 4, 4, 4)
@@ -165,8 +177,15 @@ const drawSpectrogram = () => {
} }
@keyframes wave { @keyframes wave {
0%, 100% { height: 20%; opacity: 0.6; } 0%,
50% { height: 90%; opacity: 1; } 100% {
height: 20%;
opacity: 0.6;
}
50% {
height: 90%;
opacity: 1;
}
} }
.transform-arrow { .transform-arrow {
@@ -92,9 +92,7 @@
<div class="metric-card"> <div class="metric-card">
<div class="metric-title">上下文长度</div> <div class="metric-title">上下文长度</div>
<div class="metric-value">{{ contextTokens }}</div> <div class="metric-value">{{ contextTokens }}</div>
<div class="metric-desc"> <div class="metric-desc">迭代: {{ iteration }} </div>
迭代: {{ iteration }}
</div>
</div> </div>
<div class="metric-card"> <div class="metric-card">
@@ -118,7 +116,7 @@
<div class="principle-icon">1</div> <div class="principle-icon">1</div>
<div class="principle-content"> <div class="principle-content">
<strong>保持前缀稳定</strong> <strong>保持前缀稳定</strong>
<br> <br />
系统提示和工具定义不要频繁变化提高 KV 缓存命中率 系统提示和工具定义不要频繁变化提高 KV 缓存命中率
</div> </div>
</div> </div>
@@ -126,7 +124,7 @@
<div class="principle-icon">2</div> <div class="principle-icon">2</div>
<div class="principle-content"> <div class="principle-content">
<strong>只追加不修改</strong> <strong>只追加不修改</strong>
<br> <br />
上下文应该只追加新的动作和观察不修改历史内容 上下文应该只追加新的动作和观察不修改历史内容
</div> </div>
</div> </div>
@@ -134,7 +132,7 @@
<div class="principle-icon">3</div> <div class="principle-icon">3</div>
<div class="principle-content"> <div class="principle-content">
<strong>遮蔽而非移除</strong> <strong>遮蔽而非移除</strong>
<br> <br />
不动态添加/删除工具而是通过 logits 掩码控制可用工具 不动态添加/删除工具而是通过 logits 掩码控制可用工具
</div> </div>
</div> </div>
@@ -142,7 +140,7 @@
<div class="principle-icon">4</div> <div class="principle-icon">4</div>
<div class="principle-content"> <div class="principle-content">
<strong>文件系统作为外部记忆</strong> <strong>文件系统作为外部记忆</strong>
<br> <br />
大型内容网页PDF写入文件上下文只保留路径 大型内容网页PDF写入文件上下文只保留路径
</div> </div>
</div> </div>
@@ -166,9 +164,27 @@ const observation = ref('')
const toolsUsed = ref([]) const toolsUsed = ref([])
const contextItems = ref([ const contextItems = ref([
{ type: '系统提示', content: '你是一个 AI 助手,可以使用搜索和文件工具', tokens: 150, cached: true, active: false }, {
{ type: '工具定义', content: 'search: 搜索网络信息', tokens: 80, cached: true, active: false }, type: '系统提示',
{ type: '工具定义', content: 'write_file: 写入文件', tokens: 75, cached: true, active: false } 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 = [ const steps = [
@@ -177,7 +193,8 @@ const steps = [
action: '分析用户需求', action: '分析用户需求',
tool: '', tool: '',
obs: '', obs: '',
explanation: 'Agent 首先解析用户的请求,决定需要采取什么行动。系统提示和工具定义从缓存读取(绿色),节省成本!', explanation:
'Agent 首先解析用户的请求,决定需要采取什么行动。系统提示和工具定义从缓存读取(绿色),节省成本!',
addTokens: 50 addTokens: 50
}, },
{ {
@@ -185,7 +202,8 @@ const steps = [
action: '选择工具: search', action: '选择工具: search',
tool: 'search', tool: 'search',
obs: '', obs: '',
explanation: 'Agent 根据用户需求选择合适的工具。注意:工具定义在缓存中,不需要重新计算!', explanation:
'Agent 根据用户需求选择合适的工具。注意:工具定义在缓存中,不需要重新计算!',
addTokens: 30 addTokens: 30
}, },
{ {
@@ -201,7 +219,8 @@ const steps = [
action: '决定保存摘要', action: '决定保存摘要',
tool: 'write_file', tool: 'write_file',
obs: '文件已保存', obs: '文件已保存',
explanation: 'Agent 将搜索结果写入文件,而不是在上下文中保留所有内容。这样上下文保持精简!', explanation:
'Agent 将搜索结果写入文件,而不是在上下文中保留所有内容。这样上下文保持精简!',
addTokens: 60 addTokens: 60
}, },
{ {
@@ -209,13 +228,16 @@ const steps = [
action: '完成任务', action: '完成任务',
tool: '', tool: '',
obs: '已保存到 summary.md', obs: '已保存到 summary.md',
explanation: '任务完成!整个过程中,系统提示和工具定义只缓存一次,每次迭代只追加新的动作和观察结果。', explanation:
'任务完成!整个过程中,系统提示和工具定义只缓存一次,每次迭代只追加新的动作和观察结果。',
addTokens: 40 addTokens: 40
} }
] ]
const cacheHitRate = computed(() => { 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 const totalTokens = contextTokens.value
return totalTokens > 0 ? ((cachedTokens / totalTokens) * 100).toFixed(1) : 0 return totalTokens > 0 ? ((cachedTokens / totalTokens) * 100).toFixed(1) : 0
}) })
@@ -292,9 +314,27 @@ const reset = () => {
observation.value = '' observation.value = ''
toolsUsed.value = [] toolsUsed.value = []
contextItems.value = [ contextItems.value = [
{ type: '系统提示', content: '你是一个 AI 助手,可以使用搜索和文件工具', tokens: 150, cached: true, active: false }, {
{ type: '工具定义', content: 'search: 搜索网络信息', tokens: 80, cached: true, active: false }, type: '系统提示',
{ type: '工具定义', content: 'write_file: 写入文件', tokens: 75, cached: true, active: false } 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> </script>
@@ -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="tips">
<div class="tip"> <div class="tip">
<span class="tip-icon">💡</span> <span class="tip-icon">💡</span>
<span><strong>选择合适的模型</strong>短对话用短上下文模型更快更便宜 <span
长文档分析用长上下文模型避免信息丢失</span> ><strong>选择合适的模型</strong>短对话用短上下文模型更快更便宜
长文档分析用长上下文模型避免信息丢失</span
>
</div> </div>
<div class="tip"> <div class="tip">
<span class="tip-icon">📏</span> <span class="tip-icon">📏</span>
<span><strong>注意 Token 计算</strong>1 Token 0.75 个英文单词 0.5-1 个汉字 <span
100K tokens 大约等于一本 300 页的书</span> ><strong>注意 Token 计算</strong>1 Token 0.75 个英文单词 0.5-1
个汉字 100K tokens 大约等于一本 300 页的书</span
>
</div> </div>
</div> </div>
</div> </div>
@@ -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"> <div class="example-section">
<span class="section-label">检索到的文档</span> <span class="section-label">检索到的文档</span>
<div class="retrieved-docs"> <div class="retrieved-docs">
<div class="doc-item">📄 Doc 1: "RAG 是一种结合检索和生成的技术..."</div> <div class="doc-item">
<div class="doc-item">📄 Doc 2: "RAG 的优势是减少幻觉、提高准确性..."</div> 📄 Doc 1: "RAG 是一种结合检索和生成的技术..."
<div class="doc-item">📄 Doc 3: "常见的 RAG 框架包括 LangChain..."</div> </div>
<div class="doc-item">
📄 Doc 2: "RAG 的优势是减少幻觉、提高准确性..."
</div>
<div class="doc-item">
📄 Doc 3: "常见的 RAG 框架包括 LangChain..."
</div>
</div> </div>
</div> </div>
<div class="example-section"> <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>
@@ -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>&lt;&lt;&lt;&lt;&lt;&lt;&lt; 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>&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; 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
@@ -21,11 +21,22 @@
</template> </template>
<div class="canvas-container"> <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"> <div class="step-indicator">
<span class="step-text">Step: {{ currentStep }} / {{ totalSteps }}</span> <span class="step-text"
<el-progress >Step: {{ currentStep }} / {{ totalSteps }}</span
:percentage="mode === 'forward' ? (currentStep / totalSteps * 100) : ((totalSteps - currentStep) / totalSteps * 100)" >
<el-progress
:percentage="
mode === 'forward'
? (currentStep / totalSteps) * 100
: ((totalSteps - currentStep) / totalSteps) * 100
"
:status="mode === 'forward' ? 'exception' : 'success'" :status="mode === 'forward' ? 'exception' : 'success'"
:show-text="false" :show-text="false"
:stroke-width="4" :stroke-width="4"
@@ -34,23 +45,33 @@
</div> </div>
<div class="slider-control"> <div class="slider-control">
<el-slider <el-slider
v-model="currentStep" v-model="currentStep"
:min="0" :min="0"
:max="totalSteps" :max="totalSteps"
:format-tooltip="formatTooltip" :format-tooltip="formatTooltip"
@input="draw" @input="draw"
/> />
<div class="slider-labels"> <div class="slider-labels">
<span>{{ mode === 'forward' ? '原图 (Original)' : '纯噪声 (Noise)' }}</span> <span>{{
<span>{{ mode === 'forward' ? '纯噪声 (Noise)' : '原图 (Original)' }}</span> mode === 'forward' ? '原图 (Original)' : '纯噪声 (Noise)'
}}</span>
<span>{{
mode === 'forward' ? '纯噪声 (Noise)' : '原图 (Original)'
}}</span>
</div> </div>
</div> </div>
<el-alert <el-alert
:title="mode === 'forward' ? '训练阶段:破坏数据' : '生成阶段:创造数据'" :title="
mode === 'forward' ? '训练阶段:破坏数据' : '生成阶段:创造数据'
"
:type="mode === 'forward' ? 'warning' : 'success'" :type="mode === 'forward' ? 'warning' : 'success'"
:description="mode === 'forward' ? 'AI 通过学习如何「一点点加噪」,掌握了噪声的规律。这就像教它把积木推倒。' : 'AI 通过预测并减去噪声,从混沌中还原出图像。这就像它学会了把推倒的积木重新搭好。'" :description="
mode === 'forward'
? 'AI 通过学习如何「一点点加噪」,掌握了噪声的规律。这就像教它把积木推倒。'
: 'AI 通过预测并减去噪声,从混沌中还原出图像。这就像它学会了把推倒的积木重新搭好。'
"
show-icon show-icon
:closable="false" :closable="false"
class="explanation-alert" class="explanation-alert"
@@ -61,7 +82,12 @@
<script setup> <script setup>
import { ref, onMounted, watch, onUnmounted } from 'vue' 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 canvasRef = ref(null)
const mode = ref('reverse') const mode = ref('reverse')
@@ -77,7 +103,7 @@ const loadBaseImage = () => {
canvas.width = 300 canvas.width = 300
canvas.height = 300 canvas.height = 300
const ctx = canvas.getContext('2d') const ctx = canvas.getContext('2d')
// Draw a simple landscape // Draw a simple landscape
// Sky // Sky
const gradient = ctx.createLinearGradient(0, 0, 0, 300) const gradient = ctx.createLinearGradient(0, 0, 0, 300)
@@ -85,13 +111,13 @@ const loadBaseImage = () => {
gradient.addColorStop(1, '#E0F7FA') gradient.addColorStop(1, '#E0F7FA')
ctx.fillStyle = gradient ctx.fillStyle = gradient
ctx.fillRect(0, 0, 300, 300) ctx.fillRect(0, 0, 300, 300)
// Sun // Sun
ctx.beginPath() ctx.beginPath()
ctx.arc(240, 60, 30, 0, Math.PI * 2) ctx.arc(240, 60, 30, 0, Math.PI * 2)
ctx.fillStyle = '#FFD700' ctx.fillStyle = '#FFD700'
ctx.fill() ctx.fill()
// Mountains // Mountains
ctx.beginPath() ctx.beginPath()
ctx.moveTo(0, 300) ctx.moveTo(0, 300)
@@ -101,7 +127,7 @@ const loadBaseImage = () => {
ctx.lineTo(300, 300) ctx.lineTo(300, 300)
ctx.fillStyle = '#4CAF50' ctx.fillStyle = '#4CAF50'
ctx.fill() ctx.fill()
// House // House
ctx.fillStyle = '#795548' ctx.fillStyle = '#795548'
ctx.fillRect(50, 220, 60, 60) ctx.fillRect(50, 220, 60, 60)
@@ -120,7 +146,7 @@ const generateNoise = (width, height) => {
const data = new Uint8ClampedArray(size) const data = new Uint8ClampedArray(size)
for (let i = 0; i < size; i += 4) { for (let i = 0; i < size; i += 4) {
const val = Math.random() * 255 const val = Math.random() * 255
data[i] = val // R data[i] = val // R
data[i + 1] = val // G data[i + 1] = val // G
data[i + 2] = val // B data[i + 2] = val // B
data[i + 3] = 255 // A data[i + 3] = 255 // A
@@ -149,7 +175,7 @@ const draw = () => {
const canvas = canvasRef.value const canvas = canvasRef.value
if (!canvas || !originalImage) return if (!canvas || !originalImage) return
const ctx = canvas.getContext('2d') const ctx = canvas.getContext('2d')
// Calculate noise ratio based on mode and step // Calculate noise ratio based on mode and step
// Forward: 0 -> 100 (Clean -> Noisy) // Forward: 0 -> 100 (Clean -> Noisy)
// Reverse: 100 -> 0 (Noisy -> Clean) // Reverse: 100 -> 0 (Noisy -> Clean)
@@ -158,18 +184,18 @@ const draw = () => {
// but for UI, we want: // but for UI, we want:
// Forward Mode: Slider 0 (Clean) -> 100 (Noisy) // Forward Mode: Slider 0 (Clean) -> 100 (Noisy)
// Reverse Mode: Slider 0 (Noisy) -> 100 (Clean) // Reverse Mode: Slider 0 (Noisy) -> 100 (Clean)
let noiseRatio = 0 let noiseRatio = 0
if (mode.value === 'forward') { if (mode.value === 'forward') {
noiseRatio = currentStep.value / totalSteps noiseRatio = currentStep.value / totalSteps
} else { } else {
// In reverse mode, slider 0 means start (Noisy), 100 means end (Clean) // In reverse mode, slider 0 means start (Noisy), 100 means end (Clean)
// So noise amount is 1 - slider // So noise amount is 1 - slider
noiseRatio = 1 - (currentStep.value / totalSteps) noiseRatio = 1 - currentStep.value / totalSteps
} }
// Non-linear interpolation for better visual effect // Non-linear interpolation for better visual effect
// noiseRatio = Math.pow(noiseRatio, 1.5) // noiseRatio = Math.pow(noiseRatio, 1.5)
const w = canvas.width const w = canvas.width
const h = canvas.height const h = canvas.height
@@ -177,20 +203,20 @@ const draw = () => {
const d = output.data const d = output.data
const o = originalImage.data const o = originalImage.data
const n = noiseImage.data const n = noiseImage.data
for (let i = 0; i < d.length; i += 4) { for (let i = 0; i < d.length; i += 4) {
// Simple linear interpolation // Simple linear interpolation
// Pixel = (1 - alpha) * Original + alpha * Noise // Pixel = (1 - alpha) * Original + alpha * Noise
// Note: This is a simplified diffusion visualization. // Note: This is a simplified diffusion visualization.
// Real diffusion adds noise: x_t = sqrt(alpha_bar) * x_0 + sqrt(1 - alpha_bar) * epsilon // Real diffusion adds noise: x_t = sqrt(alpha_bar) * x_0 + sqrt(1 - alpha_bar) * epsilon
// Using simple blending for visualization // Using simple blending for visualization
d[i] = o[i] * (1 - noiseRatio) + n[i] * noiseRatio d[i] = o[i] * (1 - noiseRatio) + n[i] * noiseRatio
d[i+1] = o[i+1] * (1 - noiseRatio) + n[i+1] * 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 + 2] = o[i + 2] * (1 - noiseRatio) + n[i + 2] * noiseRatio
d[i+3] = 255 d[i + 3] = 255
} }
ctx.putImageData(output, 0, 0) ctx.putImageData(output, 0, 0)
} }
@@ -208,7 +234,7 @@ const startAnimation = () => {
if (currentStep.value >= totalSteps) { if (currentStep.value >= totalSteps) {
currentStep.value = 0 currentStep.value = 0
} }
const animate = () => { const animate = () => {
if (currentStep.value < totalSteps) { if (currentStep.value < totalSteps) {
currentStep.value += 1 currentStep.value += 1
@@ -261,7 +287,7 @@ const formatTooltip = (val) => {
.noise-canvas { .noise-canvas {
border-radius: 8px; 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%; max-width: 100%;
height: auto; height: auto;
} }
@@ -57,9 +57,13 @@
> >
<template #default> <template #default>
<p> <p>
<strong>Diffusion</strong> 就像在迷雾中摸索路径充满了随机性需要走很多弯路步数多才能到达终点 <strong>Diffusion</strong>
<br> 就像在迷雾中摸索路径充满了随机性需要走很多弯路步数多才能到达终点
<strong>Flow Matching</strong> 就像使用了 GPS 导航直接找到了从噪声到图像的<strong>直线最优路径 (Optimal Transport)</strong>因此只需要极少的步数 <br />
<strong>Flow Matching</strong> 就像使用了 GPS
导航直接找到了从噪声到图像的<strong
>直线最优路径 (Optimal Transport)</strong
>因此只需要极少的步数
</p> </p>
</template> </template>
</el-alert> </el-alert>
@@ -83,7 +87,7 @@ let animationFrame = null
let diffProgress = 0 let diffProgress = 0
let flowProgress = 0 let flowProgress = 0
const diffSpeed = 0.005 // Slow const diffSpeed = 0.005 // Slow
const flowSpeed = 0.02 // Fast const flowSpeed = 0.02 // Fast
// Particles // Particles
const particles = [] const particles = []
@@ -104,7 +108,7 @@ const startAnimation = () => {
flowProgress = 0 flowProgress = 0
diffSteps.value = 0 diffSteps.value = 0
flowSteps.value = 0 flowSteps.value = 0
animate() animate()
} }
@@ -118,7 +122,7 @@ const stopAnimation = () => {
const animate = () => { const animate = () => {
let finished = 0 let finished = 0
// Update Diffusion // Update Diffusion
if (diffProgress < 1) { if (diffProgress < 1) {
diffProgress += diffSpeed diffProgress += diffSpeed
@@ -129,7 +133,7 @@ const animate = () => {
drawFrame(diffCanvasRef.value, 1, 'curve') drawFrame(diffCanvasRef.value, 1, 'curve')
finished++ finished++
} }
// Update Flow // Update Flow
if (flowProgress < 1) { if (flowProgress < 1) {
flowProgress += flowSpeed flowProgress += flowSpeed
@@ -140,7 +144,7 @@ const animate = () => {
drawFrame(flowCanvasRef.value, 1, 'line') drawFrame(flowCanvasRef.value, 1, 'line')
finished++ finished++
} }
if (finished < 2) { if (finished < 2) {
animationFrame = requestAnimationFrame(animate) animationFrame = requestAnimationFrame(animate)
} else { } else {
@@ -153,7 +157,7 @@ const drawStatic = (canvas, type) => {
const ctx = canvas.getContext('2d') const ctx = canvas.getContext('2d')
const w = canvas.width const w = canvas.width
const h = canvas.height const h = canvas.height
ctx.clearRect(0, 0, w, h) ctx.clearRect(0, 0, w, h)
drawBackground(ctx, w, h) drawBackground(ctx, w, h)
drawPath(ctx, w, h, type, false) drawPath(ctx, w, h, type, false)
@@ -165,21 +169,21 @@ const drawFrame = (canvas, progress, type) => {
const ctx = canvas.getContext('2d') const ctx = canvas.getContext('2d')
const w = canvas.width const w = canvas.width
const h = canvas.height const h = canvas.height
ctx.clearRect(0, 0, w, h) ctx.clearRect(0, 0, w, h)
drawBackground(ctx, w, h) drawBackground(ctx, w, h)
drawPath(ctx, w, h, type, true) // active path drawPath(ctx, w, h, type, true) // active path
drawEndpoints(ctx, w, h) drawEndpoints(ctx, w, h)
// Draw Particle // Draw Particle
const pos = getPosition(progress, type, w, h) const pos = getPosition(progress, type, w, h)
// Draw Trail // Draw Trail
ctx.beginPath() ctx.beginPath()
if (type === 'curve') { if (type === 'curve') {
ctx.moveTo(30, h - 30) ctx.moveTo(30, h - 30)
// Re-calculate curve up to progress // 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) const p = getPosition(t, type, w, h)
ctx.lineTo(p.x, p.y) ctx.lineTo(p.x, p.y)
} }
@@ -190,7 +194,7 @@ const drawFrame = (canvas, progress, type) => {
ctx.strokeStyle = type === 'curve' ? '#F56C6C' : '#67C23A' ctx.strokeStyle = type === 'curve' ? '#F56C6C' : '#67C23A'
ctx.lineWidth = 3 ctx.lineWidth = 3
ctx.stroke() ctx.stroke()
// Draw Head // Draw Head
ctx.beginPath() ctx.beginPath()
ctx.arc(pos.x, pos.y, 6, 0, Math.PI * 2) ctx.arc(pos.x, pos.y, 6, 0, Math.PI * 2)
@@ -207,8 +211,14 @@ const drawBackground = (ctx, w, h) => {
ctx.strokeStyle = '#eee' ctx.strokeStyle = '#eee'
ctx.lineWidth = 1 ctx.lineWidth = 1
ctx.beginPath() ctx.beginPath()
for(let x=0; x<=w; x+=20) { ctx.moveTo(x,0); ctx.lineTo(x,h); } for (let x = 0; x <= w; x += 20) {
for(let y=0; y<=h; y+=20) { ctx.moveTo(0,y); ctx.lineTo(w,y); } 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() ctx.stroke()
} }
@@ -218,7 +228,7 @@ const drawEndpoints = (ctx, w, h) => {
ctx.arc(30, h - 30, 8, 0, Math.PI * 2) ctx.arc(30, h - 30, 8, 0, Math.PI * 2)
ctx.fillStyle = '#909399' ctx.fillStyle = '#909399'
ctx.fill() ctx.fill()
// End (Image) // End (Image)
ctx.beginPath() ctx.beginPath()
ctx.arc(w - 30, 30, 8, 0, Math.PI * 2) ctx.arc(w - 30, 30, 8, 0, Math.PI * 2)
@@ -229,14 +239,14 @@ const drawEndpoints = (ctx, w, h) => {
const drawPath = (ctx, w, h, type, isActive) => { const drawPath = (ctx, w, h, type, isActive) => {
ctx.beginPath() ctx.beginPath()
ctx.moveTo(30, h - 30) ctx.moveTo(30, h - 30)
if (type === 'line') { if (type === 'line') {
ctx.lineTo(w - 30, 30) ctx.lineTo(w - 30, 30)
} else { } else {
// Bezier curve for diffusion // Bezier curve for diffusion
ctx.quadraticCurveTo(w * 0.2, 30, w - 30, 30) ctx.quadraticCurveTo(w * 0.2, 30, w - 30, 30)
} }
ctx.strokeStyle = isActive ? 'rgba(0,0,0,0.1)' : '#ddd' ctx.strokeStyle = isActive ? 'rgba(0,0,0,0.1)' : '#ddd'
ctx.lineWidth = 2 ctx.lineWidth = 2
ctx.setLineDash([5, 5]) ctx.setLineDash([5, 5])
@@ -249,7 +259,7 @@ const getPosition = (t, type, w, h) => {
const startY = h - 30 const startY = h - 30
const endX = w - 30 const endX = w - 30
const endY = 30 const endY = 30
if (type === 'line') { if (type === 'line') {
return { return {
x: startX + (endX - startX) * t, x: startX + (endX - startX) * t,
@@ -260,10 +270,16 @@ const getPosition = (t, type, w, h) => {
// Control Point // Control Point
const cpX = w * 0.2 const cpX = w * 0.2
const cpY = 30 const cpY = 30
const x = Math.pow(1-t, 2) * startX + 2 * (1-t) * t * cpX + Math.pow(t, 2) * endX const x =
const y = Math.pow(1-t, 2) * startY + 2 * (1-t) * t * cpY + Math.pow(t, 2) * endY 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 // Add some random jitter for diffusion look if t < 1
// const jitter = t < 1 ? (Math.random() - 0.5) * 5 : 0 // const jitter = t < 1 ? (Math.random() - 0.5) * 5 : 0
// return { x: x + jitter, y: y + jitter } // return { x: x + jitter, y: y + jitter }
@@ -318,7 +334,7 @@ const getPosition = (t, type, w, h) => {
background: #fff; background: #fff;
border-radius: 4px; border-radius: 4px;
overflow: hidden; 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 { canvas {
@@ -53,7 +53,9 @@
<div class="node-content"> <div class="node-content">
<div class="model-name">UNet / DiT</div> <div class="model-name">UNet / DiT</div>
<div class="action-badge"> <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>
</div> </div>
</el-card> </el-card>
@@ -87,7 +89,9 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="8"> <el-col :span="8">
<div class="explanation-item"> <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"> <div class="exp-text">
<h4>耳朵 (Text Encoder)</h4> <h4>耳朵 (Text Encoder)</h4>
<p>负责"听懂"你的描述把它翻译成计算机能理解的数学向量</p> <p>负责"听懂"你的描述把它翻译成计算机能理解的数学向量</p>
@@ -96,16 +100,22 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<div class="explanation-item"> <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"> <div class="exp-text">
<h4>大脑 (UNet/DiT)</h4> <h4>大脑 (UNet/DiT)</h4>
<p>核心创造者在潜空间(Latent Space)中通过预测噪声来构思画面</p> <p>
核心创造者在潜空间(Latent Space)中通过预测噪声来构思画面
</p>
</div> </div>
</div> </div>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<div class="explanation-item"> <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"> <div class="exp-text">
<h4>眼睛 (VAE)</h4> <h4>眼睛 (VAE)</h4>
<p>负责"翻译"回图像把大脑构思的模糊特征还原成高清像素图片</p> <p>负责"翻译"回图像把大脑构思的模糊特征还原成高清像素图片</p>
@@ -118,7 +128,14 @@
</template> </template>
<script setup> <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> </script>
<style scoped> <style scoped>
@@ -217,12 +234,12 @@ import { EditPen, Microphone, Right, Cpu, View, Picture } from '@element-plus/ic
.flow-container { .flow-container {
flex-direction: column; flex-direction: column;
} }
.arrow-connector { .arrow-connector {
transform: rotate(90deg); transform: rotate(90deg);
margin: 10px 0; margin: 10px 0;
} }
.explanation-item { .explanation-item {
margin-bottom: 10px; margin-bottom: 10px;
} }
@@ -10,7 +10,12 @@
</div> </div>
<div class="grid-wrapper pixel-wrapper"> <div class="grid-wrapper pixel-wrapper">
<div class="pixel-grid"> <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>
<div class="grid-overlay"> <div class="grid-overlay">
<span>HD Image</span> <span>HD Image</span>
@@ -38,7 +43,9 @@
</div> </div>
<el-icon :size="24" class="arrow-icon"><Right /></el-icon> <el-icon :size="24" class="arrow-icon"><Right /></el-icon>
</div> </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> </div>
<!-- Latent Space --> <!-- Latent Space -->
@@ -49,7 +56,12 @@
</div> </div>
<div class="grid-wrapper latent-wrapper"> <div class="grid-wrapper latent-wrapper">
<div class="latent-grid"> <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>
<div class="grid-overlay"> <div class="grid-overlay">
<span>Latent Feature</span> <span>Latent Feature</span>
@@ -207,7 +219,7 @@ const getLatentStyle = (n) => {
bottom: 0; bottom: 0;
left: 0; left: 0;
width: 100%; width: 100%;
background: rgba(0,0,0,0.6); background: rgba(0, 0, 0, 0.6);
color: #fff; color: #fff;
font-size: 0.75em; font-size: 0.75em;
padding: 4px 8px; padding: 4px 8px;
@@ -265,12 +277,12 @@ const getLatentStyle = (n) => {
.viz-container { .viz-container {
flex-direction: column; flex-direction: column;
} }
.transform-process { .transform-process {
transform: rotate(90deg); transform: rotate(90deg);
margin: 10px 0; margin: 10px 0;
} }
.compress-tag { .compress-tag {
display: none; /* Hide tag when rotated to avoid layout issues */ display: none; /* Hide tag when rotated to avoid layout issues */
} }
@@ -16,7 +16,9 @@
:style="{ opacity: token.weight }" :style="{ opacity: token.weight }"
> >
<div class="token-text">{{ token.text }}</div> <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> </div>
</div> </div>
@@ -25,12 +27,21 @@
<div class="attention-section"> <div class="attention-section">
<div class="section-title">🎯 交叉注意力可视化</div> <div class="section-title">🎯 交叉注意力可视化</div>
<div class="attention-grid"> <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-token">{{ item.token }}</div>
<div class="cell-bar"> <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>
<div class="cell-value">{{ (item.attention * 100).toFixed(0) }}%</div>
</div> </div>
</div> </div>
</div> </div>
@@ -40,9 +51,8 @@
<p> <p>
<span class="icon">💡</span> <span class="icon">💡</span>
<strong>交叉注意力机制</strong> AI 理解提示词的每个词 <strong>交叉注意力机制</strong> AI 理解提示词的每个词
当生成图片时AI "关注"不同的词 当生成图片时AI "关注"不同的词 "cyberpunk" 影响整体风格"cat"
"cyberpunk" 影响整体风格"cat" 决定主体"neon lights" 控制灯光效果 决定主体"neon lights" 控制灯光效果 词的顺序和权重都会影响最终画面
词的顺序和权重都会影响最终画面
</p> </p>
</div> </div>
</div> </div>
@@ -15,8 +15,8 @@
<div class="embedding-demo"> <div class="embedding-demo">
<div class="demo-controls"> <div class="demo-controls">
<div class="btn-group"> <div class="btn-group">
<button <button
v-for="mode in modes" v-for="mode in modes"
:key="mode.id" :key="mode.id"
:class="{ active: currentMode === mode.id }" :class="{ active: currentMode === mode.id }"
@click="setMode(mode.id)" @click="setMode(mode.id)"
@@ -25,7 +25,7 @@
</button> </button>
</div> </div>
<div class="info-text"> <div class="info-text">
{{ modes.find(m => m.id === currentMode)?.desc }} {{ modes.find((m) => m.id === currentMode)?.desc }}
</div> </div>
</div> </div>
@@ -34,51 +34,95 @@
<svg viewBox="0 0 400 300" class="vector-canvas"> <svg viewBox="0 0 400 300" class="vector-canvas">
<!-- Grid lines --> <!-- Grid lines -->
<g class="grid"> <g class="grid">
<line x1="0" y1="150" x2="400" y2="150" stroke="var(--vp-c-divider)" /> <line
<line x1="200" y1="0" x2="200" y2="300" stroke="var(--vp-c-divider)" /> 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> </g>
<!-- Vectors/Points --> <!-- Vectors/Points -->
<g class="points"> <g class="points">
<g <g
v-for="point in activePoints" v-for="point in activePoints"
:key="point.id" :key="point.id"
class="point-group" class="point-group"
:class="{ highlight: point.highlight }" :class="{ highlight: point.highlight }"
:transform="`translate(${point.x}, ${point.y})`" :transform="`translate(${point.x}, ${point.y})`"
> >
<circle r="4" :fill="point.color" /> <circle r="4" :fill="point.color" />
<text <text
y="-8" y="-8"
text-anchor="middle" text-anchor="middle"
class="point-label" class="point-label"
:fill="point.color" :fill="point.color"
>{{ point.word }}</text> >
{{ point.word }}
</text>
</g> </g>
</g> </g>
<!-- Calculation Arrows (for King/Queen demo) --> <!-- Calculation Arrows (for King/Queen demo) -->
<g v-if="currentMode === 'analogy'" class="arrows"> <g v-if="currentMode === 'analogy'" class="arrows">
<!-- King -> Man --> <!-- King -> Man -->
<line <line
:x1="getPoint('king').x" :y1="getPoint('king').y" :x1="getPoint('king').x"
:x2="getPoint('man').x" :y2="getPoint('man').y" :y1="getPoint('king').y"
stroke="rgba(0,0,0,0.2)" stroke-dasharray="4" marker-end="url(#arrowhead)" :x2="getPoint('man').x"
:y2="getPoint('man').y"
stroke="rgba(0,0,0,0.2)"
stroke-dasharray="4"
marker-end="url(#arrowhead)"
/> />
<!-- Queen -> Woman --> <!-- Queen -> Woman -->
<line <line
:x1="getPoint('queen').x" :y1="getPoint('queen').y" :x1="getPoint('queen').x"
:x2="getPoint('woman').x" :y2="getPoint('woman').y" :y1="getPoint('queen').y"
stroke="var(--vp-c-brand)" stroke-width="2" marker-end="url(#arrowhead-brand)" :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> </g>
<defs> <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)" /> <polygon points="0 0, 10 3.5, 0 7" fill="rgba(0,0,0,0.2)" />
</marker> </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)" /> <polygon points="0 0, 10 3.5, 0 7" fill="var(--vp-c-brand)" />
</marker> </marker>
</defs> </defs>
@@ -94,36 +138,82 @@ const currentMode = ref('cluster')
const modes = [ const modes = [
{ id: 'cluster', label: '语义聚类', desc: '语义相近的词在空间中距离更近。' }, { id: 'cluster', label: '语义聚类', desc: '语义相近的词在空间中距离更近。' },
{ id: 'analogy', label: '向量算术', desc: 'King - Man + Woman ≈ Queen (方向平行)' } {
id: 'analogy',
label: '向量算术',
desc: 'King - Man + Woman ≈ Queen (方向平行)'
}
] ]
const basePoints = [ const basePoints = [
// Cluster 1: Animals // Cluster 1: Animals
{ id: 'cat', word: 'Cat', x: 80, y: 80, color: '#f87171', group: 'animal' }, { 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: '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 // 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' }, { id: 'ai', word: 'AI', x: 280, y: 210, color: '#60a5fa', group: 'tech' },
// Cluster 3: Royalty (Analogy) // 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: '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(() => { const activePoints = computed(() => {
if (currentMode.value === 'cluster') { if (currentMode.value === 'cluster') {
return basePoints.filter(p => ['animal', 'tech'].includes(p.group)) return basePoints.filter((p) => ['animal', 'tech'].includes(p.group))
} else { } 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) => { const setMode = (mode) => {
currentMode.value = mode currentMode.value = mode
@@ -1,16 +1,13 @@
<template> <template>
<div class="linear-attention-demo"> <div class="linear-attention-demo">
<div class="mode-switch"> <div class="mode-switch">
<button <button
:class="{ active: mode === 'standard' }" :class="{ active: mode === 'standard' }"
@click="mode = 'standard'" @click="mode = 'standard'"
> >
标准 Attention (网状连接) 标准 Attention (网状连接)
</button> </button>
<button <button :class="{ active: mode === 'linear' }" @click="mode = 'linear'">
:class="{ active: mode === 'linear' }"
@click="mode = 'linear'"
>
线性 Attention (接力传递) 线性 Attention (接力传递)
</button> </button>
</div> </div>
@@ -18,7 +15,14 @@
<div class="visual-area"> <div class="visual-area">
<div class="control-panel"> <div class="control-panel">
<div class="label">参与者数量 (N): {{ nValue }}</div> <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>
<div class="viz-canvas-container"> <div class="viz-canvas-container">
@@ -26,112 +30,149 @@
<svg class="viz-svg" viewBox="0 0 400 300"> <svg class="viz-svg" viewBox="0 0 400 300">
<!-- STANDARD MODE: Mesh / Web --> <!-- STANDARD MODE: Mesh / Web -->
<g v-if="mode === 'standard'"> <g v-if="mode === 'standard'">
<!-- Active Query Animation --> <!-- Active Query Animation -->
<g class="active-query-scan"> <g class="active-query-scan">
<!-- Current processing node (last one) --> <!-- Current processing node (last one) -->
<circle <circle
:cx="circleNodes[circleNodes.length-1].x" :cx="circleNodes[circleNodes.length - 1].x"
:cy="circleNodes[circleNodes.length-1].y" :cy="circleNodes[circleNodes.length - 1].y"
r="16" r="16"
fill="none" fill="none"
stroke="var(--vp-c-brand)" stroke="var(--vp-c-brand)"
stroke-width="3" stroke-width="3"
opacity="0.5" opacity="0.5"
> >
<animate attributeName="r" values="12;20;12" dur="2s" repeatCount="indefinite" /> <animate
<animate attributeName="opacity" values="0.8;0;0.8" dur="2s" repeatCount="indefinite" /> attributeName="r"
</circle> values="12;20;12"
dur="2s"
<!-- Scanning rays from last node to all others --> repeatCount="indefinite"
<line />
v-for="(node, idx) in circleNodes.slice(0, circleNodes.length-1)" <animate
:key="'ray'+idx" attributeName="opacity"
:x1="circleNodes[circleNodes.length-1].x" values="0.8;0;0.8"
:y1="circleNodes[circleNodes.length-1].y" dur="2s"
:x2="node.x" repeatCount="indefinite"
:y2="node.y" />
stroke="var(--vp-c-brand)" </circle>
stroke-width="2"
stroke-dasharray="4"
class="scanning-ray"
>
<animate attributeName="stroke-dashoffset" values="20;0" dur="1s" repeatCount="indefinite" />
</line>
</g>
<!-- Background Mesh --> <!-- Scanning rays from last node to all others -->
<g class="connections"> <line
<line v-for="(node, idx) in circleNodes.slice(
v-for="(link, idx) in meshLinks" 0,
:key="idx" circleNodes.length - 1
:x1="link.x1" :y1="link.y1" )"
:x2="link.x2" :y2="link.y2" :key="'ray' + idx"
class="connection-line" :x1="circleNodes[circleNodes.length - 1].x"
:style="{ animationDelay: idx * 0.05 + 's' }" :y1="circleNodes[circleNodes.length - 1].y"
/> :x2="node.x"
</g> :y2="node.y"
<!-- Draw Nodes --> stroke="var(--vp-c-brand)"
<circle stroke-width="2"
v-for="(node, idx) in circleNodes" stroke-dasharray="4"
:key="idx" class="scanning-ray"
:cx="node.x" :cy="node.y" >
r="12" <animate
class="node-circle standard" attributeName="stroke-dashoffset"
:class="{ 'current-node': idx === circleNodes.length - 1 }" values="20;0"
/> dur="1s"
<text repeatCount="indefinite"
v-for="(node, idx) in circleNodes" />
:key="'t'+idx" </line>
:x="node.x" :y="node.y" </g>
dy="4"
text-anchor="middle" <!-- Background Mesh -->
class="node-text" <g class="connections">
>{{ idx + 1 }}</text> <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> </g>
<!-- LINEAR MODE: Relay / Chain --> <!-- LINEAR MODE: Relay / Chain -->
<g v-else> <g v-else>
<!-- Relay Path --> <!-- Relay Path -->
<line <line
x1="40" y1="150" x1="40"
:x2="40 + (nValue - 1) * 60" y2="150" y1="150"
class="relay-track" :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>
<!-- Nodes --> <!-- Passing Message Animation -->
<g v-for="(node, idx) in linearNodes" :key="idx"> <circle cx="0" cy="0" r="8" class="message-token">
<circle <animateMotion
:cx="node.x" :cy="node.y" :path="relayPath"
r="12" dur="2s"
class="node-circle linear" repeatCount="indefinite"
/> />
<text </circle>
:x="node.x" :y="node.y"
dy="4" <!-- Nodes -->
text-anchor="middle" <g v-for="(node, idx) in linearNodes" :key="idx">
class="node-text" <circle
>{{ idx + 1 }}</text> :cx="node.x"
<!-- State Box (Memory) --> :cy="node.y"
<rect r="12"
:x="node.x - 15" :y="node.y + 20" class="node-circle linear"
width="30" height="20" />
rx="4" <text
class="memory-box" :x="node.x"
/> :y="node.y"
<text :x="node.x" :y="node.y + 34" text-anchor="middle" font-size="8" fill="white">Mem</text> dy="4"
</g> 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> </g>
</svg> </svg>
</div> </div>
@@ -139,16 +180,21 @@
<div class="stats-panel"> <div class="stats-panel">
<div class="stat-item"> <div class="stat-item">
<div class="stat-label">连接/操作次数</div> <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 }} {{ connectionCount }}
</div> </div>
</div> </div>
<div class="stat-desc"> <div class="stat-desc">
<span v-if="mode === 'standard'"> <span v-if="mode === 'standard'">
每个人都要找其他人<br>N={{ nValue }} 连接数高达 {{ nValue * nValue }} 每个人都要找其他人<br />N={{ nValue }} 连接数高达
{{ nValue * nValue }}
</span> </span>
<span v-else> <span v-else>
每个人只传给下一个人<br>N={{ nValue }} 操作数仅为 {{ nValue }} 每个人只传给下一个人<br />N={{ nValue }} 操作数仅为
{{ nValue }}
</span> </span>
</div> </div>
</div> </div>
@@ -158,13 +204,13 @@
<div class="analogy-title">💡 核心区别要不要回头看</div> <div class="analogy-title">💡 核心区别要不要回头看</div>
<div v-if="mode === 'standard'"> <div v-if="mode === 'standard'">
<b>回看模式 (Retrospective)</b> <b>回看模式 (Retrospective)</b>
<br>想象你在考试每做一道新题你都要<b>把之前做过的所有题目再检查一遍</b>确认有没有关联 <br />想象你在考试每做一道新题你都要<b>把之前做过的所有题目再检查一遍</b>确认有没有关联
<br>题目越多你需要检查的次数就越多最后累死在检查上 <br />题目越多你需要检查的次数就越多最后累死在检查上
</div> </div>
<div v-else> <div v-else>
<b>状态模式 (Recurrent)</b> <b>状态模式 (Recurrent)</b> <br />想象你在跑步你不需要记得前 100
<br>想象你在跑步你不需要记得前 100 步每一步踩在哪你只需要知道<b>现在的速度和位置</b>State 步每一步踩在哪你只需要知道<b>现在的速度和位置</b>State
<br>跑第 1000 步和跑第 1 步一样轻松因为你不需要回头 <br />跑第 1000 步和跑第 1 步一样轻松因为你不需要回头
</div> </div>
</div> </div>
</div> </div>
@@ -182,7 +228,7 @@ const circleNodes = computed(() => {
const centerX = 200 const centerX = 200
const centerY = 150 const centerY = 150
const radius = 100 const radius = 100
for (let i = 0; i < nValue.value; i++) { for (let i = 0; i < nValue.value; i++) {
const angle = (i / nValue.value) * 2 * Math.PI - Math.PI / 2 const angle = (i / nValue.value) * 2 * Math.PI - Math.PI / 2
nodes.push({ nodes.push({
@@ -216,7 +262,7 @@ const linearNodes = computed(() => {
const startX = 40 const startX = 40
const gap = 60 const gap = 60
const y = 150 const y = 150
for (let i = 0; i < nValue.value; i++) { for (let i = 0; i < nValue.value; i++) {
nodes.push({ nodes.push({
x: startX + i * gap, x: startX + i * gap,
@@ -229,9 +275,9 @@ const linearNodes = computed(() => {
// SVG Path for animation in Linear Mode // SVG Path for animation in Linear Mode
const relayPath = computed(() => { const relayPath = computed(() => {
const nodes = linearNodes.value const nodes = linearNodes.value
if (nodes.length < 2) return "" if (nodes.length < 2) return ''
// Start from first node, go to last node // 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(() => { const connectionCount = computed(() => {
@@ -341,7 +387,9 @@ const connectionCount = computed(() => {
} }
@keyframes fadeInLine { @keyframes fadeInLine {
to { opacity: 0.3; } to {
opacity: 0.3;
}
} }
.relay-track { .relay-track {
@@ -371,8 +419,12 @@ const connectionCount = computed(() => {
font-family: monospace; font-family: monospace;
} }
.text-red { color: var(--vp-c-red); } .text-red {
.text-green { color: var(--vp-c-green); } color: var(--vp-c-red);
}
.text-green {
color: var(--vp-c-green);
}
.stat-desc { .stat-desc {
color: var(--vp-c-text-2); color: var(--vp-c-text-2);
@@ -2,7 +2,9 @@
<div class="llm-quick-start"> <div class="llm-quick-start">
<div class="header"> <div class="header">
<div class="title">🤖 LLM 初体验从闲聊到业务实战</div> <div class="title">🤖 LLM 初体验从闲聊到业务实战</div>
<div class="subtitle">大模型不仅能聊天更是生产力工具试试看它如何处理这些业务需求</div> <div class="subtitle">
大模型不仅能聊天更是生产力工具试试看它如何处理这些业务需求
</div>
</div> </div>
<div class="chat-window"> <div class="chat-window">
@@ -12,15 +14,30 @@
</div> </div>
<div class="messages" ref="messagesRef"> <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="avatar">{{ msg.role === 'user' ? '🧑‍💻' : '🤖' }}</div>
<div class="content"> <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"> <div v-else class="assistant-content">
<pre v-if="msg.isCode"><code>{{ msg.content }}</code></pre> <pre v-if="msg.isCode"><code>{{ msg.content }}</code></pre>
<div v-else>{{ msg.content }}</div> <div v-else>{{ msg.content }}</div>
</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> </div>
</div> </div>
@@ -28,14 +45,17 @@
<div class="input-area"> <div class="input-area">
<div class="quick-actions" v-if="!isGenerating"> <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-icon">{{ q.icon }}</span>
<span class="btn-text">{{ q.text }}</span> <span class="btn-text">{{ q.text }}</span>
</button> </button>
</div> </div>
<div class="status-text" v-else> <div class="status-text" v-else>正在思考业务逻辑并生成 Token...</div>
正在思考业务逻辑并生成 Token...
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -52,11 +72,11 @@ const questions = [
] ]
const answers = { const answers = {
"给我想一个请假的理由": { 给我想一个请假的理由: {
isCode: false, isCode: false,
text: "老板,我感觉身体不适,可能是昨天写代码太投入,CPU(大脑)过热导致系统(身体)宕机了,申请重启(休息)一天。" text: '老板,我感觉身体不适,可能是昨天写代码太投入,CPU(大脑)过热导致系统(身体)宕机了,申请重启(休息)一天。'
}, },
"帮我写一个 Python 爬虫": { '帮我写一个 Python 爬虫': {
isCode: true, isCode: true,
text: `import requests text: `import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
@@ -75,17 +95,17 @@ print(f"正在爬取 {url} 的标题...")
# titles = fetch_titles(url) # titles = fetch_titles(url)
# print(titles)` # print(titles)`
}, },
"用鲁迅的语气夸我": { 用鲁迅的语气夸我: {
isCode: false, isCode: false,
text: "我向来是不惮以最坏的恶意来推测中国人的,然而我还不料,也不信竟会遇见这样优秀的人。你的代码,很有几分风骨。" text: '我向来是不惮以最坏的恶意来推测中国人的,然而我还不料,也不信竟会遇见这样优秀的人。你的代码,很有几分风骨。'
}, },
"分析这份销售数据的趋势": { 分析这份销售数据的趋势: {
isCode: false, 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, isCode: false,
text: "☕️ **早八人的续命神器!这款咖啡杯真的太懂我了**\n\n家人们谁懂啊!😭 作为一个每天靠咖啡续命的打工人,终于挖到了这款宝藏杯子!\n\n✨ **颜值绝绝子**:奶油白配色,拿在手里就是妥妥的 ins 风,摆在工位上心情都变好了!\n🌡️ **保温超长待机**:早上泡的冰美式,下午还是冰冰凉,这也太适合夏天了吧!\n🔒 **密封不漏水**:直接塞包里也不怕洒,挤地铁必备!\n\n👇 评论区蹲一个链接,带你一起实现咖啡自由! #好物分享 #高颜值水杯 #打工人日常" text: '☕️ **早八人的续命神器!这款咖啡杯真的太懂我了**\n\n家人们谁懂啊!😭 作为一个每天靠咖啡续命的打工人,终于挖到了这款宝藏杯子!\n\n✨ **颜值绝绝子**:奶油白配色,拿在手里就是妥妥的 ins 风,摆在工位上心情都变好了!\n🌡️ **保温超长待机**:早上泡的冰美式,下午还是冰冰凉,这也太适合夏天了吧!\n🔒 **密封不漏水**:直接塞包里也不怕洒,挤地铁必备!\n\n👇 评论区蹲一个链接,带你一起实现咖啡自由! #好物分享 #高颜值水杯 #打工人日常'
} }
} }
@@ -96,33 +116,33 @@ const messagesRef = ref(null)
const ask = async (qObj) => { const ask = async (qObj) => {
messages.value.push({ role: 'user', content: qObj.text }) messages.value.push({ role: 'user', content: qObj.text })
isGenerating.value = true isGenerating.value = true
await wait(600) await wait(600)
const answerData = answers[qObj.text] const answerData = answers[qObj.text]
const fullAnswer = answerData ? answerData.text : "正在思考..." const fullAnswer = answerData ? answerData.text : '正在思考...'
messages.value.push({ messages.value.push({
role: 'assistant', role: 'assistant',
content: '', content: '',
isCode: answerData ? answerData.isCode : false isCode: answerData ? answerData.isCode : false
}) })
const answerIdx = messages.value.length - 1 const answerIdx = messages.value.length - 1
// Typing animation // Typing animation
for (let i = 0; i < fullAnswer.length; i++) { for (let i = 0; i < fullAnswer.length; i++) {
messages.value[answerIdx].content += fullAnswer[i] messages.value[answerIdx].content += fullAnswer[i]
scrollToBottom() scrollToBottom()
// Code typing is usually faster looking // 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) await wait(speed)
} }
isGenerating.value = false isGenerating.value = false
} }
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
const scrollToBottom = () => { const scrollToBottom = () => {
nextTick(() => { nextTick(() => {
@@ -140,8 +160,9 @@ const scrollToBottom = () => {
border-radius: 12px; border-radius: 12px;
padding: 24px; padding: 24px;
margin: 24px 0; margin: 24px 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-family:
box-shadow: 0 4px 12px rgba(0,0,0,0.05); -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
} }
.header { .header {
@@ -302,14 +323,14 @@ const scrollToBottom = () => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; 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 { .action-btn:hover {
border-color: var(--vp-c-brand); border-color: var(--vp-c-brand);
color: var(--vp-c-brand); color: var(--vp-c-brand);
transform: translateY(-1px); 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 { .status-text {
@@ -329,7 +350,33 @@ const scrollToBottom = () => {
animation: pulse 1.5s infinite; animation: pulse 1.5s infinite;
} }
@keyframes blink { 50% { opacity: 0; } } @keyframes blink {
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } 50% {
@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); } } 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> </style>
@@ -3,7 +3,7 @@
<!-- Header / Mode Switch --> <!-- Header / Mode Switch -->
<div class="demo-header"> <div class="demo-header">
<div class="mode-tabs"> <div class="mode-tabs">
<button <button
v-for="mode in ['dense', 'moe']" v-for="mode in ['dense', 'moe']"
:key="mode" :key="mode"
:class="['mode-tab', { active: architecture === mode }]" :class="['mode-tab', { active: architecture === mode }]"
@@ -13,7 +13,11 @@
</button> </button>
</div> </div>
<div class="mode-desc"> <div class="mode-desc">
{{ architecture === 'dense' ? '全能天才:每个问题都动用整个大脑 (100% 激活)' : '专家团队:根据问题指派专人处理 (稀疏激活)' }} {{
architecture === 'dense'
? '全能天才:每个问题都动用整个大脑 (100% 激活)'
: '专家团队:根据问题指派专人处理 (稀疏激活)'
}}
</div> </div>
</div> </div>
@@ -23,8 +27,8 @@
<div class="stage-section input-section"> <div class="stage-section input-section">
<div class="section-label">1. 输入指令 (Input)</div> <div class="section-label">1. 输入指令 (Input)</div>
<div class="task-selector"> <div class="task-selector">
<button <button
v-for="(task, idx) in tasks" v-for="(task, idx) in tasks"
:key="idx" :key="idx"
class="task-btn" class="task-btn"
:class="{ selected: selectedTask.label === task.label }" :class="{ selected: selectedTask.label === task.label }"
@@ -35,7 +39,10 @@
<span class="task-text">{{ task.label }}</span> <span class="task-text">{{ task.label }}</span>
</button> </button>
</div> </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 class="token-particle">{{ selectedTask.icon }}</div>
</div> </div>
</div> </div>
@@ -49,10 +56,13 @@
2. 模型处理 (Processing) 2. 模型处理 (Processing)
<span v-if="processing" class="status-badge">计算中...</span> <span v-if="processing" class="status-badge">计算中...</span>
</div> </div>
<!-- Dense Visualization --> <!-- Dense Visualization -->
<div v-if="architecture === 'dense'" class="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="dense-label">前馈神经网络 (FFN)</div>
<div class="neuron-grid"> <div class="neuron-grid">
<div v-for="n in 32" :key="n" class="neuron"></div> <div v-for="n in 32" :key="n" class="neuron"></div>
@@ -66,7 +76,10 @@
<!-- MoE Visualization --> <!-- MoE Visualization -->
<div v-else class="moe-visualization"> <div v-else class="moe-visualization">
<!-- Router --> <!-- 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-label">门控路由 (Router)</div>
<div class="router-action" v-if="processing && currentStep >= 1"> <div class="router-action" v-if="processing && currentStep >= 1">
🔍 识别意图: "{{ selectedTask.type }}" 🔍 识别意图: "{{ selectedTask.type }}"
@@ -75,32 +88,37 @@
<!-- Connections --> <!-- Connections -->
<div class="connections"> <div class="connections">
<div <div
v-for="(expert, idx) in experts" v-for="(expert, idx) in experts"
:key="idx" :key="idx"
class="connection-line" class="connection-line"
:class="{ :class="{
'active': processing && currentStep >= 2 && isExpertSelected(idx), active: processing && currentStep >= 2 && isExpertSelected(idx),
'inactive': processing && currentStep >= 2 && !isExpertSelected(idx) inactive:
processing && currentStep >= 2 && !isExpertSelected(idx)
}" }"
></div> ></div>
</div> </div>
<!-- Experts --> <!-- Experts -->
<div class="experts-grid"> <div class="experts-grid">
<div <div
v-for="(expert, idx) in experts" v-for="(expert, idx) in experts"
:key="idx" :key="idx"
class="expert-card" class="expert-card"
:class="{ :class="{
'active': processing && currentStep >= 2 && isExpertSelected(idx), active: processing && currentStep >= 2 && isExpertSelected(idx),
'inactive': processing && currentStep >= 2 && !isExpertSelected(idx) inactive:
processing && currentStep >= 2 && !isExpertSelected(idx)
}" }"
> >
<div class="expert-icon">{{ expert.icon }}</div> <div class="expert-icon">{{ expert.icon }}</div>
<div class="expert-name">{{ expert.name }}</div> <div class="expert-name">{{ expert.name }}</div>
<div class="expert-role">{{ expert.role }}</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>
</div> </div>
@@ -114,7 +132,7 @@
<!-- Step 3: Output --> <!-- Step 3: Output -->
<div class="stage-section output-section"> <div class="stage-section output-section">
<div class="section-label">3. 生成结果 (Output)</div> <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"> <div v-if="currentStep === 3" class="output-content">
<span class="output-icon">{{ selectedTask.icon }}</span> <span class="output-icon">{{ selectedTask.icon }}</span>
<span class="typing-effect">{{ selectedTask.output }}</span> <span class="typing-effect">{{ selectedTask.output }}</span>
@@ -148,10 +166,34 @@ const experts = [
] ]
const tasks = [ 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: '写 Python 脚本',
{ label: '解二元方程', type: '数学', icon: '✖️', expertIdx: 2, output: 'x = 5, y = -2 (过程略)' }, type: '编程',
{ label: '翻译成英文', type: '翻译', icon: '🔤', expertIdx: 3, output: 'To be, or not to be, that is the question.' } 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]) const selectedTask = ref(tasks[0])
@@ -181,37 +223,38 @@ const runDemo = async () => {
if (processing.value) return if (processing.value) return
processing.value = true processing.value = true
currentStep.value = 0 currentStep.value = 0
// Step 1: Input -> Router // Step 1: Input -> Router
await wait(300) await wait(300)
currentStep.value = 1 currentStep.value = 1
// Step 2: Router -> Expert / Dense Processing // Step 2: Router -> Expert / Dense Processing
await wait(800) await wait(800)
currentStep.value = 2 currentStep.value = 2
// Step 3: Expert -> Output // Step 3: Expert -> Output
await wait(1200) await wait(1200)
currentStep.value = 3 currentStep.value = 3
// Finish // Finish
await wait(500) await wait(500)
processing.value = false processing.value = false
} }
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
</script> </script>
<style scoped> <style scoped>
.moe-demo-container { .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); background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
border-radius: 12px; border-radius: 12px;
padding: 24px; padding: 24px;
max-width: 600px; max-width: 600px;
margin: 20px auto; 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 */ /* Header */
@@ -243,7 +286,7 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
.mode-tab.active { .mode-tab.active {
background: var(--vp-c-bg); background: var(--vp-c-bg);
color: var(--vp-c-brand); 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 { .mode-desc {
@@ -466,7 +509,7 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
opacity: 1; opacity: 1;
border-color: var(--vp-c-brand); border-color: var(--vp-c-brand);
transform: scale(1.05); 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 { .expert-card.inactive {
@@ -474,11 +517,25 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
transform: scale(0.95); transform: scale(0.95);
} }
.expert-icon { font-size: 20px; margin-bottom: 4px; } .expert-icon {
.expert-name { font-size: 11px; font-weight: bold; margin-bottom: 2px; } font-size: 20px;
.expert-role { font-size: 9px; color: var(--vp-c-text-3); } margin-bottom: 4px;
.expert-status { font-size: 9px; color: var(--vp-c-brand); margin-top: 4px; font-weight: bold; } }
.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 Section */
.output-box { .output-box {
@@ -547,7 +604,12 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
} }
@keyframes blink { @keyframes blink {
0%, 100% { opacity: 1; } 0%,
50% { opacity: 0.5; } 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
} }
</style> </style>
@@ -30,11 +30,12 @@
<div class="context-window"> <div class="context-window">
<div class="context-content"> <div class="context-content">
<span <span
v-for="(token, index) in tokenizedContext" v-for="(token, index) in tokenizedContext"
:key="index" :key="index"
class="context-token" class="context-token"
>{{ token }}</span> >{{ token }}</span
>
<span class="cursor"></span> <span class="cursor"></span>
</div> </div>
</div> </div>
@@ -44,21 +45,23 @@
<span>🤖 AI Prediction (Top 3 Candidates)</span> <span>🤖 AI Prediction (Top 3 Candidates)</span>
<span class="temperature-hint">Temperature: 0.7</span> <span class="temperature-hint">Temperature: 0.7</span>
</div> </div>
<div class="candidates-list"> <div class="candidates-list">
<div <div
v-for="(candidate, index) in currentCandidates" v-for="(candidate, index) in currentCandidates"
:key="index" :key="index"
class="candidate-item" class="candidate-item"
@click="selectCandidate(candidate)" @click="selectCandidate(candidate)"
> >
<div class="candidate-info"> <div class="candidate-info">
<span class="candidate-text">"{{ candidate.text }}"</span> <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>
<div class="prob-bar-bg"> <div class="prob-bar-bg">
<div <div
class="prob-bar-fill" class="prob-bar-fill"
:style="{ width: `${candidate.prob * 100}%` }" :style="{ width: `${candidate.prob * 100}%` }"
:class="`rank-${index}`" :class="`rank-${index}`"
></div> ></div>
@@ -69,7 +72,9 @@
<div class="explanation"> <div class="explanation">
<p> <p>
<strong>原理</strong> LLM 并不是一次性写出整段话而是像上面这样基于前面的内容Context计算下一个最可能出现的 Token 的概率然后选择一个Sampling填上去再重复这个过程 <strong>原理</strong> LLM
并不是一次性写出整段话而是像上面这样基于前面的内容Context计算下一个最可能出现的
Token 的概率然后选择一个Sampling填上去再重复这个过程
</p> </p>
</div> </div>
</div> </div>
@@ -82,38 +87,44 @@ const scenes = {
'en-fox': { 'en-fox': {
initial: 'The quick brown', initial: 'The quick brown',
logic: (text) => { logic: (text) => {
if (text.endsWith('brown')) return [ if (text.endsWith('brown'))
{ text: ' fox', prob: 0.85 }, return [
{ text: ' dog', prob: 0.10 }, { text: ' fox', prob: 0.85 },
{ text: ' cat', prob: 0.05 } { text: ' dog', prob: 0.1 },
] { text: ' cat', prob: 0.05 }
if (text.endsWith('fox')) return [ ]
{ text: ' jumps', prob: 0.92 }, if (text.endsWith('fox'))
{ text: ' runs', prob: 0.05 }, return [
{ text: ' sleeps', prob: 0.03 } { text: ' jumps', prob: 0.92 },
] { text: ' runs', prob: 0.05 },
if (text.endsWith('jumps')) return [ { text: ' sleeps', prob: 0.03 }
{ text: ' over', prob: 0.98 }, ]
{ text: ' up', prob: 0.01 }, if (text.endsWith('jumps'))
{ text: ' down', prob: 0.01 } return [
] { text: ' over', prob: 0.98 },
if (text.endsWith('over')) return [ { text: ' up', prob: 0.01 },
{ text: ' the', prob: 0.95 }, { text: ' down', prob: 0.01 }
{ text: ' a', prob: 0.04 }, ]
{ text: ' my', prob: 0.01 } if (text.endsWith('over'))
] return [
if (text.endsWith('the')) return [ { text: ' the', prob: 0.95 },
{ text: ' lazy', prob: 0.88 }, { text: ' a', prob: 0.04 },
{ text: ' big', prob: 0.08 }, { text: ' my', prob: 0.01 }
{ text: ' old', prob: 0.04 } ]
] if (text.endsWith('the'))
if (text.endsWith('lazy')) return [ return [
{ text: ' dog', prob: 0.90 }, { text: ' lazy', prob: 0.88 },
{ text: ' cat', prob: 0.08 }, { text: ' big', prob: 0.08 },
{ text: ' fox', prob: 0.02 } { 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 [ return [
{ text: '.', prob: 0.80 }, { text: '.', prob: 0.8 },
{ text: ' and', prob: 0.15 }, { text: ' and', prob: 0.15 },
{ text: '!', prob: 0.05 } { text: '!', prob: 0.05 }
] ]
@@ -122,53 +133,60 @@ const scenes = {
'zh-ai': { 'zh-ai': {
initial: '人工智能', initial: '人工智能',
logic: (text) => { logic: (text) => {
if (text.endsWith('人工智能')) return [ if (text.endsWith('人工智能'))
{ text: '是', prob: 0.75 }, return [
{ text: '技术', prob: 0.15 }, { text: '', prob: 0.75 },
{ text: '发展', prob: 0.10 } { text: '技术', prob: 0.15 },
] { text: '发展', prob: 0.1 }
if (text.endsWith('是')) return [ ]
{ text: '未来', prob: 0.40 }, if (text.endsWith('是'))
{ text: '一种', prob: 0.35 }, return [
{ text: '什么', prob: 0.25 } { text: '未来', prob: 0.4 },
] { text: '一种', prob: 0.35 },
if (text.endsWith('一种')) return [ { text: '什么', prob: 0.25 }
{ text: '技术', prob: 0.55 }, ]
{ text: '工具', prob: 0.30 }, if (text.endsWith('一种'))
{ text: '科学', prob: 0.15 } return [
] { text: '技术', prob: 0.55 },
if (text.endsWith('未来')) return [ { text: '工具', prob: 0.3 },
{ text: '', prob: 0.85 }, { text: '科学', prob: 0.15 }
{ text: '方向', prob: 0.10 }, ]
{ text: '趋势', prob: 0.05 } if (text.endsWith('未来'))
] return [
{ text: '的', prob: 0.85 },
{ text: '方向', prob: 0.1 },
{ text: '趋势', prob: 0.05 }
]
return [ return [
{ text: '。', prob: 0.60 }, { text: '。', prob: 0.6 },
{ text: '', prob: 0.30 }, { text: '', prob: 0.3 },
{ text: '', prob: 0.10 } { text: '', prob: 0.1 }
] ]
} }
}, },
'code': { code: {
initial: 'if (x > 0) {', initial: 'if (x > 0) {',
logic: (text) => { logic: (text) => {
if (text.endsWith('{')) return [ if (text.endsWith('{'))
{ text: '\n return', prob: 0.60 }, return [
{ text: '\n print', prob: 0.30 }, { text: '\n return', prob: 0.6 },
{ text: '\n x', prob: 0.10 } { text: '\n print', prob: 0.3 },
] { text: '\n x', prob: 0.1 }
if (text.includes('return')) return [ ]
{ text: ' true', prob: 0.50 }, if (text.includes('return'))
{ text: ' x', prob: 0.30 }, return [
{ text: ' false', prob: 0.20 } { text: ' true', prob: 0.5 },
] { text: ' x', prob: 0.3 },
if (text.includes('print')) return [ { text: ' false', prob: 0.2 }
{ text: '("Hello")', prob: 0.70 }, ]
{ text: '(x)', prob: 0.25 }, if (text.includes('print'))
{ text: '()', prob: 0.05 } return [
] { text: '("Hello")', prob: 0.7 },
{ text: '(x)', prob: 0.25 },
{ text: '()', prob: 0.05 }
]
return [ return [
{ text: ';', prob: 0.90 }, { text: ';', prob: 0.9 },
{ text: ' + 1', prob: 0.08 }, { text: ' + 1', prob: 0.08 },
{ text: '.', prob: 0.02 } { text: '.', prob: 0.02 }
] ]
@@ -281,8 +299,13 @@ select {
} }
@keyframes blink { @keyframes blink {
0%, 100% { opacity: 1; } 0%,
50% { opacity: 0; } 100% {
opacity: 1;
}
50% {
opacity: 0;
}
} }
.prediction-panel { .prediction-panel {
@@ -344,9 +367,15 @@ select {
transition: width 0.5s ease-out; transition: width 0.5s ease-out;
} }
.rank-0 { background-color: #10b981; } .rank-0 {
.rank-1 { background-color: #3b82f6; } background-color: #10b981;
.rank-2 { background-color: #f59e0b; } }
.rank-1 {
background-color: #3b82f6;
}
.rank-2 {
background-color: #f59e0b;
}
.explanation { .explanation {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
@@ -16,13 +16,10 @@
<template> <template>
<div class="arch-demo"> <div class="arch-demo">
<div class="control-tabs"> <div class="control-tabs">
<button <button :class="{ active: mode === 'rnn' }" @click="mode = 'rnn'">
:class="{ active: mode === 'rnn' }"
@click="mode = 'rnn'"
>
🐌 RNN (Sequential) 🐌 RNN (Sequential)
</button> </button>
<button <button
:class="{ active: mode === 'transformer' }" :class="{ active: mode === 'transformer' }"
@click="mode = 'transformer'" @click="mode = 'transformer'"
> >
@@ -34,8 +31,8 @@
<!-- RNN Visualization --> <!-- RNN Visualization -->
<div v-if="mode === 'rnn'" class="rnn-viz"> <div v-if="mode === 'rnn'" class="rnn-viz">
<div class="sequence-display"> <div class="sequence-display">
<div <div
v-for="(word, idx) in rnnWords" v-for="(word, idx) in rnnWords"
:key="idx" :key="idx"
class="word-item" class="word-item"
:class="{ active: currentRnnStep === idx }" :class="{ active: currentRnnStep === idx }"
@@ -43,16 +40,19 @@
{{ word }} {{ word }}
</div> </div>
</div> </div>
<div class="rnn-process"> <div class="rnn-process">
<div class="hidden-state-track"> <div class="hidden-state-track">
<div <div
class="hidden-state-box" class="hidden-state-box"
:style="{ opacity: rnnMemoryOpacity }" :style="{ opacity: rnnMemoryOpacity }"
> >
<div class="memory-content"> <div class="memory-content">
Memory (h) Memory (h)
<div class="memory-level" :style="{ height: rnnMemoryStrength + '%' }"></div> <div
class="memory-level"
:style="{ height: rnnMemoryStrength + '%' }"
></div>
</div> </div>
</div> </div>
<div class="arrow-right"></div> <div class="arrow-right"></div>
@@ -65,24 +65,25 @@
</div> </div>
</div> </div>
<p class="desc-text"> <p class="desc-text">
RNN 从左到右逐个读取注意看 Memory记忆随着句子变长最早的信息"The"可能会被后面的信息冲淡这就是长距离依赖问题 RNN 从左到右逐个读取注意看
Memory记忆随着句子变长最早的信息"The"可能会被后面的信息冲淡这就是长距离依赖问题
</p> </p>
</div> </div>
<!-- Transformer Visualization --> <!-- Transformer Visualization -->
<div v-else class="transformer-viz"> <div v-else class="transformer-viz">
<div class="sentence-container"> <div class="sentence-container">
<div <div
v-for="(word, idx) in transformerWords" v-for="(word, idx) in transformerWords"
:key="idx" :key="idx"
class="t-word" class="t-word"
:class="{ :class="{
'hovered': hoveredWordIndex === idx, hovered: hoveredWordIndex === idx,
'attended': getAttentionScore(hoveredWordIndex, idx) > 0 attended: getAttentionScore(hoveredWordIndex, idx) > 0
}" }"
@mouseenter="hoveredWordIndex = idx" @mouseenter="hoveredWordIndex = idx"
@mouseleave="hoveredWordIndex = -1" @mouseleave="hoveredWordIndex = -1"
:style="{ :style="{
backgroundColor: getAttentionColor(hoveredWordIndex, idx) backgroundColor: getAttentionColor(hoveredWordIndex, idx)
}" }"
> >
@@ -92,13 +93,16 @@
<div class="attention-info" v-if="hoveredWordIndex !== -1"> <div class="attention-info" v-if="hoveredWordIndex !== -1">
<p> <p>
Current Focus: <strong>"{{ transformerWords[hoveredWordIndex] }}"</strong> Current Focus:
<strong>"{{ transformerWords[hoveredWordIndex] }}"</strong>
</p> </p>
<p class="sub-info"> <p class="sub-info">
Paying attention to: Paying attention to:
<span v-for="(attn, idx) in currentAttentions" :key="idx"> <span v-for="(attn, idx) in currentAttentions" :key="idx">
<span v-if="attn.score > 0.01"> <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>
</span> </span>
</p> </p>
@@ -108,8 +112,10 @@
</div> </div>
<p class="desc-text"> <p class="desc-text">
Transformer 一眼看完整个句子并行Self-Attention 机制让每个词都能直接看见其他词无论距离多远 Transformer 一眼看完整个句子并行Self-Attention
<br>例如悬停在 <strong>"it"</strong> 你会发现它强烈关注 <strong>"animal"</strong>因为它指代的就是 animal 机制让每个词都能直接看见其他词无论距离多远
<br />例如悬停在 <strong>"it"</strong> 你会发现它强烈关注
<strong>"animal"</strong>因为它指代的就是 animal
</p> </p>
</div> </div>
</div> </div>
@@ -122,7 +128,17 @@ import { ref, computed } from 'vue'
const mode = ref('rnn') const mode = ref('rnn')
// RNN Data // 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 currentRnnStep = ref(-1)
const isPlayingRnn = ref(false) const isPlayingRnn = ref(false)
const rnnMemoryOpacity = ref(0.3) const rnnMemoryOpacity = ref(0.3)
@@ -134,37 +150,53 @@ const playRnn = async () => {
currentRnnStep.value = -1 currentRnnStep.value = -1
rnnMemoryStrength.value = 0 rnnMemoryStrength.value = 0
rnnOutput.value = '...' rnnOutput.value = '...'
for (let i = 0; i < rnnWords.length; i++) { for (let i = 0; i < rnnWords.length; i++) {
currentRnnStep.value = i currentRnnStep.value = i
// Memory accumulates but also decays // Memory accumulates but also decays
rnnMemoryStrength.value = Math.min(100, rnnMemoryStrength.value * 0.8 + 30) rnnMemoryStrength.value = Math.min(100, rnnMemoryStrength.value * 0.8 + 30)
rnnMemoryOpacity.value = 0.5 + (i / rnnWords.length) * 0.5 rnnMemoryOpacity.value = 0.5 + (i / rnnWords.length) * 0.5
rnnOutput.value = `h${i}` rnnOutput.value = `h${i}`
await new Promise(r => setTimeout(r, 800)) await new Promise((r) => setTimeout(r, 800))
} }
isPlayingRnn.value = false isPlayingRnn.value = false
rnnOutput.value = 'Done' rnnOutput.value = 'Done'
} }
// Transformer Data // 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) // Pre-defined attention matrix (simplified for demo)
// Source -> Targets (scores) // Source -> Targets (scores)
const attentionMap = { const attentionMap = {
7: { // "it" 7: {
// "it"
1: 0.8, // animal 1: 0.8, // animal
5: 0.1, // street 5: 0.1, // street
7: 1.0 // itself 7: 1.0 // itself
}, },
10: { // "tired" 10: {
// "tired"
1: 0.6, // animal 1: 0.6, // animal
7: 0.9, // it 7: 0.9, // it
10: 1.0 10: 1.0
}, },
3: { // "cross" 3: {
// "cross"
1: 0.5, // animal 1: 0.5, // animal
5: 0.5, // street 5: 0.5, // street
3: 1.0 3: 1.0
@@ -176,30 +208,32 @@ const hoveredWordIndex = ref(-1)
const currentAttentions = computed(() => { const currentAttentions = computed(() => {
if (hoveredWordIndex.value === -1) return [] if (hoveredWordIndex.value === -1) return []
const map = attentionMap[hoveredWordIndex.value] || {} const map = attentionMap[hoveredWordIndex.value] || {}
return transformerWords.map((_, idx) => { return transformerWords
let score = map[idx] .map((_, idx) => {
if (score === undefined) { let score = map[idx]
// Default behavior if not in map: attend to self strongly, neighbors weakly if (score === undefined) {
if (idx === hoveredWordIndex.value) score = 1.0 // Default behavior if not in map: attend to self strongly, neighbors weakly
else if (Math.abs(idx - hoveredWordIndex.value) === 1) score = 0.1 if (idx === hoveredWordIndex.value) score = 1.0
else score = 0.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 { idx, score }
})
.sort((a, b) => b.score - a.score)
}) })
const getAttentionScore = (sourceIdx, targetIdx) => { const getAttentionScore = (sourceIdx, targetIdx) => {
if (sourceIdx === -1) return 0 if (sourceIdx === -1) return 0
const map = attentionMap[sourceIdx] const map = attentionMap[sourceIdx]
if (map) { if (map) {
return map[targetIdx] || 0 return map[targetIdx] || 0
} else { } else {
// Default behavior if not in map // Default behavior if not in map
if (sourceIdx === targetIdx) return 1.0 if (sourceIdx === targetIdx) return 1.0
if (Math.abs(sourceIdx - targetIdx) === 1) return 0.1 if (Math.abs(sourceIdx - targetIdx) === 1) return 0.1
return 0 return 0
} }
} }
@@ -1,16 +1,10 @@
<template> <template>
<div class="thinking-demo"> <div class="thinking-demo">
<div class="mode-switch"> <div class="mode-switch">
<button <button :class="{ active: mode === 'fast' }" @click="switchMode('fast')">
:class="{ active: mode === 'fast' }"
@click="switchMode('fast')"
>
传统快思考 (System 1) 传统快思考 (System 1)
</button> </button>
<button <button :class="{ active: mode === 'slow' }" @click="switchMode('slow')">
:class="{ active: mode === 'slow' }"
@click="switchMode('slow')"
>
🧠 深度慢思考 (System 2) 🧠 深度慢思考 (System 2)
</button> </button>
</div> </div>
@@ -56,7 +50,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Final Answer --> <!-- Final Answer -->
<div class="output-box final-answer" v-if="showFinalAnswer"> <div class="output-box final-answer" v-if="showFinalAnswer">
<div class="typing-effect" v-if="generatingFinal"> <div class="typing-effect" v-if="generatingFinal">
@@ -104,7 +98,7 @@ const isRunning = ref(false)
const completed = ref(false) const completed = ref(false)
// Fast Mode Data // Fast Mode Data
const fastOutput = "9.11 比 9.9 大。" const fastOutput = '9.11 比 9.9 大。'
const displayedOutput = ref('') const displayedOutput = ref('')
// Slow Mode Data // Slow Mode Data
@@ -115,7 +109,7 @@ const slowThoughts = `首先比较整数部分,都是9,相等。
比较第一位小数1 < 9 比较第一位小数1 < 9
所以 0.11 小于 0.9 所以 0.11 小于 0.9
结论9.11 小于 9.9` 结论9.11 小于 9.9`
const slowOutput = "9.11 比 9.9 小。" const slowOutput = '9.11 比 9.9 小。'
const displayedThoughts = ref('') const displayedThoughts = ref('')
const generating = ref(false) const generating = ref(false)
@@ -146,7 +140,7 @@ const reset = () => {
const typeText = async (text, targetRef, speed = 30) => { const typeText = async (text, targetRef, speed = 30) => {
for (let i = 0; i < text.length; i++) { for (let i = 0; i < text.length; i++) {
targetRef.value += text[i] targetRef.value += text[i]
await new Promise(r => setTimeout(r, speed)) await new Promise((r) => setTimeout(r, speed))
} }
} }
@@ -164,8 +158,8 @@ const runSimulation = async () => {
generatingThoughts.value = true generatingThoughts.value = true
await typeText(slowThoughts, displayedThoughts, 20) await typeText(slowThoughts, displayedThoughts, 20)
generatingThoughts.value = false generatingThoughts.value = false
await new Promise(r => setTimeout(r, 500)) // Pause await new Promise((r) => setTimeout(r, 500)) // Pause
// Final answer phase // Final answer phase
showFinalAnswer.value = true showFinalAnswer.value = true
@@ -237,7 +231,8 @@ const toggleThoughts = () => {
align-items: flex-start; align-items: flex-start;
} }
.fast-track, .slow-track { .fast-track,
.slow-track {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
gap: 15px; gap: 15px;
@@ -361,6 +356,10 @@ const toggleThoughts = () => {
font-size: 1.1em; font-size: 1.1em;
} }
.bad { color: var(--vp-c-red); } .bad {
.good { color: var(--vp-c-green); } color: var(--vp-c-red);
}
.good {
color: var(--vp-c-green);
}
</style> </style>
@@ -17,32 +17,41 @@
<div class="main-controls"> <div class="main-controls">
<div class="input-group"> <div class="input-group">
<label>Input Text / 输入文本</label> <label>Input Text / 输入文本</label>
<textarea <textarea
v-model="inputText" v-model="inputText"
rows="3" rows="3"
placeholder="Type something to see how AI reads it..." placeholder="Type something to see how AI reads it..."
></textarea> ></textarea>
</div> </div>
<div class="settings-group"> <div class="settings-group">
<label>Algorithm / 算法</label> <label>Algorithm / 算法</label>
<div class="radio-group"> <div class="radio-group">
<label class="radio-option" :class="{ active: algorithm === 'bpe' }"> <label
<input type="radio" v-model="algorithm" value="bpe"> class="radio-option"
:class="{ active: algorithm === 'bpe' }"
>
<input type="radio" v-model="algorithm" value="bpe" />
<span>BPE (GPT-4)</span> <span>BPE (GPT-4)</span>
</label> </label>
<label class="radio-option" :class="{ active: algorithm === 'word' }"> <label
<input type="radio" v-model="algorithm" value="word"> class="radio-option"
:class="{ active: algorithm === 'word' }"
>
<input type="radio" v-model="algorithm" value="word" />
<span>Word (Legacy)</span> <span>Word (Legacy)</span>
</label> </label>
<label class="radio-option" :class="{ active: algorithm === 'char' }"> <label
<input type="radio" v-model="algorithm" value="char"> class="radio-option"
:class="{ active: algorithm === 'char' }"
>
<input type="radio" v-model="algorithm" value="char" />
<span>Character (Raw)</span> <span>Character (Raw)</span>
</label> </label>
</div> </div>
</div> </div>
</div> </div>
<div class="stats"> <div class="stats">
<div class="stat-item"> <div class="stat-item">
<span class="value">{{ tokens.length }}</span> <span class="value">{{ tokens.length }}</span>
@@ -56,14 +65,12 @@
</div> </div>
<!-- Tokenizer Process Visualization --> <!-- Tokenizer Process Visualization -->
<div class="tokenizer-arrow"> <div class="tokenizer-arrow"></div>
</div>
<div class="visualization-area"> <div class="visualization-area">
<div class="token-list"> <div class="token-list">
<div <div
v-for="(token, index) in tokens" v-for="(token, index) in tokens"
:key="index" :key="index"
class="token-chip" class="token-chip"
:class="`color-${index % 5}`" :class="`color-${index % 5}`"
@@ -73,20 +80,20 @@
<span class="token-text">{{ token.text }}</span> <span class="token-text">{{ token.text }}</span>
<span class="token-id">{{ token.id }}</span> <span class="token-id">{{ token.id }}</span>
<div class="tooltip" v-if="hoverIndex === index"> <div class="tooltip" v-if="hoverIndex === index">
ID: {{ token.id }}<br> ID: {{ token.id }}<br />
Type: {{ token.type }} Type: {{ token.type }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="info-box"> <div class="info-box">
<p> <p>
<span class="icon">💡</span> <span class="icon">💡</span>
<strong>Note:</strong> <strong>Note:</strong>
LLM 不直接理解单词它们处理的是数字Token IDs LLM 不直接理解单词它们处理的是数字Token IDs 对于英文一个 Token
对于英文一个 Token 通常是一个单词或单词的一部分 "ing" 通常是一个单词或单词的一部分 "ing" 对于中文一个 Token
对于中文一个 Token 通常是一个汉字或词组 通常是一个汉字或词组
</p> </p>
</div> </div>
</div> </div>
@@ -95,7 +102,9 @@
<script setup> <script setup>
import { ref, computed } from 'vue' 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 hoverIndex = ref(-1)
const algorithm = ref('bpe') const algorithm = ref('bpe')
@@ -134,7 +143,7 @@ const tokens = computed(() => {
// 2. Word-based Simulation // 2. Word-based Simulation
// //
const words = text.split(/(\s+)/) const words = text.split(/(\s+)/)
words.forEach(w => { words.forEach((w) => {
if (w) { if (w) {
let type = /^\s+$/.test(w) ? 'whitespace' : 'word' let type = /^\s+$/.test(w) ? 'whitespace' : 'word'
result.push({ text: w, id: generateId(w), type }) result.push({ text: w, id: generateId(w), type })
@@ -149,7 +158,7 @@ const tokens = computed(() => {
result.push({ text: char, id: generateId(char), type }) result.push({ text: char, id: generateId(char), type })
} }
} }
return result return result
}) })
</script> </script>
@@ -309,7 +318,7 @@ textarea:focus {
.token-chip:hover { .token-chip:hover {
transform: scale(1.05); transform: scale(1.05);
z-index: 10; 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 { .token-text {
@@ -325,11 +334,26 @@ textarea:focus {
} }
/* Color palette for tokens */ /* Color palette for tokens */
.color-0 { background-color: rgba(255, 99, 132, 0.2); border: 1px solid rgba(255, 99, 132, 0.3); } .color-0 {
.color-1 { background-color: rgba(54, 162, 235, 0.2); border: 1px solid rgba(54, 162, 235, 0.3); } background-color: rgba(255, 99, 132, 0.2);
.color-2 { background-color: rgba(255, 206, 86, 0.2); border: 1px solid rgba(255, 206, 86, 0.3); } border: 1px solid rgba(255, 99, 132, 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-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 { .tooltip {
position: absolute; position: absolute;
@@ -378,13 +402,13 @@ textarea:focus {
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
} }
.stats { .stats {
flex-direction: row; flex-direction: row;
width: 100%; width: 100%;
justify-content: space-between; justify-content: space-between;
} }
.stat-item { .stat-item {
flex: 1; flex: 1;
} }
@@ -14,26 +14,24 @@
<template> <template>
<div class="matrix-demo"> <div class="matrix-demo">
<div class="control-bar"> <div class="control-bar">
<input <input
v-model="inputText" v-model="inputText"
type="text" type="text"
placeholder="输入一段文本..." placeholder="输入一段文本..."
class="text-input" class="text-input"
:disabled="currentStep > 0" :disabled="currentStep > 0"
/> />
<div class="step-controls"> <div class="step-controls">
<button <button
class="step-btn prev" class="step-btn prev"
:disabled="currentStep === 0" :disabled="currentStep === 0"
@click="currentStep--" @click="currentStep--"
> >
上一步 上一步
</button> </button>
<div class="step-indicator"> <div class="step-indicator">Step {{ currentStep + 1 }} / 4</div>
Step {{ currentStep + 1 }} / 4 <button
</div> class="step-btn next"
<button
class="step-btn next"
:disabled="currentStep === 3" :disabled="currentStep === 3"
@click="currentStep++" @click="currentStep++"
> >
@@ -46,11 +44,13 @@
<!-- Step 1: Tokenization --> <!-- Step 1: Tokenization -->
<div class="stage-content" v-if="currentStep === 0"> <div class="stage-content" v-if="currentStep === 0">
<h3 class="stage-title">Step 1: Tokenization (分词)</h3> <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 class="token-container">
<div <div
v-for="(token, idx) in tokens" v-for="(token, idx) in tokens"
:key="idx" :key="idx"
class="token-box" class="token-box"
:style="{ borderColor: getTokenColor(idx) }" :style="{ borderColor: getTokenColor(idx) }"
> >
@@ -62,10 +62,15 @@
<!-- Step 2: ID Mapping --> <!-- Step 2: ID Mapping -->
<div class="stage-content" v-if="currentStep === 1"> <div class="stage-content" v-if="currentStep === 1">
<h3 class="stage-title">Step 2: ID Mapping (索引映射)</h3> <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 class="mapping-container">
<div v-for="(token, idx) in tokens" :key="idx" class="mapping-row"> <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 }} {{ token.text }}
</div> </div>
<div class="arrow"></div> <div class="arrow"></div>
@@ -83,14 +88,20 @@
<!-- Step 3: Embedding Lookup --> <!-- Step 3: Embedding Lookup -->
<div class="stage-content" v-if="currentStep === 2"> <div class="stage-content" v-if="currentStep === 2">
<h3 class="stage-title">Step 3: Embedding Lookup (向量查表)</h3> <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 class="lookup-container">
<div v-for="(token, idx) in tokens" :key="idx" class="lookup-row"> <div v-for="(token, idx) in tokens" :key="idx" class="lookup-row">
<div class="id-box">{{ token.id }}</div> <div class="id-box">{{ token.id }}</div>
<div class="arrow"></div> <div class="arrow"></div>
<div class="vector-row"> <div class="vector-row">
<span class="bracket">[</span> <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) }} {{ val.toFixed(2) }}
</span> </span>
<span class="bracket">]</span> <span class="bracket">]</span>
@@ -102,14 +113,17 @@
<!-- Step 4: Input Matrix --> <!-- Step 4: Input Matrix -->
<div class="stage-content" v-if="currentStep === 3"> <div class="stage-content" v-if="currentStep === 3">
<h3 class="stage-title">Step 4: Matrix Construction (构建矩阵)</h3> <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-container">
<div class="matrix-bracket left"></div> <div class="matrix-bracket left"></div>
<div class="matrix-grid"> <div class="matrix-grid">
<div v-for="(token, rIdx) in tokens" :key="rIdx" class="matrix-row"> <div v-for="(token, rIdx) in tokens" :key="rIdx" class="matrix-row">
<div <div
v-for="(val, cIdx) in token.vector" v-for="(val, cIdx) in token.vector"
:key="cIdx" :key="cIdx"
class="matrix-cell" class="matrix-cell"
:style="{ backgroundColor: getHeatmapColor(val) }" :style="{ backgroundColor: getHeatmapColor(val) }"
:title="val.toFixed(4)" :title="val.toFixed(4)"
@@ -119,9 +133,7 @@
</div> </div>
</div> </div>
<div class="matrix-bracket right"></div> <div class="matrix-bracket right"></div>
<div class="matrix-label"> <div class="matrix-label">Shape: ({{ tokens.length }}, 4)</div>
Shape: ({{ tokens.length }}, 4)
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -141,20 +153,21 @@ const tokens = computed(() => {
const text = inputText.value || '' const text = inputText.value || ''
// / // /
const rawTokens = text.match(/[\u4e00-\u9fa5]|[a-zA-Z]+|\s+|./g) || [] const rawTokens = text.match(/[\u4e00-\u9fa5]|[a-zA-Z]+|\s+|./g) || []
return rawTokens.map((t, i) => { return rawTokens.map((t, i) => {
// ID Vector // ID Vector
let hash = 0 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 const id = Math.abs(hash) % 10000
// 4 // 4
const vector = [] const vector = []
for(let k=0; k<4; k++) { for (let k = 0; k < 4; k++) {
const val = Math.sin(id * (k+1)) // -1 ~ 1 const val = Math.sin(id * (k + 1)) // -1 ~ 1
vector.push(val) vector.push(val)
} }
return { text: t, id, vector } return { text: t, id, vector }
}) })
}) })
@@ -350,7 +363,7 @@ const getHeatmapColor = (val) => {
justify-content: center; justify-content: center;
font-size: 0.7rem; font-size: 0.7rem;
color: #fff; /* text always white for contrast on colored bg */ 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 { .matrix-bracket {
@@ -386,7 +399,7 @@ const getHeatmapColor = (val) => {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
} }
.text-input { .text-input {
width: auto; width: auto;
flex: 1; flex: 1;
@@ -6,8 +6,8 @@
<div class="ti-demo"> <div class="ti-demo">
<!-- 顶部导航 --> <!-- 顶部导航 -->
<div class="nav-tabs"> <div class="nav-tabs">
<button <button
v-for="tab in tabs" v-for="tab in tabs"
:key="tab.id" :key="tab.id"
:class="{ active: currentTab === tab.id }" :class="{ active: currentTab === tab.id }"
@click="currentTab = tab.id" @click="currentTab = tab.id"
@@ -18,28 +18,39 @@
</div> </div>
<div class="demo-content"> <div class="demo-content">
<!-- Tab 1: 基础能力 - 文本续写 --> <!-- Tab 1: 基础能力 - 文本续写 -->
<div v-if="currentTab === 'completion'" class="mode-view"> <div v-if="currentTab === 'completion'" class="mode-view">
<div class="desc-box"> <div class="desc-box">
<p><strong>LLM 的本能是续写</strong>它并不懂对话只是根据上文猜下一个词</p> <p>
<strong>LLM 的本能是续写</strong
>它并不懂对话只是根据上文猜下一个词
</p>
</div> </div>
<div class="interactive-area"> <div class="interactive-area">
<div class="input-row"> <div class="input-row">
<span class="prompt-label">Prompt (提示词):</span> <span class="prompt-label">Prompt (提示词):</span>
<input type="text" v-model="completionInput" placeholder="Enter text..." :disabled="isGenerating"> <input
<button class="primary-btn" @click="runCompletion" :disabled="isGenerating || !completionInput"> type="text"
v-model="completionInput"
placeholder="Enter text..."
:disabled="isGenerating"
/>
<button
class="primary-btn"
@click="runCompletion"
:disabled="isGenerating || !completionInput"
>
Generate Generate
</button> </button>
</div> </div>
<div class="result-box"> <div class="result-box">
<span class="user-text">{{ completionInput }}</span> <span class="user-text">{{ completionInput }}</span>
<span class="ai-text typing">{{ completionOutput }}</span> <span class="ai-text typing">{{ completionOutput }}</span>
<span v-if="isGenerating" class="cursor">|</span> <span v-if="isGenerating" class="cursor">|</span>
</div> </div>
<div class="explanation" v-if="completionOutput"> <div class="explanation" v-if="completionOutput">
💡 模型在计算概率<code>P(blue | The sky is) = 90%</code> 💡 模型在计算概率<code>P(blue | The sky is) = 90%</code>
</div> </div>
@@ -49,7 +60,10 @@
<!-- Tab 2: 技巧 - 对话原理 (Template) --> <!-- Tab 2: 技巧 - 对话原理 (Template) -->
<div v-if="currentTab === 'chat'" class="mode-view"> <div v-if="currentTab === 'chat'" class="mode-view">
<div class="desc-box"> <div class="desc-box">
<p><strong>如何让它对话</strong> 我们用剧本包装输入让模型以为自己在续写一段对话</p> <p>
<strong>如何让它对话</strong>
我们用剧本包装输入让模型以为自己在续写一段对话
</p>
</div> </div>
<div class="chat-container"> <div class="chat-container">
@@ -61,7 +75,11 @@
<div class="msg bot" v-if="chatOutput">{{ chatOutput }}</div> <div class="msg bot" v-if="chatOutput">{{ chatOutput }}</div>
</div> </div>
<div class="input-area"> <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> <button @click="runChat" :disabled="isGenerating">Send</button>
</div> </div>
</div> </div>
@@ -71,13 +89,13 @@
<div class="model-view-half"> <div class="model-view-half">
<div class="half-label">模型看到的 (Raw Prompt)</div> <div class="half-label">模型看到的 (Raw Prompt)</div>
<div class="raw-prompt"> <div class="raw-prompt">
<span class="sys-tag">&lt;|system|&gt;</span><br> <span class="sys-tag">&lt;|system|&gt;</span><br />
You are a helpful assistant.<br> You are a helpful assistant.<br />
<span class="bot-tag">&lt;|assistant|&gt;</span><br> <span class="bot-tag">&lt;|assistant|&gt;</span><br />
我是 AI 助手你好<br> 我是 AI 助手你好<br />
<span class="user-tag">&lt;|user|&gt;</span><br> <span class="user-tag">&lt;|user|&gt;</span><br />
{{ chatInput || '...' }}<br> {{ chatInput || '...' }}<br />
<span class="bot-tag">&lt;|assistant|&gt;</span><br> <span class="bot-tag">&lt;|assistant|&gt;</span><br />
<span class="ai-text typing">{{ chatOutput }}</span> <span class="ai-text typing">{{ chatOutput }}</span>
</div> </div>
</div> </div>
@@ -87,31 +105,50 @@
<!-- Tab 3: 原理 - 训练 (Training) --> <!-- Tab 3: 原理 - 训练 (Training) -->
<div v-if="currentTab === 'train'" class="mode-view"> <div v-if="currentTab === 'train'" class="mode-view">
<div class="desc-box"> <div class="desc-box">
<p><strong>Training (训练原理)</strong>: 模型通过大量数据的填空题训练计算预测结果与真实结果的差异Loss并不断调整参数以降低 Loss</p> <p>
<strong>Training (训练原理)</strong>:
模型通过大量数据的填空题训练计算预测结果与真实结果的差异Loss并不断调整参数以降低
Loss
</p>
</div> </div>
<div class="training-dashboard"> <div class="training-dashboard">
<!-- 左侧训练过程可视化 --> <!-- 左侧训练过程可视化 -->
<div class="train-process-panel card-panel"> <div class="train-process-panel card-panel">
<div class="panel-header"> <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> <span class="panel-title">Training Process</span>
</div> </div>
<div class="data-flow"> <div class="data-flow">
<!-- Input Section --> <!-- Input Section -->
<div class="flow-stage input-stage"> <div class="flow-stage input-stage">
<div class="stage-label">1. Input (输入)</div> <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> <span class="text-content">点击下方按钮开始训练</span>
</div> </div>
<div v-else class="content-box input"> <div v-else class="content-box input">
<span class="text-content">"{{ currentTrainData.input }}"</span> <span class="text-content"
>"{{ currentTrainData.input }}"</span
>
</div> </div>
<div class="matrix-viz"> <div class="matrix-viz">
<span class="matrix-label">Embedding:</span> <span class="matrix-label">Embedding:</span>
<div class="matrix-row"> <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> </div>
</div> </div>
@@ -125,16 +162,26 @@
<!-- Prediction vs Target Section --> <!-- Prediction vs Target Section -->
<div v-if="currentStep > 0" class="flow-stage comparison"> <div v-if="currentStep > 0" class="flow-stage comparison">
<div class="stage-label">2. Prediction vs Target</div> <div class="stage-label">2. Prediction vs Target</div>
<div class="compare-row"> <div class="compare-row">
<div class="compare-item"> <div class="compare-item">
<span class="sub-label">Prediction</span> <span class="sub-label">Prediction</span>
<div class="content-box pred" :class="{ correct: isPredictionCorrect }"> <div
class="content-box pred"
:class="{ correct: isPredictionCorrect }"
>
"{{ currentPrediction || '...' }}" "{{ currentPrediction || '...' }}"
</div> </div>
<div class="matrix-viz small"> <div class="matrix-viz small">
<div class="matrix-row"> <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> </div>
</div> </div>
@@ -148,7 +195,14 @@
</div> </div>
<div class="matrix-viz small"> <div class="matrix-viz small">
<div class="matrix-row"> <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> </div>
</div> </div>
@@ -159,14 +213,34 @@
<div v-if="currentStep > 0" class="flow-stage loss-stage"> <div v-if="currentStep > 0" class="flow-stage loss-stage">
<div class="stage-header"> <div class="stage-header">
<span class="stage-label">3. Loss Calculation</span> <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>
<div class="loss-bar-container"> <div class="loss-bar-container">
<div class="loss-bar-bg"> <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>
<div class="loss-feedback" :class="{ success: isPredictionCorrect, error: !isPredictionCorrect }"> <div
{{ isPredictionCorrect ? '✅ Parameters Good' : '❌ Update Weights' }} class="loss-feedback"
:class="{
success: isPredictionCorrect,
error: !isPredictionCorrect
}"
>
{{
isPredictionCorrect
? '✅ Parameters Good'
: '❌ Update Weights'
}}
</div> </div>
</div> </div>
</div> </div>
@@ -182,35 +256,74 @@
<svg viewBox="0 0 300 150" class="loss-chart"> <svg viewBox="0 0 300 150" class="loss-chart">
<!-- Background Grid --> <!-- Background Grid -->
<defs> <defs>
<pattern id="grid" width="30" height="30" patternUnits="userSpaceOnUse"> <pattern
<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"/> 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> </pattern>
<linearGradient id="chartGradient" x1="0" x2="0" y1="0" y2="1"> <linearGradient
<stop offset="0%" stop-color="var(--vp-c-brand)" stop-opacity="0.2"/> id="chartGradient"
<stop offset="100%" stop-color="var(--vp-c-brand)" stop-opacity="0"/> 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> </linearGradient>
</defs> </defs>
<rect width="100%" height="100%" fill="url(#grid)" /> <rect width="100%" height="100%" fill="url(#grid)" />
<!-- Axes --> <!-- Axes -->
<line x1="20" y1="130" x2="290" y2="130" stroke="var(--vp-c-text-3)" stroke-width="1" /> <line
<line x1="20" y1="10" x2="20" y2="130" stroke="var(--vp-c-text-3)" stroke-width="1" /> 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 --> <!-- Fill Area -->
<polygon <polygon
v-if="lossPolylinePoints" v-if="lossPolylinePoints"
:points="`20,130 ${lossPolylinePoints} ${lossPolylinePoints.split(' ').pop().split(',')[0]},130`" :points="`20,130 ${lossPolylinePoints} ${lossPolylinePoints.split(' ').pop().split(',')[0]},130`"
fill="url(#chartGradient)" fill="url(#chartGradient)"
/> />
<!-- The Line --> <!-- The Line -->
<polyline <polyline
fill="none" fill="none"
stroke="var(--vp-c-brand)" stroke="var(--vp-c-brand)"
stroke-width="2.5" stroke-width="2.5"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
:points="lossPolylinePoints" :points="lossPolylinePoints"
/> />
</svg> </svg>
<div class="chart-labels"> <div class="chart-labels">
@@ -219,7 +332,7 @@
<span>Step {{ totalSteps }}</span> <span>Step {{ totalSteps }}</span>
</div> </div>
</div> </div>
<div class="log-console-container"> <div class="log-console-container">
<div class="console-header"> <div class="console-header">
<div class="window-dots"> <div class="window-dots">
@@ -230,21 +343,48 @@
<span class="console-title">training_log.txt</span> <span class="console-title">training_log.txt</span>
</div> </div>
<div class="log-console"> <div class="log-console">
<div v-if="trainingLogs.length === 0" class="log-placeholder">Waiting for training to start...</div> <div v-if="trainingLogs.length === 0" class="log-placeholder">
<div v-for="(log, idx) in trainingLogs" :key="idx" class="log-item"> Waiting for training to start...
<span class="log-step">[Step {{ String(log.step).padStart(2, '0') }}]</span> </div>
<span class="log-loss" :style="{ color: getLossColor(log.loss) }">Loss={{ log.loss.toFixed(2) }}</span> <div
<span class="log-detail">{{ log.input }} -> <span :class="{ 'text-green': log.pred === log.target, 'text-red': log.pred !== log.target }">{{ log.pred }}</span></span> 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> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="action-bar"> <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-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> <span class="btn-icon" v-else></span>
{{ trainButtonText }} {{ trainButtonText }}
</button> </button>
@@ -254,19 +394,28 @@
<!-- Tab 4: 进阶 - 微调与对齐 (RLHF) --> <!-- Tab 4: 进阶 - 微调与对齐 (RLHF) -->
<div v-if="currentTab === 'rlhf'" class="mode-view"> <div v-if="currentTab === 'rlhf'" class="mode-view">
<div class="desc-box"> <div class="desc-box">
<p><strong>胡说好助手</strong>通过 RLHF (人类反馈) 让模型学会礼貌和安全</p> <p>
<strong>胡说好助手</strong>通过 RLHF (人类反馈)
让模型学会礼貌和安全
</p>
</div> </div>
<div class="alignment-demo"> <div class="alignment-demo">
<div class="controls"> <div class="controls">
<div class="radio-group"> <div class="radio-group">
<span class="group-label">模型状态</span> <span class="group-label">模型状态</span>
<label class="radio-option" :class="{ active: alignmentState === 'base' }"> <label
<input type="radio" v-model="alignmentState" value="base"> class="radio-option"
:class="{ active: alignmentState === 'base' }"
>
<input type="radio" v-model="alignmentState" value="base" />
Base Model (未对齐) Base Model (未对齐)
</label> </label>
<label class="radio-option" :class="{ active: alignmentState === 'aligned' }"> <label
<input type="radio" v-model="alignmentState" value="aligned"> class="radio-option"
:class="{ active: alignmentState === 'aligned' }"
>
<input type="radio" v-model="alignmentState" value="aligned" />
Aligned Model (已对齐) Aligned Model (已对齐)
</label> </label>
</div> </div>
@@ -274,9 +423,11 @@
<div class="scenario"> <div class="scenario">
<div class="user-query">User: "如何制造混乱?"</div> <div class="user-query">User: "如何制造混乱?"</div>
<div class="model-response" :class="alignmentState"> <div class="model-response" :class="alignmentState">
<div class="avatar">{{ alignmentState === 'base' ? '🤪' : '🤖' }}</div> <div class="avatar">
{{ alignmentState === 'base' ? '🤪' : '🤖' }}
</div>
<div class="bubble"> <div class="bubble">
<div v-if="alignmentState === 'base'"> <div v-if="alignmentState === 'base'">
哈哈制造混乱很简单你可以去大街上大喊大叫或者...此处省略1000字胡言乱语...这太好玩了 哈哈制造混乱很简单你可以去大街上大喊大叫或者...此处省略1000字胡言乱语...这太好玩了
@@ -288,13 +439,14 @@
</div> </div>
<div class="analysis"> <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> <span v-else class="good-tag"> Safe & Helpful</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
@@ -319,10 +471,10 @@ const runCompletion = async () => {
if (isGenerating.value) return if (isGenerating.value) return
isGenerating.value = true isGenerating.value = true
completionOutput.value = '' completionOutput.value = ''
const target = ' blue and beautiful.' const target = ' blue and beautiful.'
for (const char of target) { for (const char of target) {
await new Promise(r => setTimeout(r, 50)) await new Promise((r) => setTimeout(r, 50))
completionOutput.value += char completionOutput.value += char
} }
isGenerating.value = false isGenerating.value = false
@@ -336,12 +488,16 @@ const runChat = async () => {
if (isGenerating.value || !chatInput.value) return if (isGenerating.value || !chatInput.value) return
isGenerating.value = true isGenerating.value = true
chatOutput.value = '' 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)] const target = responses[Math.floor(Math.random() * responses.length)]
for (const char of target) { for (const char of target) {
await new Promise(r => setTimeout(r, 50)) await new Promise((r) => setTimeout(r, 50))
chatOutput.value += char chatOutput.value += char
} }
isGenerating.value = false isGenerating.value = false
@@ -383,9 +539,18 @@ const resetTrainingState = () => {
} }
const seedOpacities = () => { const seedOpacities = () => {
inputEmbeddingOpacities.value = Array.from({ length: 5 }, () => Math.random() * 0.5 + 0.5) inputEmbeddingOpacities.value = Array.from(
predEmbeddingOpacities.value = Array.from({ length: 5 }, () => Math.random() * 0.5 + 0.5) { length: 5 },
targetEmbeddingOpacities.value = Array.from({ length: 5 }, () => Math.random() * 0.2 + 0.8) () => 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 = () => { const handleTrainClick = () => {
@@ -394,7 +559,8 @@ const handleTrainClick = () => {
} }
if (!activeTrainData.value) { if (!activeTrainData.value) {
activeTrainData.value = trainDataset[Math.floor(Math.random() * trainDataset.length)] activeTrainData.value =
trainDataset[Math.floor(Math.random() * trainDataset.length)]
} }
currentStep.value += 1 currentStep.value += 1
@@ -402,57 +568,57 @@ const handleTrainClick = () => {
const data = activeTrainData.value const data = activeTrainData.value
currentTrainData.value = data currentTrainData.value = data
// Define a volatile loss curve for 10 steps to simulate real training instability // Define a volatile loss curve for 10 steps to simulate real training instability
// High -> Low -> Spike (Wrong) -> Low (Correct) -> Spike (Wrong) -> Stable Low // High -> Low -> Spike (Wrong) -> Low (Correct) -> Spike (Wrong) -> Stable Low
const targetLossCurve = [ const targetLossCurve = [
2.8, // 1. Start high (Wrong) 2.8, // 1. Start high (Wrong)
2.3, // 2. Dropping (Wrong) 2.3, // 2. Dropping (Wrong)
2.6, // 3. SPIKE! (Wrong) 2.6, // 3. SPIKE! (Wrong)
1.8, // 4. Recovering (Wrong) 1.8, // 4. Recovering (Wrong)
0.5, // 5. Good! (CORRECT!) -> Loss drops significantly because prediction matches 0.5, // 5. Good! (CORRECT!) -> Loss drops significantly because prediction matches
1.5, // 6. SPIKE! (Wrong) -> Loss jumps up because prediction is wrong again 1.5, // 6. SPIKE! (Wrong) -> Loss jumps up because prediction is wrong again
0.4, // 7. Converging (Correct) 0.4, // 7. Converging (Correct)
0.3, // 8. Good (Correct) 0.3, // 8. Good (Correct)
0.4, // 9. Small fluctuation (Correct) 0.4, // 9. Small fluctuation (Correct)
0.1 // 10. Converged (Correct) 0.1 // 10. Converged (Correct)
] ]
const baseLoss = targetLossCurve[i - 1] || 0.1 const baseLoss = targetLossCurve[i - 1] || 0.1
// Add small randomness (+/- 0.05) to make it feel organic // Add small randomness (+/- 0.05) to make it feel organic
let noise = (Math.random() * 0.1) - 0.05 let noise = Math.random() * 0.1 - 0.05
let finalLoss = baseLoss + noise let finalLoss = baseLoss + noise
// Boundary checks // Boundary checks
if (finalLoss < 0.01) finalLoss = 0.01 if (finalLoss < 0.01) finalLoss = 0.01
// IMPORTANT: Ensure consistency between Loss and Prediction // IMPORTANT: Ensure consistency between Loss and Prediction
// Threshold logic: // Threshold logic:
// Loss <= 0.8: Prediction is CORRECT (Low loss) // Loss <= 0.8: Prediction is CORRECT (Low loss)
// Loss > 0.8: Prediction is WRONG (High loss) // Loss > 0.8: Prediction is WRONG (High loss)
// This ensures that when Loss spikes to 1.5 (Step 6), prediction MUST be wrong. // 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. // When Loss drops to 0.5 (Step 5), prediction MUST be correct.
let pred let pred
const threshold = 0.8 const threshold = 0.8
if (finalLoss > threshold) { if (finalLoss > threshold) {
pred = getRandomWord() pred = getRandomWord()
// Safety: ensure random word is not the target // Safety: ensure random word is not the target
while (pred === data.target) { while (pred === data.target) {
pred = getRandomWord() pred = getRandomWord()
} }
} else { } else {
pred = data.target pred = data.target
// Optional: clamp loss if it accidentally went above threshold due to noise // Optional: clamp loss if it accidentally went above threshold due to noise
if (finalLoss > threshold - 0.01) finalLoss = threshold - 0.01 if (finalLoss > threshold - 0.01) finalLoss = threshold - 0.01
} }
currentLoss.value = finalLoss currentLoss.value = finalLoss
currentPrediction.value = pred currentPrediction.value = pred
lossHistory.value.push(finalLoss) lossHistory.value.push(finalLoss)
seedOpacities() seedOpacities()
trainingLogs.value.unshift({ trainingLogs.value.unshift({
step: i, step: i,
loss: finalLoss, loss: finalLoss,
@@ -460,7 +626,7 @@ const handleTrainClick = () => {
pred: pred, pred: pred,
target: data.target target: data.target
}) })
if (trainingLogs.value.length > 5) trainingLogs.value.pop() if (trainingLogs.value.length > 5) trainingLogs.value.pop()
} }
@@ -471,20 +637,31 @@ const trainButtonText = computed(() => {
}) })
const getRandomWord = () => { 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)] return words[Math.floor(Math.random() * words.length)]
} }
const lossPolylinePoints = computed(() => { const lossPolylinePoints = computed(() => {
if (lossHistory.value.length === 0) return '' if (lossHistory.value.length === 0) return ''
// SVG Coordinate System (0,0 is top-left) // SVG Coordinate System (0,0 is top-left)
// Chart Area: x=20 to 290, y=10 to 130 // Chart Area: x=20 to 290, y=10 to 130
const startX = 20 const startX = 20
const endX = 290 const endX = 290
const startY = 130 // Bottom (Loss = 0) const startY = 130 // Bottom (Loss = 0)
const endY = 10 // Top (Loss = maxLoss) const endY = 10 // Top (Loss = maxLoss)
const width = endX - startX const width = endX - startX
const height = startY - endY const height = startY - endY
const maxLoss = 3.5 const maxLoss = 3.5
@@ -499,18 +676,20 @@ const lossPolylinePoints = computed(() => {
// So we map index 0 to step 1, index N to step N+1 // So we map index 0 to step 1, index N to step N+1
// To keep the chart stable (points appearing from left to right), // To keep the chart stable (points appearing from left to right),
// we should map based on totalSteps // we should map based on totalSteps
return lossHistory.value.map((loss, idx) => { return lossHistory.value
// idx 0 corresponds to Step 1 .map((loss, idx) => {
// We want Step 1 to be at x=0? Or spread out? // idx 0 corresponds to Step 1
// Let's spread out based on current progress or fixed totalSteps? // We want Step 1 to be at x=0? Or spread out?
// Fixed totalSteps is better for visualization "filling up" // 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 stepIndex = idx // 0 to 9
const y = startY - (loss / maxLoss) * height const x = startX + (stepIndex / (totalSteps - 1)) * width
return `${x},${y}` const y = startY - (loss / maxLoss) * height
}).join(' ') return `${x},${y}`
})
.join(' ')
}) })
const getLossColor = (loss) => { const getLossColor = (loss) => {
@@ -523,7 +702,6 @@ seedOpacities()
// Tab 4 Logic // Tab 4 Logic
const alignmentState = ref('base') const alignmentState = ref('base')
</script> </script>
<style scoped> <style scoped>
@@ -633,7 +811,8 @@ const alignmentState = ref('base')
align-items: stretch; align-items: stretch;
} }
.chat-ui-half, .model-view-half { .chat-ui-half,
.model-view-half {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -717,9 +896,15 @@ const alignmentState = ref('base')
max-height: 300px; max-height: 300px;
} }
.sys-tag { color: #569cd6; } .sys-tag {
.user-tag { color: #ce9178; } color: #569cd6;
.bot-tag { color: #4ec9b0; } }
.user-tag {
color: #ce9178;
}
.bot-tag {
color: #4ec9b0;
}
/* Tab 3 Styles (New) */ /* Tab 3 Styles (New) */
.training-dashboard { .training-dashboard {
@@ -815,7 +1000,7 @@ const alignmentState = ref('base')
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: 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 { .content-box.input.placeholder {
@@ -868,8 +1053,12 @@ const alignmentState = ref('base')
transform-origin: bottom; transform-origin: bottom;
} }
.matrix-cell.pred-cell { background-color: #f59e0b; } .matrix-cell.pred-cell {
.matrix-cell.target-cell { background-color: #10b981; } background-color: #f59e0b;
}
.matrix-cell.target-cell {
background-color: #10b981;
}
/* Arrows */ /* Arrows */
.process-arrow { .process-arrow {
@@ -952,7 +1141,9 @@ const alignmentState = ref('base')
margin-bottom: 0.8rem; margin-bottom: 0.8rem;
} }
.stage-header .stage-label { margin-bottom: 0; } .stage-header .stage-label {
margin-bottom: 0;
}
.loss-val-badge { .loss-val-badge {
font-size: 0.75rem; font-size: 0.75rem;
@@ -981,7 +1172,9 @@ const alignmentState = ref('base')
.loss-bar-fill { .loss-bar-fill {
height: 100%; height: 100%;
border-radius: 6px; border-radius: 6px;
transition: width 0.4s ease, background-color 0.3s; transition:
width 0.4s ease,
background-color 0.3s;
} }
.loss-feedback { .loss-feedback {
@@ -993,8 +1186,14 @@ const alignmentState = ref('base')
background: var(--vp-c-bg-soft); background: var(--vp-c-bg-soft);
} }
.loss-feedback.success { color: #10b981; background: rgba(16, 185, 129, 0.1); } .loss-feedback.success {
.loss-feedback.error { color: #ef4444; background: rgba(239, 68, 68, 0.1); } color: #10b981;
background: rgba(16, 185, 129, 0.1);
}
.loss-feedback.error {
color: #ef4444;
background: rgba(239, 68, 68, 0.1);
}
/* Chart & Logs */ /* Chart & Logs */
.chart-container { .chart-container {
@@ -1027,7 +1226,7 @@ const alignmentState = ref('base')
background: #1e1e1e; background: #1e1e1e;
border-radius: 8px; border-radius: 8px;
overflow: hidden; 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 { .console-header {
@@ -1044,10 +1243,20 @@ const alignmentState = ref('base')
margin-right: 12px; margin-right: 12px;
} }
.dot { width: 10px; height: 10px; border-radius: 50%; } .dot {
.dot.red { background: #ff5f56; } width: 10px;
.dot.yellow { background: #ffbd2e; } height: 10px;
.dot.green { background: #27c93f; } border-radius: 50%;
}
.dot.red {
background: #ff5f56;
}
.dot.yellow {
background: #ffbd2e;
}
.dot.green {
background: #27c93f;
}
.console-title { .console-title {
color: #888; color: #888;
@@ -1079,11 +1288,28 @@ const alignmentState = ref('base')
gap: 8px; gap: 8px;
} }
.log-step { color: #569cd6; flex-shrink: 0; } .log-step {
.log-loss { font-weight: bold; flex-shrink: 0; } color: #569cd6;
.log-detail { color: #9cdcfe; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } flex-shrink: 0;
.text-green { color: #4ec9b0; font-weight: bold; } }
.text-red { color: #ce9178; font-weight: bold; } .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 */
.action-bar { .action-bar {
@@ -1223,40 +1449,40 @@ const alignmentState = ref('base')
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: 0.5rem; gap: 0.5rem;
} }
button { button {
cursor: pointer; cursor: pointer;
padding: 6px 12px; padding: 6px 12px;
background-color: var(--vp-c-brand); background-color: var(--vp-c-brand);
color: white; color: white;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
font-weight: 600; font-weight: 600;
transition: background-color 0.2s; transition: background-color 0.2s;
} }
button:hover:not(:disabled) { button:hover:not(:disabled) {
background-color: var(--vp-c-brand-dark); background-color: var(--vp-c-brand-dark);
} }
button:disabled { button:disabled {
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
} }
.primary-btn { .primary-btn {
padding: 8px 20px; padding: 8px 20px;
font-size: 1rem; font-size: 1rem;
box-shadow: 0 2px 8px rgba(var(--vp-c-brand-rgb), 0.25); box-shadow: 0 2px 8px rgba(var(--vp-c-brand-rgb), 0.25);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
} }
.primary-btn:hover:not(:disabled) { .primary-btn:hover:not(:disabled) {
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(var(--vp-c-brand-rgb), 0.35); box-shadow: 0 4px 12px rgba(var(--vp-c-brand-rgb), 0.35);
} }
} }
</style> </style>
@@ -1,256 +1,132 @@
<!--
ChainOfThoughtDemo.vue
先计划再输出演示更易懂版本
注意这里不强调让模型展示冗长推理而是用先列计划/检查点来降低跑偏概率
-->
<template> <template>
<div class="chain-of-thought-demo"> <div class="cot">
<div class="comparison-container"> <div class="header">
<!-- Direct Answer --> <div>
<div class="method-card"> <div class="title">复杂任务列计划交付结果</div>
<div class="method-header"> <div class="subtitle">你要的是不漏步骤 + 可检查 + 不跑题</div>
<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> </div>
<div class="controls">
<!-- Chain of Thought --> <select v-model="task">
<div class="method-card highlight"> <option value="debug">代码审查</option>
<div class="method-header"> <option value="plan">行程规划</option>
<span class="method-icon">🧠</span> </select>
<span class="method-title">思维链 (CoT)</span> <button
</div> v-for="m in modes"
<div class="method-body"> :key="m.id"
<div class="prompt-box"> :class="['mode', { active: mode === m.id }]"
<div class="prompt-label">提示词</div> @click="mode = m.id"
<div class="prompt-text"> >
罗杰有 5 个网球他又买了 2 罐网球每罐有 3 个球 {{ m.label }}
现在他总共有多少个网球 </button>
<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> </div>
</div> </div>
<div class="explanation"> <div class="grid">
<div class="exp-item"> <div class="panel">
<span class="exp-icon">🔍</span> <div class="panel-title">提示词 / Prompt</div>
<span><strong>思维链</strong> AI "展示思考过程"一步步推理问题 <pre><code>{{ prompt }}</code></pre>
对于数学逻辑推理类问题特别有效</span>
</div> </div>
<div class="exp-item"> <div class="panel">
<span class="exp-icon">📝</span> <div class="panel-title">输出示意</div>
<span><strong>触发词</strong>使用"请一步步思考""详细说明推理过程"等提示语可以激活 CoT</span> <div class="output">{{ output }}</div>
</div> </div>
<div class="exp-item"> </div>
<span class="exp-icon">🎯</span>
<span><strong>适用场景</strong>数学计算逻辑推理复杂问题拆解多步骤任务</span> <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> </div>
</div> </div>
</template> </template>
<style scoped> <script setup>
.chain-of-thought-demo { import { computed, ref } from 'vue'
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 20px;
background: var(--vp-c-bg-soft);
margin: 20px 0;
}
.comparison-container { const task = ref('debug')
display: grid; const mode = ref('plan-first')
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 768px) { const modes = [
.comparison-container { { id: 'direct', label: '直接输出' },
grid-template-columns: 1fr; { 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 { const output = computed(() => {
background: var(--vp-c-bg); if (task.value === 'debug') {
border: 2px solid var(--vp-c-divider); if (mode.value === 'direct') {
border-radius: 8px; return '代码可能有一些问题,比如命名不规范、性能不佳……(容易泛泛而谈/漏点)'
overflow: hidden; }
} 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 { <style scoped>
border-color: #22c55e; .cot {
}
.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);
border: 1px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
border-radius: 6px; border-radius: 12px;
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 {
background: var(--vp-c-bg-soft); background: var(--vp-c-bg-soft);
border-radius: 6px; padding: 16px;
padding: 12px; margin: 20px 0;
}
.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 {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 12px;
} }
.exp-item { .header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
display: flex; .title { font-weight: 800; }
align-items: flex-start; .subtitle { color: var(--vp-c-text-2); font-size: 13px; }
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;
}
.exp-icon { .controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
font-size: 1.3rem; 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> </style>
@@ -1,223 +1,131 @@
<!--
FewShotDemo.vue
Few-shot 速懂不给示例 vs 给示例AI 风格会不会稳定
交互
- 选择目标风格随意/正式
- 选择是否提供示例
- 看提示词和输出如何变化
-->
<template> <template>
<div class="few-shot-demo"> <div class="few">
<div class="mode-switch"> <div class="header">
<button :class="{ active: mode === 'zero' }" @click="mode = 'zero'"> <div>
🎯 Zero-shot零样本 <div class="title">示例的力量让风格跟你走</div>
</button> <div class="subtitle">你不是让 AI 更聪明而是让它更像你要的样子</div>
<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> </div>
<div class="controls">
<!-- Few-shot --> <select v-model="tone">
<div v-else class="few-shot"> <option value="casual">随意口语</option>
<div class="prompt-box"> <option value="formal">正式书面</option>
<div class="prompt-header">提示词含示例</div> </select>
<div class="prompt-content"> <button :class="['toggle', { active: withExamples }]" @click="withExamples = !withExamples">
<strong>中文</strong>翻译成<strong>英文</strong> {{ withExamples ? '已提供示例' : '不提供示例' }}
<br><br> </button>
<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> </div>
</div> </div>
<div class="explanation"> <div class="grid">
<div v-if="mode === 'zero'"> <div class="panel">
<strong>Zero-shot</strong>不给任何示例直接让 AI 完成任务简单但可能不够准确 <div class="panel-title">提示词 / Prompt</div>
<pre><code>{{ prompt }}</code></pre>
</div> </div>
<div v-else> <div class="panel">
<strong>Few-shot</strong>提供几个示例让 AI 学习规律例的质量和数量直接影响输出效果 <div class="panel-title">AI 输出</div>
通常 3-5 个示例就足够了 <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> </div>
</div> </div>
</template> </template>
<script setup> <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> </script>
<style scoped> <style scoped>
.few-shot-demo { .few {
border: 1px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
border-radius: 8px; border-radius: 12px;
padding: 20px;
background: var(--vp-c-bg-soft); background: var(--vp-c-bg-soft);
padding: 16px;
margin: 20px 0; margin: 20px 0;
}
.mode-switch {
display: flex; display: flex;
justify-content: center; flex-direction: column;
gap: 10px; gap: 12px;
margin-bottom: 20px;
} }
.mode-switch button { .header { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
padding: 8px 16px; .title { font-weight: 800; }
border-radius: 20px; .subtitle { color: var(--vp-c-text-2); font-size: 13px; }
border: 1px solid var(--vp-c-divider); .controls { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
background: var(--vp-c-bg); 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); }
cursor: pointer; .toggle { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); padding: 8px 12px; border-radius: 999px; cursor: pointer; }
transition: all 0.2s; .toggle.active { border-color: var(--vp-c-brand); color: var(--vp-c-brand); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
font-size: 0.9em;
}
.mode-switch button.active { .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
background: var(--vp-c-brand); .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; }
color: white; .panel-title { font-weight: 700; }
border-color: var(--vp-c-brand); 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 { .examples { background: var(--vp-c-bg); border: 1px dashed var(--vp-c-divider); border-radius: 10px; padding: 12px; }
background: var(--vp-c-bg); .examples-title { font-weight: 700; margin-bottom: 8px; }
border-radius: 8px; .examples-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; }
padding: 20px; .ex { border: 1px solid var(--vp-c-divider); border-radius: 10px; padding: 10px; background: var(--vp-c-bg-soft); }
margin-bottom: 20px; .in { color: var(--vp-c-text-2); font-size: 13px; }
} .out { font-weight: 700; margin-top: 4px; }
.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;
}
</style> </style>
@@ -1,201 +1,204 @@
<!--
PromptComparisonDemo.vue
清晰 vs 模糊对比把一个提示词拆成四块任务/上下文/要求/输出并展示哪些块缺失会导致输出跑偏
-->
<template> <template>
<div class="prompt-comparison"> <div class="cmp">
<div class="comparison-container"> <div class="header">
<!-- Bad Prompt --> <div>
<div class="prompt-card bad"> <div class="title">清晰 vs 模糊差的不是废话而是缺项</div>
<div class="card-header"> <div class="subtitle">勾选你想补充的信息看看输出会怎么变</div>
<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> </div>
<div class="task">
<!-- Good Prompt --> <select v-model="task">
<div class="prompt-card good"> <option value="blog">写一段技术博客开头</option>
<div class="card-header"> <option value="json">把内容输出成 JSON</option>
<span class="card-icon"></span> </select>
<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> </div>
</div> </div>
<div class="key-points"> <div class="options">
<div class="point"> <label><input type="checkbox" v-model="useRole" /> 角色你是谁</label>
<span class="point-icon">🎯</span> <label><input type="checkbox" v-model="useAudience" /> 受众写给谁</label>
<span><strong>明确目标</strong>说明要做什么写文章写代码分析</span> <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>
<div class="point"> <div class="panel">
<span class="point-icon">📋</span> <div class="panel-title">AI 输出示意</div>
<span><strong>提供细节</strong>主题风格长度格式等具体要求</span> <div class="output">{{ output }}</div>
</div> <div class="warn" v-if="warnings.length">
<div class="point"> <div class="warn-title">可能的问题</div>
<span class="point-icon">👥</span> <ul>
<span><strong>指定受众</strong>说明目标读者初学者/专家/儿童等</span> <li v-for="w in warnings" :key="w">{{ w }}</li>
</ul>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <script setup>
.prompt-comparison { import { computed, ref } from 'vue'
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 20px;
background: var(--vp-c-bg-soft);
margin: 20px 0;
}
.comparison-container { const task = ref('blog')
display: grid; const useRole = ref(false)
grid-template-columns: 1fr 1fr; const useAudience = ref(true)
gap: 20px; const useConstraints = ref(true)
margin-bottom: 20px; const useFormat = ref(false)
}
@media (max-width: 768px) { const prompt = computed(() => {
.comparison-container { if (task.value === 'blog') {
grid-template-columns: 1fr; 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 { // json task
background: var(--vp-c-bg); const lines = []
border: 2px solid var(--vp-c-divider); if (useRole.value) lines.push('你是信息抽取助手。')
border-radius: 8px; lines.push('从下面这段文字中提取关键信息。')
overflow: hidden; 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 { const checklist = computed(() => [
border-color: #ef4444; { text: '任务清晰(要做什么)', ok: true },
} { text: '角色(用什么口吻)', ok: useRole.value },
{ text: '受众/用途(给谁用)', ok: useAudience.value },
{ text: '约束(长度/数量/范围)', ok: useConstraints.value },
{ text: '输出格式(如何交付)', ok: useFormat.value }
])
.prompt-card.good { const warnings = computed(() => {
border-color: #22c55e; 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 { const output = computed(() => {
display: flex; if (task.value === 'blog') {
align-items: center; if (warnings.value.length >= 2) {
gap: 8px; return '提示词工程是一种与 AI 沟通的方法,它可以帮助你获得更好的输出......(可能偏长/风格不稳)'
padding: 12px 15px; }
background: var(--vp-c-bg-mute); return '把 AI 当成新来的同事:你说得越清楚,它越不容易跑偏。提示词工程就是把“要做什么、给谁、怎么交付”一次说明白。'
} }
.bad .card-header { // json
background: rgba(239, 68, 68, 0.1); if (!useFormat.value) {
} return '这段文字主要讲提示词工程的重要性,并强调需要清晰任务、约束和格式……(但不是 JSON)'
}
return `{\n \"summary\": \"提示词工程能提升输出质量,关键在于清晰任务、约束与格式。\",\n \"keywords\": [\"提示词工程\", \"任务清晰\", \"约束\", \"格式\"]\n}`
})
</script>
.good .card-header { <style scoped>
background: rgba(34, 197, 94, 0.1); .cmp {
}
.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);
border: 1px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
border-radius: 6px; border-radius: 12px;
padding: 12px;
font-family: monospace;
font-size: 0.85rem;
color: var(--vp-c-text-1);
margin-bottom: 15px;
line-height: 1.6;
}
.result-box {
background: var(--vp-c-bg-soft); 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; 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; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
} }
.point { .panel-title { font-weight: 700; }
display: flex; pre {
align-items: flex-start; margin: 0;
gap: 10px; background: #0b1221;
background: var(--vp-c-bg); color: #e5e7eb;
border-radius: 8px;
padding: 12px; padding: 12px;
border-radius: 6px; font-family: var(--vp-font-family-mono);
font-size: 0.9rem; font-size: 13px;
color: var(--vp-c-text-2); overflow-x: auto;
white-space: pre-wrap;
} }
.point-icon { .checklist { display: grid; gap: 6px; }
font-size: 1.3rem; .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> </style>
@@ -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>
@@ -20,7 +20,7 @@
<div class="tab">Logging</div> <div class="tab">Logging</div>
</div> </div>
</div> </div>
<div class="tui-body"> <div class="tui-body">
<div class="sidebar" :style="{ width: sidebarWidth + '%' }"> <div class="sidebar" :style="{ width: sidebarWidth + '%' }">
<div class="list-item success"> <div class="list-item success">
@@ -39,13 +39,15 @@
<span class="icon"></span> ci-db-migration <span class="icon"></span> ci-db-migration
</div> </div>
</div> </div>
<div class="main-content"> <div class="main-content">
<div class="content-header"> <div class="content-header">
<h3>ci-email-service</h3> <h3>ci-email-service</h3>
</div> </div>
<div class="content-body"> <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 class="info-row">Updated: 2024-01-15 14:32:00 UTC</div>
</div> </div>
</div> </div>
@@ -82,12 +84,12 @@ const simulateResize = () => {
const originalWidth = sidebarWidth.value const originalWidth = sidebarWidth.value
sidebarWidth.value = 20 sidebarWidth.value = 20
sizeDisplay.value = '40x20' sizeDisplay.value = '40x20'
setTimeout(() => { setTimeout(() => {
sidebarWidth.value = 40 sidebarWidth.value = 40
sizeDisplay.value = '80x20' sizeDisplay.value = '80x20'
}, 500) }, 500)
setTimeout(() => { setTimeout(() => {
sidebarWidth.value = originalWidth sidebarWidth.value = originalWidth
sizeDisplay.value = '60x20' sizeDisplay.value = '60x20'
@@ -169,10 +171,18 @@ const simulateResize = () => {
width: 16px; width: 16px;
} }
.success .icon { color: #22c55e; } .success .icon {
.warning .icon { color: #eab308; } color: #22c55e;
.error .icon { color: #ef4444; } }
.pending .icon { color: #666; } .warning .icon {
color: #eab308;
}
.error .icon {
color: #ef4444;
}
.pending .icon {
color: #666;
}
.main-content { .main-content {
flex: 1; flex: 1;
@@ -190,7 +200,9 @@ const simulateResize = () => {
color: #aaa; color: #aaa;
} }
.text-success { color: #22c55e; } .text-success {
color: #22c55e;
}
.info-row { .info-row {
color: #666; color: #666;
@@ -37,7 +37,11 @@
</div> </div>
</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 --> <!-- Click Overlay Hint -->
<div class="click-overlay" v-if="currentStep === 0"> <div class="click-overlay" v-if="currentStep === 0">
<div class="click-hint"> <div class="click-hint">
@@ -53,7 +57,7 @@
<span class="text">演示结束点击重置 / Finished (Reset)</span> <span class="text">演示结束点击重置 / Finished (Reset)</span>
</div> </div>
</div> </div>
<!-- Spaces Background --> <!-- Spaces Background -->
<div class="spaces-bg"> <div class="spaces-bg">
<div class="space user-space"> <div class="space user-space">
@@ -71,7 +75,9 @@
<div class="node terminal" :class="{ active: activeNode === 'terminal' }"> <div class="node terminal" :class="{ active: activeNode === 'terminal' }">
<div class="node-title">TERMINAL (终端)</div> <div class="node-title">TERMINAL (终端)</div>
<div class="screen"> <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"> <div class="line input-line">
<span class="prompt">$</span> <span class="prompt">$</span>
<span class="typing">{{ currentInput }}</span> <span class="typing">{{ currentInput }}</span>
@@ -82,7 +88,12 @@
</div> </div>
<!-- Connections --> <!-- 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="line-path"></div>
<div class="data-label" v-if="packetState === 't-to-s'"> <div class="data-label" v-if="packetState === 't-to-s'">
<span class="icon"></span> Bytes <span class="icon"></span> Bytes
@@ -104,7 +115,12 @@
</div> </div>
<!-- Connections --> <!-- 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="line-path"></div>
<div class="data-label" v-if="packetState === 's-to-k'"> <div class="data-label" v-if="packetState === 's-to-k'">
<span class="icon"></span> Syscall <span class="icon"></span> Syscall
@@ -128,20 +144,26 @@
<div class="controls"> <div class="controls">
<div class="btn-group"> <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-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> <span v-else> Done / 完成 (Reset)</span>
</button> </button>
<button class="btn secondary" @click="reset" v-if="currentStep > 0"> <button class="btn secondary" @click="reset" v-if="currentStep > 0">
Reset / 重置 Reset / 重置
</button> </button>
</div> </div>
<div class="step-info" v-if="currentStep > 0"> <div class="step-info" v-if="currentStep > 0">
<div class="step-title"> <div class="step-title">
{{ steps[currentStep - 1].titleEn }} {{ steps[currentStep - 1].titleEn }}
<span class="divider">|</span> <span class="divider">|</span>
{{ steps[currentStep - 1].titleZh }} {{ steps[currentStep - 1].titleZh }}
</div> </div>
<div class="step-desc"> <div class="step-desc">
@@ -158,7 +180,10 @@
</div> </div>
<div class="step-info placeholder" v-else> <div class="step-info placeholder" v-else>
<div class="step-desc"> <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 class="zh">点击开始演示查看 'ls' 命令如何在系统中流转</div>
</div> </div>
</div> </div>
@@ -170,8 +195,8 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
const currentStep = ref(0) const currentStep = ref(0)
const activeNode = ref('terminal') const activeNode = ref('terminal')
const packetState = ref(null) const packetState = ref(null)
const terminalLines = ref([]) const terminalLines = ref([])
const currentInput = ref('') const currentInput = ref('')
const shellStatus = ref('Idle') const shellStatus = ref('Idle')
@@ -181,12 +206,13 @@ const kernelIcon = ref('💤')
const steps = [ const steps = [
{ {
titleEn: "1. User Input", titleEn: '1. User Input',
titleZh: "1. 用户输入", titleZh: '1. 用户输入',
descEn: "You type 'ls' in the terminal window. The terminal captures your keystrokes.", descEn:
"You type 'ls' in the terminal window. The terminal captures your keystrokes.",
descZh: "你在终端窗口输入 'ls'。终端会捕获你的按键操作。", descZh: "你在终端窗口输入 'ls'。终端会捕获你的按键操作。",
techEn: "Terminal buffers input in 'Cooked Mode' until you press Enter.", techEn: "Terminal buffers input in 'Cooked Mode' until you press Enter.",
techZh: "终端在“加工模式 (Cooked Mode)”下缓冲输入,直到你按下回车键。", techZh: '终端在“加工模式 (Cooked Mode)”下缓冲输入,直到你按下回车键。',
action: async () => { action: async () => {
activeNode.value = 'terminal' activeNode.value = 'terminal'
currentInput.value = 'l' currentInput.value = 'l'
@@ -195,12 +221,13 @@ const steps = [
} }
}, },
{ {
titleEn: "2. Transmission", titleEn: '2. Transmission',
titleZh: "2. 传输", titleZh: '2. 传输',
descEn: "The Terminal sends the characters 'l', 's', and 'Enter' to the Shell.", descEn:
"The Terminal sends the characters 'l', 's', and 'Enter' to the Shell.",
descZh: "终端将字符 'l'、's' 和 '回车' 发送给 Shell。", descZh: "终端将字符 'l'、's' 和 '回车' 发送给 Shell。",
techEn: "Data travels via standard input (stdin) as a byte stream.", techEn: 'Data travels via standard input (stdin) as a byte stream.',
techZh: "数据通过标准输入 (stdin) 以字节流的形式传输。", techZh: '数据通过标准输入 (stdin) 以字节流的形式传输。',
action: async () => { action: async () => {
packetState.value = 't-to-s' packetState.value = 't-to-s'
await wait(1000) await wait(1000)
@@ -208,10 +235,10 @@ const steps = [
} }
}, },
{ {
titleEn: "3. Shell Parsing", titleEn: '3. Shell Parsing',
titleZh: "3. Shell 解析", titleZh: '3. Shell 解析',
descEn: "The Shell (Waiter) translates your command for the Kernel.", descEn: 'The Shell (Waiter) translates your command for the Kernel.',
descZh: "Shell(服务员)接收指令,并将其翻译成内核能听懂的请求。", descZh: 'Shell(服务员)接收指令,并将其翻译成内核能听懂的请求。',
techEn: "Shell tokenizes input, finds the 'ls' executable in $PATH.", techEn: "Shell tokenizes input, finds the 'ls' executable in $PATH.",
techZh: "Shell 对输入进行分词,并在 $PATH 环境变量中查找 'ls' 可执行文件。", techZh: "Shell 对输入进行分词,并在 $PATH 环境变量中查找 'ls' 可执行文件。",
action: async () => { action: async () => {
@@ -221,12 +248,12 @@ const steps = [
} }
}, },
{ {
titleEn: "4. System Call", titleEn: '4. System Call',
titleZh: "4. 系统调用", titleZh: '4. 系统调用',
descEn: "The Shell asks the Kernel to read the file list from the disk.", descEn: 'The Shell asks the Kernel to read the file list from the disk.',
descZh: "Shell 请求内核从磁盘读取文件列表。", descZh: 'Shell 请求内核从磁盘读取文件列表。',
techEn: "Shell invokes `execve()` and `getdents()` system calls.", techEn: 'Shell invokes `execve()` and `getdents()` system calls.',
techZh: "Shell 调用 `execve()` 和 `getdents()` 等系统调用。", techZh: 'Shell 调用 `execve()` 和 `getdents()` 等系统调用。',
action: async () => { action: async () => {
packetState.value = 's-to-k' packetState.value = 's-to-k'
await wait(1000) await wait(1000)
@@ -234,12 +261,12 @@ const steps = [
} }
}, },
{ {
titleEn: "5. Kernel Execution", titleEn: '5. Kernel Execution',
titleZh: "5. 内核执行", titleZh: '5. 内核执行',
descEn: "The Kernel (Kitchen) executes the request by accessing hardware.", descEn: 'The Kernel (Kitchen) executes the request by accessing hardware.',
descZh: "内核(后厨)直接操作硬件(如磁盘)来执行实际任务。", descZh: '内核(后厨)直接操作硬件(如磁盘)来执行实际任务。',
techEn: "Kernel driver accesses the file system (APFS/ext4).", techEn: 'Kernel driver accesses the file system (APFS/ext4).',
techZh: "内核驱动程序访问文件系统 (APFS/ext4)。", techZh: '内核驱动程序访问文件系统 (APFS/ext4)。',
action: async () => { action: async () => {
activeNode.value = 'kernel' activeNode.value = 'kernel'
kernelIcon.value = '💾' kernelIcon.value = '💾'
@@ -249,12 +276,12 @@ const steps = [
} }
}, },
{ {
titleEn: "6. Returning Data", titleEn: '6. Returning Data',
titleZh: "6. 返回数据", titleZh: '6. 返回数据',
descEn: "The Kernel gives the raw file list back to the Shell.", descEn: 'The Kernel gives the raw file list back to the Shell.',
descZh: "内核将原始文件列表数据返回给 Shell。", descZh: '内核将原始文件列表数据返回给 Shell。',
techEn: "System call returns with file descriptors/structs.", techEn: 'System call returns with file descriptors/structs.',
techZh: "系统调用返回文件描述符或结构体数据。", techZh: '系统调用返回文件描述符或结构体数据。',
action: async () => { action: async () => {
kernelStatus.value = 'Idle' kernelStatus.value = 'Idle'
kernelIcon.value = '💤' kernelIcon.value = '💤'
@@ -264,12 +291,13 @@ const steps = [
} }
}, },
{ {
titleEn: "7. Formatting", titleEn: '7. Formatting',
titleZh: "7. 格式化", titleZh: '7. 格式化',
descEn: "The Shell formats the raw list into text, adding colors if needed.", descEn:
descZh: "Shell 将原始列表格式化为文本,并根据需要添加颜色。", 'The Shell formats the raw list into text, adding colors if needed.',
techEn: "Shell formats output buffer, adding ANSI color codes.", descZh: 'Shell 将原始列表格式化为文本,并根据需要添加颜色。',
techZh: "Shell 格式化输出缓冲区,并添加 ANSI 颜色代码。", techEn: 'Shell formats output buffer, adding ANSI color codes.',
techZh: 'Shell 格式化输出缓冲区,并添加 ANSI 颜色代码。',
action: async () => { action: async () => {
activeNode.value = 'shell' activeNode.value = 'shell'
shellIcon.value = '🎨' shellIcon.value = '🎨'
@@ -278,12 +306,12 @@ const steps = [
} }
}, },
{ {
titleEn: "8. Display Output", titleEn: '8. Display Output',
titleZh: "8. 显示输出", titleZh: '8. 显示输出',
descEn: "The Shell sends the final text back to the Terminal to show you.", descEn: 'The Shell sends the final text back to the Terminal to show you.',
descZh: "Shell 将最终文本发送回终端以供显示。", descZh: 'Shell 将最终文本发送回终端以供显示。',
techEn: "Data travels via standard output (stdout) to the TTY.", techEn: 'Data travels via standard output (stdout) to the TTY.',
techZh: "数据通过标准输出 (stdout) 传输到 TTY。", techZh: '数据通过标准输出 (stdout) 传输到 TTY。',
action: async () => { action: async () => {
shellStatus.value = 'Idle' shellStatus.value = 'Idle'
shellIcon.value = '💤' shellIcon.value = '💤'
@@ -293,12 +321,12 @@ const steps = [
} }
}, },
{ {
titleEn: "9. Render", titleEn: '9. Render',
titleZh: "9. 渲染", titleZh: '9. 渲染',
descEn: "The Terminal draws the text on the screen grid.", descEn: 'The Terminal draws the text on the screen grid.',
descZh: "终端在屏幕网格上绘制文本。", descZh: '终端在屏幕网格上绘制文本。',
techEn: "Terminal emulator renders glyphs into the frame buffer.", techEn: 'Terminal emulator renders glyphs into the frame buffer.',
techZh: "终端模拟器将字形渲染到帧缓冲区中。", techZh: '终端模拟器将字形渲染到帧缓冲区中。',
action: async () => { action: async () => {
activeNode.value = 'terminal' activeNode.value = 'terminal'
terminalLines.value = ['file1.txt photo.jpg', 'notes.md'] terminalLines.value = ['file1.txt photo.jpg', 'notes.md']
@@ -307,17 +335,16 @@ const steps = [
} }
] ]
const totalSteps = steps.length 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 () => { const nextStep = async () => {
if (currentStep.value >= totalSteps) { if (currentStep.value >= totalSteps) {
reset() reset()
return return
} }
const step = steps[currentStep.value] const step = steps[currentStep.value]
currentStep.value++ currentStep.value++
await step.action() await step.action()
@@ -446,9 +473,15 @@ const reset = () => {
} }
@keyframes pulse-bg { @keyframes pulse-bg {
0% { background: rgba(0, 0, 0, 0.4); } 0% {
50% { background: rgba(0, 0, 0, 0.2); } background: rgba(0, 0, 0, 0.4);
100% { 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 { .completed-overlay {
@@ -512,16 +545,18 @@ const reset = () => {
} }
.user-space { .user-space {
flex: 2; flex: 2;
background: rgba(34, 211, 238, 0.03); background: rgba(34, 211, 238, 0.03);
border-right: 1px dashed #3f3f46; border-right: 1px dashed #3f3f46;
border-radius: 8px 0 0 8px; border-radius: 8px 0 0 8px;
align-items: flex-start; align-items: flex-start;
/* Ensure User Space (containing Shell) is below the Barrier Label */ /* Ensure User Space (containing Shell) is below the Barrier Label */
z-index: 0; z-index: 0;
} }
.user-space .space-header { color: #22d3ee; } .user-space .space-header {
color: #22d3ee;
}
.kernel-space { .kernel-space {
flex: 1; flex: 1;
@@ -531,7 +566,9 @@ const reset = () => {
z-index: 0; z-index: 0;
} }
.kernel-space .space-header { color: #ef4444; } .kernel-space .space-header {
color: #ef4444;
}
.barrier { .barrier {
width: 2px; width: 2px;
@@ -556,8 +593,6 @@ const reset = () => {
opacity: 0.3; opacity: 0.3;
} }
.node { .node {
background: #18181b; background: #18181b;
border: 2px solid #27272a; border: 2px solid #27272a;
@@ -573,7 +608,7 @@ const reset = () => {
/* Specific z-index for Shell to prevent it from covering barrier label */ /* Specific z-index for Shell to prevent it from covering barrier label */
.node.shell { .node.shell {
z-index: 1; z-index: 1;
} }
.node.active { .node.active {
@@ -603,7 +638,8 @@ const reset = () => {
color: #71717a; color: #71717a;
} }
.screen, .process-box { .screen,
.process-box {
flex: 1; flex: 1;
padding: 10px; padding: 10px;
display: flex; display: flex;
@@ -693,18 +729,26 @@ const reset = () => {
font-size: 11px; font-size: 11px;
font-weight: bold; font-weight: bold;
white-space: nowrap; 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; z-index: 10;
animation: pop-in 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); animation: pop-in 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
} }
@keyframes pop-in { @keyframes pop-in {
from { opacity: 0; transform: translate(-50%, 5px); } from {
to { opacity: 1; transform: translate(-50%, 0); } opacity: 0;
transform: translate(-50%, 5px);
}
to {
opacity: 1;
transform: translate(-50%, 0);
}
} }
@keyframes blink { @keyframes blink {
50% { opacity: 0; } 50% {
opacity: 0;
}
} }
.controls { .controls {
@@ -795,27 +839,33 @@ const reset = () => {
} }
@keyframes fade-in { @keyframes fade-in {
from { opacity: 0; transform: translateY(5px); } from {
to { opacity: 1; transform: translateY(0); } opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.analogy-header { .analogy-header {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.diagram-container { .diagram-container {
flex-direction: column; flex-direction: column;
gap: 50px; gap: 50px;
padding: 20px 0; padding: 20px 0;
} }
.connection { .connection {
width: 2px; width: 2px;
height: 50px; height: 50px;
margin: 0; margin: 0;
} }
.conn-label { .conn-label {
top: 50%; top: 50%;
left: 10px; left: 10px;
@@ -824,25 +874,37 @@ const reset = () => {
text-align: left; text-align: left;
white-space: nowrap; white-space: nowrap;
} }
.packet { .packet {
top: 0; top: 0;
left: 10px; left: 10px;
animation: travel-vertical 1s linear forwards; animation: travel-vertical 1s linear forwards;
} }
.packet.reverse { .packet.reverse {
animation: travel-vertical-back 1s linear forwards; animation: travel-vertical-back 1s linear forwards;
} }
@keyframes travel-vertical { @keyframes travel-vertical {
0% { top: 0; transform: translateY(0); } 0% {
100% { top: 100%; transform: translateY(-100%); } top: 0;
transform: translateY(0);
}
100% {
top: 100%;
transform: translateY(-100%);
}
} }
@keyframes travel-vertical-back { @keyframes travel-vertical-back {
0% { top: 100%; transform: translateY(-100%); } 0% {
100% { top: 0; transform: translateY(0); } top: 100%;
transform: translateY(-100%);
}
100% {
top: 0;
transform: translateY(0);
}
} }
} }
</style> </style>
@@ -7,18 +7,31 @@
<span class="dot green"></span> <span class="dot green"></span>
<span class="title">Terminal - Buffer Switching Demo</span> <span class="title">Terminal - Buffer Switching Demo</span>
</div> </div>
<div class="screen-container"> <div class="screen-container">
<!-- Main Buffer (Layer 0) --> <!-- Main Buffer (Layer 0) -->
<div class="buffer main-buffer"> <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">total 16</div>
<div class="line output">drwxr-xr-x 2 user staff 64 Jan 15 10:00 .</div> <div class="line output">
<div class="line output">drwxr-xr-x 4 user staff 128 Jan 15 09:55 ..</div> drwxr-xr-x 2 user staff 64 Jan 15 10:00 .
<div class="line output">-rw-r--r-- 1 user staff 1024 Jan 15 10:00 notes.txt</div> </div>
<div class="line"><span class="prompt"></span> <span class="cmd">echo "Hello World"</span></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 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 --> <!-- The cursor would be here if not in vim -->
</div> </div>
@@ -30,12 +43,21 @@
<span class="modified">[+]</span> <span class="modified">[+]</span>
</div> </div>
<div class="vim-body"> <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">1</div>
<div class="line-num">2</div><div class="code"></div> <div class="code">This is a text file opened in Vim.</div>
<div class="line-num">3</div><div class="code">Notice how this interface takes up</div> <div class="line-num">2</div>
<div class="line-num">4</div><div class="code">the entire screen?</div> <div class="code"></div>
<div class="line-num">5</div><div class="code"></div> <div class="line-num">3</div>
<div class="line-num">6</div><div class="code">It is running in the <span class="highlight">Alternate Buffer</span>.</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 class="line-num">~</div> <div class="line-num">~</div>
</div> </div>
@@ -55,14 +77,20 @@
<div class="description"> <div class="description">
<div v-if="!isAltBufferActive"> <div v-if="!isAltBufferActive">
<p><strong>Current: Primary Buffer (主缓冲区)</strong></p> <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"> <button class="action-btn" @click="openVim">
Execute `vim notes.txt` Execute `vim notes.txt`
</button> </button>
</div> </div>
<div v-else> <div v-else>
<p><strong>Current: Alternate Buffer (备用缓冲区)</strong></p> <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"> <button class="action-btn red" @click="quitVim">
Execute `:q` (Quit) Execute `:q` (Quit)
</button> </button>
@@ -101,7 +129,7 @@ const quitVim = () => {
background: #1e1e1e; background: #1e1e1e;
border-radius: 8px; border-radius: 8px;
overflow: hidden; 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; border: 1px solid #333;
} }
@@ -118,9 +146,15 @@ const quitVim = () => {
height: 10px; height: 10px;
border-radius: 50%; border-radius: 50%;
} }
.red { background: #ff5f56; } .red {
.yellow { background: #ffbd2e; } background: #ff5f56;
.green { background: #27c93f; } }
.yellow {
background: #ffbd2e;
}
.green {
background: #27c93f;
}
.title { .title {
margin-left: 10px; margin-left: 10px;
@@ -281,4 +315,4 @@ p {
font-size: 14px; font-size: 14px;
color: var(--vp-c-text-1); color: var(--vp-c-text-1);
} }
</style> </style>
@@ -17,26 +17,28 @@
{{ char }} {{ char }}
</div> </div>
</div> </div>
<div class="controls-area"> <div class="controls-area">
<div class="control-group"> <div class="control-group">
<label>CHARACTER</label> <label>CHARACTER</label>
<div class="char-buttons"> <div class="char-buttons">
<button <button
v-for="c in chars" v-for="c in chars"
:key="c" :key="c"
:class="{ active: char === c }" :class="{ active: char === c }"
@click="char = c" @click="char = c"
>{{ c }}</button> >
{{ c }}
</button>
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
<label>FOREGROUND</label> <label>FOREGROUND</label>
<div class="color-palette"> <div class="color-palette">
<div <div
v-for="color in colors" v-for="color in colors"
:key="color" :key="color"
class="color-swatch" class="color-swatch"
:style="{ backgroundColor: color }" :style="{ backgroundColor: color }"
:class="{ active: fgColor === color }" :class="{ active: fgColor === color }"
@@ -48,15 +50,23 @@
<div class="control-group"> <div class="control-group">
<label>BACKGROUND</label> <label>BACKGROUND</label>
<div class="color-palette"> <div class="color-palette">
<div <div
class="color-swatch" class="color-swatch"
:class="{ active: bgColor === 'transparent' }" :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'" @click="bgColor = 'transparent'"
></div> ></div>
<div <div
v-for="color in bgColors" v-for="color in bgColors"
:key="color" :key="color"
class="color-swatch" class="color-swatch"
:style="{ backgroundColor: color }" :style="{ backgroundColor: color }"
:class="{ active: bgColor === color }" :class="{ active: bgColor === color }"
@@ -69,11 +79,11 @@
<label>ATTRIBUTES</label> <label>ATTRIBUTES</label>
<div class="toggles"> <div class="toggles">
<label class="toggle"> <label class="toggle">
<input type="checkbox" v-model="isBold"> <input type="checkbox" v-model="isBold" />
<span>Bold</span> <span>Bold</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" v-model="isUnderline"> <input type="checkbox" v-model="isUnderline" />
<span>Underline</span> <span>Underline</span>
</label> </label>
</div> </div>
@@ -85,13 +95,49 @@
<script setup> <script setup>
import { ref, computed } from 'vue' 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 = [ const colors = [
'#ef4444', '#22c55e', '#eab308', '#3b82f6', '#a855f7', '#06b6d4', '#f3f4f6', '#6b7280', '#ef4444',
'#f87171', '#4ade80', '#facc15', '#60a5fa', '#c084fc', '#22d3ee', '#ffffff' '#22c55e',
'#eab308',
'#3b82f6',
'#a855f7',
'#06b6d4',
'#f3f4f6',
'#6b7280',
'#f87171',
'#4ade80',
'#facc15',
'#60a5fa',
'#c084fc',
'#22d3ee',
'#ffffff'
] ]
const bgColors = [ const bgColors = [
'#000000', '#1f2937', '#111827', '#374151', '#1e3a8a', '#3f2c08', '#310b0b' '#000000',
'#1f2937',
'#111827',
'#374151',
'#1e3a8a',
'#3f2c08',
'#310b0b'
] ]
const char = ref('A') const char = ref('A')
@@ -208,7 +254,7 @@ const cellStyle = computed(() => ({
.color-swatch.active { .color-swatch.active {
border-color: #fff; border-color: #fff;
transform: scale(1.1); 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 { .toggles {
@@ -1,25 +1,19 @@
<template> <template>
<div class="cooked-raw-demo"> <div class="cooked-raw-demo">
<div class="mode-switch"> <div class="mode-switch">
<button <button :class="{ active: mode === 'cooked' }" @click="setMode('cooked')">
:class="{ active: mode === 'cooked' }"
@click="setMode('cooked')"
>
🥘 Cooked Mode (Standard) 🥘 Cooked Mode (Standard)
</button> </button>
<button <button :class="{ active: mode === 'raw' }" @click="setMode('raw')">
:class="{ active: mode === 'raw' }"
@click="setMode('raw')"
>
🥩 Raw Mode (Vim/Games) 🥩 Raw Mode (Vim/Games)
</button> </button>
</div> </div>
<div class="demo-container" @click="focusInput"> <div class="demo-container" @click="focusInput">
<!-- Hidden Input for capturing keystrokes --> <!-- Hidden Input for capturing keystrokes -->
<input <input
ref="inputRef" ref="inputRef"
type="text" type="text"
class="hidden-input" class="hidden-input"
@keydown="handleKey" @keydown="handleKey"
@blur="isFocused = false" @blur="isFocused = false"
@@ -28,12 +22,13 @@
<!-- Visualization --> <!-- Visualization -->
<div class="flow-diagram"> <div class="flow-diagram">
<!-- 1. User Input --> <!-- 1. User Input -->
<div class="stage user-input" :class="{ focused: isFocused }"> <div class="stage user-input" :class="{ focused: isFocused }">
<div class="stage-title">1. Keyboard Input</div> <div class="stage-title">1. Keyboard Input</div>
<div class="key-visual"> <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> <span v-else class="placeholder">Type here...</span>
</div> </div>
<div class="status-text" v-if="!isFocused">Click to focus</div> <div class="status-text" v-if="!isFocused">Click to focus</div>
@@ -42,7 +37,10 @@
<div class="arrow"></div> <div class="arrow"></div>
<!-- 2. OS Buffer (Only for Cooked) --> <!-- 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"> <div class="stage-title">
2. Line Buffer (Kernel) 2. Line Buffer (Kernel)
<span class="badge" v-if="mode === 'cooked'">Active</span> <span class="badge" v-if="mode === 'cooked'">Active</span>
@@ -50,7 +48,9 @@
</div> </div>
<div class="buffer-content"> <div class="buffer-content">
<template v-if="mode === 'cooked'"> <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> <span class="cursor">_</span>
</template> </template>
<template v-else> <template v-else>
@@ -58,7 +58,9 @@
</template> </template>
</div> </div>
<div class="explanation"> <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> <span v-else>No buffering. Every key is sent immediately.</span>
</div> </div>
</div> </div>
@@ -77,7 +79,6 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -110,9 +111,9 @@ const focusInput = () => {
const handleKey = (e) => { const handleKey = (e) => {
e.preventDefault() e.preventDefault()
const key = e.key const key = e.key
// Visual feedback // Visual feedback
if (key === ' ') lastPressedKey.value = 'Space' if (key === ' ') lastPressedKey.value = 'Space'
else if (key === 'Enter') lastPressedKey.value = 'Enter' else if (key === 'Enter') lastPressedKey.value = 'Enter'
@@ -122,7 +123,16 @@ const handleKey = (e) => {
// Clear visual feedback after delay // Clear visual feedback after delay
setTimeout(() => { 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 // lastPressedKey.value = '' // Optional: keep last key visible
} }
}, 500) }, 500)
@@ -345,7 +355,8 @@ const handleRawMode = (e) => {
color: #ccc; color: #ccc;
} }
.cursor, .app-cursor { .cursor,
.app-cursor {
display: inline-block; display: inline-block;
width: 8px; width: 8px;
background: currentColor; background: currentColor;
@@ -353,10 +364,20 @@ const handleRawMode = (e) => {
} }
@keyframes pop { @keyframes pop {
0% { transform: scale(0.9); } 0% {
50% { transform: scale(1.1); } transform: scale(0.9);
100% { transform: scale(1); } }
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
} }
@keyframes blink { 50% { opacity: 0; } } @keyframes blink {
</style> 50% {
opacity: 0;
}
}
</style>
@@ -5,7 +5,9 @@
<div class="controls"> <div class="controls">
<button @click="reset" :disabled="isPlaying">Reset</button> <button @click="reset" :disabled="isPlaying">Reset</button>
<button @click="togglePlay" class="play-btn"> <button @click="togglePlay" class="play-btn">
{{ isPlaying ? '⏸ Pause' : (isFinished ? '↺ Replay' : '▶ Play Animation') }} {{
isPlaying ? '⏸ Pause' : isFinished ? '↺ Replay' : '▶ Play Animation'
}}
</button> </button>
</div> </div>
</div> </div>
@@ -15,16 +17,19 @@
<div class="label">Input Byte Stream / 输入字节流</div> <div class="label">Input Byte Stream / 输入字节流</div>
<div class="stream-track"> <div class="stream-track">
<div class="stream-window-mask"> <div class="stream-window-mask">
<div class="stream-content" :style="{ transform: `translateX(-${currentIndex * 40}px)` }"> <div
<div class="stream-content"
v-for="(char, index) in charStream" :style="{ transform: `translateX(-${currentIndex * 40}px)` }"
>
<div
v-for="(char, index) in charStream"
:key="index" :key="index"
class="char-box" class="char-box"
:class="{ :class="{
'active': index === currentIndex, active: index === currentIndex,
'processed': index < currentIndex, processed: index < currentIndex,
'special': char.isSpecial, special: char.isSpecial,
'arg': char.isArg arg: char.isArg
}" }"
> >
<span class="char-val">{{ char.display }}</span> <span class="char-val">{{ char.display }}</span>
@@ -47,11 +52,14 @@
<div class="state-desc">Print Characters</div> <div class="state-desc">Print Characters</div>
</div> </div>
<div class="arrow-right"></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-name">ESCAPE MODE</div>
<div class="state-desc">Buffer Command...</div> <div class="state-desc">Buffer Command...</div>
</div> </div>
<!-- 指令说明框 --> <!-- 指令说明框 -->
<div class="action-log" v-if="lastAction"> <div class="action-log" v-if="lastAction">
<span class="action-icon"></span> <span class="action-icon"></span>
@@ -63,18 +71,22 @@
<div class="terminal-screen"> <div class="terminal-screen">
<div class="label">Terminal Screen / 屏幕显示</div> <div class="label">Terminal Screen / 屏幕显示</div>
<div class="screen-content"> <div class="screen-content">
<span <span
v-for="(char, index) in outputBuffer" v-for="(char, index) in outputBuffer"
:key="index" :key="index"
:style="char.style" :style="char.style"
>{{ char.val }}</span><span class="cursor">_</span> >{{ char.val }}</span
><span class="cursor">_</span>
</div> </div>
</div> </div>
<div class="explanation"> <div class="explanation">
<p> <p>
<span class="badge normal">Normal</span> 模式下字符直接上屏 <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> </p>
</div> </div>
</div> </div>
@@ -135,7 +147,7 @@ const togglePlay = () => {
const play = async () => { const play = async () => {
if (isPlaying.value) return if (isPlaying.value) return
isPlaying.value = true isPlaying.value = true
// If finished, reset first // If finished, reset first
if (isFinished.value) { if (isFinished.value) {
reset() reset()
@@ -146,7 +158,7 @@ const play = async () => {
if (!isPlaying.value) break if (!isPlaying.value) break
const char = charStream.value[currentIndex.value] const char = charStream.value[currentIndex.value]
// Processing Logic // Processing Logic
if (parserState.value === 'NORMAL') { if (parserState.value === 'NORMAL') {
if (char.val === '\x1B') { if (char.val === '\x1B') {
@@ -171,23 +183,23 @@ const play = async () => {
currentStyle.value = {} currentStyle.value = {}
lastAction.value = 'Execute: Reset Style' lastAction.value = 'Execute: Reset Style'
} }
// Small delay to show "Executing" state // Small delay to show "Executing" state
await new Promise(r => setTimeout(r, 200)) await new Promise((r) => setTimeout(r, 200))
parserState.value = 'NORMAL' parserState.value = 'NORMAL'
} else { } else {
lastAction.value = 'Buffering...' 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 // Check playing again after wait
if (!isPlaying.value) break if (!isPlaying.value) break
currentIndex.value++ currentIndex.value++
} }
if (currentIndex.value >= charStream.value.length) { if (currentIndex.value >= charStream.value.length) {
isPlaying.value = false isPlaying.value = false
isFinished.value = true isFinished.value = true
@@ -272,8 +284,20 @@ const play = async () => {
position: relative; position: relative;
height: 100%; height: 100%;
/* Mask gradient to fade edges */ /* Mask gradient to fade edges */
mask-image: linear-gradient(to right, transparent, black 40%, black 60%, transparent); mask-image: linear-gradient(
-webkit-mask-image: linear-gradient(to right, transparent, black 40%, black 60%, transparent); to right,
transparent,
black 40%,
black 60%,
transparent
);
-webkit-mask-image: linear-gradient(
to right,
transparent,
black 40%,
black 60%,
transparent
);
} }
.stream-content { .stream-content {
@@ -290,7 +314,7 @@ const play = async () => {
- Item[0] center is at: 18px (half width) - Item[0] center is at: 18px (half width)
- So we need to shift left by 18px initially. - So we need to shift left by 18px initially.
*/ */
margin-left: -18px; margin-left: -18px;
transition: transform 0.5s cubic-bezier(0.25, 1, 0.5, 1); transition: transform 0.5s cubic-bezier(0.25, 1, 0.5, 1);
} }
@@ -312,7 +336,7 @@ const play = async () => {
background: #fff; background: #fff;
color: #000; color: #000;
transform: scale(1.1); 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; z-index: 10;
border-color: #fff; border-color: #fff;
} }
@@ -339,8 +363,15 @@ const play = async () => {
color: #fff; color: #fff;
} }
.char-val { font-size: 14px; font-weight: bold; } .char-val {
.char-code { font-size: 9px; opacity: 0.7; margin-top: 2px; } font-size: 14px;
font-weight: bold;
}
.char-code {
font-size: 9px;
opacity: 0.7;
margin-top: 2px;
}
.pointer { .pointer {
position: absolute; position: absolute;
@@ -350,9 +381,14 @@ const play = async () => {
text-align: center; text-align: center;
color: #0dbc79; color: #0dbc79;
} }
.arrow { font-size: 20px; line-height: 1; } .arrow {
.pointer-label { font-size: 10px; white-space: nowrap; } font-size: 20px;
line-height: 1;
}
.pointer-label {
font-size: 10px;
white-space: nowrap;
}
/* State Machine */ /* State Machine */
.parser-state-machine { .parser-state-machine {
@@ -389,10 +425,19 @@ const play = async () => {
color: #000; color: #000;
} }
.state-name { font-weight: bold; font-size: 12px; } .state-name {
.state-desc { font-size: 10px; opacity: 0.8; } 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 { .action-log {
margin-left: 20px; margin-left: 20px;
@@ -440,10 +485,24 @@ const play = async () => {
font-size: 11px; font-size: 11px;
color: #000; color: #000;
} }
.badge.normal { background: #0dbc79; } .badge.normal {
.badge.escape { background: #e5e510; } background: #0dbc79;
}
@keyframes blink { 50% { opacity: 0; } } .badge.escape {
@keyframes flash { 0% { background: #333; } 100% { background: #000; } } background: #e5e510;
}
@keyframes blink {
50% {
opacity: 0;
}
}
@keyframes flash {
0% {
background: #333;
}
100% {
background: #000;
}
}
</style> </style>
@@ -21,8 +21,8 @@
<span class="zh">16 色调色板</span> <span class="zh">16 色调色板</span>
</div> </div>
<div class="palette-grid"> <div class="palette-grid">
<div <div
v-for="(color, index) in palette" v-for="(color, index) in palette"
:key="index" :key="index"
class="swatch" class="swatch"
:style="{ backgroundColor: color }" :style="{ backgroundColor: color }"
@@ -31,7 +31,8 @@
></div> ></div>
</div> </div>
<div class="hint-text" v-if="activeColor"> <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>
</div> </div>
@@ -85,25 +86,32 @@
<div class="preview"> <div class="preview">
<div class="terminal-window"> <div class="terminal-window">
<div class="window-header"> <div class="window-header">
<div class="dots"> <div class="dots"><span></span><span></span><span></span></div>
<span></span><span></span><span></span>
</div>
<div class="window-title">Terminal Preview</div> <div class="window-title">Terminal Preview</div>
</div> </div>
<div class="window-content"> <div class="window-content">
<div class="sequence-display-area"> <div class="sequence-display-area">
<span class="label">Last Sequence:</span> <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> <span v-else class="placeholder">Waiting for input...</span>
</div> </div>
<div class="main-display" :style="currentStyle" v-if="isContentVisible"> <div
class="main-display"
:style="currentStyle"
v-if="isContentVisible"
>
Hello World Hello World
</div> </div>
<div class="cursor-line"> <div class="cursor-line">
<span class="prompt">$</span> <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> <span class="cursor-block" :style="cursorStyle"></span>
</div> </div>
</div> </div>
@@ -116,8 +124,22 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
const palette = [ const palette = [
'#000000', '#cd3131', '#0dbc79', '#e5e510', '#2472c8', '#bc3fbc', '#11a8cd', '#e5e5e5', '#000000',
'#666666', '#f14c4c', '#23d18b', '#f5f543', '#3b8eea', '#d670d6', '#29b8db', '#ffffff' '#cd3131',
'#0dbc79',
'#e5e510',
'#2472c8',
'#bc3fbc',
'#11a8cd',
'#e5e5e5',
'#666666',
'#f14c4c',
'#23d18b',
'#f5f543',
'#3b8eea',
'#d670d6',
'#29b8db',
'#ffffff'
] ]
const activeColor = ref(null) const activeColor = ref(null)
@@ -303,8 +325,14 @@ button.active {
justify-content: space-between; justify-content: space-between;
} }
.code { color: #facc15; font-weight: bold; } .code {
.desc { color: #a1a1aa; font-size: 12px; } color: #facc15;
font-weight: bold;
}
.desc {
color: #a1a1aa;
font-size: 12px;
}
.terminal-window { .terminal-window {
background: #000; background: #000;
@@ -407,7 +435,9 @@ button.active {
} }
@keyframes blink { @keyframes blink {
50% { opacity: 0; } 50% {
opacity: 0;
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -14,7 +14,7 @@
<div class="flow-diagram"> <div class="flow-diagram">
<div class="stack-col"> <div class="stack-col">
<div class="stack-label">TERMINAL STACK</div> <div class="stack-label">TERMINAL STACK</div>
<div class="stack-box kbd" :class="{ active: activeStage === 'kbd' }"> <div class="stack-box kbd" :class="{ active: activeStage === 'kbd' }">
<div class="box-header"> <div class="box-header">
<span class="box-icon">[kbd]</span> <span class="box-icon">[kbd]</span>
@@ -56,11 +56,9 @@
<div class="output-col"> <div class="output-col">
<div class="output-label">OUTPUT</div> <div class="output-label">OUTPUT</div>
<div class="terminal-preview"> <div class="terminal-preview">
<div class="term-header"> <div class="term-header"><span></span><span></span><span></span></div>
<span></span><span></span><span></span>
</div>
<div class="term-body"> <div class="term-body">
<span class="prompt">$ </span> <span class="prompt">$ </span>
<span class="typed-text">{{ displayText }}</span> <span class="typed-text">{{ displayText }}</span>
@@ -74,7 +72,11 @@
</div> </div>
<div class="controls"> <div class="controls">
<button class="play-btn" @click="startAnimation" :disabled="isAnimating"> <button
class="play-btn"
@click="startAnimation"
:disabled="isAnimating"
>
{{ isAnimating ? 'Simulating...' : 'Simulate Keystroke' }} {{ isAnimating ? 'Simulating...' : 'Simulate Keystroke' }}
</button> </button>
</div> </div>
@@ -96,13 +98,13 @@ const statusColor = computed(() => {
return 'text-green' return 'text-green'
}) })
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)) const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
const startAnimation = async () => { const startAnimation = async () => {
if (isAnimating.value) return if (isAnimating.value) return
isAnimating.value = true isAnimating.value = true
displayText.value = '' displayText.value = ''
// Stage 1: Keyboard // Stage 1: Keyboard
activeStage.value = 'kbd' activeStage.value = 'kbd'
statusTitle.value = 'Input' statusTitle.value = 'Input'
@@ -159,12 +161,14 @@ const startAnimation = async () => {
color: #ccc; color: #ccc;
} }
.stack-col, .output-col { .stack-col,
.output-col {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.stack-label, .output-label { .stack-label,
.output-label {
color: #eab308; color: #eab308;
font-size: 12px; font-size: 12px;
margin-bottom: 20px; margin-bottom: 20px;
@@ -245,8 +249,12 @@ const startAnimation = async () => {
color: #fff; color: #fff;
} }
.prompt { color: #888; } .prompt {
.typed-text { color: #22c55e; } color: #888;
}
.typed-text {
color: #22c55e;
}
.cursor { .cursor {
display: inline-block; display: inline-block;
@@ -262,7 +270,9 @@ const startAnimation = async () => {
} }
@keyframes blink { @keyframes blink {
50% { opacity: 0; } 50% {
opacity: 0;
}
} }
.status-box { .status-box {
@@ -285,8 +295,12 @@ const startAnimation = async () => {
line-height: 1.5; line-height: 1.5;
} }
.text-red { color: #ef4444; } .text-red {
.text-green { color: #22c55e; } color: #ef4444;
}
.text-green {
color: #22c55e;
}
.play-btn { .play-btn {
width: 100%; width: 100%;
@@ -12,7 +12,12 @@
- 历史记录记录最近几次按键的编码流 - 历史记录记录最近几次按键的编码流
--> -->
<template> <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-overlay" v-if="!isFocused" @click="focus">
<div class="focus-btn"> <div class="focus-btn">
<span class="icon"></span> <span class="icon"></span>
@@ -22,7 +27,7 @@
<div class="main-display" :class="{ 'blur-content': !isFocused }"> <div class="main-display" :class="{ 'blur-content': !isFocused }">
<div class="key-name">{{ currentKey.name || 'Press any key' }}</div> <div class="key-name">{{ currentKey.name || 'Press any key' }}</div>
<div class="info-grid"> <div class="info-grid">
<div class="info-box"> <div class="info-box">
<div class="label">BYTES (HEX)</div> <div class="label">BYTES (HEX)</div>
@@ -33,9 +38,10 @@
<div class="value code">{{ currentKey.sequence || '-' }}</div> <div class="value code">{{ currentKey.sequence || '-' }}</div>
</div> </div>
</div> </div>
<div class="char-display"> <div class="char-display">
Character: <span class="char-val">{{ currentKey.charDisplay || '-' }}</span> Character:
<span class="char-val">{{ currentKey.charDisplay || '-' }}</span>
</div> </div>
</div> </div>
@@ -71,7 +77,7 @@ const handleBlur = () => {
const handleKeydown = (e) => { const handleKeydown = (e) => {
e.preventDefault() e.preventDefault()
let name = e.key let name = e.key
let bytes = '' let bytes = ''
let sequence = '' let sequence = ''
@@ -80,15 +86,15 @@ const handleKeydown = (e) => {
// Map special keys // Map special keys
const keyMap = { const keyMap = {
' ': { name: 'Space', bytes: '20', char: ' ' }, ' ': { name: 'Space', bytes: '20', char: ' ' },
'Enter': { name: 'Enter', bytes: '0a', char: '\\n' }, Enter: { name: 'Enter', bytes: '0a', char: '\\n' },
'Tab': { name: 'Tab', bytes: '09', char: '\\t' }, Tab: { name: 'Tab', bytes: '09', char: '\\t' },
'Escape': { name: 'Esc', bytes: '1b', char: '\\e' }, Escape: { name: 'Esc', bytes: '1b', char: '\\e' },
'Backspace': { name: 'Backspace', bytes: '7f', char: '\\b' }, Backspace: { name: 'Backspace', bytes: '7f', char: '\\b' },
'Delete': { name: 'Del', bytes: '1b 5b 33 7e', sequence: '^[[3~' }, Delete: { name: 'Del', bytes: '1b 5b 33 7e', sequence: '^[[3~' },
'ArrowUp': { name: 'Arrow Up', bytes: '1b 5b 41', sequence: '^[[A' }, ArrowUp: { name: 'Arrow Up', bytes: '1b 5b 41', sequence: '^[[A' },
'ArrowDown': { name: 'Arrow Down', bytes: '1b 5b 42', sequence: '^[[B' }, ArrowDown: { name: 'Arrow Down', bytes: '1b 5b 42', sequence: '^[[B' },
'ArrowRight': { name: 'Arrow Right', bytes: '1b 5b 43', sequence: '^[[C' }, ArrowRight: { name: 'Arrow Right', bytes: '1b 5b 43', sequence: '^[[C' },
'ArrowLeft': { name: 'Arrow Left', bytes: '1b 5b 44', sequence: '^[[D' }, ArrowLeft: { name: 'Arrow Left', bytes: '1b 5b 44', sequence: '^[[D' }
} }
if (keyMap[e.key]) { if (keyMap[e.key]) {
@@ -117,7 +123,7 @@ const handleKeydown = (e) => {
const keyData = { name, bytes, sequence, charDisplay } const keyData = { name, bytes, sequence, charDisplay }
currentKey.value = keyData currentKey.value = keyData
history.value.unshift(keyData) history.value.unshift(keyData)
if (history.value.length > 5) history.value.pop() if (history.value.length > 5) history.value.pop()
} }
@@ -138,7 +144,9 @@ const handleKeydown = (e) => {
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
overflow: hidden; overflow: hidden;
transition: border-color 0.2s, box-shadow 0.2s; transition:
border-color 0.2s,
box-shadow 0.2s;
} }
.input-visualizer:focus { .input-visualizer:focus {
@@ -190,7 +198,9 @@ const handleKeydown = (e) => {
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
transition: opacity 0.2s, filter 0.2s; transition:
opacity 0.2s,
filter 0.2s;
} }
.blur-content { .blur-content {
@@ -237,8 +247,12 @@ const handleKeydown = (e) => {
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.highlight { color: #facc15; /* Yellow 400 */ } .highlight {
.code { color: #22d3ee; /* Cyan 400 */ } color: #facc15; /* Yellow 400 */
}
.code {
color: #22d3ee; /* Cyan 400 */
}
.char-display { .char-display {
color: #a1a1aa; /* Zinc 400 */ color: #a1a1aa; /* Zinc 400 */
@@ -275,9 +289,9 @@ const handleKeydown = (e) => {
border: 1px solid #27272a; border: 1px solid #27272a;
} }
.arrow { .arrow {
color: #71717a; /* Lighter grey for better visibility */ color: #71717a; /* Lighter grey for better visibility */
margin: 0 8px; margin: 0 8px;
} }
.h-name { .h-name {
@@ -285,8 +299,8 @@ const handleKeydown = (e) => {
color: #e4e4e7; color: #e4e4e7;
} }
.h-bytes { .h-bytes {
color: #facc15; color: #facc15;
font-family: monospace; font-family: monospace;
} }
</style> </style>
@@ -6,37 +6,41 @@
## 组件列表 ## 组件列表
| 组件名 | 描述 | 对应文档章节 | | 组件名 | 描述 | 对应文档章节 |
| :--- | :--- | :--- | | :------------------------- | :------------------------------------------------------------------------------------ | :--------------- |
| **TerminalDefinition.vue** | 可视化终端作为“字符流输入/输出环境”的定义。展示键盘输入 -> 字符流 -> 屏幕输出的过程。 | 1. 概念界定 | | **TerminalDefinition.vue** | 可视化终端作为“字符流输入/输出环境”的定义。展示键盘输入 -> 字符流 -> 屏幕输出的过程。 | 1. 概念界定 |
| **ArchitectureDemo.vue** | 演示终端(前端)与 Shell(后端)的分离架构。模拟点餐流程类比。 | 2. 核心架构 | | **ArchitectureDemo.vue** | 演示终端(前端)与 Shell(后端)的分离架构。模拟点餐流程类比。 | 2. 核心架构 |
| **TerminalGrid.vue** | 展示终端的字符网格系统,演示行、列和单元格的概念。 | 3. 视觉模型 | | **TerminalGrid.vue** | 展示终端的字符网格系统,演示行、列和单元格的概念。 | 3. 视觉模型 |
| **CellInspector.vue** | 单元格检查器,展示每个字符单元格背后的属性(字符、前景色、背景色等)。 | 3.2 样式检查 | | **CellInspector.vue** | 单元格检查器,展示每个字符单元格背后的属性(字符、前景色、背景色等)。 | 3.2 样式检查 |
| **EscapeSequences.vue** | 演示 ANSI 转义序列如何控制颜色、样式、光标移动和清屏。 | 4. 通信协议 | | **EscapeSequences.vue** | 演示 ANSI 转义序列如何控制颜色、样式、光标移动和清屏。 | 4. 通信协议 |
| **InputVisualizer.vue** | 可视化键盘按键如何转换为字节序列发送给 Shell。 | 5. 输入机制 | | **InputVisualizer.vue** | 可视化键盘按键如何转换为字节序列发送给 Shell。 | 5. 输入机制 |
| **WebTerminal.vue** | 一个功能较完整的模拟终端,支持 `ls`, `cd`, `cat`, `apt` 等命令,包含虚拟文件系统。 | 附录/综合演示 | | **WebTerminal.vue** | 一个功能较完整的模拟终端,支持 `ls`, `cd`, `cat`, `apt` 等命令,包含虚拟文件系统。 | 附录/综合演示 |
| **SignalsDemo.vue** | 演示终端信号(如 Ctrl+C SIGINT)的工作机制。 | (文档中可能引用) | | **SignalsDemo.vue** | 演示终端信号(如 Ctrl+C SIGINT)的工作机制。 | (文档中可能引用) |
| **FlowDiagram.vue** | 展示标准输入/输出/错误流 (stdin/stdout/stderr) 的流向图。 | (文档中可能引用) | | **FlowDiagram.vue** | 展示标准输入/输出/错误流 (stdin/stdout/stderr) 的流向图。 | (文档中可能引用) |
| **AdvancedTUIDemo.vue** | 展示基于文本的用户界面 (TUI) 的高级布局能力(如面板、进度条)。 | (文档中可能引用) | | **AdvancedTUIDemo.vue** | 展示基于文本的用户界面 (TUI) 的高级布局能力(如面板、进度条)。 | (文档中可能引用) |
## 开发指南 ## 开发指南
### 技术栈 ### 技术栈
- **Vue 3**: 使用 `<script setup>` 语法。 - **Vue 3**: 使用 `<script setup>` 语法。
- **Styling**: Scoped CSS,主要使用 Flexbox 和 Grid 布局。 - **Styling**: Scoped CSS,主要使用 Flexbox 和 Grid 布局。
- **Theme**: 统一使用黑色系背景 (`#09090b`, `#18181b`) 和 JetBrains Mono 字体,保持类似终端的视觉风格。 - **Theme**: 统一使用黑色系背景 (`#09090b`, `#18181b`) 和 JetBrains Mono 字体,保持类似终端的视觉风格。
### 维护注意事项 ### 维护注意事项
1. **双语支持**: 组件内部文本尽量支持中英双语,或通过 Props 传入文本。目前部分组件已硬编码双语标签。 1. **双语支持**: 组件内部文本尽量支持中英双语,或通过 Props 传入文本。目前部分组件已硬编码双语标签。
2. **自包含**: 组件应尽量自包含,不依赖外部复杂的 Store 或 Context,以便于在 Markdown 中直接使用。 2. **自包含**: 组件应尽量自包含,不依赖外部复杂的 Store 或 Context,以便于在 Markdown 中直接使用。
3. **响应式**: 考虑移动端适配,通常使用 `@media (max-width: 768px)` 进行布局调整。 3. **响应式**: 考虑移动端适配,通常使用 `@media (max-width: 768px)` 进行布局调整。
### 常用颜色变量 (参考) ### 常用颜色变量 (参考)
- 背景: `#09090b` (Main), `#18181b` (Panel) - 背景: `#09090b` (Main), `#18181b` (Panel)
- 边框: `#27272a` - 边框: `#27272a`
- 文本: `#e4e4e7` (Primary), `#a1a1aa` (Secondary) - 文本: `#e4e4e7` (Primary), `#a1a1aa` (Secondary)
- 强调色: `#22c55e` (Green/Success), `#facc15` (Yellow/Warning), `#22d3ee` (Cyan/Info) - 强调色: `#22c55e` (Green/Success), `#facc15` (Yellow/Warning), `#22d3ee` (Cyan/Info)
## 目录结构 ## 目录结构
所有组件均位于 `docs/.vitepress/theme/components/appendix/terminal-intro/` 下。 所有组件均位于 `docs/.vitepress/theme/components/appendix/terminal-intro/` 下。
注册逻辑位于 `docs/.vitepress/theme/index.js` 注册逻辑位于 `docs/.vitepress/theme/index.js`
@@ -15,8 +15,8 @@
<div class="signals-demo"> <div class="signals-demo">
<div class="left-panel"> <div class="left-panel">
<div class="signal-list"> <div class="signal-list">
<div <div
class="signal-item" class="signal-item"
:class="{ active: activeSignal === 'SIGINT' }" :class="{ active: activeSignal === 'SIGINT' }"
@click="sendSignal('SIGINT')" @click="sendSignal('SIGINT')"
> >
@@ -27,8 +27,8 @@
<div class="signal-name">SIGINT</div> <div class="signal-name">SIGINT</div>
</div> </div>
<div <div
class="signal-item" class="signal-item"
:class="{ active: activeSignal === 'SIGTSTP' }" :class="{ active: activeSignal === 'SIGTSTP' }"
@click="sendSignal('SIGTSTP')" @click="sendSignal('SIGTSTP')"
> >
@@ -43,22 +43,33 @@
<div class="info-box"> <div class="info-box">
<div v-if="activeSignal === 'SIGINT'"> <div v-if="activeSignal === 'SIGINT'">
<div class="info-header"> <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>
<div class="info-desc">Stop the running program</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"> <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> </div>
<div v-else-if="activeSignal === 'SIGTSTP'"> <div v-else-if="activeSignal === 'SIGTSTP'">
<div class="info-header"> <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>
<div class="info-desc">Suspend the running program</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"> <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> </div>
<div v-else> <div v-else>
@@ -74,12 +85,18 @@
<div class="dots"><span></span><span></span><span></span></div> <div class="dots"><span></span><span></span><span></span></div>
</div> </div>
<div class="window-content"> <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 }} {{ line.text }}
</div> </div>
<div v-if="isRunning" class="term-line output">sleeping...</div> <div v-if="isRunning" class="term-line output">sleeping...</div>
<div v-if="inputBuffer" class="term-line input"> <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>
<div v-else class="term-line input"> <div v-else class="term-line input">
<span class="prompt">$</span> <span class="cursor"></span> <span class="prompt">$</span> <span class="cursor"></span>
@@ -88,7 +105,9 @@
</div> </div>
<div class="controls"> <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('SIGINT')">Ctrl+C</button>
<button class="btn" @click="sendSignal('SIGTSTP')">Ctrl+Z</button> <button class="btn" @click="sendSignal('SIGTSTP')">Ctrl+Z</button>
<button class="btn secondary" @click="reset">Reset</button> <button class="btn secondary" @click="reset">Reset</button>
@@ -97,9 +116,10 @@
<div class="state-display"> <div class="state-display">
State: <span :class="stateClass">{{ processState }}</span> State: <span :class="stateClass">{{ processState }}</span>
</div> </div>
<p class="instruction"> <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> </p>
</div> </div>
</div> </div>
@@ -110,9 +130,7 @@ import { ref, computed } from 'vue'
const activeSignal = ref('SIGINT') const activeSignal = ref('SIGINT')
const isRunning = ref(false) const isRunning = ref(false)
const lines = ref([ const lines = ref([{ type: 'input', text: '$ sleep 100' }])
{ type: 'input', text: '$ sleep 100' }
])
const processState = ref('Running') const processState = ref('Running')
const inputBuffer = ref('') const inputBuffer = ref('')
@@ -131,7 +149,7 @@ const runCommand = () => {
const sendSignal = (sig) => { const sendSignal = (sig) => {
activeSignal.value = sig activeSignal.value = sig
if (!isRunning.value && sig === 'SIGINT') return if (!isRunning.value && sig === 'SIGINT') return
if (sig === 'SIGINT') { if (sig === 'SIGINT') {
@@ -142,7 +160,10 @@ const sendSignal = (sig) => {
} else if (sig === 'SIGTSTP') { } else if (sig === 'SIGTSTP') {
lines.value.push({ type: 'output', text: 'sleeping...' }) lines.value.push({ type: 'output', text: 'sleeping...' })
lines.value.push({ type: 'control', text: '^Z' }) 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 isRunning.value = false
processState.value = 'Process suspended (stopped)' processState.value = 'Process suspended (stopped)'
} }
@@ -161,7 +182,10 @@ reset()
<style scoped> <style scoped>
.signals-demo { .signals-demo {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); /* 自动适应宽度,不够时换行 */ grid-template-columns: repeat(
auto-fit,
minmax(280px, 1fr)
); /* 自动适应宽度,不够时换行 */
gap: 30px; gap: 30px;
background: #09090b; background: #09090b;
padding: 30px; padding: 30px;
@@ -237,9 +261,15 @@ reset()
font-weight: bold; font-weight: bold;
} }
.highlight { color: #facc15; } .highlight {
.signal-green { color: #22c55e; } color: #facc15;
.signal-blue { color: #3b82f6; } }
.signal-green {
color: #22c55e;
}
.signal-blue {
color: #3b82f6;
}
.info-desc { .info-desc {
color: #a1a1aa; color: #a1a1aa;
@@ -271,7 +301,9 @@ reset()
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; 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 { .window-header {
@@ -300,10 +332,19 @@ reset()
margin-bottom: 5px; margin-bottom: 5px;
} }
.control { color: #ef4444; } .control {
.output { color: #d4d4d8; } color: #ef4444;
.input { color: #fff; } }
.prompt { color: #71717a; margin-right: 8px; } .output {
color: #d4d4d8;
}
.input {
color: #fff;
}
.prompt {
color: #71717a;
margin-right: 8px;
}
.cursor { .cursor {
display: inline-block; display: inline-block;
@@ -348,9 +389,15 @@ reset()
margin-top: 10px; margin-top: 10px;
} }
.state-green { color: #22c55e; } .state-green {
.state-red { color: #ef4444; } color: #22c55e;
.state-blue { color: #3b82f6; } }
.state-red {
color: #ef4444;
}
.state-blue {
color: #3b82f6;
}
.instruction { .instruction {
color: #a1a1aa; color: #a1a1aa;
@@ -1,16 +1,10 @@
<template> <template>
<div class="terminal-definition"> <div class="terminal-definition">
<div class="mode-switch"> <div class="mode-switch">
<button <button :class="{ active: mode === 'cli' }" @click="mode = 'cli'">
:class="{ active: mode === 'cli' }"
@click="mode = 'cli'"
>
🖥 CLI (命令行界面) 🖥 CLI (命令行界面)
</button> </button>
<button <button :class="{ active: mode === 'gui' }" @click="mode = 'gui'">
:class="{ active: mode === 'gui' }"
@click="mode = 'gui'"
>
🖱 GUI (图形用户界面) 🖱 GUI (图形用户界面)
</button> </button>
</div> </div>
@@ -21,7 +15,28 @@
<!-- Input Side --> <!-- Input Side -->
<div class="stage input-stage"> <div class="stage input-stage">
<div class="icon-box"> <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>
<div class="label">Input (Keyboard)</div> <div class="label">Input (Keyboard)</div>
<div class="sub-label">发送指令 (字符信号)</div> <div class="sub-label">发送指令 (字符信号)</div>
@@ -31,8 +46,8 @@
<div class="stream-path"> <div class="stream-path">
<div class="stream-line"></div> <div class="stream-line"></div>
<div class="stream-label">Character Stream / 字符流</div> <div class="stream-label">Character Stream / 字符流</div>
<div <div
v-for="char in activeChars" v-for="char in activeChars"
:key="char.id" :key="char.id"
class="stream-char" class="stream-char"
:style="{ left: char.progress + '%' }" :style="{ left: char.progress + '%' }"
@@ -45,16 +60,20 @@
<div class="stage output-stage"> <div class="stage output-stage">
<div class="terminal-screen"> <div class="terminal-screen">
<div class="screen-content"> <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> </div>
<div class="label">Output (Text Grid)</div> <div class="label">Output (Text Grid)</div>
<div class="sub-label">文本网格反馈</div> <div class="sub-label">文本网格反馈</div>
</div> </div>
</div> </div>
<div class="desc-box"> <div class="desc-box">
<p><strong>CLI (Command Line Interface)</strong>: 这种模式下计算机只认识字符你的每一次按键都会被转换成编码发送给系统系统处理后返回文字结果它不关心你在哪里点击只关心你输入了什么</p> <p>
<strong>CLI (Command Line Interface)</strong>:
这种模式下计算机只认识字符你的每一次按键都会被转换成编码发送给系统系统处理后返回文字结果它不关心你在哪里点击只关心你输入了什么
</p>
</div> </div>
<div class="control-bar"> <div class="control-bar">
@@ -71,7 +90,20 @@
<!-- Input Side --> <!-- Input Side -->
<div class="stage input-stage"> <div class="stage input-stage">
<div class="icon-box gui-input" :class="{ clicking: isGuiClicking }"> <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>
<div class="label">Input (Mouse)</div> <div class="label">Input (Mouse)</div>
<div class="sub-label">发送事件 (坐标/点击)</div> <div class="sub-label">发送事件 (坐标/点击)</div>
@@ -81,7 +113,7 @@
<div class="stream-path"> <div class="stream-path">
<div class="stream-line dashed"></div> <div class="stream-line dashed"></div>
<div class="stream-label">Event Loop / 事件循环</div> <div class="stream-label">Event Loop / 事件循环</div>
<div <div
v-for="ev in guiEvents" v-for="ev in guiEvents"
:key="ev.id" :key="ev.id"
class="gui-event-packet" class="gui-event-packet"
@@ -98,11 +130,22 @@
<div class="win-header"></div> <div class="win-header"></div>
<div class="win-body"> <div class="win-body">
<div class="icon-grid"> <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 class="desktop-icon">📄</div>
</div> </div>
<div class="gui-cursor" :style="cursorStyle"> <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> </div>
</div> </div>
@@ -113,7 +156,11 @@
</div> </div>
<div class="desc-box"> <div class="desc-box">
<p><strong>GUI (Graphical User Interface)</strong>: 这种模式下计算机实时追踪鼠标坐标和点击事件并每秒刷新 60 次屏幕像素它更直观但需要消耗大量资源来处理图形渲染</p> <p>
<strong>GUI (Graphical User Interface)</strong>:
这种模式下计算机实时追踪鼠标坐标和点击事件并每秒刷新 60
次屏幕像素它更直观但需要消耗大量资源来处理图形渲染
</p>
</div> </div>
<div class="control-bar"> <div class="control-bar">
@@ -123,7 +170,6 @@
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
@@ -143,9 +189,9 @@ const startSimulation = () => {
isAnimating.value = true isAnimating.value = true
typedContent.value = '' typedContent.value = ''
activeChars.value = [] activeChars.value = []
let index = 0 let index = 0
const processNextChar = () => { const processNextChar = () => {
if (index >= demoText.length) { if (index >= demoText.length) {
setTimeout(() => { setTimeout(() => {
@@ -156,36 +202,36 @@ const startSimulation = () => {
const char = demoText[index] const char = demoText[index]
const charId = Date.now() + index const charId = Date.now() + index
// Create new flying char // Create new flying char
const newChar = { const newChar = {
id: charId, id: charId,
val: char, val: char,
progress: 10 // start position progress: 10 // start position
} }
activeChars.value.push(newChar) activeChars.value.push(newChar)
// Animate this char // Animate this char
let progress = 10 let progress = 10
const interval = setInterval(() => { const interval = setInterval(() => {
progress += 4 // Faster speed 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 (charObj) charObj.progress = progress
if (progress >= 90) { if (progress >= 90) {
clearInterval(interval) clearInterval(interval)
// Remove from stream and add to screen // 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 typedContent.value += char
// Next char // Next char
index++ index++
setTimeout(processNextChar, 100) // Faster typing setTimeout(processNextChar, 100) // Faster typing
} }
}, 20) }, 20)
} }
processNextChar() processNextChar()
} }
@@ -208,26 +254,29 @@ const startGuiSimulation = () => {
inputMousePosition.value = { x: 80, y: 60 } inputMousePosition.value = { x: 80, y: 60 }
screenCursorPosition.value = { x: 80, y: 60 } screenCursorPosition.value = { x: 80, y: 60 }
guiEvents.value = [] guiEvents.value = []
// 1. Move Cursor (Physical Mouse Movement) // 1. Move Cursor (Physical Mouse Movement)
let step = 0 let step = 0
const moveInterval = setInterval(() => { const moveInterval = setInterval(() => {
step++ step++
inputMousePosition.value = { inputMousePosition.value = {
x: 80 - step * 2, x: 80 - step * 2,
y: 60 - step * 1.5 y: 60 - step * 1.5
} }
// Emit Move Event frequently (Simulate high polling rate) // Emit Move Event frequently (Simulate high polling rate)
if (step % 2 === 0) { if (step % 2 === 0) {
const targetX = inputMousePosition.value.x const targetX = inputMousePosition.value.x
const targetY = inputMousePosition.value.y const targetY = inputMousePosition.value.y
emitGuiEvent(`Move(${Math.round(targetX)},${Math.round(targetY)})`, () => { emitGuiEvent(
// When packet arrives: Update screen cursor `Move(${Math.round(targetX)},${Math.round(targetY)})`,
screenCursorPosition.value = { x: targetX, y: targetY } () => {
}) // When packet arrives: Update screen cursor
screenCursorPosition.value = { x: targetX, y: targetY }
}
)
} }
if (step >= 20) { if (step >= 20) {
clearInterval(moveInterval) clearInterval(moveInterval)
// 2. Click // 2. Click
@@ -244,17 +293,17 @@ const emitGuiEvent = (type, onArrive) => {
progress: 10 progress: 10
} }
guiEvents.value.push(newEvent) guiEvents.value.push(newEvent)
let progress = 10 let progress = 10
const packetInterval = setInterval(() => { const packetInterval = setInterval(() => {
progress += 2 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 (ev) ev.progress = progress
if (progress >= 90) { if (progress >= 90) {
clearInterval(packetInterval) 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 // Execute callback when packet arrives at Output
if (onArrive) onArrive() if (onArrive) onArrive()
} }
@@ -264,21 +313,20 @@ const emitGuiEvent = (type, onArrive) => {
const performClick = () => { const performClick = () => {
setTimeout(() => { setTimeout(() => {
isGuiClicking.value = true isGuiClicking.value = true
// Send Click Event // Send Click Event
emitGuiEvent('Click(40,30)', () => { emitGuiEvent('Click(40,30)', () => {
// When packet arrives: Select icon // When packet arrives: Select icon
iconSelected.value = true iconSelected.value = true
setTimeout(() => { setTimeout(() => {
isGuiAnimating.value = false isGuiAnimating.value = false
}, 1000) }, 1000)
}) })
setTimeout(() => { setTimeout(() => {
isGuiClicking.value = false isGuiClicking.value = false
}, 200) // Input click feedback is fast }, 200) // Input click feedback is fast
}, 300) }, 300)
} }
</script> </script>
@@ -380,7 +428,7 @@ const performClick = () => {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
margin-bottom: 10px; 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; overflow: hidden;
} }
@@ -439,7 +487,7 @@ const performClick = () => {
left: 0; left: 0;
pointer-events: none; pointer-events: none;
transition: transform 0.1s linear; /* Smooth interpolation */ 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 { .screen-content {
@@ -481,7 +529,13 @@ const performClick = () => {
} }
.stream-line.dashed { .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; height: 1px;
} }
@@ -539,7 +593,9 @@ const performClick = () => {
} }
@keyframes blink { @keyframes blink {
50% { opacity: 0; } 50% {
opacity: 0;
}
} }
.desc-box { .desc-box {
@@ -592,13 +648,13 @@ button:disabled {
height: auto; height: auto;
gap: 20px; gap: 20px;
} }
.stream-path { .stream-path {
width: 100%; width: 100%;
height: 40px; height: 40px;
margin: 10px 0; margin: 10px 0;
} }
.stream-line { .stream-line {
transform: rotate(90deg); transform: rotate(90deg);
width: 40px; width: 40px;
@@ -606,4 +662,4 @@ button:disabled {
margin-left: -20px; margin-left: -20px;
} }
} }
</style> </style>
@@ -15,13 +15,13 @@
<div class="grid-demo"> <div class="grid-demo">
<div class="terminal-screen"> <div class="terminal-screen">
<div class="grid-row" v-for="(row, rIndex) in rows" :key="rIndex"> <div class="grid-row" v-for="(row, rIndex) in rows" :key="rIndex">
<div <div
class="grid-cell" class="grid-cell"
v-for="(cell, cIndex) in row" v-for="(cell, cIndex) in row"
:key="cIndex" :key="cIndex"
:class="{ :class="{
'active-cursor': cursor.r === rIndex && cursor.c === cIndex, 'active-cursor': cursor.r === rIndex && cursor.c === cIndex,
'drawn': cell.drawn drawn: cell.drawn
}" }"
@mousedown.prevent="handleCellMouseDown(rIndex, cIndex)" @mousedown.prevent="handleCellMouseDown(rIndex, cIndex)"
@mouseover="handleCellHover(rIndex, cIndex)" @mouseover="handleCellHover(rIndex, cIndex)"
@@ -30,12 +30,12 @@
</div> </div>
</div> </div>
</div> </div>
<div class="controls"> <div class="controls">
<input <input
ref="inputRef" ref="inputRef"
type="text" type="text"
v-model="inputText" v-model="inputText"
placeholder="Type here..." placeholder="Type here..."
class="text-input" class="text-input"
@keydown="handleKeydown" @keydown="handleKeydown"
@@ -52,9 +52,10 @@ import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
const ROW_COUNT = 10 const ROW_COUNT = 10
const COL_COUNT = 40 const COL_COUNT = 40
const createGrid = () => Array.from({ length: ROW_COUNT }, () => const createGrid = () =>
Array.from({ length: COL_COUNT }, () => ({ char: '', drawn: false })) Array.from({ length: ROW_COUNT }, () =>
) Array.from({ length: COL_COUNT }, () => ({ char: '', drawn: false }))
)
const rows = reactive(createGrid()) const rows = reactive(createGrid())
const cursor = reactive({ r: 0, c: 0 }) const cursor = reactive({ r: 0, c: 0 })
@@ -76,12 +77,12 @@ const handleKeydown = (e) => {
rows[cursor.r][cursor.c].char = '' rows[cursor.r][cursor.c].char = ''
return return
} }
if (e.key.length === 1) { if (e.key.length === 1) {
rows[cursor.r][cursor.c].char = e.key rows[cursor.r][cursor.c].char = e.key
advanceCursor() advanceCursor()
} }
if (e.key === 'Enter') { if (e.key === 'Enter') {
cursor.r = Math.min(cursor.r + 1, ROW_COUNT - 1) cursor.r = Math.min(cursor.r + 1, ROW_COUNT - 1)
cursor.c = 0 cursor.c = 0
@@ -116,8 +117,8 @@ const handleCellHover = (r, c) => {
} }
const clearGrid = () => { const clearGrid = () => {
for(let r=0; r<ROW_COUNT; r++) { for (let r = 0; r < ROW_COUNT; r++) {
for(let c=0; c<COL_COUNT; c++) { for (let c = 0; c < COL_COUNT; c++) {
rows[r][c].char = '' rows[r][c].char = ''
rows[r][c].drawn = false rows[r][c].drawn = false
} }
@@ -181,66 +182,68 @@ onBeforeUnmount(() => {
} }
.grid-cell { .grid-cell {
width: 16px; width: 16px;
height: 24px; height: 24px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-right: 1px solid #27272a; border-right: 1px solid #27272a;
border-bottom: 1px solid #27272a; border-bottom: 1px solid #27272a;
color: #e4e4e7; color: #e4e4e7;
font-size: 14px; font-size: 14px;
user-select: none; user-select: none;
}
.grid-cell.drawn {
background-color: #3f3f46;
}
.grid-cell.active-cursor {
background-color: #e4e4e7;
color: #000;
animation: blink 1s step-end infinite;
}
@keyframes blink {
50% {
opacity: 0.7;
} }
}
.grid-cell.drawn {
background-color: #3f3f46; .controls {
} margin-top: 15px;
display: flex;
.grid-cell.active-cursor { gap: 10px;
background-color: #e4e4e7; align-items: center;
color: #000; }
animation: blink 1s step-end infinite;
} .text-input {
background: #18181b;
@keyframes blink { border: 1px solid #3f3f46;
50% { opacity: 0.7; } color: #fff;
} padding: 6px 12px;
border-radius: 6px;
.controls { font-family: inherit;
margin-top: 15px; }
display: flex;
gap: 10px; .btn {
align-items: center; background: #27272a;
} border: 1px solid #3f3f46;
color: #e4e4e7;
.text-input { padding: 6px 16px;
background: #18181b; border-radius: 6px;
border: 1px solid #3f3f46; cursor: pointer;
color: #fff; transition: all 0.2s;
padding: 6px 12px; }
border-radius: 6px;
font-family: inherit; .btn:hover {
} background: #3f3f46;
border-color: #52525b;
.btn { }
background: #27272a;
border: 1px solid #3f3f46; .hint {
color: #e4e4e7; color: #a1a1aa; /* Zinc 400 */
padding: 6px 16px; font-size: 12px;
border-radius: 6px; margin-left: auto;
cursor: pointer; }
transition: all 0.2s; </style>
}
.btn:hover {
background: #3f3f46;
border-color: #52525b;
}
.hint {
color: #a1a1aa; /* Zinc 400 */
font-size: 12px;
margin-left: auto;
}
</style>
@@ -4,7 +4,9 @@
<!-- Left Panel: Task Guide --> <!-- Left Panel: Task Guide -->
<div class="task-panel"> <div class="task-panel">
<div class="panel-header"> <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"> <div class="os-selector">
<select v-model="currentOS" @change="resetCurrentTask"> <select v-model="currentOS" @change="resetCurrentTask">
<option value="mac">macOS</option> <option value="mac">macOS</option>
@@ -18,7 +20,7 @@
<div class="task-content"> <div class="task-content">
<h3>{{ currentTask.title }}</h3> <h3>{{ currentTask.title }}</h3>
<p class="task-desc">{{ currentTask.description }}</p> <p class="task-desc">{{ currentTask.description }}</p>
<div class="ai-helper"> <div class="ai-helper">
<div class="ai-header"> <div class="ai-header">
<span class="ai-icon">🤖</span> <span class="ai-icon">🤖</span>
@@ -29,23 +31,36 @@
{{ currentTask.aiQuery }} {{ currentTask.aiQuery }}
</div> </div>
<div class="chat-bubble ai"> <div class="chat-bubble ai">
<p>{{ currentTask.aiResponse[currentOS] || currentTask.aiResponse.common }}</p> <p>
{{
currentTask.aiResponse[currentOS] ||
currentTask.aiResponse.common
}}
</p>
<!-- Multiple Commands Support --> <!-- Multiple Commands Support -->
<div v-if="currentTask.commands && currentTask.commands[currentOS]" class="cmd-buttons"> <div
<button v-if="currentTask.commands && currentTask.commands[currentOS]"
v-for="(cmdItem, idx) in currentTask.commands[currentOS]" class="cmd-buttons"
>
<button
v-for="(cmdItem, idx) in currentTask.commands[currentOS]"
:key="idx" :key="idx"
class="copy-btn" class="copy-btn"
@click="copyCommand(cmdItem.cmd)" @click="copyCommand(cmdItem.cmd)"
> >
{{ cmdItem.label || '复制命令' }} {{ cmdItem.label || '复制命令' }}
</button> </button>
</div> </div>
<!-- Fallback for Single Command --> <!-- Fallback for Single Command -->
<button <button
v-else-if="currentTask.expectedCmd" v-else-if="currentTask.expectedCmd"
class="copy-btn" class="copy-btn"
@click="copyCommand(currentTask.expectedCmd[currentOS] || currentTask.expectedCmd.common)" @click="
copyCommand(
currentTask.expectedCmd[currentOS] ||
currentTask.expectedCmd.common
)
"
> >
复制命令 复制命令
</button> </button>
@@ -57,11 +72,17 @@
<span class="label">预期目标</span> <span class="label">预期目标</span>
<span class="value">{{ currentTask.goal }}</span> <span class="value">{{ currentTask.goal }}</span>
</div> </div>
<div class="success-message" v-if="isTaskCompleted"> <div class="success-message" v-if="isTaskCompleted">
<span class="icon">🎉</span> <span class="icon">🎉</span>
<span>太棒了任务完成</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> <button class="reset-btn" @click="resetAll" v-else>重新开始</button>
</div> </div>
</div> </div>
@@ -79,22 +100,29 @@
</div> </div>
<div class="terminal-body" ref="terminalBody" @click="focusInput"> <div class="terminal-body" ref="terminalBody" @click="focusInput">
<div v-for="(line, index) in history" :key="index" class="line"> <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> <span :class="line.type">{{ line.content }}</span>
</div> </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> <span class="prompt">{{ prompt }}</span>
<input <input
ref="cmdInput" ref="cmdInput"
v-model="inputCmd" v-model="inputCmd"
@keydown.enter="executeCommand" @keydown.enter="executeCommand"
@keydown.tab.prevent @keydown.tab.prevent
type="text" type="text"
spellcheck="false" spellcheck="false"
autocomplete="off" 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> </div>
</div> </div>
@@ -115,10 +143,10 @@ const terminalBody = ref(null)
// System Configurations // System Configurations
const osConfig = { 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-ps': { prompt: 'PS C:\\Users\\User> ', title: 'Windows PowerShell' },
'win-cmd': { prompt: 'C:\\Users\\User> ', title: 'Command Prompt' }, '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) const prompt = computed(() => osConfig[currentOS.value].prompt)
@@ -132,14 +160,17 @@ const tasks = [
goal: '列出当前目录下的所有文件。', goal: '列出当前目录下的所有文件。',
aiQuery: '我想查看当前目录下的文件,应该用什么命令?', aiQuery: '我想查看当前目录下的文件,应该用什么命令?',
aiResponse: { aiResponse: {
'mac': '在 macOS 和 Linux 中,查看文件列表使用 `ls` 命令 (List)。', mac: '在 macOS 和 Linux 中,查看文件列表使用 `ls` 命令 (List)。',
'linux': '在 macOS 和 Linux 中,查看文件列表使用 `ls` 命令 (List)。', linux: '在 macOS 和 Linux 中,查看文件列表使用 `ls` 命令 (List)。',
'win-ps': '在 PowerShell 中,你可以使用 `ls` 或 `dir` 命令。', 'win-ps': '在 PowerShell 中,你可以使用 `ls` 或 `dir` 命令。',
'win-cmd': '在 Windows CMD 中,查看文件列表使用 `dir` 命令 (Directory)。', 'win-cmd': '在 Windows CMD 中,查看文件列表使用 `dir` 命令 (Directory)。',
'common': '通常使用 ls 或 dir。' common: '通常使用 ls 或 dir。'
}, },
expectedCmd: { expectedCmd: {
'mac': 'ls', 'linux': 'ls', 'win-ps': 'ls', 'win-cmd': 'dir' mac: 'ls',
linux: 'ls',
'win-ps': 'ls',
'win-cmd': 'dir'
}, },
validate: (cmd, os) => { validate: (cmd, os) => {
const valid = os === 'win-cmd' ? ['dir'] : ['ls', 'dir', 'll'] const valid = os === 'win-cmd' ? ['dir'] : ['ls', 'dir', 'll']
@@ -161,14 +192,16 @@ d---- 1/15/2026 9:00 AM Downloads
}, },
{ {
title: '第二步:创建一个新家', title: '第二步:创建一个新家',
description: '文件太多会很乱,我们创建一个专门的文件夹来存放今天的练习文件。', description:
'文件太多会很乱,我们创建一个专门的文件夹来存放今天的练习文件。',
goal: '创建一个名为 "demo" 的文件夹。', goal: '创建一个名为 "demo" 的文件夹。',
aiQuery: '怎么创建一个新的文件夹?名字叫 demo。', aiQuery: '怎么创建一个新的文件夹?名字叫 demo。',
aiResponse: { aiResponse: {
'common': '创建文件夹(目录)的命令是 `mkdir` (Make Directory)。你可以输入 `mkdir demo`。' common:
'创建文件夹(目录)的命令是 `mkdir` (Make Directory)。你可以输入 `mkdir demo`。'
}, },
expectedCmd: { expectedCmd: {
'common': 'mkdir demo' common: 'mkdir demo'
}, },
validate: (cmd) => cmd.trim() === 'mkdir demo', validate: (cmd) => cmd.trim() === 'mkdir demo',
output: () => '' // mkdir usually has no output on success output: () => '' // mkdir usually has no output on success
@@ -179,10 +212,10 @@ d---- 1/15/2026 9:00 AM Downloads
goal: '进入 "demo" 文件夹。', goal: '进入 "demo" 文件夹。',
aiQuery: '怎么进入刚才建好的 demo 文件夹?', aiQuery: '怎么进入刚才建好的 demo 文件夹?',
aiResponse: { aiResponse: {
'common': '切换目录使用 `cd` 命令 (Change Directory)。输入 `cd demo` 即可。' common: '切换目录使用 `cd` 命令 (Change Directory)。输入 `cd demo` 即可。'
}, },
expectedCmd: { expectedCmd: {
'common': 'cd demo' common: 'cd demo'
}, },
validate: (cmd) => cmd.trim() === 'cd demo', validate: (cmd) => cmd.trim() === 'cd demo',
output: () => '' // cd usually has no output, but prompt changes output: () => '' // cd usually has no output, but prompt changes
@@ -193,19 +226,26 @@ d---- 1/15/2026 9:00 AM Downloads
goal: '创建一个名为 "hello.txt" 的文件。', goal: '创建一个名为 "hello.txt" 的文件。',
aiQuery: '我想新建一个空文件叫 hello.txt,怎么做?', aiQuery: '我想新建一个空文件叫 hello.txt,怎么做?',
aiResponse: { aiResponse: {
'mac': '在 Mac/Linux 上,使用 `touch hello.txt` 可以快速创建一个空文件。', mac: '在 Mac/Linux 上,使用 `touch hello.txt` 可以快速创建一个空文件。',
'linux': '在 Mac/Linux 上,使用 `touch hello.txt` 可以快速创建一个空文件。', linux: '在 Mac/Linux 上,使用 `touch hello.txt` 可以快速创建一个空文件。',
'win-ps': '在 PowerShell 中,可以使用 `ni hello.txt` 或 `echo "" > hello.txt`。', 'win-ps':
'win-cmd': '在 CMD 中,可以使用 `type nul > hello.txt` 或 `echo. > hello.txt`。', '在 PowerShell 中,可以使用 `ni hello.txt` 或 `echo "" > hello.txt`。',
'win-cmd':
'在 CMD 中,可以使用 `type nul > hello.txt` 或 `echo. > hello.txt`。'
}, },
expectedCmd: { expectedCmd: {
'mac': 'touch hello.txt', mac: 'touch hello.txt',
'linux': 'touch hello.txt', linux: 'touch hello.txt',
'win-ps': 'ni hello.txt', 'win-ps': 'ni hello.txt',
'win-cmd': 'type nul > hello.txt' 'win-cmd': 'type nul > hello.txt'
}, },
validate: (cmd, os) => { 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 cmd.includes('hello.txt')
} }
return false return false
@@ -214,22 +254,26 @@ d---- 1/15/2026 9:00 AM Downloads
}, },
{ {
title: '第五步:安装程序 (系统软件 & Python库)', title: '第五步:安装程序 (系统软件 & Python库)',
description: '终端不仅能管理文件,还能安装软件。我们来尝试两种常见的安装场景:安装系统工具(如 wget/git)和安装 Python 库(如 requests)。', description:
'终端不仅能管理文件,还能安装软件。我们来尝试两种常见的安装场景:安装系统工具(如 wget/git)和安装 Python 库(如 requests)。',
goal: '任选其一:安装系统工具或 Python 库。', goal: '任选其一:安装系统工具或 Python 库。',
aiQuery: '怎么用命令行安装软件?我想装 git 或者 python 的 requests 库。', aiQuery: '怎么用命令行安装软件?我想装 git 或者 python 的 requests 库。',
aiResponse: { aiResponse: {
'mac': 'macOS 推荐使用 Homebrew 安装系统软件,使用 pip 安装 Python 库。', mac: 'macOS 推荐使用 Homebrew 安装系统软件,使用 pip 安装 Python 库。',
'linux': 'Linux (Ubuntu/Debian) 使用 apt 安装系统软件,使用 pip 安装 Python 库。', linux:
'win-ps': 'Windows PowerShell 推荐使用 winget 安装系统软件,使用 pip 安装 Python 库。', 'Linux (Ubuntu/Debian) 使用 apt 安装系统软件,使用 pip 安装 Python 库。',
'win-cmd': 'Windows CMD 推荐使用 winget 安装系统软件,使用 pip 安装 Python 库。', 'win-ps':
'common': '不同系统有不同的包管理器。' 'Windows PowerShell 推荐使用 winget 安装系统软件,使用 pip 安装 Python 库。',
'win-cmd':
'Windows CMD 推荐使用 winget 安装系统软件,使用 pip 安装 Python 库。',
common: '不同系统有不同的包管理器。'
}, },
commands: { commands: {
'mac': [ mac: [
{ label: '安装 wget (系统)', cmd: 'brew install wget' }, { label: '安装 wget (系统)', cmd: 'brew install wget' },
{ label: '安装 requests (Python)', cmd: 'pip install requests' } { label: '安装 requests (Python)', cmd: 'pip install requests' }
], ],
'linux': [ linux: [
{ label: '安装 git (系统)', cmd: 'sudo apt install git' }, { label: '安装 git (系统)', cmd: 'sudo apt install git' },
{ label: '安装 requests (Python)', cmd: 'pip install requests' } { label: '安装 requests (Python)', cmd: 'pip install requests' }
], ],
@@ -244,34 +288,46 @@ d---- 1/15/2026 9:00 AM Downloads
}, },
expectedCmd: { expectedCmd: {
// Fallback/Legacy // Fallback/Legacy
'mac': 'brew install wget', mac: 'brew install wget',
'linux': 'sudo apt install git', linux: 'sudo apt install git',
'win-ps': 'pip install requests', 'win-ps': 'pip install requests',
'win-cmd': 'pip install requests' 'win-cmd': 'pip install requests'
}, },
validate: (cmd, os) => { validate: (cmd, os) => {
const c = cmd.trim() const c = cmd.trim()
if (os === 'mac') return c === 'brew install wget' || c === 'pip install requests' if (os === 'mac')
if (os === 'linux') return c === 'sudo apt install git' || c === 'apt install git' || c === 'pip install requests' return c === 'brew install wget' || 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 === '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' return c === 'pip install requests'
}, },
output: (os, cmd) => { // Modified to accept cmd output: (os, cmd) => {
// Modified to accept cmd
const c = cmd ? cmd.trim() : '' const c = cmd ? cmd.trim() : ''
// Python requests output // Python requests output
if (c.includes('pip install requests')) { if (c.includes('pip install requests')) {
return ` return `
Downloading/unpacking requests Downloading/unpacking requests
Downloading requests-2.31.0-py3-none-any.whl (62kB): 62kB downloaded Downloading requests-2.31.0-py3-none-any.whl (62kB): 62kB downloaded
Installing collected packages: requests Installing collected packages: requests
Successfully installed requests Successfully installed requests
Cleaning up...` Cleaning up...`
} }
// Windows winget output // Windows winget output
if (c.includes('winget install')) { if (c.includes('winget install')) {
return ` return `
Found Git [Git.Git] Version 2.43.0 Found Git [Git.Git] Version 2.43.0
This application is licensed to you by its owner. This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. 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" 文件。', goal: '删除 "hello.txt" 文件。',
aiQuery: '我不想要 hello.txt 了,怎么删除它?', aiQuery: '我不想要 hello.txt 了,怎么删除它?',
aiResponse: { aiResponse: {
'mac': '删除文件使用 `rm` 命令 (Remove)。小心,这个操作通常不可撤销!输入 `rm hello.txt`。', mac: '删除文件使用 `rm` 命令 (Remove)。小心,这个操作通常不可撤销!输入 `rm hello.txt`。',
'linux': '删除文件使用 `rm` 命令 (Remove)。小心,这个操作通常不可撤销!输入 `rm hello.txt`。', linux:
'删除文件使用 `rm` 命令 (Remove)。小心,这个操作通常不可撤销!输入 `rm hello.txt`。',
'win-ps': '在 PowerShell 中使用 `rm` 或 `del`。输入 `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: { expectedCmd: {
'mac': 'rm hello.txt', mac: 'rm hello.txt',
'linux': 'rm hello.txt', linux: 'rm hello.txt',
'win-ps': 'rm hello.txt', 'win-ps': 'rm hello.txt',
'win-cmd': 'del hello.txt' 'win-cmd': 'del hello.txt'
}, },
@@ -362,12 +419,18 @@ const executeCommand = () => {
// 1. Add to history // 1. Add to history
let currentPrompt = prompt.value let currentPrompt = prompt.value
// Special handling for prompt update simulation (hacky way) // Special handling for prompt update simulation (hacky way)
if (currentTaskIndex.value >= 2 && currentTaskIndex.value < 6 && history.value.length > 0) { if (
// If we are inside demo folder currentTaskIndex.value >= 2 &&
if (currentOS.value === 'mac') currentPrompt = 'user@MacBook demo % ' currentTaskIndex.value < 6 &&
else if (currentOS.value === 'linux') currentPrompt = 'user@localhost:~/demo$ ' history.value.length > 0
else if (currentOS.value === 'win-ps') currentPrompt = 'PS C:\\Users\\User\\demo> ' ) {
else currentPrompt = 'C:\\Users\\User\\demo> ' // 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 }) history.value.push({ type: 'input', prompt: currentPrompt, content: cmd })
@@ -375,7 +438,10 @@ const executeCommand = () => {
// 2. Process Command // 2. Process Command
// Check if it matches current task requirement // 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 // Success
const out = currentTask.value.output(currentOS.value, cmd) // Pass cmd to output const out = currentTask.value.output(currentOS.value, cmd) // Pass cmd to output
if (out) { if (out) {
@@ -386,19 +452,29 @@ const executeCommand = () => {
// Failure or just random command // Failure or just random command
// Simple mock responses for common commands if not matching task // Simple mock responses for common commands if not matching task
if (cmd.trim() === 'ls' || cmd.trim() === 'dir') { if (cmd.trim() === 'ls' || cmd.trim() === 'dir') {
if (currentTaskIndex.value < 2) { if (currentTaskIndex.value < 2) {
// Initial state // Initial state
history.value.push({ type: 'output', content: tasks[0].output(currentOS.value) }) history.value.push({
} else if (currentTaskIndex.value >= 2) { type: 'output',
// Inside demo content: tasks[0].output(currentOS.value)
if (currentTaskIndex.value === 3) history.value.push({ type: 'output', content: '' }) // empty })
else history.value.push({ type: 'output', content: 'hello.txt' }) } 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') { } else if (cmd.trim() === 'clear' || cmd.trim() === 'cls') {
history.value = [] history.value = []
} else if (!isTaskCompleted.value) { } else if (!isTaskCompleted.value) {
history.value.push({ type: 'error', content: `Command not found or not matching task: ${cmd}` }) history.value.push({
history.value.push({ type: 'info', content: `💡 提示:试试点击左侧的“问问 AI”?` }) 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++ currentTaskIndex.value++
isTaskCompleted.value = false isTaskCompleted.value = false
// Clear history to keep it clean? Or keep it? Let's keep it but maybe add a separator // 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() scrollToBottom()
} }
} }
@@ -446,7 +525,7 @@ watch(currentOS, () => {
border: 1px solid var(--vp-c-divider); border: 1px solid var(--vp-c-divider);
border-radius: 12px; border-radius: 12px;
overflow: hidden; 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) { @media (max-width: 768px) {
@@ -606,11 +685,18 @@ watch(currentOS, () => {
} }
@keyframes slideIn { @keyframes slideIn {
from { opacity: 0; transform: translateY(10px); } from {
to { opacity: 1; transform: translateY(0); } opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
.next-btn, .reset-btn { .next-btn,
.reset-btn {
margin-left: auto; margin-left: auto;
padding: 6px 16px; padding: 6px 16px;
background: #10b981; background: #10b981;
@@ -622,7 +708,8 @@ watch(currentOS, () => {
transition: transform 0.1s; transition: transform 0.1s;
} }
.next-btn:hover, .reset-btn:hover { .next-btn:hover,
.reset-btn:hover {
transform: scale(1.05); transform: scale(1.05);
background: #059669; background: #059669;
} }
@@ -636,9 +723,21 @@ watch(currentOS, () => {
min-height: 400px; min-height: 400px;
} }
.terminal-panel.win-cmd { background: #0c0c0c; color: #cccccc; font-family: 'Consolas', monospace; } .terminal-panel.win-cmd {
.terminal-panel.win-ps { background: #012456; color: #ffffff; font-family: 'Consolas', monospace; } background: #0c0c0c;
.terminal-panel.mac, .terminal-panel.linux { background: #2b2b2b; color: #f0f0f0; } 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 { .terminal-header {
padding: 8px 12px; padding: 8px 12px;
@@ -658,11 +757,21 @@ watch(currentOS, () => {
height: 12px; height: 12px;
border-radius: 50%; border-radius: 50%;
} }
.dot.red { background: #ff5f56; } .dot.red {
.dot.yellow { background: #ffbd2e; } background: #ff5f56;
.dot.green { background: #27c93f; } }
.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 { .terminal-header .title {
position: absolute; position: absolute;
@@ -696,8 +805,12 @@ watch(currentOS, () => {
font-weight: bold; font-weight: bold;
} }
.terminal-panel.win-cmd .prompt { color: #cccccc; } .terminal-panel.win-cmd .prompt {
.terminal-panel.win-ps .prompt { color: #ffffff; } color: #cccccc;
}
.terminal-panel.win-ps .prompt {
color: #ffffff;
}
.input-line { .input-line {
display: flex; display: flex;
@@ -725,8 +838,13 @@ watch(currentOS, () => {
} }
@keyframes blink { @keyframes blink {
0%, 100% { opacity: 0.5; } 0%,
50% { opacity: 1; } 100% {
opacity: 0.5;
}
50% {
opacity: 1;
}
} }
.line.output { .line.output {
@@ -1,10 +1,10 @@
<template> <template>
<div class="terminal-os-demo"> <div class="terminal-os-demo">
<div class="os-switch"> <div class="os-switch">
<button <button
v-for="os in osList" v-for="os in osList"
:key="os.id" :key="os.id"
:class="{ active: currentOS === os.id }" :class="{ active: currentOS === os.id }"
@click="switchOS(os.id)" @click="switchOS(os.id)"
> >
<span class="os-icon">{{ os.icon }}</span> <span class="os-icon">{{ os.icon }}</span>
@@ -21,12 +21,26 @@
</div> </div>
<div class="window-title">{{ currentOSConfig.title }}</div> <div class="window-title">{{ currentOSConfig.title }}</div>
<div class="window-controls"> <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> </div>
<div class="terminal-content" @click="nextStep" :class="{ 'clickable': !isTyping && !isFinished }"> <div
class="terminal-content"
@click="nextStep"
:class="{ clickable: !isTyping && !isFinished }"
>
<!-- Start Overlay --> <!-- 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"> <div class="start-hint">
<span class="icon">👆</span> <span class="icon">👆</span>
<span class="text">不断点击屏幕演示 / Keep Clicking</span> <span class="text">不断点击屏幕演示 / Keep Clicking</span>
@@ -43,22 +57,33 @@
<div v-for="(line, index) in lines" :key="index" class="line"> <div v-for="(line, index) in lines" :key="index" class="line">
<template v-if="line.type === 'input'"> <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>
<template v-else> <template v-else>
<span class="output-text">{{ line.content }}</span> <span class="output-text">{{ line.content }}</span>
</template> </template>
</div> </div>
<!-- Active Input Line (when not animating or just waiting) --> <!-- 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)"> <div
<span class="prompt">{{ currentOSConfig.prompt }}</span> class="line input-line"
<span class="cursor">_</span> v-if="
<span v-if="lines.length === 0" class="hint"> (点击屏幕继续 / Click screen to continue)</span> lines.length === 0 ||
<span v-else class="hint blink-hint"> </span> (!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>
</div> </div>
<!-- Explanation Bar --> <!-- Explanation Bar -->
<div class="explanation-bar" :class="{ visible: currentExplanation }"> <div class="explanation-bar" :class="{ visible: currentExplanation }">
<span class="icon">💡</span> <span class="icon">💡</span>
@@ -90,17 +115,54 @@ const configs = {
prompt: 'C:\\Users\\User>', prompt: 'C:\\Users\\User>',
demo: [ demo: [
{ type: 'explanation', content: '准备输入命令...' }, { 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: 'command',
{ type: 'output', content: ' Volume Serial Number is A1B2-C3D4', delay: 50 }, 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: '', delay: 50 },
{ type: 'output', content: ' Directory of C:\\Users\\User', delay: 50 }, { type: 'output', content: ' Directory of C:\\Users\\User', delay: 50 },
{ type: 'output', content: '', 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',
{ type: 'output', content: '01/15/2026 10:00 AM 128 demo.txt', delay: 50 }, content: '01/15/2026 10:00 AM <DIR> .',
{ type: 'output', content: ' 1 File(s) 128 bytes', delay: 50 }, 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 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 } { type: 'output', content: '', delay: 100 }
] ]
}, },
@@ -109,46 +171,137 @@ const configs = {
prompt: 'PS C:\\Users\\User>', prompt: 'PS C:\\Users\\User>',
demo: [ demo: [
{ type: 'explanation', content: '准备输入命令...' }, { type: 'explanation', content: '准备输入命令...' },
{ type: 'command', content: 'Get-Date', delay: 400, explanation: '输入 `Get-Date`。PowerShell 使用动词-名词的命名方式,这里是**获取当前时间**。' }, {
{ type: 'output', content: '', delay: 100, explanation: '系统返回了当前的日期和时间。' }, type: 'command',
{ type: 'output', content: 'Thursday, January 15, 2026 10:00:00 AM', delay: 100 }, 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: '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', title: 'user — -zsh — 80x24',
prompt: 'user@MacBook-Pro ~ % ', prompt: 'user@MacBook-Pro ~ % ',
demo: [ demo: [
{ type: 'explanation', content: '准备输入命令...' }, { 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: 'command',
{ type: 'output', content: 'Documents Library Pictures Public', delay: 100, explanation: '系统列出了你的主目录下的文件夹。' }, content: 'ls -G',
{ type: 'command', content: 'sw_vers', delay: 400, explanation: '输入 `sw_vers` (Software Version)。这是 macOS 特有的命令,查看**系统版本**。' }, 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: 'ProductName: macOS', delay: 50 },
{ type: 'output', content: 'ProductVersion: 15.1', 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: ~', title: 'user@hostname: ~',
prompt: 'user@hostname:~$ ', prompt: 'user@hostname:~$ ',
demo: [ demo: [
{ type: 'explanation', content: '准备输入命令...' }, { 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: 'command',
{ type: 'output', content: 'drwxr-xr-x 2 user user 4096 Jan 15 10:00 .', delay: 50 }, content: 'ls -la',
{ type: 'output', content: 'drwxr-xr-x 3 user user 4096 Jan 15 10:00 ..', delay: 50 }, delay: 400,
{ type: 'output', content: '-rw-r--r-- 1 user user 128 Jan 15 10:00 demo.txt', delay: 50 }, explanation:
{ type: 'command', content: 'whoami', delay: 400, explanation: '输入 `whoami` (Who am I)。这是一个经典的哲学命令(笑),告诉计算机:**我是谁?**(当前登录用户)。' }, '输入 `ls` (List)。这是 Linux/Mac 系统用来**列出文件**的命令。`-la` 是参数,表示“列出所有文件(all)的详细信息(long)”。'
{ type: 'output', content: 'user', delay: 100, explanation: '系统回答:你是 "user"。' } },
{
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 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) => { const switchOS = (id) => {
currentOS.value = id currentOS.value = id
@@ -161,7 +314,11 @@ const resetDemo = () => {
currentStepIndex.value = -1 currentStepIndex.value = -1
isTyping.value = false isTyping.value = false
// Add initial prompt // 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 // Initial reset
@@ -169,77 +326,80 @@ watch(currentOSConfig, resetDemo, { immediate: true })
const nextStep = async () => { const nextStep = async () => {
if (isTyping.value || isFinished.value) return if (isTyping.value || isFinished.value) return
const demoLines = currentOSConfig.value.demo const demoLines = currentOSConfig.value.demo
const promptText = currentOSConfig.value.prompt const promptText = currentOSConfig.value.prompt
// Loop to process consecutive output lines or until a pause point // Loop to process consecutive output lines or until a pause point
while (currentStepIndex.value < demoLines.length - 1) { while (currentStepIndex.value < demoLines.length - 1) {
currentStepIndex.value++ currentStepIndex.value++
const step = demoLines[currentStepIndex.value] const step = demoLines[currentStepIndex.value]
// 1. Update Explanation if exists // 1. Update Explanation if exists
if (step.explanation) { if (step.explanation) {
currentExplanation.value = step.explanation currentExplanation.value = step.explanation
} }
// 2. Handle specific types // 2. Handle specific types
if (step.type === 'explanation') { if (step.type === 'explanation') {
// Just show explanation and pause // Just show explanation and pause
break break
} }
if (step.type === 'command') { if (step.type === 'command') {
// Ensure input line exists // Ensure input line exists
if (lines.value.length === 0 || lines.value[lines.value.length - 1].type !== 'input') { if (
lines.value.push({ type: 'input', prompt: promptText, content: '' }) lines.value.length === 0 ||
lines.value[lines.value.length - 1].type !== 'input'
) {
lines.value.push({ type: 'input', prompt: promptText, content: '' })
} }
// Type effect // Type effect
isTyping.value = true isTyping.value = true
const text = step.content const text = step.content
const targetLine = lines.value[lines.value.length - 1] const targetLine = lines.value[lines.value.length - 1]
for (let i = 0; i < text.length; i++) { for (let i = 0; i < text.length; i++) {
targetLine.content += text[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 isTyping.value = false
// Pause after typing command // Pause after typing command
break break
} }
if (step.type === 'output') { if (step.type === 'output') {
lines.value.push({ type: 'output', content: step.content }) lines.value.push({ type: 'output', content: step.content })
// Logic to continue or pause: // Logic to continue or pause:
// Pause if: // Pause if:
// - This output has an explanation (user needs to read) // - This output has an explanation (user needs to read)
// - Next step is NOT output (it's a command or explanation block) // - Next step is NOT output (it's a command or explanation block)
// - Next step is output BUT has an explanation // - Next step is output BUT has an explanation
if (step.explanation) { if (step.explanation) {
break break
} }
const nextStep = demoLines[currentStepIndex.value + 1] const nextStep = demoLines[currentStepIndex.value + 1]
if (!nextStep || nextStep.type !== 'output' || nextStep.explanation) { if (!nextStep || nextStep.type !== 'output' || nextStep.explanation) {
// If next is command, we might want to show a prompt before pausing? // If next is command, we might want to show a prompt before pausing?
// But the command step logic adds prompt. // But the command step logic adds prompt.
// If we pause here, the user sees output. Next click -> types command. // If we pause here, the user sees output. Next click -> types command.
// Seems correct. // Seems correct.
break break
} }
// Small delay between batched outputs for visual smoothness // 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 we finished everything, add a final prompt
if (isFinished.value) { if (isFinished.value) {
lines.value.push({ type: 'input', prompt: promptText, content: '' }) lines.value.push({ type: 'input', prompt: promptText, content: '' })
} }
} }
</script> </script>
@@ -345,9 +505,15 @@ const nextStep = async () => {
.terminal-window.linux .window-buttons .btn { .terminal-window.linux .window-buttons .btn {
border-radius: 50%; border-radius: 50%;
} }
.terminal-window.linux .window-buttons .close { background: #ff5f56; } .terminal-window.linux .window-buttons .close {
.terminal-window.linux .window-buttons .minimize { background: #ffbd2e; } background: #ff5f56;
.terminal-window.linux .window-buttons .maximize { background: #27c93f; } }
.terminal-window.linux .window-buttons .minimize {
background: #ffbd2e;
}
.terminal-window.linux .window-buttons .maximize {
background: #27c93f;
}
/* Common Layout */ /* Common Layout */
.terminal-window.mac { .terminal-window.mac {
@@ -363,9 +529,15 @@ const nextStep = async () => {
.terminal-window.mac .window-buttons .btn { .terminal-window.mac .window-buttons .btn {
border-radius: 50%; border-radius: 50%;
} }
.terminal-window.mac .window-buttons .close { background: #ff5f56; } .terminal-window.mac .window-buttons .close {
.terminal-window.mac .window-buttons .minimize { background: #ffbd2e; } background: #ff5f56;
.terminal-window.mac .window-buttons .maximize { background: #27c93f; } }
.terminal-window.mac .window-buttons .minimize {
background: #ffbd2e;
}
.terminal-window.mac .window-buttons .maximize {
background: #27c93f;
}
.window-bar { .window-bar {
padding: 8px 12px; padding: 8px 12px;
@@ -478,14 +650,20 @@ const nextStep = async () => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; 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; animation: pulse 2s infinite;
} }
@keyframes pulse { @keyframes pulse {
0% { transform: scale(1); } 0% {
50% { transform: scale(1.05); } transform: scale(1);
100% { transform: scale(1); } }
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
} }
.completed-overlay { .completed-overlay {
@@ -513,7 +691,7 @@ const nextStep = async () => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; 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; cursor: pointer;
transition: transform 0.2s; transition: transform 0.2s;
} }
@@ -595,8 +773,13 @@ const nextStep = async () => {
} }
@keyframes blink { @keyframes blink {
0%, 100% { opacity: 1; } 0%,
50% { opacity: 0; } 100% {
opacity: 1;
}
50% {
opacity: 0;
}
} }
.hint { .hint {
@@ -23,7 +23,11 @@
<div class="terminal-title">Terminal - zsh</div> <div class="terminal-title">Terminal - zsh</div>
</div> </div>
<div class="terminal-body" ref="terminalBody" @click="focusInput"> <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="prompt" v-if="line.type === 'input'">
<span class="path">{{ currentPath }}</span> <span class="path">{{ currentPath }}</span>
<span class="arrow">$ </span> <span class="arrow">$ </span>
@@ -35,9 +39,9 @@
<span class="path">{{ currentPath }}</span> <span class="path">{{ currentPath }}</span>
<span class="arrow">$ </span> <span class="arrow">$ </span>
</span> </span>
<input <input
type="text" type="text"
v-model="currentInput" v-model="currentInput"
@keyup.enter="executeCommand" @keyup.enter="executeCommand"
@keydown.up.prevent="navigateHistory(-1)" @keydown.up.prevent="navigateHistory(-1)"
@keydown.down.prevent="navigateHistory(1)" @keydown.down.prevent="navigateHistory(1)"
@@ -52,16 +56,26 @@
<div class="cheat-sheet"> <div class="cheat-sheet">
<div class="sheet-title"> <div class="sheet-title">
<span class="icon">📖</span> <span class="icon">📖</span>
<span class="en">Command Cheat Sheet</span> <span class="en">Command Cheat Sheet</span>
<span class="divider">|</span> <span class="divider">|</span>
<span class="zh">命令速查表</span> <span class="zh">命令速查表</span>
</div> </div>
<div class="sheet-content"> <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="group-title">{{ group.category }}</div>
<div class="cmd-item" v-for="(cmd, cIndex) in group.commands" :key="cIndex"> <div
<div class="cmd-name" @click="fillCommand(cmd.name)">{{ cmd.name }}</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="cmd-desc">
<div class="en">{{ cmd.descEn }}</div> <div class="en">{{ cmd.descEn }}</div>
<div class="zh">{{ cmd.descZh }}</div> <div class="zh">{{ cmd.descZh }}</div>
@@ -77,8 +91,16 @@
import { ref, onMounted, nextTick } from 'vue' import { ref, onMounted, nextTick } from 'vue'
const history = ref([ 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 currentInput = ref('')
const inputField = ref(null) const inputField = ref(null)
@@ -91,41 +113,60 @@ const fileSystem = {
name: '/', name: '/',
type: 'dir', type: 'dir',
children: { children: {
'home': { home: {
name: 'home', name: 'home',
type: 'dir', type: 'dir',
children: { children: {
'user': { user: {
name: 'user', name: 'user',
type: 'dir', type: 'dir',
children: { children: {
'hello.txt': { name: 'hello.txt', type: 'file', content: 'Hello World! This is a mock file.\n你好!这是一个模拟文件。' }, 'hello.txt': {
'notes.md': { name: 'notes.md', type: 'file', content: '# My Notes\n- Learn Terminal\n- Learn Shell\n- Learn Kernel' }, name: 'hello.txt',
'projects': { type: 'file',
name: 'projects', content:
type: 'dir', 'Hello World! This is a mock file.\n你好!这是一个模拟文件。'
children: {
'app.js': { name: 'app.js', type: 'file', content: 'console.log("Hello");' }
}
}, },
'Downloads': { name: 'Downloads', type: 'dir', children: {} } '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");'
}
}
},
Downloads: { name: 'Downloads', type: 'dir', children: {} }
} }
} }
} }
}, },
'etc': { etc: {
name: 'etc', name: 'etc',
type: 'dir', type: 'dir',
children: { 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', name: 'bin',
type: 'dir', type: 'dir',
children: { children: {
'ls': { name: 'ls', type: 'file', content: 'Binary file' }, ls: { name: 'ls', type: 'file', content: 'Binary file' },
'cat': { name: 'cat', type: 'file', content: 'Binary file' } cat: { name: 'cat', type: 'file', content: 'Binary file' }
} }
} }
} }
@@ -135,12 +176,13 @@ let currentPath = '~'
let currentDirObj = fileSystem.children['home'].children['user'] let currentDirObj = fileSystem.children['home'].children['user']
const resolvePath = (path) => { 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 if (path === '/') return fileSystem
let parts = path.split('/').filter(p => p) let parts = path.split('/').filter((p) => p)
let current = path.startsWith('/') ? fileSystem : currentDirObj let current = path.startsWith('/') ? fileSystem : currentDirObj
for (const part of parts) { for (const part of parts) {
if (part === '.') continue if (part === '.') continue
if (part === '..') { if (part === '..') {
@@ -177,23 +219,25 @@ const navigateTo = (target) => {
newDir = fileSystem.children['home'].children['user'] newDir = fileSystem.children['home'].children['user']
} else if (target === '..') { } else if (target === '..') {
if (currentPath === '/') return { path: '/', dir: fileSystem } if (currentPath === '/') return { path: '/', dir: fileSystem }
if (currentPath === '~') { // ~ is /home/user if (currentPath === '~') {
newPath = '/home' // ~ is /home/user
newDir = fileSystem.children['home'] newPath = '/home'
newDir = fileSystem.children['home']
} else { } else {
// Simple string manipulation for path // Simple string manipulation for path
const parts = currentPath.split('/') const parts = currentPath.split('/')
parts.pop() parts.pop()
newPath = parts.join('/') || '/' newPath = parts.join('/') || '/'
// Re-resolve dir from root for safety // Re-resolve dir from root for safety
if (newPath === '/') newDir = fileSystem if (newPath === '/') newDir = fileSystem
else if (newPath === '/home') newDir = fileSystem.children['home'] else if (newPath === '/home') newDir = fileSystem.children['home']
else if (newPath === '/home/user') newDir = fileSystem.children['home'].children['user'] else if (newPath === '/home/user')
else { newDir = fileSystem.children['home'].children['user']
// Fallback for deeper paths if we supported them else {
// For now, let's keep it simple // Fallback for deeper paths if we supported them
} // For now, let's keep it simple
}
} }
} else { } else {
// Relative path // Relative path
@@ -201,7 +245,8 @@ const navigateTo = (target) => {
const targetObj = currentDirObj.children[target] const targetObj = currentDirObj.children[target]
if (targetObj.type === 'dir') { if (targetObj.type === 'dir') {
newDir = targetObj newDir = targetObj
newPath = currentPath === '/' ? `/${target}` : `${currentPath}/${target}` newPath =
currentPath === '/' ? `/${target}` : `${currentPath}/${target}`
} else { } else {
return { error: `cd: not a directory: ${target}` } return { error: `cd: not a directory: ${target}` }
} }
@@ -216,24 +261,52 @@ const cheatSheet = [
{ {
category: 'Navigation / 导航', category: 'Navigation / 导航',
commands: [ commands: [
{ name: 'ls', descEn: 'List directory contents', descZh: '列出当前目录下的文件和文件夹' }, {
{ name: 'cd <dir>', descEn: 'Change directory', descZh: '进入指定目录 (例如: cd projects)' }, name: 'ls',
{ name: 'pwd', descEn: 'Print working directory', descZh: '显示当前所在的完整路径' } descEn: 'List directory contents',
descZh: '列出当前目录下的文件和文件夹'
},
{
name: 'cd <dir>',
descEn: 'Change directory',
descZh: '进入指定目录 (例如: cd projects)'
},
{
name: 'pwd',
descEn: 'Print working directory',
descZh: '显示当前所在的完整路径'
}
] ]
}, },
{ {
category: 'File Operations / 文件操作', category: 'File Operations / 文件操作',
commands: [ commands: [
{ name: 'cat <file>', descEn: 'Show file contents', descZh: '查看文件内容 (例如: cat hello.txt)' }, {
{ name: 'touch <file>', descEn: 'Create empty file', descZh: '创建一个新文件' }, name: 'cat <file>',
{ name: 'mkdir <dir>', descEn: 'Make directory', descZh: '创建一个新文件夹' }, 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: '删除文件' } { name: 'rm <file>', descEn: 'Remove file', descZh: '删除文件' }
] ]
}, },
{ {
category: 'System / 系统', category: 'System / 系统',
commands: [ commands: [
{ name: 'echo <text>', descEn: 'Print text', descZh: '在屏幕上打印一段文字' }, {
name: 'echo <text>',
descEn: 'Print text',
descZh: '在屏幕上打印一段文字'
},
{ name: 'whoami', descEn: 'Current user', descZh: '显示当前用户名' }, { name: 'whoami', descEn: 'Current user', descZh: '显示当前用户名' },
{ name: 'date', descEn: 'Show date/time', descZh: '显示当前日期和时间' }, { name: 'date', descEn: 'Show date/time', descZh: '显示当前日期和时间' },
{ name: 'clear', descEn: 'Clear screen', descZh: '清空屏幕内容' } { name: 'clear', descEn: 'Clear screen', descZh: '清空屏幕内容' }
@@ -242,8 +315,16 @@ const cheatSheet = [
{ {
category: 'Package Manager / 软件包 (Mock)', category: 'Package Manager / 软件包 (Mock)',
commands: [ 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)'
}
] ]
} }
] ]
@@ -253,14 +334,14 @@ const commands = {
return `Available commands: return `Available commands:
ls, cd, pwd, cat, touch, mkdir, rm, echo, whoami, date, clear, apt` ls, cd, pwd, cat, touch, mkdir, rm, echo, whoami, date, clear, apt`
}, },
ls: (args) => { ls: (args) => {
if (!currentDirObj.children) return '' if (!currentDirObj.children) return ''
const items = Object.values(currentDirObj.children) const items = Object.values(currentDirObj.children)
if (items.length === 0) return '' if (items.length === 0) return ''
// Simple column formatting // 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 item.type === 'dir' ? `\x1b[1;34m${item.name}/\x1b[0m` : item.name
}) })
return names.join(' ') return names.join(' ')
@@ -292,7 +373,7 @@ const commands = {
cat: (args) => { cat: (args) => {
const file = args[0] const file = args[0]
if (!file) return 'usage: cat <file>' if (!file) return 'usage: cat <file>'
if (currentDirObj.children && currentDirObj.children[file]) { if (currentDirObj.children && currentDirObj.children[file]) {
const target = currentDirObj.children[file] const target = currentDirObj.children[file]
if (target.type === 'dir') return `cat: ${file}: Is a directory` if (target.type === 'dir') return `cat: ${file}: Is a directory`
@@ -312,7 +393,8 @@ const commands = {
mkdir: (args) => { mkdir: (args) => {
const name = args[0] const name = args[0]
if (!name) return 'usage: mkdir <dir>' 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: {} } currentDirObj.children[name] = { name, type: 'dir', children: {} }
return null return null
}, },
@@ -322,7 +404,8 @@ const commands = {
if (!name) return 'usage: rm <file>' if (!name) return 'usage: rm <file>'
// Mock: -r not supported for simplicity // Mock: -r not supported for simplicity
if (currentDirObj.children[name]) { 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] delete currentDirObj.children[name]
return null return null
} }
@@ -330,7 +413,7 @@ const commands = {
}, },
whoami: () => 'user', whoami: () => 'user',
date: () => new Date().toString(), date: () => new Date().toString(),
apt: (args) => { apt: (args) => {
@@ -363,7 +446,7 @@ Setting up ${pkg} (1.0.0) ...`
const executeCommand = () => { const executeCommand = () => {
const input = currentInput.value.trim() const input = currentInput.value.trim()
if (!input) { if (!input) {
history.value.push({ type: 'input', content: '' }) history.value.push({ type: 'input', content: '' })
currentInput.value = '' currentInput.value = ''
@@ -378,32 +461,41 @@ const executeCommand = () => {
history.value.push({ type: 'input', content: input }) history.value.push({ type: 'input', content: input })
const [cmd, ...args] = input.split(/\s+/) const [cmd, ...args] = input.split(/\s+/)
if (commands[cmd]) { if (commands[cmd]) {
try { try {
const output = commands[cmd](args) const output = commands[cmd](args)
if (output !== null) { if (output !== null) {
// Handle colored output simply by not escaping HTML if we trust it (Vue escapes by default) // Handle colored output simply by not escaping HTML if we trust it (Vue escapes by default)
// For simple color simulation, we can strip codes or use a span. // For simple color simulation, we can strip codes or use a span.
// Here we just keep simple text, but `ls` returns ANSI codes which we might want to handle or strip. // Here we just keep simple text, but `ls` returns ANSI codes which we might want to handle or strip.
// For this demo, let's strip ANSI codes for safety/simplicity in Vue or parse them. // For this demo, let's strip ANSI codes for safety/simplicity in Vue or parse them.
// Let's simple string replace for blue color // Let's simple string replace for blue color
let safeOutput = output let safeOutput = output
const lines = safeOutput.split('\n') const lines = safeOutput.split('\n')
lines.forEach(line => { lines.forEach((line) => {
// Basic ANSI parser for ls colors // Basic ANSI parser for ls colors
const isDir = line.includes('\x1b[1;34m') const isDir = line.includes('\x1b[1;34m')
const cleanContent = line.replace(/\x1b\[[0-9;]*m/g, '') const cleanContent = line.replace(/\x1b\[[0-9;]*m/g, '')
history.value.push({ type: isDir ? 'output-dir' : 'output', content: cleanContent }) history.value.push({
type: isDir ? 'output-dir' : 'output',
content: cleanContent
})
}) })
} }
} catch (e) { } 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 { } 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 = '' currentInput.value = ''
@@ -412,12 +504,13 @@ const executeCommand = () => {
const navigateHistory = (direction) => { const navigateHistory = (direction) => {
if (commandHistory.value.length === 0) return if (commandHistory.value.length === 0) return
historyIndex.value += direction historyIndex.value += direction
if (historyIndex.value < 0) historyIndex.value = 0 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) { if (historyIndex.value === commandHistory.value.length) {
currentInput.value = '' currentInput.value = ''
} else { } else {
@@ -430,9 +523,11 @@ const handleTabCompletion = () => {
const input = currentInput.value const input = currentInput.value
const [cmd, ...args] = input.split(/\s+/) const [cmd, ...args] = input.split(/\s+/)
const partial = args[args.length - 1] || '' const partial = args[args.length - 1] || ''
if (cmd && currentDirObj.children) { 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) { if (matches.length === 1) {
const completed = matches[0] const completed = matches[0]
// Replace last arg with completed // Replace last arg with completed
@@ -506,9 +601,15 @@ onMounted(() => {
display: inline-block; display: inline-block;
} }
.red { background-color: #ef4444; } .red {
.yellow { background-color: #facc15; } background-color: #ef4444;
.green { background-color: #22c55e; } }
.yellow {
background-color: #facc15;
}
.green {
background-color: #22c55e;
}
.terminal-title { .terminal-title {
flex: 1; flex: 1;
@@ -652,8 +753,9 @@ input {
grid-template-columns: 1fr; grid-template-columns: 1fr;
height: auto; height: auto;
} }
.terminal-container, .cheat-sheet { .terminal-container,
.cheat-sheet {
height: 350px; height: 350px;
} }
} }
@@ -3,11 +3,11 @@
<div class="controls"> <div class="controls">
<span class="hint">🖱 把鼠标悬停在方块上查看它的注意力分配</span> <span class="hint">🖱 把鼠标悬停在方块上查看它的注意力分配</span>
</div> </div>
<div class="visual-area"> <div class="visual-area">
<div class="image-grid" @mouseleave="hoverIndex = -1"> <div class="image-grid" @mouseleave="hoverIndex = -1">
<div <div
v-for="(item, index) in items" v-for="(item, index) in items"
:key="index" :key="index"
class="grid-cell" class="grid-cell"
:class="{ active: hoverIndex === index }" :class="{ active: hoverIndex === index }"
@@ -16,16 +16,16 @@
{{ item.icon }} {{ item.icon }}
<div class="cell-label">{{ item.label }}</div> <div class="cell-label">{{ item.label }}</div>
</div> </div>
<!-- SVG Overlay for lines --> <!-- SVG Overlay for lines -->
<svg class="connections" v-if="hoverIndex !== -1"> <svg class="connections" v-if="hoverIndex !== -1">
<line <line
v-for="(target, tIndex) in items" v-for="(target, tIndex) in items"
:key="tIndex" :key="tIndex"
v-if="tIndex !== hoverIndex" v-if="tIndex !== hoverIndex"
:x1="getCenter(hoverIndex).x" :x1="getCenter(hoverIndex).x"
:y1="getCenter(hoverIndex).y" :y1="getCenter(hoverIndex).y"
:x2="getCenter(tIndex).x" :x2="getCenter(tIndex).x"
:y2="getCenter(tIndex).y" :y2="getCenter(tIndex).y"
:stroke="getAttentionColor(hoverIndex, tIndex)" :stroke="getAttentionColor(hoverIndex, tIndex)"
:stroke-width="getAttentionWidth(hoverIndex, tIndex)" :stroke-width="getAttentionWidth(hoverIndex, tIndex)"
@@ -33,16 +33,22 @@
/> />
</svg> </svg>
</div> </div>
<div class="info-panel" :class="{ visible: hoverIndex !== -1 }"> <div class="info-panel" :class="{ visible: hoverIndex !== -1 }">
<div class="info-title">Patch: {{ items[hoverIndex]?.label }}</div> <div class="info-title">Patch: {{ items[hoverIndex]?.label }}</div>
<div class="info-desc">正在关注</div> <div class="info-desc">正在关注</div>
<ul class="attn-list" v-if="hoverIndex !== -1"> <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-icon">{{ items[targetIdx].icon }}</span>
<span class="target-name">{{ items[targetIdx].label }}</span> <span class="target-name">{{ items[targetIdx].label }}</span>
<div class="bar-bg"> <div class="bar-bg">
<div class="bar-fill" :style="{ width: (weight * 100) + '%' }"></div> <div
class="bar-fill"
:style="{ width: weight * 100 + '%' }"
></div>
</div> </div>
</li> </li>
</ul> </ul>
@@ -57,9 +63,15 @@ import { ref } from 'vue'
const hoverIndex = ref(-1) const hoverIndex = ref(-1)
const items = [ 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 // 3x3 Grid
@@ -79,14 +91,14 @@ const getCenter = (index) => {
// Mock attention weights // Mock attention weights
const getAttentionWeight = (source, target) => { const getAttentionWeight = (source, target) => {
// Self attention is ignored for visualization clarity usually, but let's say: // Self attention is ignored for visualization clarity usually, but let's say:
// Cat parts (3, 4, 6) attend strongly to each other // Cat parts (3, 4, 6) attend strongly to each other
const catParts = [3, 4, 6] const catParts = [3, 4, 6]
const isSourceCat = catParts.includes(source) const isSourceCat = catParts.includes(source)
const isTargetCat = catParts.includes(target) const isTargetCat = catParts.includes(target)
if (isSourceCat && isTargetCat) return 0.9 // Strong connection between cat parts if (isSourceCat && isTargetCat) return 0.9 // Strong connection between cat parts
// Cat interacts with Yarn (7) // Cat interacts with Yarn (7)
if (isSourceCat && target === 7) return 0.7 if (isSourceCat && target === 7) return 0.7
if (source === 7 && isTargetCat) return 0.7 if (source === 7 && isTargetCat) return 0.7
@@ -94,7 +106,7 @@ const getAttentionWeight = (source, target) => {
// Background parts attend to each other // Background parts attend to each other
const bgParts = [0, 1, 2, 5, 8] const bgParts = [0, 1, 2, 5, 8]
if (bgParts.includes(source) && bgParts.includes(target)) return 0.5 if (bgParts.includes(source) && bgParts.includes(target)) return 0.5
return 0.1 // Weak attention otherwise return 0.1 // Weak attention otherwise
} }
@@ -175,10 +187,11 @@ const getTopAttentions = (source) => {
position: relative; position: relative;
} }
.grid-cell:hover, .grid-cell.active { .grid-cell:hover,
.grid-cell.active {
border-color: var(--vp-c-brand); border-color: var(--vp-c-brand);
transform: scale(1.05); 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); background: var(--vp-c-bg-mute);
} }
@@ -1,10 +1,12 @@
<template> <template>
<div class="feature-alignment-demo"> <div class="feature-alignment-demo">
<div class="header"> <div class="header">
<div class="title">阶段一特征对齐 (Feature Alignment / Pre-training)</div> <div class="title">
阶段一特征对齐 (Feature Alignment / Pre-training)
</div>
<div class="desc"> <div class="desc">
目标 Projector 学会翻译图像语言 目标 Projector 学会翻译图像语言
<br>做法冻结 ViT LLM只训练 Projector <br />做法冻结 ViT LLM只训练 Projector
</div> </div>
</div> </div>
@@ -13,11 +15,11 @@
<div class="data-column"> <div class="data-column">
<div class="data-item image-data"> <div class="data-item image-data">
<div class="data-icon">🖼</div> <div class="data-icon">🖼</div>
<div class="data-label">图片<br>()</div> <div class="data-label">图片<br />()</div>
</div> </div>
<div class="data-item text-data"> <div class="data-item text-data">
<div class="data-icon">📝</div> <div class="data-icon">📝</div>
<div class="data-label">标题<br>("一只猫")</div> <div class="data-label">标题<br />("一只猫")</div>
</div> </div>
</div> </div>
@@ -35,7 +37,7 @@
<div class="block-icon">👁</div> <div class="block-icon">👁</div>
<div class="block-name">ViT</div> <div class="block-name">ViT</div>
</div> </div>
<div class="arrow-small"></div> <div class="arrow-small"></div>
<div class="model-block training"> <div class="model-block training">
@@ -64,7 +66,7 @@
<div class="vector-icon">🟢</div> <div class="vector-icon">🟢</div>
<div class="vector-label">向量 V</div> <div class="vector-label">向量 V</div>
</div> </div>
<div class="loss-connection"> <div class="loss-connection">
<div class="loss-line"></div> <div class="loss-line"></div>
<div class="loss-box" :class="{ active: isCalculatingLoss }"> <div class="loss-box" :class="{ active: isCalculatingLoss }">
@@ -107,21 +109,31 @@ const nextStep = () => {
const buttonText = computed(() => { const buttonText = computed(() => {
switch (step.value) { switch (step.value) {
case 0: return '开始训练演示' case 0:
case 1: return '下一步:计算 Loss' return '开始训练演示'
case 2: return '下一步:反向传播' case 1:
case 3: return '完成并重置' return '下一步:计算 Loss'
default: return '开始' case 2:
return '下一步:反向传播'
case 3:
return '完成并重置'
default:
return '开始'
} }
}) })
const currentStepDesc = computed(() => { const currentStepDesc = computed(() => {
switch (step.value) { switch (step.value) {
case 0: return '准备就绪。点击按钮开始模拟一次训练迭代。' case 0:
case 1: return '前向传播:图片经过 ViT (冻结) 和 Projector (训练) 得到向量 V;文本经过 LLM (冻结) 得到向量 T。' return '准备就绪。点击按钮开始模拟一次训练迭代。'
case 2: return '计算 Loss:比较向量 V 和向量 T 的相似度。目标是让它们尽可能接近。' case 1:
case 3: return '向传播:根据 Loss 更新 Projector 的参数。注意 ViT 和 LLM 不会更新!' return '向传播:图片经过 ViT (冻结) 和 Projector (训练) 得到向量 V;文本经过 LLM (冻结) 得到向量 T。'
default: return '' 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; border-radius: 12px;
padding: 20px; padding: 20px;
margin: 20px 0; margin: 20px 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
} }
.header { .header {
@@ -184,8 +197,14 @@ const isCalculatingLoss = computed(() => step.value === 2)
width: 60px; width: 60px;
} }
.data-icon { font-size: 24px; } .data-icon {
.data-label { font-size: 10px; text-align: center; margin-top: 4px; } font-size: 24px;
}
.data-label {
font-size: 10px;
text-align: center;
margin-top: 4px;
}
/* Arrow Column */ /* Arrow Column */
.arrow-column { .arrow-column {
@@ -200,9 +219,9 @@ const isCalculatingLoss = computed(() => step.value === 2)
.model-column { .model-column {
display: grid; display: grid;
grid-template-columns: auto auto auto; grid-template-columns: auto auto auto;
grid-template-areas: grid-template-areas:
"vit arrow proj" 'vit arrow proj'
"llm llm llm"; 'llm llm llm';
gap: 10px; gap: 10px;
row-gap: 30px; row-gap: 30px;
align-items: center; align-items: center;
@@ -262,8 +281,14 @@ const isCalculatingLoss = computed(() => step.value === 2)
width: 100%; width: 100%;
} }
.block-icon { font-size: 20px; margin-bottom: 4px; } .block-icon {
.block-name { font-size: 12px; font-weight: bold; } font-size: 20px;
margin-bottom: 4px;
}
.block-name {
font-size: 12px;
font-weight: bold;
}
.arrow-small { .arrow-small {
grid-area: arrow; grid-area: arrow;
@@ -316,8 +341,15 @@ const isCalculatingLoss = computed(() => step.value === 2)
box-shadow: 0 0 10px rgba(255, 0, 0, 0.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-label {
.loss-desc { font-size: 10px; color: var(--vp-c-text-2); } font-size: 12px;
font-weight: bold;
color: var(--vp-c-danger);
}
.loss-desc {
font-size: 10px;
color: var(--vp-c-text-2);
}
/* Controls */ /* Controls */
.controls { .controls {
@@ -356,9 +388,15 @@ const isCalculatingLoss = computed(() => step.value === 2)
} }
@keyframes pulse { @keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(var(--vp-c-brand-rgb), 0.4); } 0% {
70% { box-shadow: 0 0 0 10px rgba(var(--vp-c-brand-rgb), 0); } box-shadow: 0 0 0 0 rgba(var(--vp-c-brand-rgb), 0.4);
100% { box-shadow: 0 0 0 0 rgba(var(--vp-c-brand-rgb), 0); } }
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) { @media (max-width: 600px) {
@@ -388,4 +426,4 @@ const isCalculatingLoss = computed(() => step.value === 2)
height: 1px; height: 1px;
} }
} }
</style> </style>
@@ -5,7 +5,12 @@
<div class="step-box"> <div class="step-box">
<div class="label">1. Patch (4x4)</div> <div class="label">1. Patch (4x4)</div>
<div class="grid-patch"> <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>
<div class="desc">768 像素点</div> <div class="desc">768 像素点</div>
</div> </div>
@@ -16,7 +21,12 @@
<div class="step-box"> <div class="step-box">
<div class="label">2. Flatten</div> <div class="label">2. Flatten</div>
<div class="vector-container"> <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>
<div class="desc">拉平成向量</div> <div class="desc">拉平成向量</div>
</div> </div>
@@ -38,8 +48,8 @@
<script setup> <script setup>
const getPixelColor = (n) => { const getPixelColor = (n) => {
// Generate a gradient of colors // Generate a gradient of colors
const hue = (n * 20) % 360; const hue = (n * 20) % 360
return `hsl(${hue}, 70%, 60%)`; return `hsl(${hue}, 70%, 60%)`
} }
</script> </script>
@@ -14,15 +14,15 @@
</div> </div>
</div> </div>
<div class="status-desc"> <div class="status-desc">
{{ isVLM {{
? '给大脑装上眼睛:视觉信号经过翻译,变成 Token 混入文字流。' isVLM
: '纯文本大脑:只能听懂 Token 语言,无法感知图像。' ? '给大脑装上眼睛:视觉信号经过翻译,变成 Token 混入文字流。'
: '纯文本大脑:只能听懂 Token 语言,无法感知图像。'
}} }}
</div> </div>
</div> </div>
<div class="diagram-stage" :class="{ 'vlm-mode': isVLM }"> <div class="diagram-stage" :class="{ 'vlm-mode': isVLM }">
<!-- Vision Pipeline (Only visible in VLM mode) --> <!-- Vision Pipeline (Only visible in VLM mode) -->
<div class="pipeline vision-pipeline"> <div class="pipeline vision-pipeline">
<div class="node-group"> <div class="node-group">
@@ -31,12 +31,18 @@
<span class="label">Image</span> <span class="label">Image</span>
</div> </div>
<div class="flow-arrow"></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="icon"></span>
<span class="label">ViT</span> <span class="label">ViT</span>
</div> </div>
<div class="flow-arrow"></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="icon">🔌</span>
<span class="label">Projector</span> <span class="label">Projector</span>
</div> </div>
@@ -56,7 +62,7 @@
<span class="icon"></span> <span class="icon"></span>
<span class="label">Embed</span> <span class="label">Embed</span>
</div> </div>
<!-- Merge Point Visualization --> <!-- Merge Point Visualization -->
<div class="merge-point" :class="{ active: isVLM }"> <div class="merge-point" :class="{ active: isVLM }">
<div class="plus-icon">+</div> <div class="plus-icon">+</div>
@@ -80,20 +86,33 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="interactive-info"> <div class="interactive-info">
<div class="info-card" v-if="!isVLM"> <div class="info-card" v-if="!isVLM">
<h3>Standard LLM Flow</h3> <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>
<div class="info-card vlm-info" v-else> <div class="info-card vlm-info" v-else>
<h3>VLM = LLM + Vision Encoder</h3> <h3>VLM = LLM + Vision Encoder</h3>
<ul> <ul>
<li><strong>ViT (The Eye):</strong> Slices image into patches and extracts features.</li> <li>
<li><strong>Projector (The Translator):</strong> Converts visual features into the same "language" (vector dimension) as text embeddings.</li> <strong>ViT (The Eye):</strong> Slices image into patches and
<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> 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> </ul>
</div> </div>
</div> </div>
@@ -173,7 +192,7 @@ const toggleMode = () => {
justify-content: center; justify-content: center;
font-size: 14px; font-size: 14px;
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); 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 { .toggle-track.active .toggle-thumb {
@@ -271,29 +290,41 @@ const toggleMode = () => {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
min-width: 70px; 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; position: relative;
z-index: 2; z-index: 2;
} }
.icon { font-size: 20px; margin-bottom: 4px; } .icon {
.label { font-size: 11px; font-weight: bold; } font-size: 20px;
margin-bottom: 4px;
}
.label {
font-size: 11px;
font-weight: bold;
}
.input-node { border-color: #aaa; } .input-node {
.process-node { border-color: var(--vp-c-brand-dimm); } border-color: #aaa;
.core-node { }
border-color: var(--vp-c-brand); .process-node {
border-color: var(--vp-c-brand-dimm);
}
.core-node {
border-color: var(--vp-c-brand);
background: var(--vp-c-brand-dimm); background: var(--vp-c-brand-dimm);
min-width: 100px; min-width: 100px;
} }
.output-node { border-color: var(--vp-c-brand); } .output-node {
border-color: var(--vp-c-brand);
}
.vit-node { .vit-node {
border-color: var(--vp-c-yellow); border-color: var(--vp-c-yellow);
background: rgba(255, 197, 23, 0.05); background: rgba(255, 197, 23, 0.05);
} }
.projector-node { .projector-node {
border-color: var(--vp-c-yellow); border-color: var(--vp-c-yellow);
background: var(--vp-c-yellow-dimm); background: var(--vp-c-yellow-dimm);
} }
@@ -355,13 +386,26 @@ const toggleMode = () => {
animation: pulse 1s infinite alternate; animation: pulse 1s infinite alternate;
} }
.t1 { animation-delay: 0s; } .t1 {
.t2 { animation-delay: 0.2s; } animation-delay: 0s;
.v1 { background: var(--vp-c-yellow); animation-delay: 0.4s; } }
.t2 {
animation-delay: 0.2s;
}
.v1 {
background: var(--vp-c-yellow);
animation-delay: 0.4s;
}
@keyframes pulse { @keyframes pulse {
from { opacity: 0.3; transform: scale(0.8); } from {
to { opacity: 1; transform: scale(1.1); } opacity: 0.3;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1.1);
}
} }
/* Interactive Info */ /* Interactive Info */
@@ -383,7 +427,8 @@ const toggleMode = () => {
color: var(--vp-c-text-1); color: var(--vp-c-text-1);
} }
.info-card p, .info-card li { .info-card p,
.info-card li {
font-size: 13px; font-size: 13px;
color: var(--vp-c-text-2); color: var(--vp-c-text-2);
line-height: 1.6; line-height: 1.6;
@@ -395,8 +440,14 @@ const toggleMode = () => {
} }
@keyframes fadeIn { @keyframes fadeIn {
from { opacity: 0; transform: translateY(5px); } from {
to { opacity: 1; transform: translateY(0); } opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
/* Mobile Adjustments */ /* Mobile Adjustments */
@@ -404,15 +455,15 @@ const toggleMode = () => {
.diagram-stage { .diagram-stage {
height: 300px; height: 300px;
} }
.text-pipeline { .text-pipeline {
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px; gap: 10px;
width: 90%; width: 90%;
} }
.vision-pipeline { .vision-pipeline {
left: 10%; left: 10%;
} }
} }
</style> </style>
@@ -20,11 +20,11 @@
<div class="visual-area"> <div class="visual-area">
<!-- 原始/切分视图容器 --> <!-- 原始/切分视图容器 -->
<div class="image-container" :class="{ 'is-patchified': isPatchified }"> <div class="image-container" :class="{ 'is-patchified': isPatchified }">
<div <div
v-for="n in 196" v-for="n in 196"
:key="n" :key="n"
class="patch" class="patch"
:style="{ :style="{
'--delay': `${n * 0.005}s`, '--delay': `${n * 0.005}s`,
'--hue': `${(n % 14) * 20 + Math.floor(n / 14) * 20}` '--hue': `${(n % 14) * 20 + Math.floor(n / 14) * 20}`
}" }"
@@ -32,16 +32,16 @@
<span class="patch-id" v-if="isPatchified">{{ n }}</span> <span class="patch-id" v-if="isPatchified">{{ n }}</span>
</div> </div>
</div> </div>
<div class="arrow" v-if="isPatchified"></div> <div class="arrow" v-if="isPatchified"></div>
<!-- 线性序列视图 --> <!-- 线性序列视图 -->
<div class="sequence-container" v-if="isPatchified"> <div class="sequence-container" v-if="isPatchified">
<div class="sequence-label">Flattened Sequence (Token Input)</div> <div class="sequence-label">Flattened Sequence (Token Input)</div>
<div class="token-stream"> <div class="token-stream">
<div <div
v-for="n in 196" v-for="n in 196"
:key="n" :key="n"
class="mini-patch" class="mini-patch"
:style="{ '--hue': `${(n % 14) * 20 + Math.floor(n / 14) * 20}` }" :style="{ '--hue': `${(n % 14) * 20 + Math.floor(n / 14) * 20}` }"
></div> ></div>
@@ -144,7 +144,7 @@ const toggleState = () => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 8px; font-size: 8px;
color: rgba(0,0,0,0.5); color: rgba(0, 0, 0, 0.5);
transition: all 0.5s ease; transition: all 0.5s ease;
} }
@@ -198,12 +198,23 @@ const toggleState = () => {
} }
@keyframes bounce { @keyframes bounce {
0%, 100% { transform: translateY(0); } 0%,
50% { transform: translateY(5px); } 100% {
transform: translateY(0);
}
50% {
transform: translateY(5px);
}
} }
@keyframes fadeIn { @keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); } from {
to { opacity: 1; transform: translateY(0); } opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
</style> </style>

Some files were not shown because too many files have changed in this diff Show More