Skip to content

Commit e2b911d

Browse files
committed
feat: Memory Selector dropdown on AI/Think/Agent cards
- 📚 button on every AI/Think/Agent card header for memory source selection - Multi-select dropdown: workspace, document {{Memory:}} tags, IndexedDB sources - Origin badges: 'doc' / 'saved' to distinguish source types - Quick-attach: 📂 Folder / 📄 Files buttons inside dropdown - Checkbox changes auto-sync to editor Use: field + hint badge - Use: hint badge now shown on Agent cards (was only on AI/Think) - listAllSources(docNames) API added to context-memory.js - 135 lines of CSS: dropdown, checkbox items, badges, light theme - Outside-click closes dropdown - Build passes, 30/30 context-memory tests pass
1 parent 8de2010 commit e2b911d

File tree

3 files changed

+381
-0
lines changed

3 files changed

+381
-0
lines changed

css/ai-docgen.css

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1951,4 +1951,135 @@
19511951
background: rgba(217, 119, 6, 0.1);
19521952
color: #d97706;
19531953
border-color: rgba(217, 119, 6, 0.2);
1954+
}
1955+
1956+
/* ==============================================
1957+
MEMORY SELECTOR — dropdown on AI/Think/Agent cards
1958+
============================================== */
1959+
1960+
.ai-memory-select-btn {
1961+
font-size: 0.85em !important;
1962+
padding: 2px 6px !important;
1963+
border-radius: 6px !important;
1964+
background: rgba(245, 158, 11, 0.1) !important;
1965+
color: #f59e0b !important;
1966+
border: 1px solid rgba(245, 158, 11, 0.25) !important;
1967+
cursor: pointer;
1968+
transition: all 0.2s;
1969+
}
1970+
1971+
.ai-memory-select-btn:hover {
1972+
background: rgba(245, 158, 11, 0.2) !important;
1973+
transform: scale(1.05);
1974+
}
1975+
1976+
.ai-memory-dropdown {
1977+
position: relative;
1978+
margin: 0 12px 8px 12px;
1979+
padding: 8px 0;
1980+
background: rgba(30, 30, 40, 0.95);
1981+
border: 1px solid rgba(245, 158, 11, 0.2);
1982+
border-radius: 10px;
1983+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
1984+
z-index: 10;
1985+
backdrop-filter: blur(8px);
1986+
}
1987+
1988+
.ai-memory-dropdown-header {
1989+
padding: 4px 12px 6px;
1990+
font-size: 0.75em;
1991+
font-weight: 700;
1992+
text-transform: uppercase;
1993+
letter-spacing: 0.5px;
1994+
color: #f59e0b;
1995+
border-bottom: 1px solid rgba(245, 158, 11, 0.15);
1996+
}
1997+
1998+
.ai-memory-source-list {
1999+
padding: 6px 8px;
2000+
max-height: 160px;
2001+
overflow-y: auto;
2002+
}
2003+
2004+
.ai-memory-checkbox-item {
2005+
display: flex;
2006+
align-items: center;
2007+
gap: 8px;
2008+
padding: 5px 8px;
2009+
border-radius: 6px;
2010+
cursor: pointer;
2011+
font-size: 0.82em;
2012+
color: #e2e8f0;
2013+
transition: background 0.15s;
2014+
}
2015+
2016+
.ai-memory-checkbox-item:hover {
2017+
background: rgba(245, 158, 11, 0.08);
2018+
}
2019+
2020+
.ai-memory-checkbox-item input[type="checkbox"] {
2021+
accent-color: #f59e0b;
2022+
width: 15px;
2023+
height: 15px;
2024+
cursor: pointer;
2025+
}
2026+
2027+
.ai-mem-badge {
2028+
display: inline-block;
2029+
padding: 1px 5px;
2030+
border-radius: 4px;
2031+
font-size: 0.7em;
2032+
font-weight: 600;
2033+
background: rgba(100, 116, 139, 0.2);
2034+
color: #94a3b8;
2035+
margin-left: 4px;
2036+
}
2037+
2038+
.ai-memory-loading {
2039+
display: block;
2040+
text-align: center;
2041+
padding: 8px;
2042+
font-size: 0.78em;
2043+
color: #94a3b8;
2044+
}
2045+
2046+
.ai-memory-dropdown-attach {
2047+
display: flex;
2048+
gap: 6px;
2049+
padding: 6px 12px 2px;
2050+
border-top: 1px solid rgba(245, 158, 11, 0.1);
2051+
}
2052+
2053+
.ai-memory-dropdown-attach .ai-placeholder-btn {
2054+
flex: 1;
2055+
font-size: 0.75em !important;
2056+
padding: 4px 8px !important;
2057+
text-align: center;
2058+
border-radius: 6px;
2059+
}
2060+
2061+
/* Light theme */
2062+
[data-theme="light"] .ai-memory-dropdown {
2063+
background: rgba(255, 255, 255, 0.97);
2064+
border-color: rgba(217, 119, 6, 0.25);
2065+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
2066+
}
2067+
2068+
[data-theme="light"] .ai-memory-dropdown-header {
2069+
color: #d97706;
2070+
border-bottom-color: rgba(217, 119, 6, 0.15);
2071+
}
2072+
2073+
[data-theme="light"] .ai-memory-checkbox-item {
2074+
color: #334155;
2075+
}
2076+
2077+
[data-theme="light"] .ai-memory-checkbox-item:hover {
2078+
background: rgba(245, 158, 11, 0.06);
2079+
}
2080+
2081+
[data-theme="light"] .ai-memory-select-btn {
2082+
background: rgba(217, 119, 6, 0.08) !important;
2083+
color: #d97706 !important;
2084+
border-color: rgba(217, 119, 6, 0.2) !important;
19542085
}

