Spaces:
Sleeping
Sleeping
File size: 11,539 Bytes
ad29c69 a4512b5 ad29c69 a4512b5 ad29c69 9005bad ad29c69 9005bad 2829a83 9005bad ad29c69 aa60861 ad29c69 a4512b5 5a64e8d ad29c69 a4512b5 ad29c69 5a64e8d a4512b5 5a64e8d a4512b5 ad29c69 5a64e8d ad29c69 5a64e8d ad29c69 5a64e8d a4512b5 ad29c69 a4512b5 ad29c69 a4512b5 ad29c69 d3f976c a4512b5 d3f976c a4512b5 d3f976c a4512b5 d3f976c a4512b5 d3f976c a4512b5 d3f976c ad29c69 a4512b5 ad29c69 a4512b5 ad29c69 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memvid AI Memory Layer</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { background-color: #0f172a; color: #e2e8f0; }
.glass { background: rgba(30, 41, 59, 0.7); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); }
.accent { color: #38bdf8; }
/* Ensures the raw snippet text wraps and preserves newlines */
.snippet-text {
white-space: pre-wrap;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
</style>
</head>
<body class="min-h-screen p-6 font-sans">
<div class="max-w-4xl mx-auto space-y-8">
<!-- Header -->
<div class="text-center space-y-2">
<h1 class="text-4xl font-bold tracking-tight text-white">Memvid <span class="accent">Live Demo</span></h1>
<p class="text-slate-400">Single-file memory layer for AI agents.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Left: Add Memory -->
<div class="glass p-6 rounded-xl shadow-lg">
<h2 class="text-xl font-semibold mb-4 flex items-center gap-2">
<span>📥</span> Append Memory
</h2>
<form id="addForm" class="space-y-4">
<div>
<label class="block text-sm font-medium text-slate-300 mb-1">Content</label>
<textarea id="content" name="content" rows="4" class="w-full bg-slate-800 border border-slate-700 rounded-lg p-3 text-sm focus:ring-2 focus:ring-sky-500 outline-none" placeholder="Paste meeting notes, facts, or context here..."></textarea>
</div>
<button type="submit" class="w-full bg-sky-600 hover:bg-sky-500 text-white font-medium py-2 rounded-lg transition">
Commit to Memory
</button>
</form>
<div id="addStatus" class="mt-4 text-sm text-center hidden"></div>
</div>
<!-- Right: Retrieval -->
<div class="glass p-6 rounded-xl shadow-lg flex flex-col">
<h2 class="text-xl font-semibold mb-4 flex items-center gap-2">
<span>🔍</span> Neural Retrieval
</h2>
<form id="searchForm" class="space-y-4 mb-4">
<div class="flex gap-2">
<input type="text" id="query" name="query" class="flex-1 bg-slate-800 border border-slate-700 rounded-lg p-3 text-sm focus:ring-2 focus:ring-sky-500 outline-none" placeholder="Search your memory...">
<button type="submit" class="bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2 rounded-lg transition">Search</button>
</div>
</form>
<!-- Results Area -->
<div id="resultsArea" class="flex-1 overflow-y-auto space-y-3 max-h-[400px] pr-2">
<p class="text-slate-500 text-sm text-center italic mt-10">Waiting for query...</p>
</div>
</div>
</div>
<!-- Footer -->
<div class="text-center text-xs text-slate-500 pt-8 border-t border-slate-800">
Video Memory File: <a href="https://huggingface.co/datasets/broadfield-dev/memvid-storage" class="text-sky-400 hover:underline">https://huggingface.co/datasets/.../knowledge.mv2</a>
</div>
<div class="text-center text-xs text-slate-500 pt-8 border-t border-slate-800">
Powered by <a href="https://github.com/memvid/memvid" class="text-sky-400 hover:underline">Memvid</a> & Flask
</div>
</div>
<script>
// Helper to format date nicely
function formatDate(dateString) {
if (!dateString) return '';
try {
const date = new Date(dateString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
} catch (e) {
return dateString;
}
}
// Handle Add Memory (Streaming Version)
document.getElementById('addForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const status = document.getElementById('addStatus');
const btn = e.target.querySelector('button');
// UI Reset
btn.disabled = true;
status.classList.remove('hidden');
status.textContent = "Starting stream...";
status.className = "mt-4 text-sm text-center text-indigo-400 animate-pulse";
try {
const response = await fetch('/add', { method: 'POST', body: formData });
// Reader to handle the stream
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Decode chunk and split by newline (NDJSON)
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n').filter(line => line.trim() !== '');
for (const line of lines) {
try {
const data = JSON.parse(line);
if (data.status === 'processing') {
// Show progress
status.textContent = "⏳ " + data.message;
} else if (data.status === 'success') {
// Success state
status.textContent = "✅ " + data.message;
status.className = "mt-4 text-sm text-center text-green-400";
e.target.reset();
setTimeout(() => status.classList.add('hidden'), 4000);
} else if (data.status === 'error') {
// Error state
throw new Error(data.message);
}
} catch (err) {
console.error("Stream parse error:", err);
}
}
}
} catch (err) {
status.textContent = "❌ " + err.message;
status.className = "mt-4 text-sm text-center text-red-400";
} finally {
btn.disabled = false;
btn.textContent = "Commit to Memory";
}
});
// Handle Search
document.getElementById('searchForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const resultsArea = document.getElementById('resultsArea');
resultsArea.innerHTML = '<p class="text-slate-500 text-center animate-pulse">Scanning Memory Timeline...</p>';
try {
const res = await fetch('/search', { method: 'POST', body: formData });
const data = await res.json();
resultsArea.innerHTML = '';
if (data.success && data.results.length > 0) {
data.results.forEach(item => {
// Create Card
const div = document.createElement('div');
div.className = "bg-slate-800/50 p-4 rounded-lg border border-slate-700 hover:border-indigo-500/50 transition flex flex-col gap-3";
// 1. Header: Title, Date, Score
const header = document.createElement('div');
header.className = "flex justify-between items-start";
header.innerHTML = `
<div>
<h3 class="font-semibold text-slate-200 text-sm">${item.title}</h3>
<span class="text-xs text-slate-500">${formatDate(item.date)}</span>
</div>
<span class="bg-indigo-900/50 text-indigo-300 text-xs px-2 py-1 rounded font-mono border border-indigo-500/20">
${item.score}
</span>
`;
// 2. Body: Clean Text
const body = document.createElement('div');
body.className = "text-sm text-slate-300 snippet-text pl-2 border-l-2 border-slate-600";
body.textContent = item.text;
// 3. Footer: Tags & Labels
const footer = document.createElement('div');
footer.className = "flex flex-wrap gap-2 mt-1";
// Render Tags (Green)
if (item.tags && item.tags.length > 0) {
item.tags.forEach(tag => {
const span = document.createElement('span');
span.className = "px-2 py-0.5 rounded text-[10px] uppercase font-bold tracking-wider bg-emerald-900/40 text-emerald-400 border border-emerald-700/50";
span.textContent = tag;
footer.appendChild(span);
});
}
// Render Labels (Blue)
if (item.labels && item.labels.length > 0) {
item.labels.forEach(lbl => {
// Filter out generic 'text' label to reduce noise
if(lbl === 'text') return;
const span = document.createElement('span');
span.className = "px-2 py-0.5 rounded text-[10px] uppercase font-bold tracking-wider bg-sky-900/40 text-sky-400 border border-sky-700/50";
span.textContent = lbl;
footer.appendChild(span);
});
}
div.appendChild(header);
div.appendChild(body);
if (footer.hasChildNodes()) div.appendChild(footer);
resultsArea.appendChild(div);
});
} else if (data.success) {
resultsArea.innerHTML = '<p class="text-slate-500 text-sm text-center">No high-confidence memories found.</p>';
} else {
resultsArea.innerHTML = `<p class="text-red-400 text-sm text-center">Error: ${data.error}</p>`;
}
} catch (err) {
console.error(err);
resultsArea.innerHTML = `<p class="text-red-400 text-sm text-center">Connection Error</p>`;
}
});
</script>
</body>
</html> |