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>