js/ai-docgen.js

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,30 @@
214214
+ '<option value="brave">🦁 Brave</option>'
215215
+ '<option value="serper">🔎 Serper</option>';
216216

217+
// Use: hint for Agent
218+
var agentUseMatch = prompt.match(/^Use:\s*(.+)$/m);
219+
var agentUseHint = agentUseMatch ? '<span class="ai-use-hint">📚 ' + escapeHtml(agentUseMatch[1]) + '</span>' : '';
220+
217221
result += '<div class="ai-placeholder-card ai-agent-card" data-ai-type="Agent" data-ai-index="' + blockIndex + '">'
218222
+ '<div class="ai-placeholder-header">'
219223
+ '<span class="ai-placeholder-icon">' + icon + '</span>'
220224
+ '<span class="ai-placeholder-label">' + label + '</span>'
225+
+ agentUseHint
221226
+ '<div class="ai-placeholder-actions">'
227+
+ '<button class="ai-placeholder-btn ai-memory-select-btn" data-ai-index="' + blockIndex + '" title="Select memory sources">📚</button>'
222228
+ '<select class="ai-agent-search-select" data-ai-index="' + blockIndex + '" title="Search provider">' + searchOpts + '</select>'
223229
+ '<select class="ai-card-model-select" data-ai-index="' + blockIndex + '" title="Model for this flow">' + cardModelOpts + '</select>'
224230
+ '<button class="ai-placeholder-btn ai-fill-one" data-ai-index="' + blockIndex + '" title="Run this agent flow">▶</button>'
225231
+ '<button class="ai-placeholder-btn ai-remove-tag" data-ai-index="' + blockIndex + '" title="Remove tag">✕</button>'
226232
+ '</div></div>'
233+
+ '<div class="ai-memory-dropdown" data-ai-index="' + blockIndex + '" style="display:none">'
234+
+ '<div class="ai-memory-dropdown-header">📚 Memory Sources</div>'
235+
+ '<div class="ai-memory-source-list" data-ai-index="' + blockIndex + '"><span class="ai-memory-loading">Loading…</span></div>'
236+
+ '<div class="ai-memory-dropdown-attach">'
237+
+ '<button class="ai-placeholder-btn ai-memory-quick-folder" data-ai-index="' + blockIndex + '" title="Attach folder">📂 Folder</button>'
238+
+ '<button class="ai-placeholder-btn ai-memory-quick-files" data-ai-index="' + blockIndex + '" title="Attach files">📄 Files</button>'
239+
+ '</div>'
240+
+ '</div>'
227241
+ '<div class="ai-agent-steps">' + stepsHtml + '</div>'
228242
+ '</div>';
229243
} else {
@@ -238,10 +252,19 @@
238252
+ '<span class="ai-placeholder-label">' + label + '</span>'
239253
+ useHint
240254
+ '<div class="ai-placeholder-actions">'
255+
+ '<button class="ai-placeholder-btn ai-memory-select-btn" data-ai-index="' + blockIndex + '" title="Select memory sources">📚</button>'
241256
+ '<select class="ai-card-model-select" data-ai-index="' + blockIndex + '" title="Model for this generation">' + cardModelOpts + '</select>'
242257
+ '<button class="ai-placeholder-btn ai-fill-one" data-ai-index="' + blockIndex + '" title="Generate this block">▶</button>'
243258
+ '<button class="ai-placeholder-btn ai-remove-tag" data-ai-index="' + blockIndex + '" title="Remove tag">✕</button>'
244259
+ '</div></div>'
260+
+ '<div class="ai-memory-dropdown" data-ai-index="' + blockIndex + '" style="display:none">'
261+
+ '<div class="ai-memory-dropdown-header">📚 Memory Sources</div>'
262+
+ '<div class="ai-memory-source-list" data-ai-index="' + blockIndex + '"><span class="ai-memory-loading">Loading…</span></div>'
263+
+ '<div class="ai-memory-dropdown-attach">'
264+
+ '<button class="ai-placeholder-btn ai-memory-quick-folder" data-ai-index="' + blockIndex + '" title="Attach folder">📂 Folder</button>'
265+
+ '<button class="ai-placeholder-btn ai-memory-quick-files" data-ai-index="' + blockIndex + '" title="Attach files">📄 Files</button>'
266+
+ '</div>'
267+
+ '</div>'
245268
+ '<div class="ai-placeholder-prompt">' + escapeHtml(displayPrompt) + '</div>'
246269
+ '</div>';
247270
}
@@ -362,6 +385,209 @@
362385
}).catch(function () { /* ignore */ });
363386
});
364387

