wiki-plugin-similarity 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client/similarity.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/* wiki-plugin-similarity - 0.4.
|
|
1
|
+
/* wiki-plugin-similarity - 0.4.3 - Mon, 06 Jul 2026 18:49:20 GMT */
|
|
2
2
|
(()=>{var F={high:.78,medium:.68,low:.58},O=F.medium,q=10,N=t=>{let e=[],i=null,s=null,o="search",x=!1,y=!1,v=null,b=null,p=(n,c)=>n===c||n.startsWith(c)&&/^[\s:]/.test(n.slice(c.length)),w=(n,c)=>n.slice(c.length).replace(/^\s*:?\s*/,"").trim();for(let n of t.split(`
|
|
3
|
-
`)){let c=n.trim();if(!c||c.startsWith("#"))continue;let
|
|
3
|
+
`)){let c=n.trim();if(!c||c.startsWith("#"))continue;let h=c.toUpperCase();if(p(h,"LIVE")){x=!0;continue}if(p(h,"AUTHOR")){!e.length&&o==="search"&&(o="author");continue}if(p(h,"REPORT")){o==="search"&&(o="report");continue}if(p(h,"BUILD")){o==="search"&&(o="build");continue}if(p(h,"FORCE")){y=!0;continue}if(p(h,"GHOST")){v=w(c,"GHOST"),o==="search"&&(o="ghost");continue}if(p(h,"BUTTON")){b=w(c,"BUTTON");continue}if(p(h,"LIST")){!e.length&&o==="search"&&(o="list");continue}if(p(h,"SIMILAR")){let k=w(h,"SIMILAR").toLowerCase();i=F[k]||O,!e.length&&o==="search"&&(o="similar");continue}if(p(h,"THRESHOLD")){let k=w(c,"THRESHOLD");i=F[k.toLowerCase()]??(parseFloat(k)||O);continue}if(p(h,"LIMIT")){s=parseInt(w(c,"LIMIT"))||q;continue}e.push(["PUBLIC","LOCAL","PRIVATE"].includes(h)?h:c)}return{mode:o,specs:e,threshold:i??O,limit:s??q,live:x,force:y,ghostUrl:v,label:b,thresholdSet:i!==null}},Y=t=>t.includes("*")||t.includes("?"),Q=t=>t==="PUBLIC"||t==="LOCAL"||t==="PRIVATE",X=t=>t.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,""),M=new Map,Z=async(t,e)=>{if(M.has(t))return M.get(t);let i=`${e}/system/indexed-domains.json?pattern=${encodeURIComponent(t)}`,s=await fetch(i);if(!s.ok)throw new Error(`indexed-domains failed: ${s.status}`);let o=await s.json();return M.set(t,o),o},B=async(t,e)=>{t.length||(t=[window.location.hostname]);let i=new Set,s=[];for(let o of t)if(o==="*"||Y(o)||Q(o))for(let x of await Z(o,e))i.has(x.domain)||(i.add(x.domain),s.push(x));else i.has(o)||(i.add(o),s.push({domain:o,page_count:null}));return s},U=new Map,tt=t=>`${window.location.origin}/system/semantic-vectors.json?domain=${encodeURIComponent(t)}`,V=async t=>{if(U.has(t))return U.get(t);let e=await fetch(tt(t));if(!e.ok)return[];let i=await e.json();return U.set(t,i),i},H=async(t,e)=>{let i=await fetch(`${e}/system/embed.json?text=${encodeURIComponent(t)}`);if(!i.ok)throw new Error(`embed failed: ${i.status}`);return(await i.json()).vector},et=async(t,e)=>{let s=(await V(e)).find(o=>o.slug===t);return s?s.vector:null},P=(t,e,{threshold:i,limit:s,excludeSlug:o,excludeDomain:x})=>{let y=[];for(let{domain:v,pages:b}of e)for(let{slug:p,title:w,vector:n}of b){if(p===o&&v===x)continue;let c=0;for(let h=0;h<t.length;h++)c+=t[h]*n[h];c>=i&&y.push({domain:v,slug:p,title:w,score:c})}return y.sort((v,b)=>b.score-v.score),y.slice(0,s)},A=async(t,e)=>{let i=await B(t,e);return(await Promise.all(i.map(async({domain:o})=>({domain:o,pages:await V(o)})))).filter(o=>o.pages.length>0)},G=t=>`sim-cache-${t}`,st=t=>{try{let e=JSON.parse(localStorage.getItem(G(t.id))||"null");return e?.text===(t.text||"")?e:null}catch{return null}},I=(t,e)=>{try{localStorage.setItem(G(t.id),JSON.stringify({text:t.text||"",ts:Date.now(),...e}))}catch{}},nt=t=>{let e=Math.floor((Date.now()-t)/1e3);return e<60?`${e}s ago`:e<3600?`${Math.floor(e/60)}m ago`:e<86400?`${Math.floor(e/3600)}h ago`:`${Math.floor(e/86400)}d ago`},j=`
|
|
4
4
|
.sim-form { display:flex; gap:6px; margin-bottom:8px; }
|
|
5
5
|
.sim-input { flex:1; padding:6px 8px; font-size:14px; border:1px solid #ccc; border-radius:3px; }
|
|
6
6
|
.sim-btn { padding:6px 14px; background:#c4561d; color:white; border:none;
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
.sim-list td { padding:3px 8px 3px 0; border-bottom:1px solid #f0f0f0; vertical-align:middle; }
|
|
25
25
|
.sim-list td:last-child { text-align:right; color:#999; font-size:11px; }
|
|
26
26
|
.sim-list .sim-flag { margin-right:6px; }
|
|
27
|
-
`,
|
|
27
|
+
`,it=(t,e)=>`<img class="sim-flag remote" src="${window.wiki.site(t).flag()}"
|
|
28
28
|
title="${t} \u2014 score ${e.toFixed(3)}"
|
|
29
|
-
data-site="${t}">`,z=(t,e,i,s)=>`<a class="sim-link" data-title="${i}" data-slug="${e}" data-site="${t}" href="#">${
|
|
29
|
+
data-site="${t}">`,z=(t,e,i,s)=>`<a class="sim-link" data-title="${i}" data-slug="${e}" data-site="${t}" href="#">${it(t,s)} ${i}</a>`,ot=(t,e)=>{let{mode:i,specs:s,threshold:o,limit:x,force:y,ghostUrl:v,label:b}=N(e?.text||"");if(i==="ghost")t.html(`
|
|
30
30
|
<style>${j}</style>
|
|
31
31
|
<div class="similarity" data-id="${e.id}">
|
|
32
32
|
<div class="sim-form">
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
</div>
|
|
69
69
|
<div class="sim-status">Domains: ${p}</div>
|
|
70
70
|
<div class="sim-results"></div>
|
|
71
|
-
</div>`)}},
|
|
71
|
+
</div>`)}},at=(t,e)=>{let{mode:i,specs:s,threshold:o,limit:x,live:y,force:v,ghostUrl:b,thresholdSet:p}=N(e?.text||""),w=window.location.origin,n=t.find(".sim-status")[0],c=y?null:st(e),h=(d,g,r)=>{let l=[`${d} \u2014 ${g.toLocaleString()} pages across ${r} domains`];return p&&l.push(`threshold ${o}`),l.push(`limit ${x}`),y&&l.push("LIVE"),l.join(" \xB7 ")};t.on("dblclick",d=>{$(d.target).closest(".sim-input").length||window.wiki.textEditor(t,e)}),t.on("click",".sim-link",function(d){d.preventDefault();let g=$(this),r=d.shiftKey?null:t.parents(".page");window.wiki.doInternalLink(g.data("title"),r,g.data("site"))});let k=!s.length||s.length===1&&s[0]==="*"?"on farm":s.length===1?`on ${s[0]}`:"in domains",R=d=>d?` \xB7 cached ${nt(d)}`:"";if(i==="list"){let d=t.find(".sim-list")[0],g=s.length?s.join(","):"*",r=(l,m)=>{let a=l.reduce((u,f)=>u+(f.page_count||0),0);n.style.display="none",d.innerHTML=`<h3>Indexed Farm Domains</h3>
|
|
72
72
|
<table>
|
|
73
73
|
<tr><th>Domain</th><th>Pages</th></tr>
|
|
74
74
|
${l.map(({domain:u,page_count:f})=>`
|
|
@@ -78,9 +78,9 @@
|
|
|
78
78
|
<td>${f!=null?f.toLocaleString():"\u2014"}</td>
|
|
79
79
|
</tr>`).join("")}
|
|
80
80
|
</table>
|
|
81
|
-
<p class="sim-count">${l.length} domains \u2014 ${a.toLocaleString()} pages${R(m)}</p>`};c?.domains?r(c.domains,c.ts):(async()=>{try{let l=`${w}/system/indexed-domains.json?pattern=${encodeURIComponent(
|
|
82
|
-
`);L=await H(C||l,w)}let S=P(L,u,{threshold:o,limit:x,excludeSlug:m,excludeDomain:a});
|
|
81
|
+
<p class="sim-count">${l.length} domains \u2014 ${a.toLocaleString()} pages${R(m)}</p>`};c?.domains?r(c.domains,c.ts):(async()=>{try{let l=`${w}/system/indexed-domains.json?pattern=${encodeURIComponent(g)}&limit=${x}`,m=await fetch(l);if(!m.ok)throw new Error(`indexed-domains failed: ${m.status}`);let a=await m.json();if(!a.length){n.textContent="No indexed domains found";return}r(a,null),I(e,{domains:a})}catch(l){n.textContent=`Error: ${l.message}`}})()}else if(i==="similar"){let d=t.find(".sim-results")[0],g=(r,l)=>{if(!r.length){n.textContent=`No similar pages found above threshold ${o}`;return}d.innerHTML=`<h3>Similar Pages</h3><ul>${r.map(({domain:m,slug:a,title:u,score:f})=>`<li>${z(m,a,u,f)}</li>`).join("")}</ul><p class="sim-count">${r.length} found ${k}${R(l)}</p>`,n.style.display="none"};c?.scored?g(c.scored,c.ts):(async()=>{try{let r=t.parents(".page"),l=r.find(".title").text().trim()||document.title,m=X(l),a=window.location.hostname,u=await A(s,w),f=u.reduce((C,T)=>C+T.pages.length,0);n.textContent=`Searching ${f.toLocaleString()} pages\u2026`;let L=await et(m,a);if(!L){n.textContent="Embedding page (not yet indexed)\u2026";let C=r.find(".item").map((T,D)=>$(D).text().trim()).get().filter(Boolean).join(`
|
|
82
|
+
`);L=await H(C||l,w)}let S=P(L,u,{threshold:o,limit:x,excludeSlug:m,excludeDomain:a});g(S,null),S.length&&I(e,{scored:S})}catch(r){n.textContent=`Error: ${r.message}`}})()}else if(i==="ghost"){let d=t.find(".sim-btn")[0],g=async()=>{if(!b){n.textContent="No URL \u2014 GHOST needs a page-json URL";return}d.disabled=!0,n.textContent="Fetching\u2026";try{let r=await fetch(b);if(!r.ok)throw new Error(`fetch failed: ${r.status}`);let l=await r.json();window.wiki.showResult(window.wiki.newPage(l),{$page:t.parents(".page")}),n.textContent=""}catch(r){n.textContent=`Error: ${r.message}`}finally{d.disabled=!1}};d.addEventListener("click",g)}else if(i==="build"){let d=t.find(".sim-btn")[0],g=async()=>{d.disabled=!0,n.textContent="Building index\u2026 (may take a while for large scopes)";try{let r=encodeURIComponent((s.length?s:["*"]).join(",")),l=await fetch(`http://api.localhost/build-index.json?domains=${r}&force=${v?1:0}`);if(!l.ok)throw new Error(`build-index failed: ${l.status}`);let m=await l.json();window.wiki.showResult(window.wiki.newPage(m),{$page:t.parents(".page")}),n.textContent=""}catch(r){n.textContent=`Error: ${r.message}`}finally{d.disabled=!1}};d.addEventListener("click",g)}else if(i==="report"){let d=t.find(".sim-input")[0],g=t.find(".sim-btn")[0],r=`Domains: ${s.length?s.join(", "):"*"}`;(async()=>{try{let m=await B(s.length?s:["*"],w),a=m.reduce((u,f)=>u+(f.page_count||0),0);r=h("Report ready",a,m.length),n.textContent=r}catch(m){n.textContent=`Domain listing unavailable: ${m.message}`}})();let l=async()=>{let m=d.value.trim();if(m){g.disabled=!0,n.textContent="Generating report\u2026";try{let a={query:m,domains:s.length?s:["*"],limit:x,live:y};p&&(a.threshold=o);let u=await fetch("http://api.localhost/search-report",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)});if(!u.ok)throw new Error(`search-report failed: ${u.status}`);let f=await u.json(),L=window.wiki.newPage(f);window.wiki.showResult(L,{$page:t.parents(".page")}),n.textContent=r}catch(a){n.textContent=`Error: ${a.message}`}finally{g.disabled=!1}}};g.addEventListener("click",l),d.addEventListener("keydown",m=>{m.key==="Enter"&&l()})}else if(i==="author"){let d=t.find(".sim-input")[0],g=t.find(".sim-btn")[0],r=t.find(".sim-results")[0],l=null;(async()=>{try{c||(n.textContent="Resolving domains\u2026"),l=await A(s,w);let a=l.reduce((u,f)=>u+f.pages.length,0);n.textContent=h("Author ready",a,l.length)}catch(a){n.textContent=`Load error: ${a.message}`}})();let m=async()=>{let a=d.value.trim();if(!(!a||!l)){g.disabled=!0,n.textContent="Embedding query\u2026",r.innerHTML="";try{let u=await H(a,w),f=P(u,l,{threshold:o,limit:x,excludeSlug:null,excludeDomain:null}),L=new Set,S=[];for(let{title:E}of f)L.has(E)||(L.add(E),S.push(E));let C=()=>Math.floor(Math.random()*18446744073709552e3).toString(16).padStart(16,"0"),T=S.map(E=>`- [[${E}]]`).join(`
|
|
83
83
|
`),D=[{type:"markdown",id:C(),text:`# Similar Pages
|
|
84
84
|
|
|
85
|
-
${T}`},{type:"markdown",id:C(),text:"# Reference Links"},...f.map(({domain:E,slug:J,title:
|
|
85
|
+
${T}`},{type:"markdown",id:C(),text:"# Reference Links"},...f.map(({domain:E,slug:J,title:K,score:W})=>({type:"reference",id:C(),site:E,slug:J,title:K,text:`score ${W.toFixed(3)}`}))],_=window.wiki.newPage({title:`${a} Results`,story:D,journal:[]});window.wiki.showResult(_,{$page:t.parents(".page")}),n.textContent=`${f.length} pages found`,I(e,{scored:f,query:a})}catch(u){n.textContent=`Error: ${u.message}`}finally{g.disabled=!1}}};g.addEventListener("click",m),d.addEventListener("keydown",a=>{a.key==="Enter"&&m()})}else{let d=t.find(".sim-input")[0],g=t.find(".sim-btn")[0],r=t.find(".sim-results")[0],l=null;c?.scored&&(d.value=c.query||"",r.innerHTML=c.scored.map(({domain:a,slug:u,title:f,score:L})=>`<div class="sim-result">${z(a,u,f,L)}</div>`).join("")+`<p class="sim-count">Top ${c.scored.length} for "${c.query||""}"${R(c.ts)}</p>`,n.textContent=""),(async()=>{try{c||(n.textContent="Resolving domains\u2026"),l=await A(s,w);let a=l.reduce((u,f)=>u+f.pages.length,0);n.textContent=h("Search ready",a,l.length)}catch(a){n.textContent=`Load error: ${a.message}`}})();let m=async()=>{let a=d.value.trim();if(!(!a||!l)){g.disabled=!0,n.textContent="Embedding query\u2026",r.innerHTML="";try{let u=await H(a,w),f=P(u,l,{threshold:o,limit:x,excludeSlug:null,excludeDomain:null});r.innerHTML=f.map(({domain:L,slug:S,title:C,score:T})=>`<div class="sim-result">${z(L,S,C,T)}</div>`).join("")+`<p class="sim-count">Top ${f.length} for "${a}"</p>`,n.textContent="",I(e,{scored:f,query:a})}catch(u){n.textContent=`Error: ${u.message}`}finally{g.disabled=!1}}};g.addEventListener("click",m),d.addEventListener("keydown",a=>{a.key==="Enter"&&m()})}};typeof window<"u"&&(window.plugins=window.plugins||{},window.plugins.similarity={emit:ot,bind:at});})();
|
|
86
86
|
//# sourceMappingURL=similarity.js.map
|
package/client/similarity.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/client/similarity.js"],
|
|
4
|
-
"sourcesContent": ["// wiki-plugin-similarity\n//\n// One item type: similarity\n//\n// DSL in item.text (newline-separated lines):\n// * all indexed domains on this server\n// david.* glob pattern\n// *.fish glob pattern\n// david.hitchhikers.earth explicit domain name\n// # comment ignored\n// LIST list indexed domains (mode directive)\n// SIMILAR: high threshold preset AND ambient mode trigger\n// THRESHOLD: 0.72 exact cosine threshold (overrides SIMILAR:)\n// LIMIT: 8 max results shown (default 10)\n//\n// Mode is determined by the FIRST meaningful line (Ward's ALL-CAPS convention):\n// LIST \u2192 show a table of all indexed domains and their page counts;\n// optional glob patterns on subsequent lines filter the list\n// SIMILAR: \u2192 ambient mode \u2014 automatically find pages similar to this page\n// (other) \u2192 search form mode \u2014 user types a query, results appear\n//\n// Server endpoints required:\n// GET /system/indexed-domains.json?pattern=glob1,glob2\n// GET /system/semantic-vectors.json?domain=\n// GET /system/embed.json?text=query\n\n// \u2500\u2500 DSL Parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst SIMILAR_THRESHOLDS = { high: 0.78, medium: 0.68, low: 0.58 }\nconst DEFAULT_THRESHOLD = SIMILAR_THRESHOLDS.medium\nconst DEFAULT_LIMIT = 10\n\n// parseDSL returns { mode, specs, threshold, limit }\n//\n// mode: 'similar' if SIMILAR: is the first meaningful line (ambient auto-run),\n// 'search' otherwise (interactive search form).\n//\n// Ward's convention: ALL-CAPS keyword as first word signals a mode switch.\n// Placing SIMILAR: first makes the item ambient; placing domain specs first\n// (or leaving text empty) keeps it as an interactive search form.\n\nconst parseDSL = text => {\n const specs = []\n let threshold = null\n let limit = null\n let mode = 'search' // default: interactive search form\n let live = false // default: cache results in localStorage\n let force = false // BUILD mode: re-embed even when index is fresh\n let ghostUrl = null // GHOST mode: page-json URL to open as a ghost page\n let label = null // BUTTON: custom button caption (GHOST / BUILD modes)\n\n // Match a keyword at the start of a line (case-insensitive), requiring it to\n // be followed by end-of-string, whitespace, or colon \u2014 not by more word chars.\n // This prevents domain specs like \"similarity.example.com\" matching SIMILAR.\n const isCmd = (upper, kw) => upper === kw || (upper.startsWith(kw) && /^[\\s:]/.test(upper.slice(kw.length)))\n // Extract the value after the keyword, tolerating optional colon and whitespace.\n // Returns '' for bare commands (e.g. \"SIMILAR\" alone), callers use their default.\n const val = (line, kw) => line.slice(kw.length).replace(/^\\s*:?\\s*/, '').trim()\n\n for (const raw of text.split('\\n')) {\n const line = raw.trim()\n if (!line || line.startsWith('#')) continue\n\n const upper = line.toUpperCase()\n if (isCmd(upper, 'LIVE')) { live = true; continue }\n if (isCmd(upper, 'AUTHOR')) {\n if (!specs.length && mode === 'search') mode = 'author'\n continue\n }\n if (isCmd(upper, 'REPORT')) {\n if (mode === 'search') mode = 'report'\n continue\n }\n if (isCmd(upper, 'BUILD')) {\n if (mode === 'search') mode = 'build'\n continue\n }\n if (isCmd(upper, 'FORCE')) { force = true; continue }\n if (isCmd(upper, 'GHOST')) {\n ghostUrl = val(line, 'GHOST')\n if (mode === 'search') mode = 'ghost'\n continue\n }\n if (isCmd(upper, 'BUTTON')) { label = val(line, 'BUTTON'); continue }\n if (isCmd(upper, 'LIST')) {\n if (!specs.length && mode === 'search') mode = 'list'\n continue\n }\n if (isCmd(upper, 'SIMILAR')) {\n const level = val(upper, 'SIMILAR').toLowerCase()\n threshold = SIMILAR_THRESHOLDS[level] || DEFAULT_THRESHOLD // '' \u2192 medium\n if (!specs.length && mode === 'search') mode = 'similar'\n continue\n }\n if (isCmd(upper, 'THRESHOLD')) {\n const tv = val(line, 'THRESHOLD')\n threshold = SIMILAR_THRESHOLDS[tv.toLowerCase()] ?? (parseFloat(tv) || DEFAULT_THRESHOLD)\n continue\n }\n if (isCmd(upper, 'LIMIT')) {\n limit = parseInt(val(line, 'LIMIT')) || DEFAULT_LIMIT // '' \u2192 10\n continue\n }\n // Anything else is a domain spec (glob, explicit domain, or scope keyword)\n specs.push(['PUBLIC', 'LOCAL', 'PRIVATE'].includes(upper) ? upper : line)\n }\n\n return {\n mode,\n specs,\n threshold: threshold ?? DEFAULT_THRESHOLD,\n limit: limit ?? DEFAULT_LIMIT,\n live,\n force,\n ghostUrl,\n label,\n thresholdSet: threshold !== null, // explicit THRESHOLD/SIMILAR in the DSL\n }\n}\n\nconst isGlob = spec => spec.includes('*') || spec.includes('?')\n\n// Scope keywords expand server-side: PUBLIC (Nextcloud mirror farms),\n// LOCAL (primary farm), PRIVATE (public domains with restricted: true)\nconst isScope = spec => spec === 'PUBLIC' || spec === 'LOCAL' || spec === 'PRIVATE'\n\n// \u2500\u2500 Slug \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst slugify = title => title.toLowerCase().replace(/\\s+/g, '-').replace(/[^a-z0-9-]/g, '')\n\n// \u2500\u2500 Domain resolution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst domainCache = new Map()\n\nconst resolveDomainsForSpec = async (spec, origin) => {\n if (domainCache.has(spec)) return domainCache.get(spec)\n const url = `${origin}/system/indexed-domains.json?pattern=${encodeURIComponent(spec)}`\n const res = await fetch(url)\n if (!res.ok) throw new Error(`indexed-domains failed: ${res.status}`)\n const list = await res.json()\n domainCache.set(spec, list)\n return list\n}\n\nconst resolveDomains = async (specs, origin) => {\n if (!specs.length) specs = [window.location.hostname]\n const seen = new Set()\n const result = []\n for (const spec of specs) {\n if (spec === '*' || isGlob(spec) || isScope(spec)) {\n for (const item of await resolveDomainsForSpec(spec, origin)) {\n if (!seen.has(item.domain)) { seen.add(item.domain); result.push(item) }\n }\n } else if (!seen.has(spec)) {\n seen.add(spec); result.push({ domain: spec, page_count: null })\n }\n }\n return result\n}\n\n// \u2500\u2500 Vector loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst vectorCache = new Map()\n\n// Detect whether the wiki is running on localhost (or a .localhost subdomain).\n// On localhost we proxy all domain vector fetches through the local server's\n// ?domain= parameter \u2014 this reads vectors directly from the farm folder on disk,\n// avoiding any need for remote servers to have this plugin installed, and\n// sidestepping CORS entirely.\nconst isLocalhost = () => {\n const h = window.location.hostname\n return h === 'localhost' || h.endsWith('.localhost') || h === '127.0.0.1'\n}\n\nconst vectorUrl = domain => {\n if (isLocalhost()) {\n // Route through our local server \u2014 serves any farm domain from disk\n return `${window.location.origin}/system/semantic-vectors.json?domain=${encodeURIComponent(domain)}`\n }\n // On a real server, fetch directly from the remote wiki \u2014 use same protocol\n // as the current page to avoid mixed-content blocks on HTTPS sites\n return `${window.location.protocol}//${domain}/system/semantic-vectors.json`\n}\n\nconst loadVectors = async domain => {\n if (vectorCache.has(domain)) return vectorCache.get(domain)\n const res = await fetch(vectorUrl(domain))\n if (!res.ok) return []\n const data = await res.json()\n vectorCache.set(domain, data)\n return data\n}\n\n// \u2500\u2500 Embedding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst getEmbedding = async (text, origin) => {\n const res = await fetch(`${origin}/system/embed.json?text=${encodeURIComponent(text)}`)\n if (!res.ok) throw new Error(`embed failed: ${res.status}`)\n return (await res.json()).vector\n}\n\n// Look up an existing page vector from the current domain's cached index.\n// Returns the float[] vector if found, null otherwise.\nconst lookupPageVector = async (slug, domain) => {\n const pages = await loadVectors(domain)\n const entry = pages.find(p => p.slug === slug)\n return entry ? entry.vector : null\n}\n\n// \u2500\u2500 Cosine search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst cosineScan = (queryVec, domainEntries, { threshold, limit, excludeSlug, excludeDomain }) => {\n const results = []\n for (const { domain, pages } of domainEntries) {\n for (const { slug, title, vector } of pages) {\n if (slug === excludeSlug && domain === excludeDomain) continue\n let dot = 0\n for (let i = 0; i < queryVec.length; i++) dot += queryVec[i] * vector[i]\n if (dot >= threshold) results.push({ domain, slug, title, score: dot })\n }\n }\n results.sort((a, b) => b.score - a.score)\n return results.slice(0, limit)\n}\n\n// \u2500\u2500 Shared domain loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst loadDomainEntries = async (specs, origin) => {\n const domains = await resolveDomains(specs, origin)\n const entries = await Promise.all(\n domains.map(async ({ domain }) => ({ domain, pages: await loadVectors(domain) }))\n )\n return entries.filter(e => e.pages.length > 0)\n}\n\n// \u2500\u2500 Result cache (localStorage) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Cache is keyed by item id. Invalidated when item.text changes (DSL edited).\n// LIVE mode bypasses the cache entirely.\n\nconst cacheKey = id => `sim-cache-${id}`\n\nconst readCache = item => {\n try {\n const c = JSON.parse(localStorage.getItem(cacheKey(item.id)) || 'null')\n return c?.text === (item.text || '') ? c : null\n } catch { return null }\n}\n\nconst writeCache = (item, data) => {\n try {\n localStorage.setItem(cacheKey(item.id), JSON.stringify({\n text: item.text || '',\n ts: Date.now(),\n ...data,\n }))\n } catch { /* storage unavailable or full */ }\n}\n\nconst cacheAge = ts => {\n const s = Math.floor((Date.now() - ts) / 1000)\n if (s < 60) return `${s}s ago`\n if (s < 3600) return `${Math.floor(s / 60)}m ago`\n if (s < 86400) return `${Math.floor(s / 3600)}h ago`\n return `${Math.floor(s / 86400)}d ago`\n}\n\n// \u2500\u2500 Styles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst STYLES = `\n .sim-form { display:flex; gap:6px; margin-bottom:8px; }\n .sim-input { flex:1; padding:6px 8px; font-size:14px; border:1px solid #ccc; border-radius:3px; }\n .sim-btn { padding:6px 14px; background:#c4561d; color:white; border:none;\n border-radius:3px; cursor:pointer; font-size:14px; }\n .sim-btn:disabled { opacity:0.5; cursor:not-allowed; }\n .sim-status { font-size:12px; color:#888; margin-bottom:6px; min-height:16px; }\n .sim-results { margin-top:4px; }\n .sim-result { display:flex; align-items:center; gap:8px; padding:3px 0;\n border-bottom:1px solid #f0f0f0; }\n .sim-flag { width:16px; height:16px; vertical-align:middle; margin-right:4px; }\n .sim-link { font-size:14px; color:#406; flex:1; }\n .sim-domain { font-size:11px; color:#999; }\n .similar-results h3, .sim-list h3 { margin:4px 0 6px; font-size:14px; color:#555; }\n .similar-results ul { margin:0; padding-left:18px; }\n .similar-results li { font-size:14px; padding:2px 0; }\n .similar-results .sim-domain { margin-left:6px; }\n .sim-count { font-size:12px; color:#888; margin:4px 0 0; }\n .sim-list table { border-collapse:collapse; width:100%; font-size:13px; }\n .sim-list th { text-align:left; font-size:11px; color:#888; padding:2px 8px 4px 0;\n border-bottom:1px solid #ddd; }\n .sim-list td { padding:3px 8px 3px 0; border-bottom:1px solid #f0f0f0; vertical-align:middle; }\n .sim-list td:last-child { text-align:right; color:#999; font-size:11px; }\n .sim-list .sim-flag { margin-right:6px; }\n`\n\nconst siteFlag = (domain, score) =>\n `<img class=\"sim-flag remote\" src=\"${window.wiki.site(domain).flag()}\"\n title=\"${domain} \u2014 score ${score.toFixed(3)}\"\n data-site=\"${domain}\">`\n\n// \u2500\u2500 similarity item \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst simLink = (domain, slug, title, score) =>\n `<a class=\"sim-link\" data-title=\"${title}\" data-slug=\"${slug}\" data-site=\"${domain}\" href=\"#\">` +\n `${siteFlag(domain, score)} ${title}</a>`\n\nexport const emit = (div, item) => {\n const { mode, specs, threshold, limit, force, ghostUrl, label } = parseDSL(item?.text || '')\n if (mode === 'ghost') {\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-form\">\n <button class=\"sim-btn\">${label || 'Open'}</button>\n </div>\n <div class=\"sim-status\"></div>\n </div>`)\n } else if (mode === 'build') {\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-form\">\n <button class=\"sim-btn\">${label || `Index ${specs.length ? specs.join(', ') : '*'}${force ? ' (force)' : ''}`}</button>\n </div>\n <div class=\"sim-status\"></div>\n </div>`)\n } else if (mode === 'list') {\n const label = specs.length ? specs.join(', ') : '*'\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-status\">Loading indexed domains (${label})\u2026</div>\n <div class=\"sim-list\"></div>\n </div>`)\n } else if (mode === 'similar') {\n const label = specs.length ? specs.join(', ') : 'current domain'\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-status\">Finding similar pages across ${label}\u2026</div>\n <div class=\"sim-results\"></div>\n </div>`)\n } else if (mode === 'author' || mode === 'report') {\n const label = specs.length ? specs.join(', ') : '(current domain)'\n const btnLabel = mode === 'report' ? 'Report' : 'Author'\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-form\">\n <input class=\"sim-input\" type=\"text\" placeholder=\"Search wiki pages\u2026\" />\n <button class=\"sim-btn\">${btnLabel}</button>\n </div>\n <div class=\"sim-status\">Domains: ${label}</div>\n <div class=\"sim-results\"></div>\n </div>`)\n } else {\n const label = specs.length ? specs.join(', ') : '(current domain)'\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-form\">\n <input class=\"sim-input\" type=\"text\" placeholder=\"Search wiki pages\u2026\" />\n <button class=\"sim-btn\">Search</button>\n </div>\n <div class=\"sim-status\">Domains: ${label}</div>\n <div class=\"sim-results\"></div>\n </div>`)\n }\n}\n\nexport const bind = (div, item) => {\n const { mode, specs, threshold, limit, live, force, ghostUrl, thresholdSet } =\n parseDSL(item?.text || '')\n const origin = window.location.origin\n const status = div.find('.sim-status')[0]\n const cache = live ? null : readCache(item)\n\n // Standardised pre-search status: what will run, over how much, with what config.\n // e.g. \"Report ready \u2014 18,583 pages across 267 domains \u00B7 threshold 0.68 \u00B7 limit 20 \u00B7 LIVE\"\n const configSummary = (verb, pages, nDomains) => {\n const parts = [`${verb} \u2014 ${pages.toLocaleString()} pages across ${nDomains} domains`]\n if (thresholdSet) parts.push(`threshold ${threshold}`)\n parts.push(`limit ${limit}`)\n if (live) parts.push('LIVE')\n return parts.join(' \u00B7 ')\n }\n\n div.on('dblclick', e => {\n if ($(e.target).closest('.sim-input').length) return\n window.wiki.textEditor(div, item)\n })\n div.on('click', '.sim-link', function (e) {\n e.preventDefault()\n const $a = $(this)\n // shift-click appends at the end of the lineup instead of truncating after this page\n const $page = e.shiftKey ? null : div.parents('.page')\n window.wiki.doInternalLink($a.data('title'), $page, $a.data('site'))\n })\n\n const scopeLabel = !specs.length || (specs.length === 1 && specs[0] === '*')\n ? 'on farm'\n : specs.length === 1 ? `on ${specs[0]}` : 'in domains'\n\n const cacheNote = ts => ts ? ` \u00B7 cached ${cacheAge(ts)}` : ''\n\n if (mode === 'list') {\n const listDiv = div.find('.sim-list')[0]\n const patterns = specs.length ? specs.join(',') : '*'\n\n const renderList = (domains, ts) => {\n const totalPages = domains.reduce((n, d) => n + (d.page_count || 0), 0)\n status.style.display = 'none'\n listDiv.innerHTML = `<h3>Indexed Farm Domains</h3>\n <table>\n <tr><th>Domain</th><th>Pages</th></tr>\n ${domains.map(({ domain, page_count }) => `\n <tr>\n <td><img class=\"sim-flag remote\" src=\"${window.wiki.site(domain).flag()}\"\n title=\"${domain}\" data-site=\"${domain}\"> ${domain}</td>\n <td>${page_count != null ? page_count.toLocaleString() : '\u2014'}</td>\n </tr>`).join('')}\n </table>\n <p class=\"sim-count\">${domains.length} domains \u2014 ${totalPages.toLocaleString()} pages${cacheNote(ts)}</p>`\n }\n\n if (cache?.domains) {\n renderList(cache.domains, cache.ts)\n } else {\n ;(async () => {\n try {\n const url = `${origin}/system/indexed-domains.json?pattern=${encodeURIComponent(patterns)}&limit=${limit}`\n const res = await fetch(url)\n if (!res.ok) throw new Error(`indexed-domains failed: ${res.status}`)\n const domains = await res.json()\n if (!domains.length) { status.textContent = 'No indexed domains found'; return }\n renderList(domains, null)\n writeCache(item, { domains })\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n }\n })()\n }\n\n } else if (mode === 'similar') {\n const results = div.find('.sim-results')[0]\n\n const renderScored = (scored, ts) => {\n if (!scored.length) {\n status.textContent = `No similar pages found above threshold ${threshold}`\n return\n }\n results.innerHTML = `<h3>Similar Pages</h3><ul>${\n scored.map(({ domain, slug, title, score }) =>\n `<li>${simLink(domain, slug, title, score)}</li>`).join('')\n }</ul><p class=\"sim-count\">${scored.length} found ${scopeLabel}${cacheNote(ts)}</p>`\n status.style.display = 'none'\n }\n\n if (cache?.scored) {\n renderScored(cache.scored, cache.ts)\n } else {\n ;(async () => {\n try {\n const $page = div.parents('.page')\n const pageTitle = $page.find('.title').text().trim() || document.title\n const currentSlug = slugify(pageTitle)\n const currentDomain = window.location.hostname\n\n const domainEntries = await loadDomainEntries(specs, origin)\n const total = domainEntries.reduce((n, e) => n + e.pages.length, 0)\n status.textContent = `Searching ${total.toLocaleString()} pages\u2026`\n\n let qVec = await lookupPageVector(currentSlug, currentDomain)\n if (!qVec) {\n status.textContent = 'Embedding page (not yet indexed)\u2026'\n const pageText = $page.find('.item').map((_, el) => $(el).text().trim()).get().filter(Boolean).join('\\n')\n qVec = await getEmbedding(pageText || pageTitle, origin)\n }\n\n const scored = cosineScan(qVec, domainEntries, {\n threshold, limit, excludeSlug: currentSlug, excludeDomain: currentDomain,\n })\n renderScored(scored, null)\n if (scored.length) writeCache(item, { scored })\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n }\n })()\n }\n\n } else if (mode === 'ghost') {\n // Ghost mode \u2014 fetch page-json from any URL and open it as a ghost page\n const btn = div.find('.sim-btn')[0]\n\n const doGhost = async () => {\n if (!ghostUrl) { status.textContent = 'No URL \u2014 GHOST needs a page-json URL'; return }\n btn.disabled = true\n status.textContent = 'Fetching\u2026'\n try {\n const res = await fetch(ghostUrl)\n if (!res.ok) throw new Error(`fetch failed: ${res.status}`)\n const page = await res.json()\n window.wiki.showResult(window.wiki.newPage(page), { $page: div.parents('.page') })\n status.textContent = ''\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n } finally {\n btn.disabled = false\n }\n }\n\n btn.addEventListener('click', doGhost)\n\n } else if (mode === 'build') {\n // Build mode \u2014 trigger a semantic index build; result opens as a ghost page\n const btn = div.find('.sim-btn')[0]\n\n const doBuild = async () => {\n btn.disabled = true\n status.textContent = 'Building index\u2026 (may take a while for large scopes)'\n try {\n const domains = encodeURIComponent((specs.length ? specs : ['*']).join(','))\n const res = await fetch(\n `http://api.localhost/build-index.json?domains=${domains}&force=${force ? 1 : 0}`)\n if (!res.ok) throw new Error(`build-index failed: ${res.status}`)\n const page = await res.json()\n window.wiki.showResult(window.wiki.newPage(page), { $page: div.parents('.page') })\n status.textContent = ''\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n } finally {\n btn.disabled = false\n }\n }\n\n btn.addEventListener('click', doBuild)\n\n } else if (mode === 'report') {\n // Report mode \u2014 server-side ranked/bundled search, opens result as ghost page\n const input = div.find('.sim-input')[0]\n const btn = div.find('.sim-btn')[0]\n let readyLine = `Domains: ${specs.length ? specs.join(', ') : '*'}`\n\n // Preload the lightweight domain listing (counts only, no vectors) so the\n // status line shows scope and config before any search is issued.\n ;(async () => {\n try {\n const domains = await resolveDomains(specs.length ? specs : ['*'], origin)\n const pages = domains.reduce((n, d) => n + (d.page_count || 0), 0)\n readyLine = configSummary('Report ready', pages, domains.length)\n status.textContent = readyLine\n } catch (e) {\n status.textContent = `Domain listing unavailable: ${e.message}`\n }\n })()\n\n const doReport = async () => {\n const query = input.value.trim()\n if (!query) return\n btn.disabled = true\n status.textContent = 'Generating report\u2026'\n try {\n const body = { query, domains: specs.length ? specs : ['*'], limit, live }\n if (thresholdSet) body.threshold = threshold\n const res = await fetch('http://api.localhost/search-report', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!res.ok) throw new Error(`search-report failed: ${res.status}`)\n const page = await res.json()\n const pageObj = window.wiki.newPage(page)\n window.wiki.showResult(pageObj, { $page: div.parents('.page') })\n status.textContent = readyLine\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n } finally {\n btn.disabled = false\n }\n }\n\n btn.addEventListener('click', doReport)\n input.addEventListener('keydown', e => { if (e.key === 'Enter') doReport() })\n\n } else if (mode === 'author') {\n // Author mode \u2014 same search form but creates a ghost page from results\n const input = div.find('.sim-input')[0]\n const btn = div.find('.sim-btn')[0]\n const results = div.find('.sim-results')[0]\n let domainEntries = null\n\n ;(async () => {\n try {\n if (!cache) status.textContent = 'Resolving domains\u2026'\n domainEntries = await loadDomainEntries(specs, origin)\n const total = domainEntries.reduce((n, e) => n + e.pages.length, 0)\n status.textContent = configSummary('Author ready', total, domainEntries.length)\n } catch (e) {\n status.textContent = `Load error: ${e.message}`\n }\n })()\n\n const doAuthor = async () => {\n const query = input.value.trim()\n if (!query || !domainEntries) return\n btn.disabled = true\n status.textContent = 'Embedding query\u2026'\n results.innerHTML = ''\n try {\n const qVec = await getEmbedding(query, origin)\n const scored = cosineScan(qVec, domainEntries,\n { threshold, limit, excludeSlug: null, excludeDomain: null })\n\n // Unique titles for wiki-link list\n const seenTitles = new Set()\n const uniqueTitles = []\n for (const { title } of scored) {\n if (!seenTitles.has(title)) { seenTitles.add(title); uniqueTitles.push(title) }\n }\n\n const hexId = () => Math.floor(Math.random() * 0xffffffffffffffff).toString(16).padStart(16, '0')\n const primaryLines = uniqueTitles.map(t => `- [[${t}]]`).join('\\n')\n\n const story = [\n { type: 'markdown', id: hexId(), text: `# Similar Pages\\n\\n${primaryLines}` },\n { type: 'markdown', id: hexId(), text: '# Reference Links' },\n ...scored.map(({ domain, slug, title, score }) => ({\n type: 'reference', id: hexId(), site: domain, slug, title,\n text: `score ${score.toFixed(3)}`,\n })),\n ]\n\n const pageObj = window.wiki.newPage({ title: `${query} Results`, story, journal: [] })\n window.wiki.showResult(pageObj, { $page: div.parents('.page') })\n\n status.textContent = `${scored.length} pages found`\n writeCache(item, { scored, query })\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n } finally {\n btn.disabled = false\n }\n }\n\n btn.addEventListener('click', doAuthor)\n input.addEventListener('keydown', e => { if (e.key === 'Enter') doAuthor() })\n\n } else {\n // Search form mode\n const input = div.find('.sim-input')[0]\n const btn = div.find('.sim-btn')[0]\n const results = div.find('.sim-results')[0]\n let domainEntries = null\n\n // Show cached results immediately while domains preload in background\n if (cache?.scored) {\n input.value = cache.query || ''\n results.innerHTML = cache.scored.map(({ domain, slug, title, score }) =>\n `<div class=\"sim-result\">${simLink(domain, slug, title, score)}</div>`).join('') +\n `<p class=\"sim-count\">Top ${cache.scored.length} for \"${cache.query || ''}\"${cacheNote(cache.ts)}</p>`\n status.textContent = ''\n }\n\n ;(async () => {\n try {\n if (!cache) status.textContent = 'Resolving domains\u2026'\n domainEntries = await loadDomainEntries(specs, origin)\n const total = domainEntries.reduce((n, e) => n + e.pages.length, 0)\n status.textContent = configSummary('Search ready', total, domainEntries.length)\n } catch (e) {\n status.textContent = `Load error: ${e.message}`\n }\n })()\n\n const doSearch = async () => {\n const query = input.value.trim()\n if (!query || !domainEntries) return\n btn.disabled = true\n status.textContent = 'Embedding query\u2026'\n results.innerHTML = ''\n try {\n const qVec = await getEmbedding(query, origin)\n const scored = cosineScan(qVec, domainEntries,\n { threshold, limit, excludeSlug: null, excludeDomain: null })\n results.innerHTML = scored.map(({ domain, slug, title, score }) =>\n `<div class=\"sim-result\">${simLink(domain, slug, title, score)}</div>`).join('') +\n `<p class=\"sim-count\">Top ${scored.length} for \"${query}\"</p>`\n status.textContent = ''\n writeCache(item, { scored, query })\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n } finally {\n btn.disabled = false\n }\n }\n\n btn.addEventListener('click', doSearch)\n input.addEventListener('keydown', e => { if (e.key === 'Enter') doSearch() })\n }\n}\n\n// \u2500\u2500 Register item type with the wiki \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nif (typeof window !== 'undefined') {\n window.plugins = window.plugins || {}\n window.plugins.similarity = { emit, bind }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["SIMILAR_THRESHOLDS", "DEFAULT_THRESHOLD", "DEFAULT_LIMIT", "parseDSL", "text", "specs", "threshold", "limit", "mode", "live", "force", "ghostUrl", "label", "isCmd", "upper", "kw", "val", "line", "raw", "level", "tv", "isGlob", "spec", "isScope", "slugify", "title", "domainCache", "resolveDomainsForSpec", "origin", "url", "res", "list", "resolveDomains", "seen", "result", "item", "vectorCache", "
|
|
4
|
+
"sourcesContent": ["// wiki-plugin-similarity\n//\n// One item type: similarity\n//\n// DSL in item.text (newline-separated lines):\n// * all indexed domains on this server\n// david.* glob pattern\n// *.fish glob pattern\n// david.hitchhikers.earth explicit domain name\n// # comment ignored\n// LIST list indexed domains (mode directive)\n// SIMILAR: high threshold preset AND ambient mode trigger\n// THRESHOLD: 0.72 exact cosine threshold (overrides SIMILAR:)\n// LIMIT: 8 max results shown (default 10)\n//\n// Mode is determined by the FIRST meaningful line (Ward's ALL-CAPS convention):\n// LIST \u2192 show a table of all indexed domains and their page counts;\n// optional glob patterns on subsequent lines filter the list\n// SIMILAR: \u2192 ambient mode \u2014 automatically find pages similar to this page\n// (other) \u2192 search form mode \u2014 user types a query, results appear\n//\n// Server endpoints required:\n// GET /system/indexed-domains.json?pattern=glob1,glob2\n// GET /system/semantic-vectors.json?domain=\n// GET /system/embed.json?text=query\n\n// \u2500\u2500 DSL Parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst SIMILAR_THRESHOLDS = { high: 0.78, medium: 0.68, low: 0.58 }\nconst DEFAULT_THRESHOLD = SIMILAR_THRESHOLDS.medium\nconst DEFAULT_LIMIT = 10\n\n// parseDSL returns { mode, specs, threshold, limit }\n//\n// mode: 'similar' if SIMILAR: is the first meaningful line (ambient auto-run),\n// 'search' otherwise (interactive search form).\n//\n// Ward's convention: ALL-CAPS keyword as first word signals a mode switch.\n// Placing SIMILAR: first makes the item ambient; placing domain specs first\n// (or leaving text empty) keeps it as an interactive search form.\n\nconst parseDSL = text => {\n const specs = []\n let threshold = null\n let limit = null\n let mode = 'search' // default: interactive search form\n let live = false // default: cache results in localStorage\n let force = false // BUILD mode: re-embed even when index is fresh\n let ghostUrl = null // GHOST mode: page-json URL to open as a ghost page\n let label = null // BUTTON: custom button caption (GHOST / BUILD modes)\n\n // Match a keyword at the start of a line (case-insensitive), requiring it to\n // be followed by end-of-string, whitespace, or colon \u2014 not by more word chars.\n // This prevents domain specs like \"similarity.example.com\" matching SIMILAR.\n const isCmd = (upper, kw) => upper === kw || (upper.startsWith(kw) && /^[\\s:]/.test(upper.slice(kw.length)))\n // Extract the value after the keyword, tolerating optional colon and whitespace.\n // Returns '' for bare commands (e.g. \"SIMILAR\" alone), callers use their default.\n const val = (line, kw) => line.slice(kw.length).replace(/^\\s*:?\\s*/, '').trim()\n\n for (const raw of text.split('\\n')) {\n const line = raw.trim()\n if (!line || line.startsWith('#')) continue\n\n const upper = line.toUpperCase()\n if (isCmd(upper, 'LIVE')) { live = true; continue }\n if (isCmd(upper, 'AUTHOR')) {\n if (!specs.length && mode === 'search') mode = 'author'\n continue\n }\n if (isCmd(upper, 'REPORT')) {\n if (mode === 'search') mode = 'report'\n continue\n }\n if (isCmd(upper, 'BUILD')) {\n if (mode === 'search') mode = 'build'\n continue\n }\n if (isCmd(upper, 'FORCE')) { force = true; continue }\n if (isCmd(upper, 'GHOST')) {\n ghostUrl = val(line, 'GHOST')\n if (mode === 'search') mode = 'ghost'\n continue\n }\n if (isCmd(upper, 'BUTTON')) { label = val(line, 'BUTTON'); continue }\n if (isCmd(upper, 'LIST')) {\n if (!specs.length && mode === 'search') mode = 'list'\n continue\n }\n if (isCmd(upper, 'SIMILAR')) {\n const level = val(upper, 'SIMILAR').toLowerCase()\n threshold = SIMILAR_THRESHOLDS[level] || DEFAULT_THRESHOLD // '' \u2192 medium\n if (!specs.length && mode === 'search') mode = 'similar'\n continue\n }\n if (isCmd(upper, 'THRESHOLD')) {\n const tv = val(line, 'THRESHOLD')\n threshold = SIMILAR_THRESHOLDS[tv.toLowerCase()] ?? (parseFloat(tv) || DEFAULT_THRESHOLD)\n continue\n }\n if (isCmd(upper, 'LIMIT')) {\n limit = parseInt(val(line, 'LIMIT')) || DEFAULT_LIMIT // '' \u2192 10\n continue\n }\n // Anything else is a domain spec (glob, explicit domain, or scope keyword)\n specs.push(['PUBLIC', 'LOCAL', 'PRIVATE'].includes(upper) ? upper : line)\n }\n\n return {\n mode,\n specs,\n threshold: threshold ?? DEFAULT_THRESHOLD,\n limit: limit ?? DEFAULT_LIMIT,\n live,\n force,\n ghostUrl,\n label,\n thresholdSet: threshold !== null, // explicit THRESHOLD/SIMILAR in the DSL\n }\n}\n\nconst isGlob = spec => spec.includes('*') || spec.includes('?')\n\n// Scope keywords expand server-side: PUBLIC (Nextcloud mirror farms),\n// LOCAL (primary farm), PRIVATE (public domains with restricted: true)\nconst isScope = spec => spec === 'PUBLIC' || spec === 'LOCAL' || spec === 'PRIVATE'\n\n// \u2500\u2500 Slug \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst slugify = title => title.toLowerCase().replace(/\\s+/g, '-').replace(/[^a-z0-9-]/g, '')\n\n// \u2500\u2500 Domain resolution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst domainCache = new Map()\n\nconst resolveDomainsForSpec = async (spec, origin) => {\n if (domainCache.has(spec)) return domainCache.get(spec)\n const url = `${origin}/system/indexed-domains.json?pattern=${encodeURIComponent(spec)}`\n const res = await fetch(url)\n if (!res.ok) throw new Error(`indexed-domains failed: ${res.status}`)\n const list = await res.json()\n domainCache.set(spec, list)\n return list\n}\n\nconst resolveDomains = async (specs, origin) => {\n if (!specs.length) specs = [window.location.hostname]\n const seen = new Set()\n const result = []\n for (const spec of specs) {\n if (spec === '*' || isGlob(spec) || isScope(spec)) {\n for (const item of await resolveDomainsForSpec(spec, origin)) {\n if (!seen.has(item.domain)) { seen.add(item.domain); result.push(item) }\n }\n } else if (!seen.has(spec)) {\n seen.add(spec); result.push({ domain: spec, page_count: null })\n }\n }\n return result\n}\n\n// \u2500\u2500 Vector loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst vectorCache = new Map()\n\n// Route vector requests through the current wiki's plugin server. This keeps\n// public HTTPS pages same-origin, avoids CORS failures, and lets the server read\n// indices from its configured farm roots.\nconst vectorUrl = domain =>\n `${window.location.origin}/system/semantic-vectors.json?domain=${encodeURIComponent(domain)}`\n\nconst loadVectors = async domain => {\n if (vectorCache.has(domain)) return vectorCache.get(domain)\n const res = await fetch(vectorUrl(domain))\n if (!res.ok) return []\n const data = await res.json()\n vectorCache.set(domain, data)\n return data\n}\n\n// \u2500\u2500 Embedding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst getEmbedding = async (text, origin) => {\n const res = await fetch(`${origin}/system/embed.json?text=${encodeURIComponent(text)}`)\n if (!res.ok) throw new Error(`embed failed: ${res.status}`)\n return (await res.json()).vector\n}\n\n// Look up an existing page vector from the current domain's cached index.\n// Returns the float[] vector if found, null otherwise.\nconst lookupPageVector = async (slug, domain) => {\n const pages = await loadVectors(domain)\n const entry = pages.find(p => p.slug === slug)\n return entry ? entry.vector : null\n}\n\n// \u2500\u2500 Cosine search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst cosineScan = (queryVec, domainEntries, { threshold, limit, excludeSlug, excludeDomain }) => {\n const results = []\n for (const { domain, pages } of domainEntries) {\n for (const { slug, title, vector } of pages) {\n if (slug === excludeSlug && domain === excludeDomain) continue\n let dot = 0\n for (let i = 0; i < queryVec.length; i++) dot += queryVec[i] * vector[i]\n if (dot >= threshold) results.push({ domain, slug, title, score: dot })\n }\n }\n results.sort((a, b) => b.score - a.score)\n return results.slice(0, limit)\n}\n\n// \u2500\u2500 Shared domain loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst loadDomainEntries = async (specs, origin) => {\n const domains = await resolveDomains(specs, origin)\n const entries = await Promise.all(\n domains.map(async ({ domain }) => ({ domain, pages: await loadVectors(domain) }))\n )\n return entries.filter(e => e.pages.length > 0)\n}\n\n// \u2500\u2500 Result cache (localStorage) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Cache is keyed by item id. Invalidated when item.text changes (DSL edited).\n// LIVE mode bypasses the cache entirely.\n\nconst cacheKey = id => `sim-cache-${id}`\n\nconst readCache = item => {\n try {\n const c = JSON.parse(localStorage.getItem(cacheKey(item.id)) || 'null')\n return c?.text === (item.text || '') ? c : null\n } catch { return null }\n}\n\nconst writeCache = (item, data) => {\n try {\n localStorage.setItem(cacheKey(item.id), JSON.stringify({\n text: item.text || '',\n ts: Date.now(),\n ...data,\n }))\n } catch { /* storage unavailable or full */ }\n}\n\nconst cacheAge = ts => {\n const s = Math.floor((Date.now() - ts) / 1000)\n if (s < 60) return `${s}s ago`\n if (s < 3600) return `${Math.floor(s / 60)}m ago`\n if (s < 86400) return `${Math.floor(s / 3600)}h ago`\n return `${Math.floor(s / 86400)}d ago`\n}\n\n// \u2500\u2500 Styles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst STYLES = `\n .sim-form { display:flex; gap:6px; margin-bottom:8px; }\n .sim-input { flex:1; padding:6px 8px; font-size:14px; border:1px solid #ccc; border-radius:3px; }\n .sim-btn { padding:6px 14px; background:#c4561d; color:white; border:none;\n border-radius:3px; cursor:pointer; font-size:14px; }\n .sim-btn:disabled { opacity:0.5; cursor:not-allowed; }\n .sim-status { font-size:12px; color:#888; margin-bottom:6px; min-height:16px; }\n .sim-results { margin-top:4px; }\n .sim-result { display:flex; align-items:center; gap:8px; padding:3px 0;\n border-bottom:1px solid #f0f0f0; }\n .sim-flag { width:16px; height:16px; vertical-align:middle; margin-right:4px; }\n .sim-link { font-size:14px; color:#406; flex:1; }\n .sim-domain { font-size:11px; color:#999; }\n .similar-results h3, .sim-list h3 { margin:4px 0 6px; font-size:14px; color:#555; }\n .similar-results ul { margin:0; padding-left:18px; }\n .similar-results li { font-size:14px; padding:2px 0; }\n .similar-results .sim-domain { margin-left:6px; }\n .sim-count { font-size:12px; color:#888; margin:4px 0 0; }\n .sim-list table { border-collapse:collapse; width:100%; font-size:13px; }\n .sim-list th { text-align:left; font-size:11px; color:#888; padding:2px 8px 4px 0;\n border-bottom:1px solid #ddd; }\n .sim-list td { padding:3px 8px 3px 0; border-bottom:1px solid #f0f0f0; vertical-align:middle; }\n .sim-list td:last-child { text-align:right; color:#999; font-size:11px; }\n .sim-list .sim-flag { margin-right:6px; }\n`\n\nconst siteFlag = (domain, score) =>\n `<img class=\"sim-flag remote\" src=\"${window.wiki.site(domain).flag()}\"\n title=\"${domain} \u2014 score ${score.toFixed(3)}\"\n data-site=\"${domain}\">`\n\n// \u2500\u2500 similarity item \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst simLink = (domain, slug, title, score) =>\n `<a class=\"sim-link\" data-title=\"${title}\" data-slug=\"${slug}\" data-site=\"${domain}\" href=\"#\">` +\n `${siteFlag(domain, score)} ${title}</a>`\n\nexport const emit = (div, item) => {\n const { mode, specs, threshold, limit, force, ghostUrl, label } = parseDSL(item?.text || '')\n if (mode === 'ghost') {\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-form\">\n <button class=\"sim-btn\">${label || 'Open'}</button>\n </div>\n <div class=\"sim-status\"></div>\n </div>`)\n } else if (mode === 'build') {\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-form\">\n <button class=\"sim-btn\">${label || `Index ${specs.length ? specs.join(', ') : '*'}${force ? ' (force)' : ''}`}</button>\n </div>\n <div class=\"sim-status\"></div>\n </div>`)\n } else if (mode === 'list') {\n const label = specs.length ? specs.join(', ') : '*'\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-status\">Loading indexed domains (${label})\u2026</div>\n <div class=\"sim-list\"></div>\n </div>`)\n } else if (mode === 'similar') {\n const label = specs.length ? specs.join(', ') : 'current domain'\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-status\">Finding similar pages across ${label}\u2026</div>\n <div class=\"sim-results\"></div>\n </div>`)\n } else if (mode === 'author' || mode === 'report') {\n const label = specs.length ? specs.join(', ') : '(current domain)'\n const btnLabel = mode === 'report' ? 'Report' : 'Author'\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-form\">\n <input class=\"sim-input\" type=\"text\" placeholder=\"Search wiki pages\u2026\" />\n <button class=\"sim-btn\">${btnLabel}</button>\n </div>\n <div class=\"sim-status\">Domains: ${label}</div>\n <div class=\"sim-results\"></div>\n </div>`)\n } else {\n const label = specs.length ? specs.join(', ') : '(current domain)'\n div.html(`\n <style>${STYLES}</style>\n <div class=\"similarity\" data-id=\"${item.id}\">\n <div class=\"sim-form\">\n <input class=\"sim-input\" type=\"text\" placeholder=\"Search wiki pages\u2026\" />\n <button class=\"sim-btn\">Search</button>\n </div>\n <div class=\"sim-status\">Domains: ${label}</div>\n <div class=\"sim-results\"></div>\n </div>`)\n }\n}\n\nexport const bind = (div, item) => {\n const { mode, specs, threshold, limit, live, force, ghostUrl, thresholdSet } =\n parseDSL(item?.text || '')\n const origin = window.location.origin\n const status = div.find('.sim-status')[0]\n const cache = live ? null : readCache(item)\n\n // Standardised pre-search status: what will run, over how much, with what config.\n // e.g. \"Report ready \u2014 18,583 pages across 267 domains \u00B7 threshold 0.68 \u00B7 limit 20 \u00B7 LIVE\"\n const configSummary = (verb, pages, nDomains) => {\n const parts = [`${verb} \u2014 ${pages.toLocaleString()} pages across ${nDomains} domains`]\n if (thresholdSet) parts.push(`threshold ${threshold}`)\n parts.push(`limit ${limit}`)\n if (live) parts.push('LIVE')\n return parts.join(' \u00B7 ')\n }\n\n div.on('dblclick', e => {\n if ($(e.target).closest('.sim-input').length) return\n window.wiki.textEditor(div, item)\n })\n div.on('click', '.sim-link', function (e) {\n e.preventDefault()\n const $a = $(this)\n // shift-click appends at the end of the lineup instead of truncating after this page\n const $page = e.shiftKey ? null : div.parents('.page')\n window.wiki.doInternalLink($a.data('title'), $page, $a.data('site'))\n })\n\n const scopeLabel = !specs.length || (specs.length === 1 && specs[0] === '*')\n ? 'on farm'\n : specs.length === 1 ? `on ${specs[0]}` : 'in domains'\n\n const cacheNote = ts => ts ? ` \u00B7 cached ${cacheAge(ts)}` : ''\n\n if (mode === 'list') {\n const listDiv = div.find('.sim-list')[0]\n const patterns = specs.length ? specs.join(',') : '*'\n\n const renderList = (domains, ts) => {\n const totalPages = domains.reduce((n, d) => n + (d.page_count || 0), 0)\n status.style.display = 'none'\n listDiv.innerHTML = `<h3>Indexed Farm Domains</h3>\n <table>\n <tr><th>Domain</th><th>Pages</th></tr>\n ${domains.map(({ domain, page_count }) => `\n <tr>\n <td><img class=\"sim-flag remote\" src=\"${window.wiki.site(domain).flag()}\"\n title=\"${domain}\" data-site=\"${domain}\"> ${domain}</td>\n <td>${page_count != null ? page_count.toLocaleString() : '\u2014'}</td>\n </tr>`).join('')}\n </table>\n <p class=\"sim-count\">${domains.length} domains \u2014 ${totalPages.toLocaleString()} pages${cacheNote(ts)}</p>`\n }\n\n if (cache?.domains) {\n renderList(cache.domains, cache.ts)\n } else {\n ;(async () => {\n try {\n const url = `${origin}/system/indexed-domains.json?pattern=${encodeURIComponent(patterns)}&limit=${limit}`\n const res = await fetch(url)\n if (!res.ok) throw new Error(`indexed-domains failed: ${res.status}`)\n const domains = await res.json()\n if (!domains.length) { status.textContent = 'No indexed domains found'; return }\n renderList(domains, null)\n writeCache(item, { domains })\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n }\n })()\n }\n\n } else if (mode === 'similar') {\n const results = div.find('.sim-results')[0]\n\n const renderScored = (scored, ts) => {\n if (!scored.length) {\n status.textContent = `No similar pages found above threshold ${threshold}`\n return\n }\n results.innerHTML = `<h3>Similar Pages</h3><ul>${\n scored.map(({ domain, slug, title, score }) =>\n `<li>${simLink(domain, slug, title, score)}</li>`).join('')\n }</ul><p class=\"sim-count\">${scored.length} found ${scopeLabel}${cacheNote(ts)}</p>`\n status.style.display = 'none'\n }\n\n if (cache?.scored) {\n renderScored(cache.scored, cache.ts)\n } else {\n ;(async () => {\n try {\n const $page = div.parents('.page')\n const pageTitle = $page.find('.title').text().trim() || document.title\n const currentSlug = slugify(pageTitle)\n const currentDomain = window.location.hostname\n\n const domainEntries = await loadDomainEntries(specs, origin)\n const total = domainEntries.reduce((n, e) => n + e.pages.length, 0)\n status.textContent = `Searching ${total.toLocaleString()} pages\u2026`\n\n let qVec = await lookupPageVector(currentSlug, currentDomain)\n if (!qVec) {\n status.textContent = 'Embedding page (not yet indexed)\u2026'\n const pageText = $page.find('.item').map((_, el) => $(el).text().trim()).get().filter(Boolean).join('\\n')\n qVec = await getEmbedding(pageText || pageTitle, origin)\n }\n\n const scored = cosineScan(qVec, domainEntries, {\n threshold, limit, excludeSlug: currentSlug, excludeDomain: currentDomain,\n })\n renderScored(scored, null)\n if (scored.length) writeCache(item, { scored })\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n }\n })()\n }\n\n } else if (mode === 'ghost') {\n // Ghost mode \u2014 fetch page-json from any URL and open it as a ghost page\n const btn = div.find('.sim-btn')[0]\n\n const doGhost = async () => {\n if (!ghostUrl) { status.textContent = 'No URL \u2014 GHOST needs a page-json URL'; return }\n btn.disabled = true\n status.textContent = 'Fetching\u2026'\n try {\n const res = await fetch(ghostUrl)\n if (!res.ok) throw new Error(`fetch failed: ${res.status}`)\n const page = await res.json()\n window.wiki.showResult(window.wiki.newPage(page), { $page: div.parents('.page') })\n status.textContent = ''\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n } finally {\n btn.disabled = false\n }\n }\n\n btn.addEventListener('click', doGhost)\n\n } else if (mode === 'build') {\n // Build mode \u2014 trigger a semantic index build; result opens as a ghost page\n const btn = div.find('.sim-btn')[0]\n\n const doBuild = async () => {\n btn.disabled = true\n status.textContent = 'Building index\u2026 (may take a while for large scopes)'\n try {\n const domains = encodeURIComponent((specs.length ? specs : ['*']).join(','))\n const res = await fetch(\n `http://api.localhost/build-index.json?domains=${domains}&force=${force ? 1 : 0}`)\n if (!res.ok) throw new Error(`build-index failed: ${res.status}`)\n const page = await res.json()\n window.wiki.showResult(window.wiki.newPage(page), { $page: div.parents('.page') })\n status.textContent = ''\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n } finally {\n btn.disabled = false\n }\n }\n\n btn.addEventListener('click', doBuild)\n\n } else if (mode === 'report') {\n // Report mode \u2014 server-side ranked/bundled search, opens result as ghost page\n const input = div.find('.sim-input')[0]\n const btn = div.find('.sim-btn')[0]\n let readyLine = `Domains: ${specs.length ? specs.join(', ') : '*'}`\n\n // Preload the lightweight domain listing (counts only, no vectors) so the\n // status line shows scope and config before any search is issued.\n ;(async () => {\n try {\n const domains = await resolveDomains(specs.length ? specs : ['*'], origin)\n const pages = domains.reduce((n, d) => n + (d.page_count || 0), 0)\n readyLine = configSummary('Report ready', pages, domains.length)\n status.textContent = readyLine\n } catch (e) {\n status.textContent = `Domain listing unavailable: ${e.message}`\n }\n })()\n\n const doReport = async () => {\n const query = input.value.trim()\n if (!query) return\n btn.disabled = true\n status.textContent = 'Generating report\u2026'\n try {\n const body = { query, domains: specs.length ? specs : ['*'], limit, live }\n if (thresholdSet) body.threshold = threshold\n const res = await fetch('http://api.localhost/search-report', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!res.ok) throw new Error(`search-report failed: ${res.status}`)\n const page = await res.json()\n const pageObj = window.wiki.newPage(page)\n window.wiki.showResult(pageObj, { $page: div.parents('.page') })\n status.textContent = readyLine\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n } finally {\n btn.disabled = false\n }\n }\n\n btn.addEventListener('click', doReport)\n input.addEventListener('keydown', e => { if (e.key === 'Enter') doReport() })\n\n } else if (mode === 'author') {\n // Author mode \u2014 same search form but creates a ghost page from results\n const input = div.find('.sim-input')[0]\n const btn = div.find('.sim-btn')[0]\n const results = div.find('.sim-results')[0]\n let domainEntries = null\n\n ;(async () => {\n try {\n if (!cache) status.textContent = 'Resolving domains\u2026'\n domainEntries = await loadDomainEntries(specs, origin)\n const total = domainEntries.reduce((n, e) => n + e.pages.length, 0)\n status.textContent = configSummary('Author ready', total, domainEntries.length)\n } catch (e) {\n status.textContent = `Load error: ${e.message}`\n }\n })()\n\n const doAuthor = async () => {\n const query = input.value.trim()\n if (!query || !domainEntries) return\n btn.disabled = true\n status.textContent = 'Embedding query\u2026'\n results.innerHTML = ''\n try {\n const qVec = await getEmbedding(query, origin)\n const scored = cosineScan(qVec, domainEntries,\n { threshold, limit, excludeSlug: null, excludeDomain: null })\n\n // Unique titles for wiki-link list\n const seenTitles = new Set()\n const uniqueTitles = []\n for (const { title } of scored) {\n if (!seenTitles.has(title)) { seenTitles.add(title); uniqueTitles.push(title) }\n }\n\n const hexId = () => Math.floor(Math.random() * 0xffffffffffffffff).toString(16).padStart(16, '0')\n const primaryLines = uniqueTitles.map(t => `- [[${t}]]`).join('\\n')\n\n const story = [\n { type: 'markdown', id: hexId(), text: `# Similar Pages\\n\\n${primaryLines}` },\n { type: 'markdown', id: hexId(), text: '# Reference Links' },\n ...scored.map(({ domain, slug, title, score }) => ({\n type: 'reference', id: hexId(), site: domain, slug, title,\n text: `score ${score.toFixed(3)}`,\n })),\n ]\n\n const pageObj = window.wiki.newPage({ title: `${query} Results`, story, journal: [] })\n window.wiki.showResult(pageObj, { $page: div.parents('.page') })\n\n status.textContent = `${scored.length} pages found`\n writeCache(item, { scored, query })\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n } finally {\n btn.disabled = false\n }\n }\n\n btn.addEventListener('click', doAuthor)\n input.addEventListener('keydown', e => { if (e.key === 'Enter') doAuthor() })\n\n } else {\n // Search form mode\n const input = div.find('.sim-input')[0]\n const btn = div.find('.sim-btn')[0]\n const results = div.find('.sim-results')[0]\n let domainEntries = null\n\n // Show cached results immediately while domains preload in background\n if (cache?.scored) {\n input.value = cache.query || ''\n results.innerHTML = cache.scored.map(({ domain, slug, title, score }) =>\n `<div class=\"sim-result\">${simLink(domain, slug, title, score)}</div>`).join('') +\n `<p class=\"sim-count\">Top ${cache.scored.length} for \"${cache.query || ''}\"${cacheNote(cache.ts)}</p>`\n status.textContent = ''\n }\n\n ;(async () => {\n try {\n if (!cache) status.textContent = 'Resolving domains\u2026'\n domainEntries = await loadDomainEntries(specs, origin)\n const total = domainEntries.reduce((n, e) => n + e.pages.length, 0)\n status.textContent = configSummary('Search ready', total, domainEntries.length)\n } catch (e) {\n status.textContent = `Load error: ${e.message}`\n }\n })()\n\n const doSearch = async () => {\n const query = input.value.trim()\n if (!query || !domainEntries) return\n btn.disabled = true\n status.textContent = 'Embedding query\u2026'\n results.innerHTML = ''\n try {\n const qVec = await getEmbedding(query, origin)\n const scored = cosineScan(qVec, domainEntries,\n { threshold, limit, excludeSlug: null, excludeDomain: null })\n results.innerHTML = scored.map(({ domain, slug, title, score }) =>\n `<div class=\"sim-result\">${simLink(domain, slug, title, score)}</div>`).join('') +\n `<p class=\"sim-count\">Top ${scored.length} for \"${query}\"</p>`\n status.textContent = ''\n writeCache(item, { scored, query })\n } catch (e) {\n status.textContent = `Error: ${e.message}`\n } finally {\n btn.disabled = false\n }\n }\n\n btn.addEventListener('click', doSearch)\n input.addEventListener('keydown', e => { if (e.key === 'Enter') doSearch() })\n }\n}\n\n// \u2500\u2500 Register item type with the wiki \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nif (typeof window !== 'undefined') {\n window.plugins = window.plugins || {}\n window.plugins.similarity = { emit, bind }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["SIMILAR_THRESHOLDS", "DEFAULT_THRESHOLD", "DEFAULT_LIMIT", "parseDSL", "text", "specs", "threshold", "limit", "mode", "live", "force", "ghostUrl", "label", "isCmd", "upper", "kw", "val", "line", "raw", "level", "tv", "isGlob", "spec", "isScope", "slugify", "title", "domainCache", "resolveDomainsForSpec", "origin", "url", "res", "list", "resolveDomains", "seen", "result", "item", "vectorCache", "vectorUrl", "domain", "loadVectors", "data", "getEmbedding", "lookupPageVector", "slug", "entry", "p", "cosineScan", "queryVec", "domainEntries", "excludeSlug", "excludeDomain", "results", "pages", "vector", "dot", "i", "a", "loadDomainEntries", "domains", "e", "cacheKey", "id", "readCache", "c", "writeCache", "cacheAge", "ts", "s", "STYLES", "siteFlag", "score", "simLink", "emit", "div", "btnLabel", "bind", "thresholdSet", "status", "cache", "configSummary", "verb", "nDomains", "parts", "$a", "$page", "scopeLabel", "cacheNote", "listDiv", "patterns", "renderList", "totalPages", "n", "d", "page_count", "renderScored", "scored", "pageTitle", "currentSlug", "currentDomain", "total", "qVec", "pageText", "_", "el", "btn", "doGhost", "page", "doBuild", "input", "readyLine", "doReport", "query", "body", "pageObj", "doAuthor", "seenTitles", "uniqueTitles", "hexId", "primaryLines", "t", "story", "doSearch"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"type": "markdown",
|
|
11
11
|
"id": "a3f7c8d2e1b94056",
|
|
12
|
-
"text": "**Version:** 0.4.
|
|
12
|
+
"text": "**Version:** 0.4.3 — [GitHub](https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity) · [npm](https://www.npmjs.com/package/wiki-plugin-similarity)"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"type": "similarity",
|
package/server/server.js
CHANGED
|
@@ -213,7 +213,12 @@ export const startServer = ({ argv, app }) => {
|
|
|
213
213
|
if (!vecFile) {
|
|
214
214
|
return res.status(404).json({ error: `vectors not found for ${domain}` })
|
|
215
215
|
}
|
|
216
|
-
|
|
216
|
+
fs.readFile(vecFile, 'utf8', (err, data) => {
|
|
217
|
+
if (err) {
|
|
218
|
+
return res.status(500).json({ error: `unable to read vectors for ${domain}: ${err.message}` })
|
|
219
|
+
}
|
|
220
|
+
res.type('application/json').send(data)
|
|
221
|
+
})
|
|
217
222
|
})
|
|
218
223
|
|
|
219
224
|
// ── GET /system/embed.json?text=… ────────────────────────────────────────
|