73f4788d7e
- 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
316 lines
6.7 KiB
Vue
316 lines
6.7 KiB
Vue
<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>
|