388+
// ==============================================
389+
// MEMORY SELECTOR — dropdown on AI/Think/Agent cards
390+
// ==============================================
391+
392+
// Helper: get document {{Memory:}} tag names from editor
393+
function getDocMemoryNames() {
394+
var text = M.markdownEditor ? M.markdownEditor.value : '';
395+
var names = [];
396+
var re = /\{\{Memory:[^}]*Name:\s*([^\s}]+)/gi;
397+
var m;
398+
while ((m = re.exec(text)) !== null) {
399+
var n = m[1].replace(/[,}]/g, '').trim();
400+
if (n && names.indexOf(n) === -1) names.push(n);
401+
}
402+
return names;
403+
}
404+
405+
// Helper: get current Use: sources for a block
406+
function getBlockUseSources(blockIndex) {
407+
var text = M.markdownEditor ? M.markdownEditor.value : '';
408+
var blocks = parseDocgenBlocks(text);
409+
if (blockIndex < blocks.length && blocks[blockIndex].useMemory) {
410+
return blocks[blockIndex].useMemory;
411+
}
412+
return [];
413+
}
414+
415+
// Helper: update the Use: field in the editor for a block
416+
function updateBlockUseField(blockIndex, selectedSources) {
417+
var text = M.markdownEditor ? M.markdownEditor.value : '';
418+
var blocks = parseDocgenBlocks(text);
419+
if (blockIndex >= blocks.length) return;
420+
var block = blocks[blockIndex];
421+
422+
// Build the new Use: line
423+
var useLine = selectedSources.length > 0 ? 'Use: ' + selectedSources.join(', ') : '';
424+
425+
// Get the raw tag content
426+
var tagContent = text.substring(block.start, block.end);
427+
var innerStart = tagContent.indexOf(':') + 1;
428+
var innerEnd = tagContent.lastIndexOf('}}');
429+
var inner = tagContent.substring(innerStart, innerEnd).trim();
430+
431+
// Remove existing Use: line
432+
inner = inner.replace(/^Use:\s*.+$/m, '').trim();
433+
434+
// Prepend new Use: line
435+
if (useLine) {
436+
inner = useLine + '\n' + inner;
437+
}
438+
439+
// Rebuild tag
440+
var tagType = block.type;
441+
var newTag = '{{' + tagType + ': ' + inner + ' }}';
442+
443+
M.markdownEditor.value = text.substring(0, block.start) + newTag + text.substring(block.end);
444+
445+
// Update the Use: hint badge on the card
446+
var card = container.querySelector('.ai-placeholder-card[data-ai-index="' + blockIndex + '"]');
447+
if (card) {
448+
var hintEl = card.querySelector('.ai-use-hint');
449+
if (selectedSources.length > 0) {
450+
if (!hintEl) {
451+
hintEl = document.createElement('span');
452+
hintEl.className = 'ai-use-hint';
453+
var label = card.querySelector('.ai-placeholder-label');
454+
if (label) label.after(hintEl);
455+
}
456+
hintEl.textContent = '📚 ' + selectedSources.join(', ');
457+
} else if (hintEl) {
458+
hintEl.remove();
459+
}
460+
}
461+
}
462+
463+
// Toggle memory selector dropdown
464+
container.querySelectorAll('.ai-memory-select-btn').forEach(function (btn) {
465+
btn.addEventListener('click', function (e) {
466+
e.preventDefault();
467+
e.stopPropagation();
468+
var idx = parseInt(this.dataset.aiIndex, 10);
469+
var dropdown = container.querySelector('.ai-memory-dropdown[data-ai-index="' + idx + '"]');
470+
if (!dropdown) return;
471+
472+
var isOpen = dropdown.style.display !== 'none';
473+
// Close all open dropdowns first
474+
container.querySelectorAll('.ai-memory-dropdown').forEach(function (d) { d.style.display = 'none'; });
475+
476+
if (isOpen) return;
477+
478+
dropdown.style.display = 'block';
479+
480+
// Populate sources
481+
var listEl = dropdown.querySelector('.ai-memory-source-list');
482+
if (!listEl) return;
483+
listEl.innerHTML = '<span class="ai-memory-loading">Loading…</span>';
484+
485+
var currentSources = getBlockUseSources(idx);
486+
var docNames = getDocMemoryNames();
487+
488+
if (!M._memory || !M._memory.listAllSources) {
489+
listEl.innerHTML = '<label class="ai-memory-checkbox-item">'
490+
+ '<input type="checkbox" value="workspace" ' + (currentSources.indexOf('workspace') !== -1 ? 'checked' : '') + '> workspace</label>';
491+
return;
492+
}
493+
494+
M._memory.listAllSources(docNames).then(function (sources) {
495+
var html = '';
496+
sources.forEach(function (src) {
497+
var checked = currentSources.indexOf(src.name) !== -1 ? ' checked' : '';
498+
var badge = src.origin === 'document' ? ' <small class="ai-mem-badge">doc</small>'
499+
: src.origin === 'stored' ? ' <small class="ai-mem-badge">saved</small>' : '';
500+
html += '<label class="ai-memory-checkbox-item">'
501+
+ '<input type="checkbox" value="' + escapeHtml(src.name) + '"' + checked + '> '
502+
+ escapeHtml(src.name) + badge + '</label>';
503+
});
504+
listEl.innerHTML = html || '<span class="ai-memory-loading">No sources available</span>';
505+
506+
// Bind checkbox changes
507+
listEl.querySelectorAll('input[type="checkbox"]').forEach(function (cb) {
508+
cb.addEventListener('change', function () {
509+
var selected = [];
510+
listEl.querySelectorAll('input[type="checkbox"]:checked').forEach(function (c) {
511+
selected.push(c.value);
512+
});
513+
updateBlockUseField(idx, selected);
514+
});
515+
});
516+
});
517+
});
518+
});
519+
520+
// Close dropdown on outside click
521+
document.addEventListener('click', function (e) {
522+
if (!e.target.closest('.ai-memory-dropdown') && !e.target.closest('.ai-memory-select-btn')) {
523+
container.querySelectorAll('.ai-memory-dropdown').forEach(function (d) { d.style.display = 'none'; });
524+
}
525+
});
526+
527+
// Quick-attach Folder from AI/Agent card
528+
container.querySelectorAll('.ai-memory-quick-folder').forEach(function (btn) {
529+
btn.addEventListener('click', function (e) {
530+
e.preventDefault();
531+
e.stopPropagation();
532+
var idx = parseInt(this.dataset.aiIndex, 10);
533+
if (!M._memory) { M.showToast('Memory engine not loaded yet.', 'warning'); return; }
534+
535+
// Prompt for name
536+
var name = prompt('Memory source name:', 'external-' + Date.now());
537+
if (!name) return;
538+
name = name.replace(/\s+/g, '-').toLowerCase();
539+
540+
btn.disabled = true;
541+
btn.textContent = '⏳ Scanning...';
542+
M._memory.attachFolder(name).then(function (info) {
543+
M.showToast('📚 Indexed ' + info.chunkCount + ' chunks from "' + info.folderName + '"', 'success');
544+
// Add to checked sources
545+
var current = getBlockUseSources(idx);
546+
if (current.indexOf(name) === -1) current.push(name);
547+
updateBlockUseField(idx, current);
548+
// Refresh dropdown
549+
btn.closest('.ai-memory-dropdown').querySelector('.ai-memory-select-btn');
550+
var selBtn = container.querySelector('.ai-memory-select-btn[data-ai-index="' + idx + '"]');
551+
if (selBtn) selBtn.click();
552+
}).catch(function (err) {
553+
if (err.name !== 'AbortError') M.showToast('Failed: ' + err.message, 'error');
554+
}).finally(function () {
555+
btn.disabled = false;
556+
btn.textContent = '📂 Folder';
557+
});
558+
});
559+
});
560+
561+
// Quick-attach Files from AI/Agent card
562+
container.querySelectorAll('.ai-memory-quick-files').forEach(function (btn) {
563+
btn.addEventListener('click', function (e) {
564+
e.preventDefault();
565+
e.stopPropagation();
566+
var idx = parseInt(this.dataset.aiIndex, 10);
567+
if (!M._memory) { M.showToast('Memory engine not loaded yet.', 'warning'); return; }
568+
569+
var name = prompt('Memory source name:', 'files-' + Date.now());
570+
if (!name) return;
571+
name = name.replace(/\s+/g, '-').toLowerCase();
572+
573+
btn.disabled = true;
574+
btn.textContent = '⏳ Reading...';
575+
M._memory.attachFiles(name).then(function (info) {
576+
M.showToast('📚 Added ' + info.addedChunks + ' chunks', 'success');
577+
var current = getBlockUseSources(idx);
578+
if (current.indexOf(name) === -1) current.push(name);
579+
updateBlockUseField(idx, current);
580+
var selBtn = container.querySelector('.ai-memory-select-btn[data-ai-index="' + idx + '"]');
581+
if (selBtn) selBtn.click();
582+
}).catch(function (err) {
583+
if (err.name !== 'AbortError') M.showToast('Failed: ' + err.message, 'error');
584+
}).finally(function () {
585+
btn.disabled = false;
586+
btn.textContent = '📄 Files';
587+
});
588+
});
589+
});
590+
365591
// API key prompt when selecting a search provider that requires one
366592
container.querySelectorAll('.ai-agent-search-select').forEach(function (sel) {
367593
sel.addEventListener('change', function () {

0 commit comments

Comments
 (0)