Files
test-repo/docs/.vitepress/theme/components/appendix/terminal-intro/TerminalDefinition.vue
T
sanbuphy 73f4788d7e 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
2026-01-16 19:10:51 +08:00

666 lines
15 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="terminal-definition">
<div class="mode-switch">
<button :class="{ active: mode === 'cli' }" @click="mode = 'cli'">
🖥 CLI (命令行界面)
</button>
<button :class="{ active: mode === 'gui' }" @click="mode = 'gui'">
🖱 GUI (图形用户界面)
</button>
</div>
<!-- CLI Visualization -->
<div v-if="mode === 'cli'" class="visualization-container">
<div class="flow-container">
<!-- Input Side -->
<div class="stage input-stage">
<div class="icon-box">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="2" y="4" width="20" height="16" rx="2" ry="2"></rect>
<path d="M6 8h.001"></path>
<path d="M10 8h.001"></path>
<path d="M14 8h.001"></path>
<path d="M18 8h.001"></path>
<path d="M6 12h.001"></path>
<path d="M10 12h.001"></path>
<path d="M14 12h.001"></path>
<path d="M18 12h.001"></path>
<path d="M7 16h10"></path>
</svg>
</div>
<div class="label">Input (Keyboard)</div>
<div class="sub-label">发送指令 (字符信号)</div>
</div>
<!-- Stream Animation -->
<div class="stream-path">
<div class="stream-line"></div>
<div class="stream-label">Character Stream / 字符流</div>
<div
v-for="char in activeChars"
:key="char.id"
class="stream-char"
:style="{ left: char.progress + '%' }"
>
{{ char.val }}
</div>
</div>
<!-- Output Side -->
<div class="stage output-stage">
<div class="terminal-screen">
<div class="screen-content">
<span class="prompt">$</span> {{ typedContent
}}<span class="cursor">_</span>
</div>
</div>
<div class="label">Output (Text Grid)</div>
<div class="sub-label">文本网格反馈</div>
</div>
</div>
<div class="desc-box">
<p>
<strong>CLI (Command Line Interface)</strong>:
这种模式下计算机只认识字符你的每一次按键都会被转换成编码发送给系统系统处理后返回文字结果它不关心你在哪里点击只关心你输入了什么
</p>
</div>
<div class="control-bar">
<button @click="startSimulation" :disabled="isAnimating">
<span v-if="!isAnimating"> Play Simulation / 演示输入流</span>
<span v-else>Simulating... / 演示中...</span>
</button>
</div>
</div>
<!-- GUI Visualization -->
<div v-else class="visualization-container">
<div class="flow-container">
<!-- Input Side -->
<div class="stage input-stage">
<div class="icon-box gui-input" :class="{ clicking: isGuiClicking }">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path>
<path d="M13 13l6 6"></path>
</svg>
</div>
<div class="label">Input (Mouse)</div>
<div class="sub-label">发送事件 (坐标/点击)</div>
</div>
<!-- Event Animation -->
<div class="stream-path">
<div class="stream-line dashed"></div>
<div class="stream-label">Event Loop / 事件循环</div>
<div
v-for="ev in guiEvents"
:key="ev.id"
class="gui-event-packet"
:style="{ left: ev.progress + '%' }"
>
{{ ev.type }}
</div>
</div>
<!-- Output Side -->
<div class="stage output-stage">
<div class="gui-screen">
<div class="window-frame">
<div class="win-header"></div>
<div class="win-body">
<div class="icon-grid">
<div class="desktop-icon" :class="{ selected: iconSelected }">
📁
</div>
<div class="desktop-icon">📄</div>
</div>
<div class="gui-cursor" :style="cursorStyle">
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="white"
stroke="black"
stroke-width="2"
>
<path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path>
</svg>
</div>
</div>
</div>
</div>
<div class="label">Output (Graphics)</div>
<div class="sub-label">像素图形渲染</div>
</div>
</div>
<div class="desc-box">
<p>
<strong>GUI (Graphical User Interface)</strong>:
这种模式下计算机实时追踪鼠标坐标和点击事件并每秒刷新 60
次屏幕像素它更直观但需要消耗大量资源来处理图形渲染
</p>
</div>
<div class="control-bar">
<button @click="startGuiSimulation" :disabled="isGuiAnimating">
<span v-if="!isGuiAnimating"> Play Interaction / 演示交互</span>
<span v-else>Simulating... / 演示中...</span>
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const mode = ref('cli') // 'cli' | 'gui'
// CLI Logic
const isAnimating = ref(false)
const activeChars = ref([])
const typedContent = ref('')
const demoText = 'echo "hello world"'
const startSimulation = () => {
if (isAnimating.value) return
isAnimating.value = true
typedContent.value = ''
activeChars.value = []
let index = 0
const processNextChar = () => {
if (index >= demoText.length) {
setTimeout(() => {
isAnimating.value = false
}, 1000)
return
}
const char = demoText[index]
const charId = Date.now() + index
// Create new flying char
const newChar = {
id: charId,
val: char,
progress: 10 // start position
}
activeChars.value.push(newChar)
// Animate this char
let progress = 10
const interval = setInterval(() => {
progress += 4 // Faster speed
const charObj = activeChars.value.find((c) => c.id === charId)
if (charObj) charObj.progress = progress
if (progress >= 90) {
clearInterval(interval)
// Remove from stream and add to screen
activeChars.value = activeChars.value.filter((c) => c.id !== charId)
typedContent.value += char
// Next char
index++
setTimeout(processNextChar, 100) // Faster typing
}
}, 20)
}
processNextChar()
}
// GUI Logic
const isGuiAnimating = ref(false)
const isGuiClicking = ref(false)
const guiEvents = ref([]) // Array of events
const iconSelected = ref(false)
const inputMousePosition = ref({ x: 80, y: 60 }) // Input side (Invisible physical mouse)
const screenCursorPosition = ref({ x: 80, y: 60 }) // Output side (Visible screen cursor)
const cursorStyle = computed(() => ({
transform: `translate(${screenCursorPosition.value.x}px, ${screenCursorPosition.value.y}px)`
}))
const startGuiSimulation = () => {
if (isGuiAnimating.value) return
isGuiAnimating.value = true
iconSelected.value = false
inputMousePosition.value = { x: 80, y: 60 }
screenCursorPosition.value = { x: 80, y: 60 }
guiEvents.value = []
// 1. Move Cursor (Physical Mouse Movement)
let step = 0
const moveInterval = setInterval(() => {
step++
inputMousePosition.value = {
x: 80 - step * 2,
y: 60 - step * 1.5
}
// Emit Move Event frequently (Simulate high polling rate)
if (step % 2 === 0) {
const targetX = inputMousePosition.value.x
const targetY = inputMousePosition.value.y
emitGuiEvent(
`Move(${Math.round(targetX)},${Math.round(targetY)})`,
() => {
// When packet arrives: Update screen cursor
screenCursorPosition.value = { x: targetX, y: targetY }
}
)
}
if (step >= 20) {
clearInterval(moveInterval)
// 2. Click
performClick()
}
}, 50)
}
const emitGuiEvent = (type, onArrive) => {
const eventId = Date.now() + Math.random()
const newEvent = {
id: eventId,
type: type,
progress: 10
}
guiEvents.value.push(newEvent)
let progress = 10
const packetInterval = setInterval(() => {
progress += 2
const ev = guiEvents.value.find((e) => e.id === eventId)
if (ev) ev.progress = progress
if (progress >= 90) {
clearInterval(packetInterval)
guiEvents.value = guiEvents.value.filter((e) => e.id !== eventId)
// Execute callback when packet arrives at Output
if (onArrive) onArrive()
}
}, 10)
}
const performClick = () => {
setTimeout(() => {
isGuiClicking.value = true
// Send Click Event
emitGuiEvent('Click(40,30)', () => {
// When packet arrives: Select icon
iconSelected.value = true
setTimeout(() => {
isGuiAnimating.value = false
}, 1000)
})
setTimeout(() => {
isGuiClicking.value = false
}, 200) // Input click feedback is fast
}, 300)
}
</script>
<style scoped>
.terminal-definition {
background: #09090b;
border: 1px solid #27272a;
border-radius: 12px;
padding: 20px;
font-family: 'JetBrains Mono', monospace;
margin: 20px 0;
}
.mode-switch {
display: flex;
gap: 10px;
margin-bottom: 20px;
border-bottom: 1px solid #27272a;
padding-bottom: 15px;
}
.mode-switch button {
background: transparent;
border: 1px solid transparent;
color: #71717a;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
transition: all 0.2s;
}
.mode-switch button.active {
background: #27272a;
color: #e4e4e7;
border-color: #3f3f46;
}
.mode-switch button:hover {
color: #e4e4e7;
}
.flow-container {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
height: 120px;
}
.stage {
display: flex;
flex-direction: column;
align-items: center;
z-index: 2;
position: relative;
width: 100px;
}
.input-stage {
flex: 0 0 auto;
}
.output-stage {
flex: 0 0 auto;
}
.icon-box {
width: 60px;
height: 60px;
background: #18181b;
border: 1px solid #3f3f46;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: #a1a1aa;
margin-bottom: 10px;
transition: all 0.2s;
}
.icon-box.clicking {
transform: scale(0.9);
border-color: #22d3ee;
color: #22d3ee;
}
.terminal-screen {
width: 140px;
height: 80px;
background: #000;
border: 1px solid #3f3f46;
border-radius: 8px;
padding: 10px;
color: #22c55e;
font-size: 12px;
display: flex;
align-items: flex-start;
margin-bottom: 10px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.gui-screen {
width: 140px;
height: 80px;
background: #27272a;
border: 1px solid #52525b;
border-radius: 4px;
margin-bottom: 10px;
overflow: hidden;
position: relative;
}
.window-frame {
background: #3f3f46;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.win-header {
height: 12px;
background: #52525b;
border-bottom: 1px solid #27272a;
}
.win-body {
flex: 1;
background: #18181b; /* Wallpaper */
position: relative;
padding: 10px;
}
.icon-grid {
display: flex;
gap: 10px;
}
.desktop-icon {
font-size: 16px;
padding: 2px;
border-radius: 4px;
border: 1px solid transparent;
}
.desktop-icon.selected {
background: rgba(34, 211, 238, 0.2);
border-color: #22d3ee;
}
.gui-cursor {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
transition: transform 0.1s linear; /* Smooth interpolation */
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
}
.screen-content {
word-break: break-all;
}
.label {
font-size: 13px;
font-weight: 600;
color: #e4e4e7;
text-align: center;
}
.sub-label {
font-size: 10px;
color: #71717a;
margin-top: 2px;
text-align: center;
}
.stream-path {
flex: 1;
height: 60px;
position: relative;
margin: 0 10px;
display: flex;
align-items: center;
justify-content: center;
}
.stream-line {
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 2px;
background: #27272a;
transform: translateY(-50%);
}
.stream-line.dashed {
background: repeating-linear-gradient(
90deg,
#27272a 0,
#27272a 6px,
transparent 6px,
transparent 10px
);
height: 1px;
}
.stream-line::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
border-left: 6px solid #27272a;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
}
.stream-label {
position: absolute;
top: 10px;
font-size: 10px;
color: #52525b;
background: #09090b;
padding: 0 8px;
}
.stream-char {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
background: #22d3ee;
color: #000;
font-weight: bold;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
box-shadow: 0 0 10px rgba(34, 211, 238, 0.3);
z-index: 10;
}
.gui-event-packet {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
background: #facc15;
color: #000;
font-weight: bold;
padding: 2px 6px;
border-radius: 10px;
font-size: 10px;
box-shadow: 0 0 5px rgba(250, 204, 21, 0.3);
white-space: nowrap;
z-index: 10;
}
.cursor {
animation: blink 1s step-end infinite;
}
@keyframes blink {
50% {
opacity: 0;
}
}
.desc-box {
background: #18181b;
padding: 12px;
border-radius: 6px;
margin-bottom: 15px;
font-size: 13px;
color: #a1a1aa;
line-height: 1.5;
}
.desc-box strong {
color: #e4e4e7;
}
.control-bar {
display: flex;
justify-content: center;
}
button {
background: #18181b;
color: #e4e4e7;
border: 1px solid #27272a;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-family: inherit;
font-size: 13px;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 8px;
}
button:hover:not(:disabled) {
background: #27272a;
border-color: #52525b;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
@media (max-width: 600px) {
.flow-container {
flex-direction: column;
height: auto;
gap: 20px;
}
.stream-path {
width: 100%;
height: 40px;
margin: 10px 0;
}
.stream-line {
transform: rotate(90deg);
width: 40px;
left: 50%;
margin-left: -20px;
}
}
</style>