diff --git a/docs/.vitepress/theme/components/appendix/terminal-intro/EscapeParserDemo.vue b/docs/.vitepress/theme/components/appendix/terminal-intro/EscapeParserDemo.vue
index 53e1545..15e343e 100644
--- a/docs/.vitepress/theme/components/appendix/terminal-intro/EscapeParserDemo.vue
+++ b/docs/.vitepress/theme/components/appendix/terminal-intro/EscapeParserDemo.vue
@@ -4,8 +4,8 @@
转义序列解析原理 (Parser Mechanism)
-
@@ -81,7 +81,7 @@
@@ -241,14 +263,14 @@ const play = async () => {
.stream-track {
position: relative;
height: 60px;
- display: flex;
- justify-content: center; /* Center the focus area */
+ /* Use a fixed height to contain the items */
}
.stream-window-mask {
width: 100%;
overflow: hidden;
position: relative;
+ height: 100%;
/* Mask gradient to fade edges */
mask-image: linear-gradient(to right, transparent, black 40%, black 60%, transparent);
-webkit-mask-image: linear-gradient(to right, transparent, black 40%, black 60%, transparent);
@@ -258,9 +280,18 @@ const play = async () => {
display: flex;
gap: 4px;
position: absolute;
- left: 50%; /* Start from center */
+ left: 50%; /* Center the container start */
+ /*
+ Correct centering logic:
+ - Item width: 36px
+ - Gap: 4px
+ - Total unit: 40px
+ - We want Item[0] center to be at left:0 (relative to left:50%)
+ - Item[0] center is at: 18px (half width)
+ - So we need to shift left by 18px initially.
+ */
+ margin-left: -18px;
transition: transform 0.5s cubic-bezier(0.25, 1, 0.5, 1);
- padding-left: 20px; /* Offset for first item */
}
.char-box {
diff --git a/docs/.vitepress/theme/components/appendix/terminal-intro/TerminalHandsOn.vue b/docs/.vitepress/theme/components/appendix/terminal-intro/TerminalHandsOn.vue
index 2270790..c69f731 100644
--- a/docs/.vitepress/theme/components/appendix/terminal-intro/TerminalHandsOn.vue
+++ b/docs/.vitepress/theme/components/appendix/terminal-intro/TerminalHandsOn.vue
@@ -20,18 +20,33 @@
{{ currentTask.description }}
-
@@ -91,7 +107,7 @@ import { ref, computed, nextTick, watch } from 'vue'
const currentOS = ref('win-cmd')
const currentTaskIndex = ref(0)
-const isAiOpen = ref(false)
+const isAiOpen = ref(true)
const inputCmd = ref('')
const history = ref([])
const cmdInput = ref(null)
@@ -197,23 +213,80 @@ d---- 1/15/2026 9:00 AM Downloads
output: () => ''
},
{
- title: '第五步:安装程序',
- description: '终端不仅能管理文件,还能安装软件。比如我们想安装一个 Python 库 "requests"。',
- goal: '使用 pip 安装 requests 库。',
- aiQuery: '怎么用命令行安装 python 的 requests 库?',
+ title: '第五步:安装程序 (系统软件 & Python库)',
+ description: '终端不仅能管理文件,还能安装软件。我们来尝试两种常见的安装场景:安装系统工具(如 wget/git)和安装 Python 库(如 requests)。',
+ goal: '任选其一:安装系统工具或 Python 库。',
+ aiQuery: '怎么用命令行安装软件?我想装 git 或者 python 的 requests 库。',
aiResponse: {
- 'common': '安装 Python 库通常使用 `pip` (Python Package Installer)。命令是 `pip install requests`。'
+ 'mac': 'macOS 推荐使用 Homebrew 安装系统软件,使用 pip 安装 Python 库。',
+ 'linux': 'Linux (Ubuntu/Debian) 使用 apt 安装系统软件,使用 pip 安装 Python 库。',
+ 'win-ps': 'Windows PowerShell 可以使用 pip 安装 Python 库。系统软件通常用 winget (这里暂只演示 pip)。',
+ 'win-cmd': 'CMD 也可以使用 pip 安装 Python 库。',
+ 'common': '不同系统有不同的包管理器。'
+ },
+ commands: {
+ 'mac': [
+ { label: '安装 wget (系统)', cmd: 'brew install wget' },
+ { label: '安装 requests (Python)', cmd: 'pip install requests' }
+ ],
+ 'linux': [
+ { label: '安装 git (系统)', cmd: 'sudo apt install git' },
+ { label: '安装 requests (Python)', cmd: 'pip install requests' }
+ ],
+ 'win-ps': [
+ { label: '安装 requests (Python)', cmd: 'pip install requests' }
+ ],
+ 'win-cmd': [
+ { label: '安装 requests (Python)', cmd: 'pip install requests' }
+ ]
},
expectedCmd: {
- 'common': 'pip install requests'
+ // Fallback/Legacy
+ 'mac': 'brew install wget',
+ 'linux': 'sudo apt install git',
+ 'win-ps': 'pip install requests',
+ 'win-cmd': 'pip install requests'
},
- validate: (cmd) => cmd.trim() === 'pip install requests',
- output: () => `
+ validate: (cmd, os) => {
+ const c = cmd.trim()
+ if (os === 'mac') return c === 'brew install wget' || c === 'pip install requests'
+ if (os === 'linux') return c === 'sudo apt install git' || c === 'apt install git' || c === 'pip install requests'
+ return c === 'pip install requests'
+ },
+ output: (os, cmd) => { // Modified to accept cmd
+ const c = cmd ? cmd.trim() : ''
+
+ // Python requests output
+ if (c.includes('pip install requests')) {
+ return `
Downloading/unpacking requests
Downloading requests-2.31.0-py3-none-any.whl (62kB): 62kB downloaded
Installing collected packages: requests
Successfully installed requests
Cleaning up...`
+ }
+
+ // System tools output
+ if (os === 'mac') {
+ return `
+==> Downloading https://ghcr.io/v2/homebrew/core/wget/manifests/1.21.4
+######################################################################## 100.0%
+==> Installing wget
+🍺 /usr/local/Cellar/wget/1.21.4: 90 files, 4.2MB`
+ }
+ if (os === 'linux') {
+ return `
+Reading package lists... Done
+Building dependency tree... Done
+The following NEW packages will be installed:
+ git
+0 upgraded, 1 newly installed, 0 to remove.
+Get:1 http://archive.ubuntu.com/ubuntu jammy/main amd64 git amd64 1:2.34.1 [3MB]
+Fetched 3MB in 1s (2560 kB/s)
+Setting up git (1:2.34.1-1ubuntu1.9) ...`
+ }
+ return `Successfully installed.`
+ }
},
{
title: '第六步:打扫战场',
@@ -288,7 +361,7 @@ const executeCommand = () => {
// Check if it matches current task requirement
if (!isTaskCompleted.value && currentTask.value.validate(cmd, currentOS.value)) {
// Success
- const out = currentTask.value.output(currentOS.value)
+ const out = currentTask.value.output(currentOS.value, cmd) // Pass cmd to output
if (out) {
history.value.push({ type: 'output', content: out })
}
@@ -320,7 +393,6 @@ const nextTask = () => {
if (currentTaskIndex.value < tasks.length - 1) {
currentTaskIndex.value++
isTaskCompleted.value = false
- isAiOpen.value = false
// Clear history to keep it clean? Or keep it? Let's keep it but maybe add a separator
history.value.push({ type: 'info', content: `--- 进入下一关: ${currentTask.value.title} ---` })
scrollToBottom()
@@ -330,7 +402,6 @@ const nextTask = () => {
const resetCurrentTask = () => {
isTaskCompleted.value = false
inputCmd.value = ''
- isAiOpen.value = false
history.value = []
}
@@ -426,7 +497,6 @@ watch(currentOS, () => {
.ai-header {
padding: 10px 15px;
background: linear-gradient(to right, rgba(16, 185, 129, 0.1), transparent);
- cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
@@ -440,16 +510,6 @@ watch(currentOS, () => {
background: linear-gradient(to right, rgba(16, 185, 129, 0.2), transparent);
}
-.ai-header.active .ai-arrow {
- transform: rotate(180deg);
-}
-
-.ai-arrow {
- margin-left: auto;
- font-size: 0.8rem;
- transition: transform 0.2s;
-}
-
.ai-chat {
padding: 15px;
border-top: 1px solid var(--vp-c-divider);
@@ -478,15 +538,22 @@ watch(currentOS, () => {
border-bottom-left-radius: 2px;
}
+.cmd-buttons {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-top: 10px;
+}
+
.copy-btn {
- margin-top: 5px;
font-size: 0.8rem;
- padding: 2px 8px;
+ padding: 4px 10px;
border: 1px solid var(--vp-c-brand);
color: var(--vp-c-brand);
background: transparent;
border-radius: 4px;
cursor: pointer;
+ text-align: left;
}
.copy-btn:hover {
@@ -633,6 +700,19 @@ watch(currentOS, () => {
margin: 0;
}
+.enter-hint {
+ color: #666;
+ font-size: 12px;
+ margin-left: 10px;
+ animation: blink 1.5s infinite;
+ white-space: nowrap;
+}
+
+@keyframes blink {
+ 0%, 100% { opacity: 0.5; }
+ 50% { opacity: 1; }
+}
+
.line.output {
color: inherit;
opacity: 0.9;
diff --git a/docs/zh-cn/appendix/terminal-intro.md b/docs/zh-cn/appendix/terminal-intro.md
index 0ee62c9..4b85346 100644
--- a/docs/zh-cn/appendix/terminal-intro.md
+++ b/docs/zh-cn/appendix/terminal-intro.md
@@ -31,10 +31,12 @@
> 💡 **提示**:为了安全和方便,推荐你在下方的**网页模拟器**中操作。如果你有信心,也可以按照第 0 章的方法打开你电脑上真实的终端,跟随步骤一起练习(效果是一样的)。
在这个练习中,你将学会:
-1. 查看当前有什么文件。
-2. 创建文件夹和文件。
-3. 删除它们。
-4. **学会向 AI 提问**:当你忘记命令时,如何让 AI 告诉你答案。
+1. **查看文件**:学会用 `ls` 或 `dir` 看看当前目录下有什么。
+2. **创建与进入**:学会用 `mkdir` 创建新文件夹,用 `cd` 像传送门一样进入它。
+3. **新建文件**:学会用命令快速创建一个新文件。
+4. **安装软件**:体验一行代码安装 Python 库或系统软件的快感。
+5. **删除清理**:学会如何删除不需要的文件(慎用!)。
+6. **求助 AI**:这是最重要的!当你忘记命令时,学会问 AI:“在 Mac 上怎么删除文件?”,它会直接告诉你答案。
*请在下方选择你常用的操作系统,然后跟随引导开始操作:*