|
214 | 214 | + '<option value="brave">🦁 Brave</option>' |
215 | 215 | + '<option value="serper">🔎 Serper</option>'; |
216 | 216 |
|
| 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 | + |
217 | 221 | result += '<div class="ai-placeholder-card ai-agent-card" data-ai-type="Agent" data-ai-index="' + blockIndex + '">' |
218 | 222 | + '<div class="ai-placeholder-header">' |
219 | 223 | + '<span class="ai-placeholder-icon">' + icon + '</span>' |
220 | 224 | + '<span class="ai-placeholder-label">' + label + '</span>' |
| 225 | + + agentUseHint |
221 | 226 | + '<div class="ai-placeholder-actions">' |
| 227 | + + '<button class="ai-placeholder-btn ai-memory-select-btn" data-ai-index="' + blockIndex + '" title="Select memory sources">📚</button>' |
222 | 228 | + '<select class="ai-agent-search-select" data-ai-index="' + blockIndex + '" title="Search provider">' + searchOpts + '</select>' |
223 | 229 | + '<select class="ai-card-model-select" data-ai-index="' + blockIndex + '" title="Model for this flow">' + cardModelOpts + '</select>' |
224 | 230 | + '<button class="ai-placeholder-btn ai-fill-one" data-ai-index="' + blockIndex + '" title="Run this agent flow">▶</button>' |
225 | 231 | + '<button class="ai-placeholder-btn ai-remove-tag" data-ai-index="' + blockIndex + '" title="Remove tag">✕</button>' |
226 | 232 | + '</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>' |
227 | 241 | + '<div class="ai-agent-steps">' + stepsHtml + '</div>' |
228 | 242 | + '</div>'; |
229 | 243 | } else { |
|
238 | 252 | + '<span class="ai-placeholder-label">' + label + '</span>' |
239 | 253 | + useHint |
240 | 254 | + '<div class="ai-placeholder-actions">' |
| 255 | + + '<button class="ai-placeholder-btn ai-memory-select-btn" data-ai-index="' + blockIndex + '" title="Select memory sources">📚</button>' |
241 | 256 | + '<select class="ai-card-model-select" data-ai-index="' + blockIndex + '" title="Model for this generation">' + cardModelOpts + '</select>' |
242 | 257 | + '<button class="ai-placeholder-btn ai-fill-one" data-ai-index="' + blockIndex + '" title="Generate this block">▶</button>' |
243 | 258 | + '<button class="ai-placeholder-btn ai-remove-tag" data-ai-index="' + blockIndex + '" title="Remove tag">✕</button>' |
244 | 259 | + '</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>' |
245 | 268 | + '<div class="ai-placeholder-prompt">' + escapeHtml(displayPrompt) + '</div>' |
246 | 269 | + '</div>'; |
247 | 270 | } |
|
362 | 385 | }).catch(function () { /* ignore */ }); |
363 | 386 | }); |
364 | 387 |
|
| 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 | + |
365 | 591 | // API key prompt when selecting a search provider that requires one |
366 | 592 | container.querySelectorAll('.ai-agent-search-select').forEach(function (sel) { |
367 | 593 | sel.addEventListener('change', function () { |
|
0 commit comments