Files
test-repo/docs/.vitepress/theme/components/appendix/context-engineering/SlidingWindowDemo.vue
T
sanbuphy e5b1c6cc88 docs: update content and components across multiple files
- Refine chapter introductions in zh-cn docs for clarity and conciseness
- Update navigation links to include '/easy-vibe' prefix
- Simplify UI components (ChapterIntroduction, ContextWindowVisualizer)
- Add new agent-related demo components (AgentMemoryDemo, AgentToolUseDemo)
- Improve context compression demo with better visuals and metrics
- Adjust styling and layout across various components
2026-02-03 01:46:03 +08:00

451 lines
9.6 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.
<!--
SlidingWindowDemo.vue
滑动窗口机制演示
用途
展示 "Sliding Window" (滑动窗口) 如何处理长对话
当新消息进入时最旧的消息被移除上下文演示遗忘机制
交互功能
- 发送消息用户可发送消息AI 自动回复
- 自动演示一键模拟长对话观察窗口滑动
- 视觉反馈清晰展示哪些消息在"窗口内"活跃哪些在"窗口外"遗忘
-->
<template>
<div class="sliding-window-demo">
<div class="control-panel">
<div class="info-stat">
<span class="label">Window Size / 窗口大小</span>
<span class="value">{{ windowSize }} Messages</span>
</div>
<div class="actions">
<button class="action-btn" @click="autoPlay" :disabled="isAutoPlaying">
Auto Play
</button>
<button class="action-btn outline" @click="reset">
Reset
</button>
</div>
</div>
<div class="visualization-area">
<div class="conversation-stream">
<!-- Forgotten / History Zone -->
<div class="zone history-zone">
<div class="zone-label">
<span class="icon">🗑</span> Forgotten (History)
</div>
<transition-group name="fade-list">
<div
v-for="msg in historyMessages"
:key="msg.id"
class="message-bubble history"
:class="msg.role.toLowerCase()"
>
<div class="avatar">{{ msg.role === 'User' ? '👤' : '🤖' }}</div>
<div class="content">
<div class="role-name">{{ msg.role }}</div>
<div class="text">{{ msg.content }}</div>
</div>
</div>
</transition-group>
<div v-if="historyMessages.length === 0" class="empty-placeholder">
No history yet...
</div>
</div>
<!-- Divider -->
<div class="window-divider">
<span> Out of Context</span>
<div class="divider-line"></div>
<span> In Context</span>
</div>
<!-- Active Window Zone -->
<div class="zone active-zone">
<div class="zone-label">
<span class="icon">🖼</span> Active Context Window
</div>
<transition-group name="slide-list">
<div
v-for="msg in activeMessages"
:key="msg.id"
class="message-bubble active"
:class="msg.role.toLowerCase()"
>
<div class="avatar">{{ msg.role === 'User' ? '👤' : '🤖' }}</div>
<div class="content">
<div class="role-name">{{ msg.role }}</div>
<div class="text">{{ msg.content }}</div>
</div>
</div>
</transition-group>
<div v-if="activeMessages.length === 0" class="empty-placeholder">
Start the conversation...
</div>
</div>
</div>
</div>
<div class="input-section">
<input
v-model="newMessage"
@keyup.enter="sendMessage"
placeholder="Type a message..."
:disabled="isAutoPlaying"
/>
<button class="send-btn" @click="sendMessage" :disabled="!newMessage.trim() || isAutoPlaying">
Send
</button>
</div>
<div class="info-box">
<p>
<span class="icon">💡</span>
<strong>Note:</strong>
滑动窗口是最简单的记忆管理策略它保证了 Token 永远不会溢出但代价是"健忘"
一旦消息滑出窗口进入上方灰色区域模型就完全不知道它的存在了
</p>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const windowSize = 4
const messages = ref([])
const newMessage = ref('')
const isAutoPlaying = ref(false)
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
addMessage('User', newMessage.value)
const userText = newMessage.value
newMessage.value = ''
// Simulate AI response
setTimeout(() => {
addMessage('AI', `I heard you say "${userText}". Interesting!`)
}, 600)
}
const addMessage = (role, content) => {
messages.value.push({
id: msgId++,
role,
content
})
}
const autoPlay = async () => {
isAutoPlaying.value = true
const script = [
"Hello there!",
"Hi! I'm an AI assistant.",
"What is your name?",
"I am Model GPT-X.",
"Do you remember my first message?",
"Yes, you said 'Hello there!'.",
"Tell me a joke.",
"Why did the chicken cross the road?",
"To get to the other side!",
"Haha, classic.",
"Wait, what was my name again?",
"I... I don't remember. It fell out of my context window!"
]
for (const line of script) {
if (!isAutoPlaying.value) break
const role = messages.value.length % 2 === 0 ? 'User' : 'AI'
addMessage(role, line)
await new Promise(r => setTimeout(r, 1500))
}
isAutoPlaying.value = false
}
const reset = () => {
messages.value = []
msgId = 0
isAutoPlaying.value = false
}
</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);
}
.control-panel {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
background: var(--vp-c-bg);
padding: 1rem;
border-radius: 6px;
border: 1px solid var(--vp-c-divider);
}
.info-stat {
display: flex;
flex-direction: column;
}
.info-stat .label {
font-size: 0.75rem;
color: var(--vp-c-text-2);
}
.info-stat .value {
font-weight: bold;
font-size: 1.1rem;
}
.actions {
display: flex;
gap: 0.5rem;
}
.action-btn {
padding: 0.25rem 0.75rem;
border-radius: 4px;
background-color: var(--vp-c-brand);
color: white;
font-size: 0.85rem;
border: none;
cursor: pointer;
transition: opacity 0.2s;
}
.action-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.action-btn.outline {
background-color: transparent;
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-1);
}
.visualization-area {
margin-bottom: 1.5rem;
background: var(--vp-c-bg-alt);
border-radius: 8px;
padding: 1rem;
border: 1px solid var(--vp-c-divider);
}
.conversation-stream {
display: flex;
flex-direction: column;
gap: 0;
}
.zone {
padding: 1rem;
border-radius: 6px;
transition: all 0.3s;
}
.history-zone {
background-color: rgba(0, 0, 0, 0.03);
border: 1px dashed var(--vp-c-divider);
margin-bottom: 0.5rem;
opacity: 0.6;
}
.active-zone {
background-color: var(--vp-c-bg);
border: 2px solid var(--vp-c-brand);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
margin-top: 0.5rem;
min-height: 150px;
}
.zone-label {
font-size: 0.8rem;
font-weight: bold;
color: var(--vp-c-text-2);
margin-bottom: 0.8rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.window-divider {
display: flex;
align-items: center;
gap: 1rem;
color: var(--vp-c-text-3);
font-size: 0.75rem;
margin: 0.5rem 0;
}
.divider-line {
flex: 1;
height: 1px;
background-color: var(--vp-c-divider);
}
.message-bubble {
display: flex;
gap: 0.8rem;
margin-bottom: 0.8rem;
padding: 0.6rem;
border-radius: 6px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
transition: all 0.5s ease;
}
.message-bubble.history {
filter: grayscale(100%);
opacity: 0.7;
}
.message-bubble.user .avatar {
order: 1;
}
.message-bubble.user {
flex-direction: row-reverse;
text-align: right;
}
.message-bubble.user .content {
align-items: flex-end;
}
.avatar {
font-size: 1.2rem;
width: 2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
background: var(--vp-c-bg-soft);
border-radius: 50%;
}
.content {
display: flex;
flex-direction: column;
max-width: 80%;
}
.role-name {
font-size: 0.7rem;
color: var(--vp-c-text-3);
margin-bottom: 0.2rem;
}
.text {
font-size: 0.9rem;
line-height: 1.4;
}
.empty-placeholder {
text-align: center;
color: var(--vp-c-text-3);
font-style: italic;
padding: 1rem;
font-size: 0.9rem;
}
.input-section {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
input {
flex: 1;
padding: 0.75rem;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
}
input:focus {
outline: none;
border-color: var(--vp-c-brand);
}
.send-btn {
padding: 0 1.5rem;
background: var(--vp-c-brand);
color: white;
border: none;
border-radius: 6px;
font-weight: bold;
cursor: pointer;
transition: background 0.2s;
}
.send-btn:hover {
background: var(--vp-c-brand-dark);
}
.send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.info-box {
background-color: var(--vp-c-bg-alt);
padding: 1rem;
border-radius: 6px;
font-size: 0.9rem;
line-height: 1.5;
color: var(--vp-c-text-2);
}
.info-box .icon {
margin-right: 0.5rem;
}
/* Animations */
.slide-list-enter-active,
.slide-list-leave-active,
.fade-list-enter-active,
.fade-list-leave-active {
transition: all 0.5s ease;
}
.slide-list-enter-from {
opacity: 0;
transform: translateY(20px);
}
.slide-list-leave-to {
opacity: 0;
transform: translateY(-20px);
}
.fade-list-enter-from {
opacity: 0;
}
.fade-list-leave-to {
opacity: 0;
transform: translateY(-10px);
}
</style>