feat(docs): add interactive demo components for technical appendices

Add placeholder Vue components for visualizing technical concepts across multiple domains including frontend routing, browser rendering, cache design, queue design, database principles, API design, cloud services, and backend evolution. These components provide interactive educational content for the documentation.

Update documentation structure to include new appendix sections and enhance existing content with visual components. Remove unused 'codex' dependency from package.json.
This commit is contained in:
sanbuphy
2026-02-06 03:34:50 +08:00
parent e8bba6f7c0
commit 7c70c37072
171 changed files with 69830 additions and 6689 deletions
@@ -2,196 +2,375 @@
import { ref, computed } from 'vue'
const round = ref(1)
const minRound = 1
const maxRound = 5
const maxRound = 20
const windowLimit = 4000
const contextTokens = computed(() => 120 + (round.value - 1) * 80)
// 模拟数据配置
const systemPromptTokens = 1000
const tokensPerRound = 300
const costPer1kTokens = 0.002
const cacheHitRate = computed(() =>
round.value === 1 ? 0 : Math.min(80, (round.value - 1) * 20)
)
// 计算属性
const historyTokens = computed(() => (round.value - 1) * tokensPerRound)
const currentInputTokens = 200
const totalTokens = computed(() => systemPromptTokens + historyTokens.value + currentInputTokens)
const baseCostPerRound = 0.025
// 是否溢出
const isOverflow = computed(() => totalTokens.value > windowLimit)
const overflowAmount = computed(() => Math.max(0, totalTokens.value - windowLimit))
const forgottenRounds = computed(() => Math.floor(overflowAmount.value / tokensPerRound))
const currentCost = computed(() => {
const rate = cacheHitRate.value / 100
const cost = baseCostPerRound * (1 - rate * 0.9)
return cost.toFixed(4)
})
// 成本计算
const currentCost = computed(() => (totalTokens.value / 1000 * costPer1kTokens).toFixed(4))
const savedPercent = computed(() => {
const cost = Number(currentCost.value)
const saved = ((baseCostPerRound - cost) / baseCostPerRound) * 100
return saved.toFixed(1)
})
// 高度计算 (相对于 windowLimit)
const systemHeight = computed(() => (systemPromptTokens / windowLimit) * 100)
const inputHeight = computed(() => (currentInputTokens / windowLimit) * 100)
// History 高度展示逻辑:
// 我们希望展示“总高度”,即使超过 100%。
// 父容器会限制显示区域,溢出部分通过视觉暗示。
const historyHeight = computed(() => (historyTokens.value / windowLimit) * 100)
const increaseRound = () => {
if (round.value < maxRound) round.value += 1
}
const decreaseRound = () => {
if (round.value > minRound) round.value -= 1
}
const totalHeight = computed(() => systemHeight.value + historyHeight.value + inputHeight.value)
</script>
<template>
<div class="agent-context-intro">
<div class="header">
<h3>三个关键数字轮次上下文长度缓存命中率</h3>
<p>拖动轮次看看这三个数字是怎么一起变化的</p>
</div>
<div class="round-control">
<button class="round-btn" @click="decreaseRound" :disabled="round === minRound">
-
</button>
<div class="round-text">
当前假设我们已经聊到了
<strong> {{ round }} </strong>拖动右侧滑块看看聊多几轮之后黑板会写满到什么程度背课文本比例会涨到多高
</div>
<input
class="round-slider"
type="range"
:min="minRound"
:max="maxRound"
v-model.number="round"
/>
<button class="round-btn" @click="increaseRound" :disabled="round === maxRound">
+
</button>
</div>
<div class="metrics-row">
<div class="metric-card">
<div class="metric-label">聊了几轮</div>
<div class="metric-value"> {{ round }} </div>
<div class="metric-desc">对话轮次</div>
</div>
<div class="metric-card">
<div class="metric-label">记了多少字</div>
<div class="metric-value">{{ contextTokens }}</div>
<div class="metric-desc">大致对应 token </div>
</div>
<div class="metric-card">
<div class="metric-label">背课文本比例</div>
<div class="metric-value">{{ cacheHitRate }}%</div>
<div class="metric-desc">前缀复用比例</div>
</div>
<div class="metric-card">
<div class="metric-label">这轮大概多少钱</div>
<div class="metric-value">${{ currentCost }}</div>
<div class="metric-desc">比不做优化便宜了 {{ savedPercent }}%</div>
<div class="agent-context-flow">
<!-- 1. 顶部统计栏 -->
<div class="control-panel">
<div class="stat-group">
<div class="stat-item">
<span class="value">{{ round }}</span>
<span class="label">当前轮次</span>
</div>
<div class="stat-divider"></div>
<div class="stat-item">
<span class="value" :class="{ error: isOverflow }">{{ totalTokens }}</span>
<span class="label">Token 占用</span>
</div>
<div class="stat-divider"></div>
<div class="stat-item">
<span class="value">${{ currentCost }}</span>
<span class="label">本轮成本</span>
</div>
</div>
</div>
<div class="summary-line">
参考基准一轮完全不做优化大约 {{ baseCostPerRound.toFixed(4) }} 美元
在当前轮次下通过复用前缀这轮的成本约为 {{ currentCost }} 美元
<!-- 2. 可视化区域 -->
<div class="visualization-area">
<!-- 上方预留空间给溢出提示 -->
<div class="overflow-zone">
<transition name="fade">
<div v-if="isOverflow" class="overflow-badge">
<span class="icon">🗑</span>
<span>溢出截断 {{ forgottenRounds }} 轮对话已被遗忘</span>
</div>
<div v-else class="safe-badge">
<span class="icon"></span>
<span>记忆完整</span>
</div>
</transition>
</div>
<!-- 窗口容器 -->
<div class="window-frame">
<div class="limit-line">
<span>Context Window Limit ({{ windowLimit }})</span>
</div>
<!-- 堆叠内容容器 -->
<!-- 使用 flex-direction: column-reverse 让底部对齐 -->
<div class="stack-container">
<!-- System (基座) -->
<div class="block system" :style="{ height: `${systemHeight}%` }">
<span class="block-text">System Prompt ({{ systemPromptTokens }})</span>
</div>
<!-- History (中间) -->
<div class="block history" :style="{ height: `${historyHeight}%` }">
<span class="block-text" v-if="historyHeight > 10">
History ({{ round - 1 }} rounds)
</span>
<!-- 溢出遮罩当溢出时History 的底部实际上是被挤出去 -->
<!-- 但为了可视化简单我们让顶部溢出或者我们让整个 stack 向上移动 -->
<!-- 修正逻辑Context Window 只有那么大内容是先进先出 -->
<!-- 所以 System 永远在History 的旧内容被挤出New 在最上 -->
<!-- 这里的可视化如果不溢出自底向上堆叠 -->
<!-- 如果溢出System 在底New 在顶History 中间部分被挤压/溢出 -->
<!-- 真实的 LLM 是滑动窗口System 通常是 Pinned -->
<!-- 让我们展示总量超过窗口 -->
</div>
<!-- Input (最新) -->
<div class="block input" :style="{ height: `${inputHeight}%` }">
<span class="block-text">New Input</span>
</div>
</div>
<!-- 溢出遮罩层如果 totalHeight > 100%显示一个红色的遮罩在顶部表示这部分虽然生成了但塞不进去/或者旧的被挤走了 -->
<!-- 为了简化我们让 stack-container 的高度允许超过 100%然后 window-frame overflow: hidden -->
<!-- 但这样用户看不到溢出了多少 -->
<!-- 更好的方式window-frame 是视口stack-container 绝对定位 -->
</div>
</div>
<!-- 3. 底部控制 -->
<div class="input-section">
<div class="slider-wrapper">
<span class="slider-hint">拖动滑块增加对话轮次</span>
<input
type="range"
min="1"
:max="maxRound"
v-model.number="round"
class="custom-slider"
/>
<div class="slider-labels">
<span> 1 </span>
<span> {{ maxRound }} </span>
</div>
</div>
<div class="info-box">
<p v-if="!isOverflow">
💡 <strong>一切正常</strong>当前 Token ({{ totalTokens }}) 未超过窗口限制模型能完美回忆起所有对话细节
</p>
<p v-else class="warning-text">
<strong>发生遗忘</strong>Token 总量 ({{ totalTokens }}) 已超过窗口限制 ({{ windowLimit }})
为了放入新对话系统被迫丢弃了最早的 <strong>{{ forgottenRounds }}</strong> 轮历史记录
</p>
</div>
</div>
</div>
</template>
<style scoped>
.agent-context-intro {
.agent-context-flow {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background-color: var(--vp-c-bg-soft);
padding: 1rem;
overflow: hidden;
margin: 1rem 0;
}
/* 1. 顶部统计栏 */
.control-panel {
padding: 1.25rem;
background: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
}
.stat-group {
display: flex;
justify-content: space-around;
align-items: center;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
}
.stat-item .value {
font-size: 1.5rem;
font-weight: 700;
color: var(--vp-c-text-1);
font-family: var(--vp-font-family-mono);
}
.header {
margin-bottom: 0.75rem;
.stat-item .value.error {
color: var(--vp-c-red);
}
.header h3 {
margin: 0 0 0.25rem;
font-size: 1rem;
}
.header p {
margin: 0;
font-size: 0.85rem;
.stat-item .label {
font-size: 0.875rem;
color: var(--vp-c-text-2);
}
.round-control {
.stat-divider {
width: 1px;
height: 2rem;
background-color: var(--vp-c-divider);
}
/* 2. 可视化区域 */
.visualization-area {
padding: 1rem 2rem;
background-color: var(--vp-c-bg-alt); /* 稍微深一点的背景 */
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.overflow-zone {
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
}
.overflow-badge {
color: var(--vp-c-red);
font-weight: 600;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
font-size: 0.85rem;
}
.round-btn {
padding: 0.2rem 0.6rem;
background: var(--vp-c-red-dimm);
padding: 0.25rem 0.75rem;
border-radius: 4px;
border: 1px solid var(--vp-c-divider);
}
.safe-badge {
color: var(--vp-c-green);
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.window-frame {
width: 100%;
max-width: 300px; /* 限制宽度,像手机屏幕 */
height: 300px;
border: 2px solid var(--vp-c-divider);
border-top: 2px dashed var(--vp-c-red); /* 顶部虚线表示 Limit */
border-radius: 0 0 8px 8px;
background: var(--vp-c-bg);
cursor: pointer;
font-size: 0.85rem;
position: relative;
display: flex;
flex-direction: column-reverse; /* 底部对齐 */
overflow: visible; /* 允许溢出显示 */
}
.round-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
.limit-line {
position: absolute;
top: -12px;
left: 0;
right: 0;
display: flex;
justify-content: center;
}
.round-text {
flex: 1;
color: var(--vp-c-text-2);
}
.round-slider {
width: 120px;
}
.metrics-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.metric-card {
padding: 0.75rem;
border-radius: 6px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
text-align: center;
}
.metric-label {
.limit-line span {
background: var(--vp-c-red);
color: white;
font-size: 0.75rem;
color: var(--vp-c-text-2);
margin-bottom: 0.25rem;
padding: 0 8px;
border-radius: 10px;
}
.metric-value {
font-size: 1.4rem;
.stack-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column-reverse; /* 让 System 在最底 */
/* 这里不设 overflow: hidden,让它自然溢出,但是我们通过高度控制 */
}
.block {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 0.8rem;
font-weight: 500;
transition: all 0.3s ease;
position: relative;
border-top: 1px solid rgba(255,255,255,0.1);
}
.block-text {
z-index: 1;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.block.system {
background-color: #10b981; /* Green */
flex-shrink: 0; /* System 不会被压缩 */
}
.block.history {
background-color: #3b82f6; /* Blue */
/* 溢出逻辑:当高度增加时,history 会向上顶 */
}
.block.input {
background-color: #f59e0b; /* Amber */
flex-shrink: 0;
}
/* 溢出样式处理 */
/* 当总高度超过 100% 时,stack-container 会溢出 window-frame */
/* 我们希望溢出的部分变红或者虚化 */
/* 3. 底部控制 */
.input-section {
padding: 1.25rem;
background: var(--vp-c-bg);
border-top: 1px solid var(--vp-c-divider);
display: flex;
flex-direction: column;
gap: 1rem;
}
.slider-wrapper {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.slider-hint {
font-size: 0.9rem;
font-weight: 600;
color: var(--vp-c-text-1);
margin-bottom: 0.25rem;
}
.metric-desc {
font-size: 0.75rem;
color: var(--vp-c-text-2);
.custom-slider {
width: 100%;
accent-color: var(--vp-c-brand);
cursor: pointer;
}
.summary-line {
.slider-labels {
display: flex;
justify-content: space-between;
font-size: 0.8rem;
color: var(--vp-c-text-2);
padding: 0.6rem 0.75rem;
border-radius: 6px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
}
@media (max-width: 768px) {
.metrics-row {
grid-template-columns: repeat(2, 1fr);
.info-box {
padding: 0.75rem;
background: var(--vp-c-bg-soft);
border-radius: 6px;
font-size: 0.9rem;
line-height: 1.5;
color: var(--vp-c-text-2);
}
.info-box p {
margin: 0;
}
.warning-text {
color: var(--vp-c-red-text);
}
/* 移动端适配 */
@media (max-width: 640px) {
.stat-group {
gap: 0.5rem;
}
.stat-item .value {
font-size: 1.2rem;
}
.window-frame {
height: 250px;
}
}
</style>
</style>