(() => { // —— State let ALL = []; let GROUPED = {}; let VIEW = "libraries"; // "libraries" | "memories" let CURR = null; // pagination state for memories let PAGE = 1; const PAGE_SIZE = 30; let CURRENT_MEM_LIST = []; // —— DOM const $ = (id) => document.getElementById(id); const elLoading = $("ml-loading"); const elError = $("ml-error"); const elRetry = $("ml-retry"); const elLibraries = $("ml-libraries"); const elMemories = $("ml-memories"); const elPagination = $("ml-pagination"); const elPageRange = $("ml-page-range"); const elPrev = $("ml-prev"); const elNext = $("ml-next"); const elEmpty = $("ml-empty"); const elSearch = $("ml-search"); const elClear = $("ml-clear"); const elStats = $("ml-stats"); const elCount = $("ml-count"); const elTotal = $("ml-total"); const elType = $("ml-type"); const elCrumb = $("ml-crumb"); const elBack = $("ml-back"); const elCrumbTitle = $("ml-crumb-title"); const dlg = $("ml-modal"); const mLib = $("ml-modal-lib"); const mScore = $("ml-modal-score"); const mWhen = $("ml-modal-when"); const mCont = $("ml-modal-content"); const mAuth = $("ml-modal-author"); const mCreated = $("ml-modal-created"); const mId = $("ml-modal-id"); const mWs = $("ml-modal-ws"); const THIS_SCRIPT = document.currentScript || (() => { const scripts = document.getElementsByTagName('script'); return scripts[scripts.length - 1]; })(); const SCRIPT_DIR = new URL('./', THIS_SCRIPT.src); const DATA_BASE = new URL('./data/', SCRIPT_DIR).href; // —— Categories const CATEGORY_MAP = { "Academic Datasets": ["appworld", "bfcl_v3"], "Finance": ["research_plan", "research_tips"], "Medical/Law/Education": [] // header only if empty }; const FILES = Array.from(new Set( Object.values(CATEGORY_MAP).flat().map(n => `${n}.jsonl`) )); // —— Utils function show(el){ el.hidden = false; } function hide(el){ el.hidden = true; } function setLoading(on){ on ? (show(elLoading), [elError, elLibraries, elMemories, elEmpty, elStats, elCrumb, elPagination].forEach(hide)) : hide(elLoading); } function setError(on){ on ? (show(elError), [elLoading].forEach(hide)) : hide(elError); } function clampTxt(s, n){ if(!s) return ""; return s.length<=n? s : s.slice(0,n)+"…"; } const fmtDate = (t)=> t ? new Date(t).toLocaleDateString() : "Unknown"; function debounce(fn, ms=250){ let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a), ms); }; } function fileBase(name){ return name.replace(/\.jsonl$/,""); } // —— Data Loading async function loadAll(){ setLoading(true); setError(false); try{ const arr = await Promise.all(FILES.map(async f=>{ try{ const res = await fetch(new URL(f, DATA_BASE)); if(!res.ok) return []; const txt = await res.text(); return txt.split("\n").filter(l=>l.trim()).map(line=>{ try{ const obj = JSON.parse(line); obj._library = fileBase(f); return obj; }catch{ return null; } }).filter(Boolean); }catch{ return []; } })); ALL = arr.flat(); if(!ALL.length) throw new Error("no data"); GROUPED = ALL.reduce((acc,m)=>{ (acc[m._library] ||= []).push(m); return acc; }, {}); renderLibraries(); }catch(e){ setError(true); }finally{ setLoading(false); } } function createMemoryModal() { // 外层 const dlg = document.createElement('dialog'); dlg.id = 'ml-modal'; dlg.className = 'ml-modal'; dlg.innerHTML = `
When to use
Memory
Metadata
Author
Created
Memory ID
Workspace
`; document.body.appendChild(dlg); // 缓存内部节点(创建后一定存在,不会为 null) const els = { lib: dlg.querySelector('#ml-modal-lib'), score: dlg.querySelector('#ml-modal-score'), when: dlg.querySelector('#ml-modal-when'), content: dlg.querySelector('#ml-modal-content'), author: dlg.querySelector('#ml-modal-author'), created: dlg.querySelector('#ml-modal-created'), id: dlg.querySelector('#ml-modal-id'), ws: dlg.querySelector('#ml-modal-ws'), closeBtn: dlg.querySelector('.ml-close'), card: dlg.querySelector('.ml-modal-card') }; // —— 打开/关闭(含退化) function openDialog() { try { if (typeof dlg.showModal === 'function') { dlg.showModal(); } else { dlg.setAttribute('open', ''); dlg.classList.add('is-open-fallback'); document.documentElement.style.overflow = 'hidden'; } } catch { dlg.setAttribute('open', ''); dlg.classList.add('is-open-fallback'); document.documentElement.style.overflow = 'hidden'; } } function closeDialog() { try { if (typeof dlg.close === 'function') dlg.close(); } finally { dlg.removeAttribute('open'); dlg.classList.remove('is-open-fallback'); document.documentElement.style.overflow = ''; } } // —— 交互 els.closeBtn?.addEventListener('click', (e) => { e.preventDefault(); closeDialog(); }); dlg.addEventListener('close', () => { // 原生 close 触发也兜一层 dlg.removeAttribute('open'); dlg.classList.remove('is-open-fallback'); document.documentElement.style.overflow = ''; }); // 退化模式:点击遮罩关闭 dlg.addEventListener('click', (e) => { if (!dlg.classList.contains('is-open-fallback')) return; const r = els.card?.getBoundingClientRect(); if (!r) return; const inside = e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom; if (!inside) closeDialog(); }); // —— 对外:填充并打开 function fmtDate(t){ return t ? new Date(t).toLocaleDateString() : 'Unknown'; } function open(m) { // 只在这里填充内容;字段缺失时给默认值 els.lib.textContent = m._library || 'Unknown'; if ('score' in m && m.score !== null && m.score !== undefined) { els.score.textContent = `Score: ${m.score}`; els.score.hidden = false; } else { els.score.hidden = true; } els.when.textContent = m.when_to_use || 'No specific guidance provided'; els.content.textContent = m.content || 'No content available'; els.author.textContent = m.author || 'Unknown'; els.created.textContent = fmtDate(m.time_created); els.id.textContent = m.memory_id || 'N/A'; els.ws.textContent = m.workspace_id || 'N/A'; openDialog(); } return { open, close: closeDialog }; } // —— Render — Libraries (stacked categories) function renderLibraries(){ VIEW = "libraries"; CURR = null; PAGE = 1; CURRENT_MEM_LIST = []; hide(elMemories); hide(elEmpty); hide(elPagination); show(elLibraries); hide(elCrumb); elCrumbTitle.textContent = "Libraries"; elType.textContent = "libraries"; const availableLibs = Object.keys(GROUPED); const sections = Object.entries(CATEGORY_MAP).map(([cat, prefixes])=>{ // build libraries list for this category const libs = (prefixes || []).filter(p => availableLibs.includes(p)); const itemsHtml = libs.map(name=>{ const arr = GROUPED[name]; const sample = arr[0] || {}; const sampleText = sample.when_to_use || sample.content || "No description available"; const author = sample.author || "Unknown"; return `
${name}
${arr.length} memories
DB
${clampTxt(sampleText, 180)}
👤 ${author} View →
`; }).join(""); // Category header with Finance (beta) chip const betaChip = (cat === "Finance") ? `beta` : ""; const contributeChip = (cat === "Medical/Law/Education") ? `Feel free to contribute` : ""; return `

${cat} ${betaChip} ${contributeChip}

${itemsHtml}
`; }).join(""); elLibraries.innerHTML = sections; bindLibraryClicks(); show(elStats); const catsShown = Object.keys(CATEGORY_MAP).length; const libsShown = Object.values(CATEGORY_MAP) .reduce((acc, prefixes) => acc + prefixes.filter(p => availableLibs.includes(p)).length, 0); $("ml-count").textContent = libsShown; $("ml-total").textContent = libsShown; } // —— Render — Memories with Pagination function renderMemories(memList){ VIEW = "memories"; hide(elLibraries); hide(elEmpty); show(elMemories); show(elCrumb); elType.textContent = "memories"; elCrumbTitle.textContent = `Exploring ${CURR}`; CURRENT_MEM_LIST = memList || []; if(!CURRENT_MEM_LIST.length){ hide(elMemories); hide(elPagination); show(elEmpty); hide(elStats); return; } const total = CURRENT_MEM_LIST.length; const pages = Math.max(1, Math.ceil(total / PAGE_SIZE)); if(PAGE > pages) PAGE = pages; const startIdx = (PAGE - 1) * PAGE_SIZE; const endIdx = Math.min(startIdx + PAGE_SIZE, total); const pageItems = CURRENT_MEM_LIST.slice(startIdx, endIdx); elMemories.innerHTML = pageItems.map((m,idxOnPage)=>`
${m._library}
${("score" in m && m.score !== null && m.score !== undefined) ? `
Score: ${m.score}
` : ""}
When to use: ${clampTxt(m.when_to_use || "No specific guidance provided", 140)}
👤 ${m.author || "Unknown"} Details →
`).join(""); const modal = createMemoryModal(); // modal binding [...elMemories.querySelectorAll(".ml-card-item")].forEach(card=>{ card.addEventListener("click", ()=>{ const absIdx = Number(card.getAttribute("data-idx")); const m = CURRENT_MEM_LIST[absIdx]; modal.open(m); }); }); // pagination controls show(elPagination); elPageRange.textContent = `Showing ${startIdx + 1}–${endIdx} of ${total}`; elPrev.disabled = PAGE <= 1; elNext.disabled = PAGE >= pages; elPrev.onclick = ()=>{ if(PAGE > 1){ PAGE--; renderMemories(CURRENT_MEM_LIST); } }; elNext.onclick = ()=>{ if(PAGE < pages){ PAGE++; renderMemories(CURRENT_MEM_LIST); } }; show(elStats); elCount.textContent = pageItems.length; elTotal.textContent = total; } function bindLibraryClicks(){ [...elLibraries.querySelectorAll(".ml-card-item[data-lib]")].forEach(card=>{ card.addEventListener("click", ()=>{ CURR = card.getAttribute("data-lib"); PAGE = 1; renderMemories(GROUPED[CURR]); }); }); } // —— Search function handleSearch(){ const q = elSearch.value.trim().toLowerCase(); if(!q){ if(VIEW==="libraries") renderLibraries(); else { PAGE = 1; renderMemories(GROUPED[CURR]); } return; } if(VIEW==="libraries"){ // filter categories if name matches, or any of their libs/memories match const availableLibs = Object.keys(GROUPED); const filteredEntries = Object.entries(CATEGORY_MAP).filter(([cat, prefixes])=>{ if(cat.toLowerCase().includes(q)) return true; return (prefixes || []).some(name=>{ if(!availableLibs.includes(name)) return false; const arr = GROUPED[name] || []; if(name.toLowerCase().includes(q)) return true; return arr.some(m => (m.when_to_use||"").toLowerCase().includes(q) || (m.content||"").toLowerCase().includes(q) || (m.author||"").toLowerCase().includes(q) ); }); }); const tmp = Object.fromEntries(filteredEntries); const backup = {...CATEGORY_MAP}; Object.keys(CATEGORY_MAP).forEach(k=> delete CATEGORY_MAP[k]); Object.assign(CATEGORY_MAP, tmp); renderLibraries(); Object.keys(CATEGORY_MAP).forEach(k=> delete CATEGORY_MAP[k]); Object.assign(CATEGORY_MAP, backup); }else{ const arr = GROUPED[CURR] || []; const filtered = arr.filter(m => (m.when_to_use||"").toLowerCase().includes(q) || (m.content||"").toLowerCase().includes(q) || (m.author||"").toLowerCase().includes(q) ); PAGE = 1; renderMemories(filtered); } } // —— Events elRetry?.addEventListener("click", loadAll); elBack?.addEventListener("click", ()=> renderLibraries()); elSearch?.addEventListener("input", debounce(handleSearch, 250)); elClear?.addEventListener("click", ()=>{ elSearch.value = ""; handleSearch(); }); // —— Init document.addEventListener("DOMContentLoaded", loadAll); })();