wiki-plugin-similarity 0.24.1 → 0.25.0

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.
@@ -1,9 +1,9 @@
1
- /* wiki-plugin-similarity - 0.24.1 - Sat, 05 Sep 2026 14:50:31 GMT */
2
- (()=>{var he={high:.78,medium:.68,low:.58},de=he.medium,Le=10,ue=e=>{let t=[],n=[],s=[],i=null,a=null,l="search",u=!1,E=!1,f=!1,R=null,O=null,D=null,U=null,p=!1,I=(d,m)=>d===m||d.startsWith(m)&&/^[\s:]/.test(d.slice(m.length)),x=(d,m)=>d.slice(m.length).replace(/^\s*:?\s*/,"").trim();for(let d of e.split(`
3
- `)){let m=d.trim();if(!m||m.startsWith("#"))continue;let b=m.toUpperCase();if(I(b,"LIVE")){u=!0;continue}if(I(b,"AUTO")){p=!0;continue}if(I(b,"SUBJECT")){E=!0;continue}if(I(b,"AUTHOR")){!t.length&&l==="search"&&(l="author");continue}if(I(b,"REPORT")){l==="search"&&(l="report");continue}if(I(b,"KEYWORD")){l==="search"&&(l="keyword");continue}if(I(b,"SITES")){l==="search"&&(l="sites");continue}if(I(b,"BUILD")){l==="search"&&(l="build");continue}if(I(b,"FORCE")){f=!0;continue}if(I(b,"GHOST")){R=x(m,"GHOST"),l==="search"&&(l="ghost");continue}if(I(b,"BUTTON")){O=x(m,"BUTTON");continue}if(I(b,"BATCH")){let C=x(m,"BATCH").toLowerCase();D=C==="off"||C==="no"||C==="0"?0:parseInt(C)||100;continue}if(I(b,"ALGORITHM")){U=x(m,"ALGORITHM").replace(/^\[\[|\]\]$/g,"").trim()||"search-algorithm",l==="search"&&!t.length&&(l="algorithm");continue}if(I(b,"ROSTER")){let C=x(m,"ROSTER");C&&n.push(C);continue}if(I(b,"FARM")){let C=x(m,"FARM");C&&s.push(C);continue}if(I(b,"LIST")){!t.length&&l==="search"&&(l="list");continue}if(I(b,"STATUS")){l==="search"&&(l="status");continue}if(I(b,"SIMILAR")){let C=x(b,"SIMILAR").toLowerCase();i=he[C]||de,!t.length&&l==="search"&&(l="similar");continue}if(I(b,"THRESHOLD")){let C=x(m,"THRESHOLD");i=he[C.toLowerCase()]??(parseFloat(C)||de);continue}if(I(b,"LIMIT")){a=parseInt(x(m,"LIMIT"))||Le;continue}t.push(["PUBLIC","LOCAL","PRIVATE","GALAXY","SITE"].includes(b)?b:m)}return{mode:l,specs:t,rosterRefs:n,farms:s,threshold:i??de,limit:a??Le,live:u,subject:E,force:f,ghostUrl:R,label:O,batch:D,algorithm:U,auto:p,thresholdSet:i!==null}},ne=e=>e.includes("*")||e.includes("?"),se=e=>e==="PUBLIC"||e==="LOCAL"||e==="PRIVATE"||e==="GALAXY";var je=e=>e.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,""),me=new Map,rt=async(e,t)=>{if(me.has(e))return me.get(e);let n=`${t}/system/indexed-domains.json?pattern=${encodeURIComponent(e)}`,s=await fetch(n);if(!s.ok)throw new Error(`indexed-domains failed: ${s.status}`);let i=await s.json();return me.set(e,i),i},te=async(e,t)=>{e.length||(e=[window.location.hostname]);let n=new Set,s=[];for(let i of e)if(i==="*"||ne(i)||se(i))for(let a of await rt(i,t))n.has(a.domain)||(n.add(a.domain),s.push(a));else n.has(i)||(n.add(i),s.push({domain:i,page_count:null}));return s},lt=/^([a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+|localhost)(:\d+)?$/,ct=/^ROSTER ([A-Za-z0-9.\-:]+\/[a-z0-9-]+)$/,dt=/^REFERENCES ([A-Za-z0-9.\-:]+\/[a-z0-9-]+)$/,Ee=async e=>{let t=e.indexOf("/"),n=await fetch(`//${e.slice(0,t)}/${e.slice(t+1)}.json`);if(!n.ok)throw new Error(`roster page ${e} failed: ${n.status}`);return n.json()},Ce=async(e,t,n)=>{if(n.has(e))return;n.add(e);let s;try{s=await Ee(e)}catch(i){console.warn(i.message);return}for(let i of s.story||[])if(i.type==="roster")for(let a of(i.text||"").split(/\r?\n/)){let l=a.trim();if(!l)continue;let u=l.match(lt);if(u){t.add(u[0]);continue}let E=l.match(ct);if(E){await Ce(E[1],t,n);continue}let f=l.match(dt);if(f)try{let R=await Ee(f[1]);for(let O of R.story||[])O.type==="reference"&&O.site&&t.add(O.site)}catch(R){console.warn(R.message)}}},ht=async e=>{let t=new Set,n=new Set;for(let s of e)await Ce(s,t,n);return[...t]},Te=async(e,t)=>{let n=typeof window<"u"&&window.location?window.location.hostname:null,s=e.map(i=>i.toUpperCase()==="SITE"&&n?n:i);return t.length?[...s,...await ht(t)]:s};var pe=new Map,ut=e=>`${window.location.origin}/system/semantic-vectors.json?domain=${encodeURIComponent(e)}`,mt=async e=>{if(pe.has(e))return pe.get(e);let t=await fetch(ut(e));if(!t.ok)return[];let n=await t.json();return pe.set(e,n),n},fe=async(e,t)=>{let n=await fetch(`${t}/system/embed.json`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:e})});if(!n.ok)throw new Error(`embed failed: ${n.status}`);return(await n.json()).vector};var ge=(e,t,{threshold:n,limit:s,excludeSlug:i,excludeDomain:a})=>{let l=[];for(let{domain:u,pages:E}of t)for(let{slug:f,title:R,vector:O}of E){if(f===i&&u===a)continue;let D=0;for(let U=0;U<e.length;U++)D+=e[U]*O[U];D>=n&&l.push({domain:u,slug:f,title:R,score:D})}return l.sort((u,E)=>E.score-u.score),l.slice(0,s)},we=async(e,t)=>{let n=await te(e,t);return(await Promise.all(n.map(async({domain:i})=>({domain:i,pages:await mt(i)})))).filter(i=>i.pages.length>0)};var pt=new Set(["selected-plugin-pages","search-tool","like-this-page","semantic-search","search-algorithm","personal-search-demo","search-tool-demo"]),ft=e=>(e.attr("id")||"").split("_rev")[0],Ie=e=>{let t=e.parents(".page:first"),n=t.prev(".page");for(;n.length&&pt.has(ft(n));)n=n.prev(".page");let s=n.length?n:t,i=(s.attr("id")||"").split("_rev")[0],a=s.data("site")||window.location.hostname,l=s.data("data")||{},u=l.title||s.find(".title").text().trim()||i,E=[u,...(l.story||[]).filter(f=>f.type==="markdown"||f.type==="paragraph").map(f=>(f.text||"").trim()).filter(Boolean)].join(`
4
- `).slice(0,2e3);return{slug:i,site:a,title:u,text:E,isSelf:!n.length}},J=e=>`Subject: ${e.title}${e.isSelf?" (this page)":` @ ${e.site}`}`;var Ae=e=>`sim-cache-${e}`,Re=e=>{try{let t=JSON.parse(localStorage.getItem(Ae(e.id))||"null");return t?.text===(e.text||"")?t:null}catch{return null}},ee=(e,t)=>{try{localStorage.setItem(Ae(e.id),JSON.stringify({text:e.text||"",ts:Date.now(),...t}))}catch{}},ye=e=>{let t=Math.floor((Date.now()-e)/1e3);return t<60?`${t}s ago`:t<3600?`${Math.floor(t/60)}m ago`:t<86400?`${Math.floor(t/3600)}h ago`:`${Math.floor(t/86400)}d ago`};var F=e=>`${e.site} ${e.slug}`,gt=(e,t,n)=>{let s=new Set(e.map(F));return t.slice(0,n).filter(i=>!s.has(F(i))).length},wt=(e,t,n,s)=>{let i=new Map;e.forEach((R,O)=>{s.has(F(R))&&i.set(O,R)});let a=new Set([...i.values()].map(F)),l=t.filter(R=>!a.has(F(R))),u=[],E=0,f=Math.max(n,i.size?Math.max(...i.keys())+1:0);for(let R=0;R<f;R++)i.has(R)?u.push(i.get(R)):E<l.length&&u.push(l[E++]);return u},yt=(e,t)=>{let n=new Map(t.map(s=>[F(s),s]));return e.map(s=>n.get(F(s))||s)},$t=()=>{let e=new Set;try{for(let t of Object.keys(window.wiki?.neighborhood||{}))e.add(t);document.querySelectorAll(".page").forEach(t=>{let n=window.$?window.$(t).data("site"):null;e.add(n||window.location.hostname)})}catch{}return[...e]},Oe=async(e,t)=>{let n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!n.ok){let s=new Error(`${e.split("/").pop()} failed: ${n.status}`);throw s.status=n.status,s.body=await n.json().catch(()=>({})),s}return n.json()},bt=(e,t,n)=>{let s=e.slice(0,n).map(F).join("|"),i=t.slice(0,n).map(F).join("|");return s===i},Me=async e=>{let{origin:t,query:n,seeded:s={},specs:i,limit:a,batch:l,algorithm:u,render:E,resume:f}=e,R=M=>e.status&&e.status(M),O;if(f&&Array.isArray(f.remaining))O={batches:f.remaining,count:f.total,sites:f.sites||[],siteIndex:f.siteIndex||null,unindexed:f.unindexed||[]};else{let M={roster:[...e.roster||[],...u&&u.liked||[]],neighborhood:$t(),followed:!0,...e.prefer||{}};R("Ranking sites\u2026"),O=await Oe(`${t}/system/site-rank.json`,{query:n,...s,domains:i,prefer:M,algorithm:u||{},batch:l||100})}let D=O.batches||[],U=O.count||D.reduce((M,G)=>M+G.length,0),p={searched:f?.searched||0,total:U,batches:D.length,done:0,stopped:!1,converged:!1,running:!0,unindexed:O.unindexed||[],answered:f?.answered||{local:0},siteIndex:O.siteIndex||null,siteInfo:new Map((O.sites||[]).map(M=>[M.domain,M])),shown:f?.shown||[],pending:0,opened:new Set(f?.opened||[]),resumed:!!f},I=new Map((f?.merged||[]).map(M=>[F(M),M])),x=[...I.values()].sort((M,G)=>G.score-M.score),d=0,m=()=>{p.stopped=!0};p.stop=m;let b=()=>{try{e.save&&e.save(p.snapshot())}catch{}};p.foldIn=()=>{p.shown=wt(p.shown,x,a,p.opened),p.pending=0,E(x,p),b()},p.markOpened=M=>{p.opened.add(M),E(x,p),b()},p.snapshot=()=>({query:n,total:U,searched:p.searched,converged:p.converged,stopped:p.stopped,merged:x.slice(0,150),shown:p.shown,opened:[...p.opened],remaining:D.slice(p.done),answered:p.answered,siteIndex:p.siteIndex,unindexed:p.unindexed,sites:[...p.siteInfo.values()].slice(0,400)});let C=async M=>{let G={query:n,...s,domains:M,limit:Math.max(a*3,30),flat:!0};e.thresholdSet&&(G.threshold=e.threshold);let h=await Oe(`${t}/system/search-report.json`,G);for(let r of h.results||[]){let o=F(r),g=I.get(o);(!g||g.score<r.score)&&I.set(o,r)}let y=0;for(let r of h.peers||[])p.answered[r.host]=(p.answered[r.host]||0)+(r.domains||0),y+=r.domains||0;p.answered.local+=Math.max(0,(h.stats&&h.stats.domains||0)-y);let c=[...I.values()].sort((r,o)=>o.score-r.score),w=bt(x,c,a);x=c,p.searched+=M.length,p.done+=1,d=w?d+1:0,p.shown.length?p.shown=yt(p.shown,c):p.shown=c.slice(0,a),p.pending=gt(p.shown,c,a)},W=()=>D.slice(p.done),X=async(M=!1)=>{for(p.running=!0,p.stopped=!1;p.done<D.length&&!p.stopped;)if(await C(D[p.done]),E(x,p),b(),!M&&d>=3&&p.done<D.length){p.converged=!0;break}return p.running=!1,E(x,p),b(),x};return p.continueAll=()=>(p.converged=!1,d=-1/0,X(!0)),f&&f.remaining&&!f.remaining.length?(p.running=!1,p.converged=!!f.converged,E(x,p),{results:x,state:p,remaining:W}):(await X(!1),{results:x,state:p,remaining:W})};var ie="similarity:learned",xt=new Set(["liked","visited","neighbourhood","neighborhood","followed","centroid","fresh","reliable","stale"]),oe=/^([a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+|localhost)(:\d+)?$/,$e=e=>{let t={weights:{},always:[],never:[],batch:null,trust:null};for(let n of(e||"").split(`
5
- `)){let s=n.trim();if(!s||s.startsWith("#"))continue;let[i,...a]=s.split(/\s+/),l=i.toUpperCase().replace(/:$/,"");if(l==="WEIGHT"&&a.length>=2){let u=a[0].toLowerCase().replace("neighborhood","neighbourhood"),E=parseFloat(a[1]);xt.has(u)&&Number.isFinite(E)&&(t.weights[u]=E)}else l==="ALWAYS"&&a[0]&&oe.test(a[0])?t.always.push(a[0].toLowerCase()):l==="NEVER"&&a[0]&&oe.test(a[0])?t.never.push(a[0].toLowerCase()):l==="BATCH"&&a[0]?t.batch=parseInt(a[0])||null:l==="TRUST"&&a[0]&&["solid","flaky","any"].includes(a[0].toLowerCase())&&(t.trust=a[0].toLowerCase())}return t},be=()=>{try{let e=JSON.parse(localStorage.getItem(ie)||"{}"),t={};for(let[n,s]of Object.entries(e.sites||e)){let i=typeof s=="number"?s:s&&typeof s.score=="number"?s.score:0;i>0&&(t[n.toLowerCase()]=Math.min(1,i))}return t}catch{return{}}},St=(e,t)=>{if(!e)return`${t}/search-algorithm.json`;let n=e.indexOf("/");if(n>0&&oe.test(e.slice(0,n)))return`//${e.slice(0,n)}/${e.slice(n+1)}.json`;let s=e.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"");return`${t}/${s}.json`},vt=e=>{let t=new Set;for(let n of e.story||[])if(n.type==="roster")for(let s of(n.text||"").split(/\r?\n/)){let i=s.trim();oe.test(i)&&t.add(i.toLowerCase())}return[...t]},De=new Map,Pe=async(e,t)=>{let n=St(e,t),s=De.get(n);if(!s){s={weights:{},always:[],never:[],liked:[],batch:null,trust:null,page:null};try{let i=await fetch(n);if(i.ok){let a=await i.json(),l=(a.story||[]).find(u=>u.type==="similarity"&&/^\s*ALGORITHM\b/im.test(u.text||""));l&&Object.assign(s,$e(l.text)),s.liked=vt(a),s.page={url:n,title:a.title}}}catch{}De.set(n,s)}return{...s,learned:be()}};var kt={neighbourhood:.1,visited:.2,clicked:.35},Lt=7*864e5,Ue=200,Be=()=>{try{return JSON.parse(localStorage.getItem(ie)||"{}").sites||{}}catch{return{}}},Fe=e=>{try{localStorage.setItem(ie,JSON.stringify({sites:e,savedAt:Date.now()}))}catch{}},ae=(e,t)=>{let n=Math.max(0,(t-(e.at||t))/Lt);return(e.score||0)*Math.pow(.5,n)},re=(e,t)=>{if(!e||e==="localhost")return 0;let n=e.toLowerCase(),s=Date.now(),i=Be(),a=i[n]?ae(i[n],s):0,l=Math.min(1,a+(kt[t]||.1));i[n]={score:l,at:s};let u=Object.keys(i);if(u.length>Ue){u.sort((E,f)=>ae(i[f],s)-ae(i[E],s));for(let E of u.slice(Ue))delete i[E]}return Fe(i),l},Et=()=>{let e=Date.now();return Object.entries(Be()).map(([t,n])=>[t,ae(n,e)]).filter(([,t])=>t>.01).sort((t,n)=>n[1]-t[1])},Ne=()=>Fe({}),qe=!1,He=e=>{try{return window.$&&window.$(e).data("site")||window.location.hostname}catch{return window.location.hostname}},_e=()=>{if(qe||typeof document>"u")return;qe=!0;let e=new Set;document.querySelectorAll(".main > .page").forEach(n=>{let s=He(n);e.has(s)||(e.add(s),re(s,"neighbourhood"))});let t=document.querySelector(".main");!t||typeof MutationObserver>"u"||new MutationObserver(n=>{for(let s of n)for(let i of s.addedNodes)i.nodeType===1&&i.classList.contains("page")&&!i.classList.contains("ghost")&&re(He(i),"visited")}).observe(t,{childList:!0})},Ge=(e,t=20)=>new Promise((n,s)=>{let a=((e.data("data")||{}).story||[]).find(f=>f.type==="roster"&&/^\s*sites i visit\b/i.test(f.text||""));if(!a)return s(new Error('no "Sites I visit" roster on this page'));let l=Et().slice(0,t);if(!l.length)return s(new Error("nothing learned yet"));let u=["Sites I visit",...l.map(([f])=>f)].join(`
6
- `),E={...a,text:u};try{window.wiki.pageHandler.put(e,{type:"edit",id:a.id,item:E});let f=e.find(`.item[data-id="${a.id}"]`);f.length&&(f.data("item",E),window.wiki.plugins?.roster?.emit?.(f,E)),n(l.length)}catch(f){s(f)}});var K=`.sim-results li.sim-opened{opacity:.72}.sim-results .sim-mark{color:#6a6;margin-left:.3em}.sim-results li.sim-pending{list-style:none;margin-left:-1.2em}.sim-results .sim-fold{font-size:90%;padding:0 .4em}.sim-results .sim-progress{width:100%;height:6px;display:block;margin:2px 0 6px}
1
+ /* wiki-plugin-similarity - 0.25.0 - Sat, 05 Sep 2026 15:23:44 GMT */
2
+ (()=>{var ue={high:.78,medium:.68,low:.58},he=ue.medium,je=10,me=e=>{let t=[],n=[],s=[],i=null,a=null,l="search",u=!1,E=!1,f=!1,R=null,O=null,D=null,U=null,p=!1,T=(h,m)=>h===m||h.startsWith(m)&&/^[\s:]/.test(h.slice(m.length)),x=(h,m)=>h.slice(m.length).replace(/^\s*:?\s*/,"").trim();for(let h of e.split(`
3
+ `)){let m=h.trim();if(!m||m.startsWith("#"))continue;let b=m.toUpperCase();if(T(b,"LIVE")){u=!0;continue}if(T(b,"AUTO")){p=!0;continue}if(T(b,"SUBJECT")){E=!0;continue}if(T(b,"AUTHOR")){!t.length&&l==="search"&&(l="author");continue}if(T(b,"REPORT")){l==="search"&&(l="report");continue}if(T(b,"KEYWORD")){l==="search"&&(l="keyword");continue}if(T(b,"SITES")){l==="search"&&(l="sites");continue}if(T(b,"BUILD")){l==="search"&&(l="build");continue}if(T(b,"FORCE")){f=!0;continue}if(T(b,"GHOST")){R=x(m,"GHOST"),l==="search"&&(l="ghost");continue}if(T(b,"BUTTON")){O=x(m,"BUTTON");continue}if(T(b,"BATCH")){let C=x(m,"BATCH").toLowerCase();D=C==="off"||C==="no"||C==="0"?0:parseInt(C)||100;continue}if(T(b,"ALGORITHM")){U=x(m,"ALGORITHM").replace(/^\[\[|\]\]$/g,"").trim()||"search-algorithm",l==="search"&&!t.length&&(l="algorithm");continue}if(T(b,"ROSTER")){let C=x(m,"ROSTER");C&&n.push(C);continue}if(T(b,"FARM")){let C=x(m,"FARM");C&&s.push(C);continue}if(T(b,"LIST")){!t.length&&l==="search"&&(l="list");continue}if(T(b,"STATUS")){l==="search"&&(l="status");continue}if(T(b,"DOOR")){l==="search"&&(l="door");continue}if(T(b,"SIMILAR")){let C=x(b,"SIMILAR").toLowerCase();i=ue[C]||he,!t.length&&l==="search"&&(l="similar");continue}if(T(b,"THRESHOLD")){let C=x(m,"THRESHOLD");i=ue[C.toLowerCase()]??(parseFloat(C)||he);continue}if(T(b,"LIMIT")){a=parseInt(x(m,"LIMIT"))||je;continue}t.push(["PUBLIC","LOCAL","PRIVATE","GALAXY","SITE"].includes(b)?b:m)}return{mode:l,specs:t,rosterRefs:n,farms:s,threshold:i??he,limit:a??je,live:u,subject:E,force:f,ghostUrl:R,label:O,batch:D,algorithm:U,auto:p,thresholdSet:i!==null}},se=e=>e.includes("*")||e.includes("?"),oe=e=>e==="PUBLIC"||e==="LOCAL"||e==="PRIVATE"||e==="GALAXY";var Ie=e=>e.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,""),pe=new Map,ut=async(e,t)=>{if(pe.has(e))return pe.get(e);let n=`${t}/system/indexed-domains.json?pattern=${encodeURIComponent(e)}`,s=await fetch(n);if(!s.ok)throw new Error(`indexed-domains failed: ${s.status}`);let i=await s.json();return pe.set(e,i),i},te=async(e,t)=>{e.length||(e=[window.location.hostname]);let n=new Set,s=[];for(let i of e)if(i==="*"||se(i)||oe(i))for(let a of await ut(i,t))n.has(a.domain)||(n.add(a.domain),s.push(a));else n.has(i)||(n.add(i),s.push({domain:i,page_count:null}));return s},mt=/^([a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+|localhost)(:\d+)?$/,pt=/^ROSTER ([A-Za-z0-9.\-:]+\/[a-z0-9-]+)$/,ft=/^REFERENCES ([A-Za-z0-9.\-:]+\/[a-z0-9-]+)$/,Ce=async e=>{let t=e.indexOf("/"),n=await fetch(`//${e.slice(0,t)}/${e.slice(t+1)}.json`);if(!n.ok)throw new Error(`roster page ${e} failed: ${n.status}`);return n.json()},Te=async(e,t,n)=>{if(n.has(e))return;n.add(e);let s;try{s=await Ce(e)}catch(i){console.warn(i.message);return}for(let i of s.story||[])if(i.type==="roster")for(let a of(i.text||"").split(/\r?\n/)){let l=a.trim();if(!l)continue;let u=l.match(mt);if(u){t.add(u[0]);continue}let E=l.match(pt);if(E){await Te(E[1],t,n);continue}let f=l.match(ft);if(f)try{let R=await Ce(f[1]);for(let O of R.story||[])O.type==="reference"&&O.site&&t.add(O.site)}catch(R){console.warn(R.message)}}},gt=async e=>{let t=new Set,n=new Set;for(let s of e)await Te(s,t,n);return[...t]},Ae=async(e,t)=>{let n=typeof window<"u"&&window.location?window.location.hostname:null,s=e.map(i=>i.toUpperCase()==="SITE"&&n?n:i);return t.length?[...s,...await gt(t)]:s};var fe=new Map,wt=e=>`${window.location.origin}/system/semantic-vectors.json?domain=${encodeURIComponent(e)}`,yt=async e=>{if(fe.has(e))return fe.get(e);let t=await fetch(wt(e));if(!t.ok)return[];let n=await t.json();return fe.set(e,n),n},ge=async(e,t)=>{let n=await fetch(`${t}/system/embed.json`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:e})});if(!n.ok)throw new Error(`embed failed: ${n.status}`);return(await n.json()).vector};var we=(e,t,{threshold:n,limit:s,excludeSlug:i,excludeDomain:a})=>{let l=[];for(let{domain:u,pages:E}of t)for(let{slug:f,title:R,vector:O}of E){if(f===i&&u===a)continue;let D=0;for(let U=0;U<e.length;U++)D+=e[U]*O[U];D>=n&&l.push({domain:u,slug:f,title:R,score:D})}return l.sort((u,E)=>E.score-u.score),l.slice(0,s)},ye=async(e,t)=>{let n=await te(e,t);return(await Promise.all(n.map(async({domain:i})=>({domain:i,pages:await yt(i)})))).filter(i=>i.pages.length>0)};var $t=new Set(["selected-plugin-pages","search-tool","like-this-page","semantic-search","search-algorithm","personal-search-demo","search-tool-demo"]),bt=e=>(e.attr("id")||"").split("_rev")[0],Re=e=>{let t=e.parents(".page:first"),n=t.prev(".page");for(;n.length&&$t.has(bt(n));)n=n.prev(".page");let s=n.length?n:t,i=(s.attr("id")||"").split("_rev")[0],a=s.data("site")||window.location.hostname,l=s.data("data")||{},u=l.title||s.find(".title").text().trim()||i,E=[u,...(l.story||[]).filter(f=>f.type==="markdown"||f.type==="paragraph").map(f=>(f.text||"").trim()).filter(Boolean)].join(`
4
+ `).slice(0,2e3);return{slug:i,site:a,title:u,text:E,isSelf:!n.length}},J=e=>`Subject: ${e.title}${e.isSelf?" (this page)":` @ ${e.site}`}`;var Oe=e=>`sim-cache-${e}`,Me=e=>{try{let t=JSON.parse(localStorage.getItem(Oe(e.id))||"null");return t?.text===(e.text||"")?t:null}catch{return null}},ee=(e,t)=>{try{localStorage.setItem(Oe(e.id),JSON.stringify({text:e.text||"",ts:Date.now(),...t}))}catch{}},$e=e=>{let t=Math.floor((Date.now()-e)/1e3);return t<60?`${t}s ago`:t<3600?`${Math.floor(t/60)}m ago`:t<86400?`${Math.floor(t/3600)}h ago`:`${Math.floor(t/86400)}d ago`};var F=e=>`${e.site} ${e.slug}`,xt=(e,t,n)=>{let s=new Set(e.map(F));return t.slice(0,n).filter(i=>!s.has(F(i))).length},vt=(e,t,n,s)=>{let i=new Map;e.forEach((R,O)=>{s.has(F(R))&&i.set(O,R)});let a=new Set([...i.values()].map(F)),l=t.filter(R=>!a.has(F(R))),u=[],E=0,f=Math.max(n,i.size?Math.max(...i.keys())+1:0);for(let R=0;R<f;R++)i.has(R)?u.push(i.get(R)):E<l.length&&u.push(l[E++]);return u},St=(e,t)=>{let n=new Map(t.map(s=>[F(s),s]));return e.map(s=>n.get(F(s))||s)},kt=()=>{let e=new Set;try{for(let t of Object.keys(window.wiki?.neighborhood||{}))e.add(t);document.querySelectorAll(".page").forEach(t=>{let n=window.$?window.$(t).data("site"):null;e.add(n||window.location.hostname)})}catch{}return[...e]},De=async(e,t)=>{let n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!n.ok){let s=new Error(`${e.split("/").pop()} failed: ${n.status}`);throw s.status=n.status,s.body=await n.json().catch(()=>({})),s}return n.json()},Lt=(e,t,n)=>{let s=e.slice(0,n).map(F).join("|"),i=t.slice(0,n).map(F).join("|");return s===i},Pe=async e=>{let{origin:t,query:n,seeded:s={},specs:i,limit:a,batch:l,algorithm:u,render:E,resume:f}=e,R=M=>e.status&&e.status(M),O;if(f&&Array.isArray(f.remaining))O={batches:f.remaining,count:f.total,sites:f.sites||[],siteIndex:f.siteIndex||null,unindexed:f.unindexed||[]};else{let M={roster:[...e.roster||[],...u&&u.liked||[]],neighborhood:kt(),followed:!0,...e.prefer||{}};R("Ranking sites\u2026"),O=await De(`${t}/system/site-rank.json`,{query:n,...s,domains:i,prefer:M,algorithm:u||{},batch:l||100})}let D=O.batches||[],U=O.count||D.reduce((M,G)=>M+G.length,0),p={searched:f?.searched||0,total:U,batches:D.length,done:0,stopped:!1,converged:!1,running:!0,unindexed:O.unindexed||[],answered:f?.answered||{local:0},siteIndex:O.siteIndex||null,siteInfo:new Map((O.sites||[]).map(M=>[M.domain,M])),shown:f?.shown||[],pending:0,opened:new Set(f?.opened||[]),resumed:!!f},T=new Map((f?.merged||[]).map(M=>[F(M),M])),x=[...T.values()].sort((M,G)=>G.score-M.score),h=0,m=()=>{p.stopped=!0};p.stop=m;let b=()=>{try{e.save&&e.save(p.snapshot())}catch{}};p.foldIn=()=>{p.shown=vt(p.shown,x,a,p.opened),p.pending=0,E(x,p),b()},p.markOpened=M=>{p.opened.add(M),E(x,p),b()},p.snapshot=()=>({query:n,total:U,searched:p.searched,converged:p.converged,stopped:p.stopped,merged:x.slice(0,150),shown:p.shown,opened:[...p.opened],remaining:D.slice(p.done),answered:p.answered,siteIndex:p.siteIndex,unindexed:p.unindexed,sites:[...p.siteInfo.values()].slice(0,400)});let C=async M=>{let G={query:n,...s,domains:M,limit:Math.max(a*3,30),flat:!0};e.thresholdSet&&(G.threshold=e.threshold);let c=await De(`${t}/system/search-report.json`,G);for(let r of c.results||[]){let o=F(r),g=T.get(o);(!g||g.score<r.score)&&T.set(o,r)}let y=0;for(let r of c.peers||[])p.answered[r.host]=(p.answered[r.host]||0)+(r.domains||0),y+=r.domains||0;p.answered.local+=Math.max(0,(c.stats&&c.stats.domains||0)-y);let d=[...T.values()].sort((r,o)=>o.score-r.score),w=Lt(x,d,a);x=d,p.searched+=M.length,p.done+=1,h=w?h+1:0,p.shown.length?p.shown=St(p.shown,d):p.shown=d.slice(0,a),p.pending=xt(p.shown,d,a)},W=()=>D.slice(p.done),X=async(M=!1)=>{for(p.running=!0,p.stopped=!1;p.done<D.length&&!p.stopped;)if(await C(D[p.done]),E(x,p),b(),!M&&h>=3&&p.done<D.length){p.converged=!0;break}return p.running=!1,E(x,p),b(),x};return p.continueAll=()=>(p.converged=!1,h=-1/0,X(!0)),f&&f.remaining&&!f.remaining.length?(p.running=!1,p.converged=!!f.converged,E(x,p),{results:x,state:p,remaining:W}):(await X(!1),{results:x,state:p,remaining:W})};var ae="similarity:learned",Et=new Set(["liked","visited","neighbourhood","neighborhood","followed","centroid","fresh","reliable","stale"]),ie=/^([a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+|localhost)(:\d+)?$/,be=e=>{let t={weights:{},always:[],never:[],batch:null,trust:null};for(let n of(e||"").split(`
5
+ `)){let s=n.trim();if(!s||s.startsWith("#"))continue;let[i,...a]=s.split(/\s+/),l=i.toUpperCase().replace(/:$/,"");if(l==="WEIGHT"&&a.length>=2){let u=a[0].toLowerCase().replace("neighborhood","neighbourhood"),E=parseFloat(a[1]);Et.has(u)&&Number.isFinite(E)&&(t.weights[u]=E)}else l==="ALWAYS"&&a[0]&&ie.test(a[0])?t.always.push(a[0].toLowerCase()):l==="NEVER"&&a[0]&&ie.test(a[0])?t.never.push(a[0].toLowerCase()):l==="BATCH"&&a[0]?t.batch=parseInt(a[0])||null:l==="TRUST"&&a[0]&&["solid","flaky","any"].includes(a[0].toLowerCase())&&(t.trust=a[0].toLowerCase())}return t},xe=()=>{try{let e=JSON.parse(localStorage.getItem(ae)||"{}"),t={};for(let[n,s]of Object.entries(e.sites||e)){let i=typeof s=="number"?s:s&&typeof s.score=="number"?s.score:0;i>0&&(t[n.toLowerCase()]=Math.min(1,i))}return t}catch{return{}}},jt=(e,t)=>{if(!e)return`${t}/search-algorithm.json`;let n=e.indexOf("/");if(n>0&&ie.test(e.slice(0,n)))return`//${e.slice(0,n)}/${e.slice(n+1)}.json`;let s=e.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"");return`${t}/${s}.json`},Ct=e=>{let t=new Set;for(let n of e.story||[])if(n.type==="roster")for(let s of(n.text||"").split(/\r?\n/)){let i=s.trim();ie.test(i)&&t.add(i.toLowerCase())}return[...t]},Ue=new Map,qe=async(e,t)=>{let n=jt(e,t),s=Ue.get(n);if(!s){s={weights:{},always:[],never:[],liked:[],batch:null,trust:null,page:null};try{let i=await fetch(n);if(i.ok){let a=await i.json(),l=(a.story||[]).find(u=>u.type==="similarity"&&/^\s*ALGORITHM\b/im.test(u.text||""));l&&Object.assign(s,be(l.text)),s.liked=Ct(a),s.page={url:n,title:a.title}}}catch{}Ue.set(n,s)}return{...s,learned:xe()}};var It={neighbourhood:.1,visited:.2,clicked:.35},Tt=7*864e5,He=200,Ne=()=>{try{return JSON.parse(localStorage.getItem(ae)||"{}").sites||{}}catch{return{}}},_e=e=>{try{localStorage.setItem(ae,JSON.stringify({sites:e,savedAt:Date.now()}))}catch{}},re=(e,t)=>{let n=Math.max(0,(t-(e.at||t))/Tt);return(e.score||0)*Math.pow(.5,n)},le=(e,t)=>{if(!e||e==="localhost")return 0;let n=e.toLowerCase(),s=Date.now(),i=Ne(),a=i[n]?re(i[n],s):0,l=Math.min(1,a+(It[t]||.1));i[n]={score:l,at:s};let u=Object.keys(i);if(u.length>He){u.sort((E,f)=>re(i[f],s)-re(i[E],s));for(let E of u.slice(He))delete i[E]}return _e(i),l},At=()=>{let e=Date.now();return Object.entries(Ne()).map(([t,n])=>[t,re(n,e)]).filter(([,t])=>t>.01).sort((t,n)=>n[1]-t[1])},ze=()=>_e({}),Be=!1,Fe=e=>{try{return window.$&&window.$(e).data("site")||window.location.hostname}catch{return window.location.hostname}},Ge=()=>{if(Be||typeof document>"u")return;Be=!0;let e=new Set;document.querySelectorAll(".main > .page").forEach(n=>{let s=Fe(n);e.has(s)||(e.add(s),le(s,"neighbourhood"))});let t=document.querySelector(".main");!t||typeof MutationObserver>"u"||new MutationObserver(n=>{for(let s of n)for(let i of s.addedNodes)i.nodeType===1&&i.classList.contains("page")&&!i.classList.contains("ghost")&&le(Fe(i),"visited")}).observe(t,{childList:!0})},Ke=(e,t=20)=>new Promise((n,s)=>{let a=((e.data("data")||{}).story||[]).find(f=>f.type==="roster"&&/^\s*sites i visit\b/i.test(f.text||""));if(!a)return s(new Error('no "Sites I visit" roster on this page'));let l=At().slice(0,t);if(!l.length)return s(new Error("nothing learned yet"));let u=["Sites I visit",...l.map(([f])=>f)].join(`
6
+ `),E={...a,text:u};try{window.wiki.pageHandler.put(e,{type:"edit",id:a.id,item:E});let f=e.find(`.item[data-id="${a.id}"]`);f.length&&(f.data("item",E),window.wiki.plugins?.roster?.emit?.(f,E)),n(l.length)}catch(f){s(f)}});var K=`.sim-results li.sim-opened{opacity:.72}.sim-results .sim-mark{color:#6a6;margin-left:.3em}.sim-results li.sim-pending{list-style:none;margin-left:-1.2em}.sim-results .sim-fold{font-size:90%;padding:0 .4em}.sim-results .sim-progress{width:100%;height:6px;display:block;margin:2px 0 6px}.sim-door{display:flex;gap:8px;align-items:center;font-size:15px;margin:4px 0}
7
7
 
8
8
  .sim-form { display:flex; gap:6px; margin-bottom:8px; }
9
9
  .sim-input { flex:1; padding:6px 8px; font-size:14px; border:1px solid #ccc; border-radius:3px; }
@@ -28,10 +28,10 @@
28
28
  .sim-list td { padding:3px 8px 3px 0; border-bottom:1px solid #f0f0f0; vertical-align:middle; }
29
29
  .sim-list td:last-child { text-align:right; color:#999; font-size:11px; }
30
30
  .sim-list .sim-flag { margin-right:6px; }
31
- `,ze=(e,t)=>`<img class="sim-flag remote" src="${window.wiki.site(e).flag()}"
31
+ `,Ye=(e,t)=>`${/(^|\.)localhost(:\d+)?$/.test(String(e))?"http:":"https:"}//${e}/view/${t}`,Ve=(e,t)=>`<img class="sim-flag remote" src="${window.wiki.site(e).flag()}"
32
32
  title="${e} \u2014 score ${t.toFixed(3)}"
33
- data-site="${e}">`;var jt="Search Tool",Ct="search-tool",le=null,Tt=()=>fetch(`/${Ct}.json`,{method:"GET"}).then(e=>{le=e.ok}).catch(()=>{le=!1}),It=e=>{let t=window.$(e).parents(".page");return t.length?t:null},Ke=(e,t)=>{window.wiki.doInternalLink(jt,t?null:e)},Ve=()=>{Rt(),!(typeof document>"u"||!window.wiki||!window.$)&&(Tt(),document.addEventListener("click",e=>{if(le!==!0)return;let t=e.target&&e.target.closest&&e.target.closest("a.search");if(!t)return;let n=It(t);n&&(e.preventDefault(),e.stopPropagation(),Ke(n,e.shiftKey))},!0),document.addEventListener("keydown",e=>{if(le!==!0||e.key!=="Enter")return;let t=e.target;if(!t||!t.classList||!t.classList.contains("search")||!t.closest(".searchbox"))return;let n=(t.value||"").trim();n&&(e.preventDefault(),e.stopPropagation(),window.$(".incremental-search").remove(),t.value="",Ot(n),Ke(window.$(".page").last(),!1))},!0))},Ye="selected-plugin-pages",At=()=>{let e=window.$,t=document.getElementById(Ye);if(!t||!e||!window.plugins||!window.plugins.reference)return 0;let s=(e(t).data("data")||{}).story||[],i=0;return t.querySelectorAll(".item.reference").forEach(a=>{if(a.classList.contains("emit")||a.textContent.trim())return;let l=s.find(u=>u.id===a.dataset.id);if(l)try{let u=window.plugins.reference;u.emit(e(a),l),u.bind&&u.bind(e(a),l),a.classList.add("emit"),i++}catch{}}),i},Rt=()=>{let e=document.querySelector(".main")||document.body,t=null,n=()=>{document.getElementById(Ye)&&(clearTimeout(t),t=setTimeout(()=>{let s=At();s&&console.log(`[wiki-plugin-similarity] drew ${s} menu entries the client left blank`)},4e3))};try{new MutationObserver(n).observe(e,{childList:!0})}catch{}n()},xe=null,Ot=e=>{xe=e},Je=()=>{let e=xe;return xe=null,e};var ce=(e,t,n,s)=>`<a class="sim-link" data-title="${n}" data-slug="${t}" data-site="${e}" href="#">${ze(e,s)} ${n}</a>`,We=e=>{try{return new Date(e).toLocaleDateString(void 0,{day:"numeric",month:"short"})}catch{return""}},Mt=e=>{if(!e||!e.feed)return"";let t=e.by||{},s=["abandoned","stale","dead","moved","lapsed","unreliable","flaky"].filter(u=>t[u]).map(u=>`${t[u].toLocaleString()} ${u}`),i=String(e.feed).replace(/^https?:\/\//,""),a=e.fetchedAt?new Date(e.fetchedAt).toLocaleString(void 0,{day:"numeric",month:"short",hour:"2-digit",minute:"2-digit"}):"\u2014";return`<tr><th>Graveyard verdicts</th><td>${e.sites?`${e.sites.toLocaleString()} sites${s.length?` \u2014 ${s.join(", ")}`:""}`:"none yet"} \u2014 read from <code>${i}</code>'s sitemap, ${a}</td></tr>`},Dt=(e,t)=>{let n=()=>Math.random().toString(16).slice(2,18).padEnd(16,"0"),s=[{type:"markdown",id:n(),text:`Federated search for **${e}** \u2014 ${t.length} results, searched a batch at a time, nearest sites first.`}],i=[...new Set(t.map(a=>a.title))];i.length&&s.push({type:"markdown",id:n(),text:i.map(a=>`- [[${a}]]`).join(`
34
- `)}),s.push({type:"markdown",id:n(),text:"# Results"});for(let a of t)s.push({type:"reference",id:n(),site:a.site,slug:a.slug,title:a.title,text:(a.synopsis||`score ${a.score}`)+(a.via?` \u2014 via ${a.via}`:"")});return{title:`${e} Federated Search`,story:s}},Pt=(e,t)=>{let{mode:n,specs:s,threshold:i,limit:a,force:l,ghostUrl:u,label:E}=ue(t?.text||"");if(n==="ghost")e.html(`
33
+ data-site="${e}">`;var ve="similarity:door",Je=()=>{try{return typeof localStorage<"u"?localStorage:null}catch{return null}},ne=(e=Je())=>{try{return!!e&&e.getItem(ve)==="on"}catch{return!1}},We=(e,t=Je())=>{try{return t?(e?t.setItem(ve,"on"):t.removeItem(ve),!0):!1}catch{return!1}};var Rt="Search Tool",Ot="search-tool",ce=null,Mt=()=>fetch(`/${Ot}.json`,{method:"GET"}).then(e=>{ce=e.ok}).catch(()=>{ce=!1}),Dt=e=>{let t=window.$(e).parents(".page");return t.length?t:null},Xe=(e,t)=>{window.wiki.doInternalLink(Rt,t?null:e)},Ze=()=>{Ut(),!(typeof document>"u"||!window.wiki||!window.$)&&(Mt(),document.addEventListener("click",e=>{if(ce!==!0)return;let t=e.target&&e.target.closest&&e.target.closest("a.search");if(!t)return;let n=Dt(t);n&&(e.preventDefault(),e.stopPropagation(),Xe(n,e.shiftKey))},!0),document.addEventListener("keydown",e=>{if(ce!==!0||!ne()||e.key!=="Enter")return;let t=e.target;if(!t||!t.classList||!t.classList.contains("search")||!t.closest(".searchbox"))return;let n=(t.value||"").trim();n&&(e.preventDefault(),e.stopPropagation(),window.$(".incremental-search").remove(),t.value="",qt(n),Xe(window.$(".page").last(),!1))},!0))},Qe="selected-plugin-pages",Pt=()=>{let e=window.$,t=document.getElementById(Qe);if(!t||!e||!window.plugins||!window.plugins.reference)return 0;let s=(e(t).data("data")||{}).story||[],i=0;return t.querySelectorAll(".item.reference").forEach(a=>{if(a.classList.contains("emit")||a.textContent.trim())return;let l=s.find(u=>u.id===a.dataset.id);if(l)try{let u=window.plugins.reference;u.emit(e(a),l),u.bind&&u.bind(e(a),l),a.classList.add("emit"),i++}catch{}}),i},Ut=()=>{let e=document.querySelector(".main")||document.body,t=null,n=()=>{document.getElementById(Qe)&&(clearTimeout(t),t=setTimeout(()=>{let s=Pt();s&&console.log(`[wiki-plugin-similarity] drew ${s} menu entries the client left blank`)},4e3))};try{new MutationObserver(n).observe(e,{childList:!0})}catch{}n()},Se=null,qt=e=>{Se=e},et=()=>{let e=Se;return Se=null,e};var de=(e,t,n,s)=>`<a class="sim-link" data-title="${n}" data-slug="${t}" data-site="${e}" href="${Ye(e,t)}">${Ve(e,s)} ${n}</a>`,tt=e=>{try{return new Date(e).toLocaleDateString(void 0,{day:"numeric",month:"short"})}catch{return""}},Ht=e=>{if(!e||!e.feed)return"";let t=e.by||{},s=["abandoned","stale","dead","moved","lapsed","unreliable","flaky"].filter(u=>t[u]).map(u=>`${t[u].toLocaleString()} ${u}`),i=String(e.feed).replace(/^https?:\/\//,""),a=e.fetchedAt?new Date(e.fetchedAt).toLocaleString(void 0,{day:"numeric",month:"short",hour:"2-digit",minute:"2-digit"}):"\u2014";return`<tr><th>Graveyard verdicts</th><td>${e.sites?`${e.sites.toLocaleString()} sites${s.length?` \u2014 ${s.join(", ")}`:""}`:"none yet"} \u2014 read from <code>${i}</code>'s sitemap, ${a}</td></tr>`},Bt=(e,t)=>{let n=()=>Math.random().toString(16).slice(2,18).padEnd(16,"0"),s=[{type:"markdown",id:n(),text:`Federated search for **${e}** \u2014 ${t.length} results, searched a batch at a time, nearest sites first.`}],i=[...new Set(t.map(a=>a.title))];i.length&&s.push({type:"markdown",id:n(),text:i.map(a=>`- [[${a}]]`).join(`
34
+ `)}),s.push({type:"markdown",id:n(),text:"# Results"});for(let a of t)s.push({type:"reference",id:n(),site:a.site,slug:a.slug,title:a.title,text:(a.synopsis||`score ${a.score}`)+(a.via?` \u2014 via ${a.via}`:"")});return{title:`${e} Federated Search`,story:s}},Ft=(e,t)=>{let{mode:n,specs:s,threshold:i,limit:a,force:l,ghostUrl:u,label:E}=me(t?.text||"");if(n==="ghost")e.html(`
35
35
  <style>${K}</style>
36
36
  <div class="similarity" data-id="${t.id}" data-mode="${n}" data-scope="${s.join(",")||""}">
37
37
  <div class="sim-form">
@@ -55,6 +55,11 @@
55
55
  <div class="similarity" data-id="${t.id}" data-mode="${n}" data-scope="">
56
56
  <div class="sim-status">Reading this search algorithm\u2026</div>
57
57
  <div class="sim-list"></div>
58
+ </div>`);else if(n==="door")e.html(`
59
+ <style>${K}</style>
60
+ <div class="similarity" data-id="${t.id}" data-mode="${n}">
61
+ <label class="sim-door"><input type="checkbox" class="sim-door-toggle"> Send the search box at the foot of every page to this page</label>
62
+ <div class="sim-status"></div>
58
63
  </div>`);else if(n==="status")e.html(`
59
64
  <style>${K}</style>
60
65
  <div class="similarity" data-id="${t.id}" data-mode="${n}" data-scope="${s.join(",")||""}">
@@ -83,21 +88,21 @@
83
88
  </div>
84
89
  <div class="sim-status">Domains: ${f}</div>
85
90
  <div class="sim-results"></div>
86
- </div>`)}},Ut=(e,t)=>{let{mode:n,specs:s,rosterRefs:i,farms:a,threshold:l,limit:u,live:E,subject:f,force:R,ghostUrl:O,thresholdSet:D,batch:U,algorithm:p,auto:I}=ue(t?.text||""),x=window.location.origin,d=e.find(".sim-status")[0];_e();let m=f?Ie(e):null,b=E||m?null:Re(t),C=Te(s,i),W=()=>m?{seed:{site:m.site,slug:m.slug},text:m.text,excludePage:{site:m.site,slug:m.slug}}:{},X=(h,y,c)=>{let w=y>0?`${y.toLocaleString()} pages across ${c} domains`:`${c} domain${c===1?"":"s"}`,r=[`${h} \u2014 ${w}`];return D&&r.push(`threshold ${l}`),r.push(`limit ${u}`),E&&r.push("LIVE"),r.join(" \xB7 ")};e.on("dblclick",h=>{$(h.target).closest(".sim-input").length||window.wiki.textEditor(e,t)}),e.on("click",".sim-link",function(h){h.preventDefault();let y=$(this),c=h.shiftKey?null:e.parents(".page");window.wiki.doInternalLink(y.data("title"),c,y.data("site"))});let M=!s.length||s.length===1&&s[0]==="*"?"on farm":s.length===1?`on ${s[0]}`:"in domains",G=h=>h?` \xB7 cached ${ye(h)}`:"";if(n==="status"){let h=e.find(".sim-list")[0],y=r=>{if(!r)return"unknown";let o=(Date.now()-new Date(r))/864e5;return o<1?"today":o<2?"yesterday":`${Math.floor(o)} days ago`},c=r=>r?new Date(r).toISOString().slice(0,10):"\u2014",w=r=>{if(!r||!r.warm)return"";let o=r.warm,g=Math.round((r.bytes||0)/1e6),j=o.state==="warm"?`warm \u2014 ${o.done.toLocaleString()} sites resident in ${Math.round(o.ms/1e3)} s`:o.state==="warming"?`warming \u2014 ${o.done.toLocaleString()} of ${o.total.toLocaleString()} sites so far`:"not warmed",T=o.capped?" \xB7 <strong>cap reached</strong>, some sites re-parse per query":"";return`<tr><th>Vector store</th><td>${(r.pages||0).toLocaleString()} pages \xB7 ${g} MB of ${Math.round((r.capBytes||0)/1e6)} MB \xB7 ${j}${T}</td></tr>`};(async()=>{try{let r=()=>fetch(`${x}/system/embed.json?text=ping`).then(async k=>k.ok?{ok:!0}:{ok:!1,detail:((await k.json().catch(()=>({}))).error||`status ${k.status}`).replace(/^embedding unavailable: /,"")}).catch(k=>({ok:!1,detail:k.message})),[o,g,j]=await Promise.all([fetch(`${x}/system/similarity-health.json`).then(k=>k.json()),fetch(`${x}/system/indexed-domains.json`).then(k=>k.ok?k.json():[]),r()]),T=o.embedder||{},P=g.map(k=>k.built).filter(Boolean).sort(),S=g.reduce((k,B)=>k+(B.page_count||0),0),v=!j.ok,H=v?`<strong>Semantic search is down</strong> \u2014 a query cannot be turned into a vector right now (${j.detail}), so searches on this page will answer by keyword instead.`:"Semantic search is <strong>up</strong> \u2014 checked just now by embedding a word.",N=T.via==="url"?`delegated to <code>${T.url}</code> (from ${T.source||"config"})`:T.via==="semindex"?"in this process, via the SemIndex plugin":"in this process, in a supervised child";d.style.display="none",h.innerHTML=`<h3>The Index Behind These Answers</h3>
91
+ </div>`)}},Nt=(e,t)=>{let{mode:n,specs:s,rosterRefs:i,farms:a,threshold:l,limit:u,live:E,subject:f,force:R,ghostUrl:O,thresholdSet:D,batch:U,algorithm:p,auto:T}=me(t?.text||""),x=window.location.origin,h=e.find(".sim-status")[0];Ge();let m=f?Re(e):null,b=E||m?null:Me(t),C=Ae(s,i),W=()=>m?{seed:{site:m.site,slug:m.slug},text:m.text,excludePage:{site:m.site,slug:m.slug}}:{},X=(c,y,d)=>{let w=y>0?`${y.toLocaleString()} pages across ${d} domains`:`${d} domain${d===1?"":"s"}`,r=[`${c} \u2014 ${w}`];return D&&r.push(`threshold ${l}`),r.push(`limit ${u}`),E&&r.push("LIVE"),r.join(" \xB7 ")};e.on("dblclick",c=>{$(c.target).closest(".sim-input").length||window.wiki.textEditor(e,t)}),e.on("click",".sim-link",function(c){if(c.metaKey||c.ctrlKey||c.button===1)return;c.preventDefault();let y=$(this),d=c.shiftKey?null:e.parents(".page");window.wiki.doInternalLink(y.data("title"),d,y.data("site"))});let M=!s.length||s.length===1&&s[0]==="*"?"on farm":s.length===1?`on ${s[0]}`:"in domains",G=c=>c?` \xB7 cached ${$e(c)}`:"";if(n==="door"){let c=e.find(".sim-door-toggle")[0],y=()=>{h.textContent=ne()?"On \u2014 Enter in the search box at the foot of any page opens this page with the query, searching the whole farm.":"Off \u2014 the search box behaves as the wiki built it. Remembered by this browser only."};c.checked=ne(),y(),c.addEventListener("change",()=>{We(c.checked),y()})}else if(n==="status"){let c=e.find(".sim-list")[0],y=r=>{if(!r)return"unknown";let o=(Date.now()-new Date(r))/864e5;return o<1?"today":o<2?"yesterday":`${Math.floor(o)} days ago`},d=r=>r?new Date(r).toISOString().slice(0,10):"\u2014",w=r=>{if(!r||!r.warm)return"";let o=r.warm,g=Math.round((r.bytes||0)/1e6),j=o.state==="warm"?`warm \u2014 ${o.done.toLocaleString()} sites resident in ${Math.round(o.ms/1e3)} s`:o.state==="warming"?`warming \u2014 ${o.done.toLocaleString()} of ${o.total.toLocaleString()} sites so far`:"not warmed",I=o.capped?" \xB7 <strong>cap reached</strong>, some sites re-parse per query":"";return`<tr><th>Vector store</th><td>${(r.pages||0).toLocaleString()} pages \xB7 ${g} MB of ${Math.round((r.capBytes||0)/1e6)} MB \xB7 ${j}${I}</td></tr>`};(async()=>{try{let r=()=>fetch(`${x}/system/embed.json?text=ping`).then(async k=>k.ok?{ok:!0}:{ok:!1,detail:((await k.json().catch(()=>({}))).error||`status ${k.status}`).replace(/^embedding unavailable: /,"")}).catch(k=>({ok:!1,detail:k.message})),[o,g,j]=await Promise.all([fetch(`${x}/system/similarity-health.json`).then(k=>k.json()),fetch(`${x}/system/indexed-domains.json`).then(k=>k.ok?k.json():[]),r()]),I=o.embedder||{},P=g.map(k=>k.built).filter(Boolean).sort(),v=g.reduce((k,B)=>k+(B.page_count||0),0),S=!j.ok,H=S?`<strong>Semantic search is down</strong> \u2014 a query cannot be turned into a vector right now (${j.detail}), so searches on this page will answer by keyword instead.`:"Semantic search is <strong>up</strong> \u2014 checked just now by embedding a word.",N=I.via==="url"?`delegated to <code>${I.url}</code> (from ${I.source||"config"})`:I.via==="semindex"?"in this process, via the SemIndex plugin":"in this process, in a supervised child";h.style.display="none",c.innerHTML=`<h3>The Index Behind These Answers</h3>
87
92
  <p>${H}</p>
88
93
  <table>
89
94
  <tr><th>Model</th><td>${o.model||"\u2014"} \xB7 ${o.dim||"\u2014"} dimensions</td></tr>
90
- <tr><th>Embedder</th><td>${N}${v?" \u2014 <strong>currently down</strong>":""}</td></tr>
91
- <tr><th>Indexed</th><td>${g.length.toLocaleString()} domains \xB7 ${S.toLocaleString()} pages</td></tr>
92
- <tr><th>Newest page vectors</th><td>${c(P[P.length-1])} (${y(P[P.length-1])})</td></tr>
93
- <tr><th>Oldest page vectors</th><td>${c(P[0])} (${y(P[0])})</td></tr>
95
+ <tr><th>Embedder</th><td>${N}${S?" \u2014 <strong>currently down</strong>":""}</td></tr>
96
+ <tr><th>Indexed</th><td>${g.length.toLocaleString()} domains \xB7 ${v.toLocaleString()} pages</td></tr>
97
+ <tr><th>Newest page vectors</th><td>${d(P[P.length-1])} (${y(P[P.length-1])})</td></tr>
98
+ <tr><th>Oldest page vectors</th><td>${d(P[0])} (${y(P[0])})</td></tr>
94
99
  <tr><th>Plugin</th><td>wiki-plugin-similarity ${o.version||"\u2014"}</td></tr>
95
100
  ${w(o.store)}
96
- ${Mt(o.verdicts)}
101
+ ${Ht(o.verdicts)}
97
102
  </table>
98
103
  <p class="sim-count">A domain's vectors are only rebuilt when its pages change, so an old
99
104
  date means a quiet site, not a broken one. Pages saved since the last build are findable
100
- by keyword but not yet by meaning.</p>`}catch(r){d.textContent=`Index state unavailable: ${r.message}`}})()}else if(n==="algorithm")setTimeout(()=>{let h=e.find(".sim-list")[0];try{let y=$e(t.text||""),c=be(),w=Object.entries(c).sort((o,g)=>g[1]-o[1]).slice(0,8);d.style.display="none",h.innerHTML=`<h3>This Search Algorithm</h3>
105
+ by keyword but not yet by meaning.</p>`}catch(r){h.textContent=`Index state unavailable: ${r.message}`}})()}else if(n==="algorithm")setTimeout(()=>{let c=e.find(".sim-list")[0];try{let y=be(t.text||""),d=xe(),w=Object.entries(d).sort((o,g)=>g[1]-o[1]).slice(0,8);h.style.display="none",c.innerHTML=`<h3>This Search Algorithm</h3>
101
106
  <table>
102
107
  ${Object.entries(y.weights).map(([o,g])=>`<tr><th>weight ${o}</th><td>${g}</td></tr>`).join("")}
103
108
  <tr><th>always</th><td>${y.always.length?y.always.join(", "):"\u2014"}</td></tr>
@@ -106,19 +111,19 @@
106
111
  </table>
107
112
  <p class="sim-count">Learned in this browser: ${w.length?w.map(([o,g])=>`${o} (${g.toFixed(2)})`).join(", "):"nothing yet \u2014 sites you are near, open and click will appear here"}</p>
108
113
  <p class="sim-count"><button class="sim-remember">Remember</button> <button class="sim-forget">Forget</button>
109
- <span class="sim-remember-note"></span></p>`;let r=h.querySelector(".sim-remember-note");h.querySelector(".sim-remember").addEventListener("click",async()=>{try{let o=await Ge(e.parents(".page"));r.textContent=`${o} sites written into "Sites I visit" \u2014 read the roster above, delete any line.`}catch(o){r.textContent=`Could not remember here: ${o.message}`}}),h.querySelector(".sim-forget").addEventListener("click",()=>{Ne(),r.textContent="Forgotten \u2014 the browser starts learning again from here."})}catch(y){d.textContent=`Algorithm unreadable: ${y.message}`}},0);else if(n==="list"){let h=e.find(".sim-list")[0],y=(c,w)=>{let r=c.reduce((o,g)=>o+(g.page_count||0),0);d.style.display="none",h.innerHTML=`<h3>Indexed Farm Domains</h3>
114
+ <span class="sim-remember-note"></span></p>`;let r=c.querySelector(".sim-remember-note");c.querySelector(".sim-remember").addEventListener("click",async()=>{try{let o=await Ke(e.parents(".page"));r.textContent=`${o} sites written into "Sites I visit" \u2014 read the roster above, delete any line.`}catch(o){r.textContent=`Could not remember here: ${o.message}`}}),c.querySelector(".sim-forget").addEventListener("click",()=>{ze(),r.textContent="Forgotten \u2014 the browser starts learning again from here."})}catch(y){h.textContent=`Algorithm unreadable: ${y.message}`}},0);else if(n==="list"){let c=e.find(".sim-list")[0],y=(d,w)=>{let r=d.reduce((o,g)=>o+(g.page_count||0),0);h.style.display="none",c.innerHTML=`<h3>Indexed Farm Domains</h3>
110
115
  <table>
111
116
  <tr><th>Domain</th><th>Pages</th></tr>
112
- ${c.map(({domain:o,page_count:g})=>`
117
+ ${d.map(({domain:o,page_count:g})=>`
113
118
  <tr>
114
119
  <td><img class="sim-flag remote" src="${window.wiki.site(o).flag()}"
115
120
  title="${o}" data-site="${o}"> ${o}</td>
116
121
  <td>${g!=null?g.toLocaleString():"\u2014"}</td>
117
122
  </tr>`).join("")}
118
123
  </table>
119
- <p class="sim-count">${c.length} domains \u2014 ${r.toLocaleString()} pages${G(w)}</p>`};b?.domains?y(b.domains,b.ts):(async()=>{try{let c=await C,w=c.length?c.join(","):"*",r=`${x}/system/indexed-domains.json?pattern=${encodeURIComponent(w)}&limit=${u}`,o=await fetch(r);if(!o.ok)throw new Error(`indexed-domains failed: ${o.status}`);let g=await o.json();if(!g.length){d.textContent="No indexed domains found";return}y(g,null),ee(t,{domains:g})}catch(c){d.textContent=`Error: ${c.message}`}})()}else if(n==="similar"){let h=e.find(".sim-results")[0],y=(c,w,r)=>{if(!c.length){d.textContent=`No similar pages found above threshold ${l}`;return}let o=r!=null?` \xB7 one request, ${r<1e3?`${Math.round(r)} ms`:`${(r/1e3).toFixed(1)} s`}`:"";h.innerHTML=`<h3>Similar Pages</h3><ul>${c.map(({domain:g,slug:j,title:T,score:P})=>`<li>${ce(g,j,T,P)}</li>`).join("")}</ul><p class="sim-count">${c.length} found ${M}${o}${G(w)}</p>`,d.style.display="none"};b?.scored?y(b.scored,b.ts):(async()=>{try{let c=e.parents(".page"),w=m||(()=>{let v=c.find(".title").text().trim()||document.title;return{slug:je(v),site:window.location.hostname,title:v,text:null,isSelf:!0}})(),r=await C;d.textContent=(m?`${J(w)} \xB7 `:"")+"Asking for pages like this one\u2026";let o=w.text||c.find(".item").map((v,H)=>$(H).text().trim()).get().filter(Boolean).join(`
120
- `).slice(0,2e3),g={query:w.title,domains:r.length?r:["*"],limit:u,flat:!0,seed:{site:w.site,slug:w.slug},text:o||w.title,excludePage:{site:w.site,slug:w.slug}};D&&(g.threshold=l);let j=performance.now(),T=await fetch(`${x}/system/search-report.json`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)});if(!T.ok)throw new Error(`search-report failed: ${T.status}`);let S=((await T.json()).results||[]).map(v=>({domain:v.site,slug:v.slug,title:v.title,score:v.semantic??v.score}));y(S,null,performance.now()-j),m&&(d.textContent=J(w),d.style.display=""),S.length&&!m&&ee(t,{scored:S})}catch(c){d.textContent=`Error: ${c.message}`}})()}else if(n==="ghost"){let h=e.find(".sim-btn")[0],y=async()=>{if(!O){d.textContent="No URL \u2014 GHOST needs a page-json URL";return}h.disabled=!0,d.textContent="Fetching\u2026";try{let c=await fetch(O);if(!c.ok)throw new Error(`fetch failed: ${c.status}`);let w=await c.json();window.wiki.showResult(window.wiki.newPage(w),{$page:e.parents(".page")}),d.textContent=""}catch(c){d.textContent=`Error: ${c.message}`}finally{h.disabled=!1}};h.addEventListener("click",y)}else if(n==="build"){let h=e.find(".sim-btn")[0],y=async()=>{h.disabled=!0,d.textContent="Building index\u2026 (may take a while for large scopes)";try{let c=encodeURIComponent((s.length?s:["*"]).join(",")),w=await fetch(`${x}/system/build-index.json?domains=${c}&force=${R?1:0}`);if(w.status===501){let o=await w.json().catch(()=>({}));throw new Error(o.hint||"indexing runs on the farm indexer, not this server")}if(!w.ok)throw new Error(`build-index failed: ${w.status}`);let r=await w.json();window.wiki.showResult(window.wiki.newPage(r),{$page:e.parents(".page")}),d.textContent=""}catch(c){d.textContent=`Error: ${c.message}`}finally{h.disabled=!1}};h.addEventListener("click",y)}else if(n==="report"){let h=e.find(".sim-input")[0],y=e.find(".sim-btn")[0],c=`Domains: ${s.length?s.join(", "):"*"}`;m&&!h.value&&(h.value=m.title),(async()=>{try{let S=await C,v=await te(S.length?S:["*"],x),H=v.reduce((k,B)=>k+(B.page_count||0),0);if(c=X("Report ready",H,v.length),U!=null?U>0:S.some(k=>k.toUpperCase()==="GALAXY"))try{let B=(await fetch(`${x}/system/similarity-health.json`).then(Q=>Q.json())).siteIndex||{},_=S.filter(Q=>/\./.test(Q)&&!/[*?]/.test(Q)&&!["PUBLIC","LOCAL","PRIVATE","GALAXY"].includes(Q.toUpperCase())),V=[`Ready \u2014 ${B.count?`ranks ${B.count.toLocaleString()} sites from the Site Index${B.via==="peer"?" (a peer's copy)":""}`:"no Site Index on this farm \u2014 batches ask its peers"}`];_.length&&V.push(`${_.length} named site${_.length>1?"s":""} searched wherever held`),D&&V.push(`threshold ${l}`),V.push(`limit ${u}`),c=V.join(" \xB7 ")}catch{}a.length&&(c+=` \xB7 +${a.length} peer farm${a.length>1?"s":""}`),m&&(c=`${J(m)} \xB7 ${c}`),d.textContent=c}catch(S){d.textContent=`Domain listing unavailable: ${S.message}`}})();let w=new Set([502,503]),r=async(S,v)=>{let H=encodeURIComponent((v.length?v:["*"]).join(",")),N=a.length?`&farms=${encodeURIComponent(a.join(","))}`:"",k=await fetch(`${x}/system/farm-search.json?q=${encodeURIComponent(S)}&pattern=${H}&limit=${u}${N}`);if(!k.ok)throw new Error(`keyword search also failed: ${k.status}`);let B=await k.json();window.wiki.showResult(window.wiki.newPage(B),{$page:e.parents(".page")})},o=S=>U!=null?U>0:S.some(v=>v.toUpperCase()==="GALAXY"),g=e.find(".sim-results")[0],j=async(S,v,H,N=null)=>{let k=await Pe(p,x),B=v.filter(q=>!se(q)&&q!=="*"&&!ne(q)),_=null,Z=q=>{let L=Dt(S,q);_&&_.remove(),window.wiki.showResult(window.wiki.newPage(L),{$page:e.parents(".page")}),_=e.parents(".page").next(".page")},V=(q,L)=>{let Xe=L.shown&&L.shown.length?L.shown:q.slice(0,u),Ze=L.running?`Searched ${L.searched.toLocaleString()} of ${L.total.toLocaleString()} sites \u2014 ${q.length} results \u2014 searching\u2026`:L.converged?`Searched ${L.searched.toLocaleString()} of ${L.total.toLocaleString()} sites \u2014 ${q.length} results \u2014 the top ${u} stopped changing`:`Searched ${L.searched.toLocaleString()} of ${L.total.toLocaleString()} sites \u2014 ${q.length} results`,Se=L.total-L.searched,ve=Object.entries(L.answered||{}).filter(([,A])=>A>0).map(([A,z])=>`${A==="local"?"this farm":A} (${z.toLocaleString()} site${z===1?"":"s"})`),Qe=L.siteIndex?.builtAt?` \xB7 site index built ${We(L.siteIndex.builtAt)}`:"",et=`<small class="sim-tier">semantic \xB7 nearest sites first${ve.length?` \xB7 answered by ${ve.join(", ")}`:""}${Qe}</small>`,tt=A=>{let z=L.siteInfo?.get(A.site),Y=[];return A.via&&Y.push(`via ${A.via}`),z?.indexedAt&&Y.push(`indexed ${We(z.indexedAt*1e3)}${z.placedBy?` by ${z.placedBy}`:""}`),Y.length?` <small class="sim-src">${Y.join(" \xB7 ")}</small>`:""},ke=L.opened||new Set,nt=L.pending?`<li class="sim-pending"><small>${L.pending} new result${L.pending>1?"s":""} rank${L.pending>1?"":"s"} above \u2014 <button class="sim-fold">fold in</button></small></li>`:"",st=L.resumed&&!L.running&&b?.batched?.ts?` \xB7 ${ye(b.batched.ts)}`:"",ot=L.running&&L.total?`<progress class="sim-progress" max="${L.total}" value="${L.searched}"></progress>`:"";g.innerHTML=`<h3>${Ze}${st}</h3>${ot}${et}<ul>${nt}${Xe.map(A=>`<li${ke.has(F(A))?' class="sim-opened"':""}>${ce(A.site,A.slug,A.title,A.semantic)}`+tt(A)+(ke.has(F(A))?' <small class="sim-mark">opened</small>':"")+(A.siblings?.length?` <small>+${A.siblings.length}</small>`:"")+(A.movedFrom?` <small>moved here from ${A.movedFrom}</small>`:"")+(A.gone?` <small>site ${A.gone}${A.movedTo?`, probably now ${A.movedTo}`:""}</small>`:"")+"</li>").join("")}</ul><p class="sim-count sim-batch-controls">`+(L.running?'<button class="sim-stop">Stop</button>':"")+(!L.running&&Se>0?`<button class="sim-more">Search the remaining ${Se.toLocaleString()} sites</button>`:"")+(!L.running&&q.length?'<button class="sim-open">Open as page</button>':"")+(L.unindexed?.length&&!L.running?`<br><small>Sites that look relevant but carry no page vectors yet: ${L.unindexed.slice(0,8).join(", ")}</small>`:"")+"</p>",g.querySelector(".sim-stop")?.addEventListener("click",()=>L.stop()),g.querySelector(".sim-more")?.addEventListener("click",()=>L.continueAll()),g.querySelector(".sim-open")?.addEventListener("click",()=>Z(q)),g.querySelector(".sim-fold")?.addEventListener("click",()=>L.foldIn()),g.querySelectorAll(".sim-link").forEach(A=>A.addEventListener("click",z=>{z.preventDefault();let{site:Y,slug:it,title:at}=A.dataset;re(Y,"clicked"),L.markOpened(F({site:Y,slug:it})),window.wiki.pageHandler.context={site:window.location.hostname,slug:"search-tool"},window.wiki.doInternalLink(at,e.parents(".page"),Y)}))};d.textContent="";let Q=await Me({origin:x,query:S,seeded:H,specs:v.length?v:["GALAXY"],limit:u,threshold:l,thresholdSet:D,batch:U||100,roster:B,algorithm:k,render:V,resume:N,status:q=>{d.textContent=q},save:q=>ee(t,{batched:{...q,ts:Date.now()}})});return d.textContent=c,Q},T=async()=>{let S=h.value.trim();if(S){y.disabled=!0,d.textContent=a.length?"Generating report (asking peer farms)\u2026":"Generating report\u2026";try{let v=await C;if(o(v)){let Z=m&&S===m.title?W():{};await j(S,v,Z);return}let H=m&&S===m.title?W():{},N={query:S,domains:v.length?v:["*"],limit:u,live:E,...H};a.length&&(N.farms=a),D&&(N.threshold=l);let k=await fetch(`${x}/system/search-report.json`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(N)});if(w.has(k.status)){let V=((await k.json().catch(()=>({}))).error||`status ${k.status}`).replace(/^embedding unavailable: /,"");d.textContent="Semantic search unavailable \u2014 searching by keyword instead\u2026",await r(S,v),d.textContent=`Semantic search unavailable (${V}) \u2014 these are keyword matches, ranked by word not meaning.`;return}if(!k.ok)throw new Error(`search-report failed: ${k.status}`);let B=await k.json(),_=window.wiki.newPage(B);window.wiki.showResult(_,{$page:e.parents(".page")}),d.textContent=c}catch(v){d.textContent=`Error: ${v.message}`}finally{y.disabled=!1}}};y.addEventListener("click",T),h.addEventListener("keydown",S=>{S.key==="Enter"&&T()});let P=!1;if(n==="report"&&s.join(",")==="*"){let S=Je();S&&(h.value=S,T(),P=!0)}if(!P&&n==="report"&&I&&h.value&&!b?.batched?.query&&(P=!0,C.then(()=>T())),!P&&n==="report"&&b?.batched?.query){let S=b.batched;h.value=S.query,(async()=>{try{let v=await C;if(!o(v))return;let H=m&&S.query===m.title?W():{};await j(S.query,v,H,S)}catch(v){d.textContent=`Remembered list unavailable: ${v.message}`}})()}}else if(n==="sites"){let h=e.find(".sim-input")[0],y=e.find(".sim-btn")[0],c=`Domains: ${s.length?s.join(", "):"*"}`;m&&!h.value&&(h.value=m.title),(async()=>{try{let r=await C,o=await te(r.length?r:["*"],x),g=o.reduce((j,T)=>j+(T.page_count||0),0);c=X("Site report ready",g,o.length),m&&(c=`${J(m)} \xB7 ${c}`),d.textContent=c}catch(r){d.textContent=`Domain listing unavailable: ${r.message}`}})();let w=async()=>{let r=h.value.trim();if(r){y.disabled=!0,d.textContent="Ranking sites\u2026";try{let o=await C,g=m&&r===m.title?W():{},j=await fetch(`${x}/system/site-report.json`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({query:r,domains:o.length?o:["*"],limit:u,...g})});if(!j.ok)throw new Error(`site-report failed: ${j.status}`);let T=await j.json();window.wiki.showResult(window.wiki.newPage(T),{$page:e.parents(".page")}),d.textContent=c}catch(o){d.textContent=`Error: ${o.message}`}finally{y.disabled=!1}}};y.addEventListener("click",w),h.addEventListener("keydown",r=>{r.key==="Enter"&&w()})}else if(n==="keyword"){let h=e.find(".sim-input")[0],y=e.find(".sim-btn")[0];m&&!h.value&&(h.value=m.title),d.textContent=(m?`${J(m)} \xB7 `:"")+`Keyword search ready \u2014 domains: ${s.length?s.join(", "):"*"} \xB7 limit ${u}`;let c=async()=>{let w=h.value.trim();if(w){y.disabled=!0,d.textContent="Searching live site indexes\u2026";try{let r=await C,o=encodeURIComponent((r.length?r:["*"]).join(",")),g=a.length?`&farms=${encodeURIComponent(a.join(","))}`:"",j=await fetch(`${x}/system/farm-search.json?q=${encodeURIComponent(w)}&pattern=${o}&limit=${u}${g}`);if(!j.ok)throw new Error(`farm-search failed: ${j.status}`);let T=await j.json();window.wiki.showResult(window.wiki.newPage(T),{$page:e.parents(".page")}),d.textContent=`Keyword search ready \u2014 domains: ${s.length?s.join(", "):"*"} \xB7 limit ${u}`}catch(r){d.textContent=`Error: ${r.message}`}finally{y.disabled=!1}}};y.addEventListener("click",c),h.addEventListener("keydown",w=>{w.key==="Enter"&&c()})}else if(n==="author"){let h=e.find(".sim-input")[0],y=e.find(".sim-btn")[0],c=e.find(".sim-results")[0],w=null;m&&!h.value&&(h.value=m.title),(async()=>{try{b||(d.textContent="Resolving domains\u2026"),w=await we(await C,x);let o=w.reduce((g,j)=>g+j.pages.length,0);d.textContent=(m?`${J(m)} \xB7 `:"")+X("Author ready",o,w.length)}catch(o){d.textContent=`Load error: ${o.message}`}})();let r=async()=>{let o=h.value.trim();if(!(!o||!w)){y.disabled=!0,d.textContent="Embedding query\u2026",c.innerHTML="";try{let g=await fe(o,x),j=ge(g,w,{threshold:l,limit:u,excludeSlug:null,excludeDomain:null}),T=new Set,P=[];for(let{title:k}of j)T.has(k)||(T.add(k),P.push(k));let S=()=>Math.floor(Math.random()*18446744073709552e3).toString(16).padStart(16,"0"),v=P.map(k=>`- [[${k}]]`).join(`
121
- `),H=[{type:"markdown",id:S(),text:`# Similar Pages
124
+ <p class="sim-count">${d.length} domains \u2014 ${r.toLocaleString()} pages${G(w)}</p>`};b?.domains?y(b.domains,b.ts):(async()=>{try{let d=await C,w=d.length?d.join(","):"*",r=`${x}/system/indexed-domains.json?pattern=${encodeURIComponent(w)}&limit=${u}`,o=await fetch(r);if(!o.ok)throw new Error(`indexed-domains failed: ${o.status}`);let g=await o.json();if(!g.length){h.textContent="No indexed domains found";return}y(g,null),ee(t,{domains:g})}catch(d){h.textContent=`Error: ${d.message}`}})()}else if(n==="similar"){let c=e.find(".sim-results")[0],y=(d,w,r)=>{if(!d.length){h.textContent=`No similar pages found above threshold ${l}`;return}let o=r!=null?` \xB7 one request, ${r<1e3?`${Math.round(r)} ms`:`${(r/1e3).toFixed(1)} s`}`:"";c.innerHTML=`<h3>Similar Pages</h3><ul>${d.map(({domain:g,slug:j,title:I,score:P})=>`<li>${de(g,j,I,P)}</li>`).join("")}</ul><p class="sim-count">${d.length} found ${M}${o}${G(w)}</p>`,h.style.display="none"};b?.scored?y(b.scored,b.ts):(async()=>{try{let d=e.parents(".page"),w=m||(()=>{let S=d.find(".title").text().trim()||document.title;return{slug:Ie(S),site:window.location.hostname,title:S,text:null,isSelf:!0}})(),r=await C;h.textContent=(m?`${J(w)} \xB7 `:"")+"Asking for pages like this one\u2026";let o=w.text||d.find(".item").map((S,H)=>$(H).text().trim()).get().filter(Boolean).join(`
125
+ `).slice(0,2e3),g={query:w.title,domains:r.length?r:["*"],limit:u,flat:!0,seed:{site:w.site,slug:w.slug},text:o||w.title,excludePage:{site:w.site,slug:w.slug}};D&&(g.threshold=l);let j=performance.now(),I=await fetch(`${x}/system/search-report.json`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)});if(!I.ok)throw new Error(`search-report failed: ${I.status}`);let v=((await I.json()).results||[]).map(S=>({domain:S.site,slug:S.slug,title:S.title,score:S.semantic??S.score}));y(v,null,performance.now()-j),m&&(h.textContent=J(w),h.style.display=""),v.length&&!m&&ee(t,{scored:v})}catch(d){h.textContent=`Error: ${d.message}`}})()}else if(n==="ghost"){let c=e.find(".sim-btn")[0],y=async()=>{if(!O){h.textContent="No URL \u2014 GHOST needs a page-json URL";return}c.disabled=!0,h.textContent="Fetching\u2026";try{let d=await fetch(O);if(!d.ok)throw new Error(`fetch failed: ${d.status}`);let w=await d.json();window.wiki.showResult(window.wiki.newPage(w),{$page:e.parents(".page")}),h.textContent=""}catch(d){h.textContent=`Error: ${d.message}`}finally{c.disabled=!1}};c.addEventListener("click",y)}else if(n==="build"){let c=e.find(".sim-btn")[0],y=async()=>{c.disabled=!0,h.textContent="Building index\u2026 (may take a while for large scopes)";try{let d=encodeURIComponent((s.length?s:["*"]).join(",")),w=await fetch(`${x}/system/build-index.json?domains=${d}&force=${R?1:0}`);if(w.status===501){let o=await w.json().catch(()=>({}));throw new Error(o.hint||"indexing runs on the farm indexer, not this server")}if(!w.ok)throw new Error(`build-index failed: ${w.status}`);let r=await w.json();window.wiki.showResult(window.wiki.newPage(r),{$page:e.parents(".page")}),h.textContent=""}catch(d){h.textContent=`Error: ${d.message}`}finally{c.disabled=!1}};c.addEventListener("click",y)}else if(n==="report"){let c=e.find(".sim-input")[0],y=e.find(".sim-btn")[0],d=`Domains: ${s.length?s.join(", "):"*"}`;m&&!c.value&&(c.value=m.title),(async()=>{try{let v=await C,S=await te(v.length?v:["*"],x),H=S.reduce((k,B)=>k+(B.page_count||0),0);if(d=X("Report ready",H,S.length),U!=null?U>0:v.some(k=>k.toUpperCase()==="GALAXY"))try{let B=(await fetch(`${x}/system/similarity-health.json`).then(Q=>Q.json())).siteIndex||{},z=v.filter(Q=>/\./.test(Q)&&!/[*?]/.test(Q)&&!["PUBLIC","LOCAL","PRIVATE","GALAXY"].includes(Q.toUpperCase())),Y=[`Ready \u2014 ${B.count?`ranks ${B.count.toLocaleString()} sites from the Site Index${B.via==="peer"?" (a peer's copy)":""}`:"no Site Index on this farm \u2014 batches ask its peers"}`];z.length&&Y.push(`${z.length} named site${z.length>1?"s":""} searched wherever held`),D&&Y.push(`threshold ${l}`),Y.push(`limit ${u}`),d=Y.join(" \xB7 ")}catch{}a.length&&(d+=` \xB7 +${a.length} peer farm${a.length>1?"s":""}`),m&&(d=`${J(m)} \xB7 ${d}`),h.textContent=d}catch(v){h.textContent=`Domain listing unavailable: ${v.message}`}})();let w=new Set([502,503]),r=async(v,S)=>{let H=encodeURIComponent((S.length?S:["*"]).join(",")),N=a.length?`&farms=${encodeURIComponent(a.join(","))}`:"",k=await fetch(`${x}/system/farm-search.json?q=${encodeURIComponent(v)}&pattern=${H}&limit=${u}${N}`);if(!k.ok)throw new Error(`keyword search also failed: ${k.status}`);let B=await k.json();window.wiki.showResult(window.wiki.newPage(B),{$page:e.parents(".page")})},o=v=>U!=null?U>0:v.some(S=>S.toUpperCase()==="GALAXY"),g=e.find(".sim-results")[0],j=async(v,S,H,N=null)=>{let k=await qe(p,x),B=S.filter(q=>!oe(q)&&q!=="*"&&!se(q)),z=null,Z=q=>{let L=Bt(v,q);z&&z.remove(),window.wiki.showResult(window.wiki.newPage(L),{$page:e.parents(".page")}),z=e.parents(".page").next(".page")},Y=(q,L)=>{let nt=L.shown&&L.shown.length?L.shown:q.slice(0,u),st=L.running?`Searched ${L.searched.toLocaleString()} of ${L.total.toLocaleString()} sites \u2014 ${q.length} results \u2014 searching\u2026`:L.converged?`Searched ${L.searched.toLocaleString()} of ${L.total.toLocaleString()} sites \u2014 ${q.length} results \u2014 the top ${u} stopped changing`:`Searched ${L.searched.toLocaleString()} of ${L.total.toLocaleString()} sites \u2014 ${q.length} results`,ke=L.total-L.searched,Le=Object.entries(L.answered||{}).filter(([,A])=>A>0).map(([A,_])=>`${A==="local"?"this farm":A} (${_.toLocaleString()} site${_===1?"":"s"})`),ot=L.siteIndex?.builtAt?` \xB7 site index built ${tt(L.siteIndex.builtAt)}`:"",it=`<small class="sim-tier">semantic \xB7 nearest sites first${Le.length?` \xB7 answered by ${Le.join(", ")}`:""}${ot}</small>`,at=A=>{let _=L.siteInfo?.get(A.site),V=[];return A.via&&V.push(`via ${A.via}`),_?.indexedAt&&V.push(`indexed ${tt(_.indexedAt*1e3)}${_.placedBy?` by ${_.placedBy}`:""}`),V.length?` <small class="sim-src">${V.join(" \xB7 ")}</small>`:""},Ee=L.opened||new Set,rt=L.pending?`<li class="sim-pending"><small>${L.pending} new result${L.pending>1?"s":""} rank${L.pending>1?"":"s"} above \u2014 <button class="sim-fold">fold in</button></small></li>`:"",lt=L.resumed&&!L.running&&b?.batched?.ts?` \xB7 ${$e(b.batched.ts)}`:"",ct=L.running&&L.total?`<progress class="sim-progress" max="${L.total}" value="${L.searched}"></progress>`:"";g.innerHTML=`<h3>${st}${lt}</h3>${ct}${it}<ul>${rt}${nt.map(A=>`<li${Ee.has(F(A))?' class="sim-opened"':""}>${de(A.site,A.slug,A.title,A.semantic)}`+at(A)+(Ee.has(F(A))?' <small class="sim-mark">opened</small>':"")+(A.siblings?.length?` <small>+${A.siblings.length}</small>`:"")+(A.movedFrom?` <small>moved here from ${A.movedFrom}</small>`:"")+(A.gone?` <small>site ${A.gone}${A.movedTo?`, probably now ${A.movedTo}`:""}</small>`:"")+"</li>").join("")}</ul><p class="sim-count sim-batch-controls">`+(L.running?'<button class="sim-stop">Stop</button>':"")+(!L.running&&ke>0?`<button class="sim-more">Search the remaining ${ke.toLocaleString()} sites</button>`:"")+(!L.running&&q.length?'<button class="sim-open">Open as page</button>':"")+(L.unindexed?.length&&!L.running?`<br><small>Sites that look relevant but carry no page vectors yet: ${L.unindexed.slice(0,8).join(", ")}</small>`:"")+"</p>",g.querySelector(".sim-stop")?.addEventListener("click",()=>L.stop()),g.querySelector(".sim-more")?.addEventListener("click",()=>L.continueAll()),g.querySelector(".sim-open")?.addEventListener("click",()=>Z(q)),g.querySelector(".sim-fold")?.addEventListener("click",()=>L.foldIn()),g.querySelectorAll(".sim-link").forEach(A=>A.addEventListener("click",_=>{if(_.metaKey||_.ctrlKey||_.button===1)return;_.preventDefault();let{site:V,slug:dt,title:ht}=A.dataset;le(V,"clicked"),L.markOpened(F({site:V,slug:dt})),window.wiki.pageHandler.context={site:window.location.hostname,slug:"search-tool"},window.wiki.doInternalLink(ht,e.parents(".page"),V)}))};h.textContent="";let Q=await Pe({origin:x,query:v,seeded:H,specs:S.length?S:["GALAXY"],limit:u,threshold:l,thresholdSet:D,batch:U||100,roster:B,algorithm:k,render:Y,resume:N,status:q=>{h.textContent=q},save:q=>ee(t,{batched:{...q,ts:Date.now()}})});return h.textContent=d,Q},I=async()=>{let v=c.value.trim();if(v){y.disabled=!0,h.textContent=a.length?"Generating report (asking peer farms)\u2026":"Generating report\u2026";try{let S=await C;if(o(S)){let Z=m&&v===m.title?W():{};await j(v,S,Z);return}let H=m&&v===m.title?W():{},N={query:v,domains:S.length?S:["*"],limit:u,live:E,...H};a.length&&(N.farms=a),D&&(N.threshold=l);let k=await fetch(`${x}/system/search-report.json`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(N)});if(w.has(k.status)){let Y=((await k.json().catch(()=>({}))).error||`status ${k.status}`).replace(/^embedding unavailable: /,"");h.textContent="Semantic search unavailable \u2014 searching by keyword instead\u2026",await r(v,S),h.textContent=`Semantic search unavailable (${Y}) \u2014 these are keyword matches, ranked by word not meaning.`;return}if(!k.ok)throw new Error(`search-report failed: ${k.status}`);let B=await k.json(),z=window.wiki.newPage(B);window.wiki.showResult(z,{$page:e.parents(".page")}),h.textContent=d}catch(S){h.textContent=`Error: ${S.message}`}finally{y.disabled=!1}}};y.addEventListener("click",I),c.addEventListener("keydown",v=>{v.key==="Enter"&&I()});let P=!1;if(n==="report"&&s.join(",")==="*"){let v=et();v&&(c.value=v,I(),P=!0)}if(!P&&n==="report"&&T&&c.value&&!b?.batched?.query&&(P=!0,C.then(()=>I())),!P&&n==="report"&&b?.batched?.query){let v=b.batched;c.value=v.query,(async()=>{try{let S=await C;if(!o(S))return;let H=m&&v.query===m.title?W():{};await j(v.query,S,H,v)}catch(S){h.textContent=`Remembered list unavailable: ${S.message}`}})()}}else if(n==="sites"){let c=e.find(".sim-input")[0],y=e.find(".sim-btn")[0],d=`Domains: ${s.length?s.join(", "):"*"}`;m&&!c.value&&(c.value=m.title),(async()=>{try{let r=await C,o=await te(r.length?r:["*"],x),g=o.reduce((j,I)=>j+(I.page_count||0),0);d=X("Site report ready",g,o.length),m&&(d=`${J(m)} \xB7 ${d}`),h.textContent=d}catch(r){h.textContent=`Domain listing unavailable: ${r.message}`}})();let w=async()=>{let r=c.value.trim();if(r){y.disabled=!0,h.textContent="Ranking sites\u2026";try{let o=await C,g=m&&r===m.title?W():{},j=await fetch(`${x}/system/site-report.json`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({query:r,domains:o.length?o:["*"],limit:u,...g})});if(!j.ok)throw new Error(`site-report failed: ${j.status}`);let I=await j.json();window.wiki.showResult(window.wiki.newPage(I),{$page:e.parents(".page")}),h.textContent=d}catch(o){h.textContent=`Error: ${o.message}`}finally{y.disabled=!1}}};y.addEventListener("click",w),c.addEventListener("keydown",r=>{r.key==="Enter"&&w()})}else if(n==="keyword"){let c=e.find(".sim-input")[0],y=e.find(".sim-btn")[0];m&&!c.value&&(c.value=m.title),h.textContent=(m?`${J(m)} \xB7 `:"")+`Keyword search ready \u2014 domains: ${s.length?s.join(", "):"*"} \xB7 limit ${u}`;let d=async()=>{let w=c.value.trim();if(w){y.disabled=!0,h.textContent="Searching live site indexes\u2026";try{let r=await C,o=encodeURIComponent((r.length?r:["*"]).join(",")),g=a.length?`&farms=${encodeURIComponent(a.join(","))}`:"",j=await fetch(`${x}/system/farm-search.json?q=${encodeURIComponent(w)}&pattern=${o}&limit=${u}${g}`);if(!j.ok)throw new Error(`farm-search failed: ${j.status}`);let I=await j.json();window.wiki.showResult(window.wiki.newPage(I),{$page:e.parents(".page")}),h.textContent=`Keyword search ready \u2014 domains: ${s.length?s.join(", "):"*"} \xB7 limit ${u}`}catch(r){h.textContent=`Error: ${r.message}`}finally{y.disabled=!1}}};y.addEventListener("click",d),c.addEventListener("keydown",w=>{w.key==="Enter"&&d()})}else if(n==="author"){let c=e.find(".sim-input")[0],y=e.find(".sim-btn")[0],d=e.find(".sim-results")[0],w=null;m&&!c.value&&(c.value=m.title),(async()=>{try{b||(h.textContent="Resolving domains\u2026"),w=await ye(await C,x);let o=w.reduce((g,j)=>g+j.pages.length,0);h.textContent=(m?`${J(m)} \xB7 `:"")+X("Author ready",o,w.length)}catch(o){h.textContent=`Load error: ${o.message}`}})();let r=async()=>{let o=c.value.trim();if(!(!o||!w)){y.disabled=!0,h.textContent="Embedding query\u2026",d.innerHTML="";try{let g=await ge(o,x),j=we(g,w,{threshold:l,limit:u,excludeSlug:null,excludeDomain:null}),I=new Set,P=[];for(let{title:k}of j)I.has(k)||(I.add(k),P.push(k));let v=()=>Math.floor(Math.random()*18446744073709552e3).toString(16).padStart(16,"0"),S=P.map(k=>`- [[${k}]]`).join(`
126
+ `),H=[{type:"markdown",id:v(),text:`# Similar Pages
122
127
 
123
- ${v}`},{type:"markdown",id:S(),text:"# Reference Links"},...j.map(({domain:k,slug:B,title:_,score:Z})=>({type:"reference",id:S(),site:k,slug:B,title:_,text:`score ${Z.toFixed(3)}`}))],N=window.wiki.newPage({title:`${o} Results`,story:H,journal:[]});window.wiki.showResult(N,{$page:e.parents(".page")}),d.textContent=`${j.length} pages found`,ee(t,{scored:j,query:o})}catch(g){d.textContent=`Error: ${g.message}`}finally{y.disabled=!1}}};y.addEventListener("click",r),h.addEventListener("keydown",o=>{o.key==="Enter"&&r()})}else{let h=e.find(".sim-input")[0],y=e.find(".sim-btn")[0],c=e.find(".sim-results")[0],w=null;b?.scored&&(h.value=b.query||"",c.innerHTML=b.scored.map(({domain:o,slug:g,title:j,score:T})=>`<div class="sim-result">${ce(o,g,j,T)}</div>`).join("")+`<p class="sim-count">Top ${b.scored.length} for "${b.query||""}"${G(b.ts)}</p>`,d.textContent=""),m&&!h.value&&(h.value=m.title),(async()=>{try{b||(d.textContent="Resolving domains\u2026"),w=await we(await C,x);let o=w.reduce((g,j)=>g+j.pages.length,0);d.textContent=(m?`${J(m)} \xB7 `:"")+X("Search ready",o,w.length)}catch(o){d.textContent=`Load error: ${o.message}`}})();let r=async()=>{let o=h.value.trim();if(!(!o||!w)){y.disabled=!0,d.textContent="Embedding query\u2026",c.innerHTML="";try{let g=await fe(o,x),j=ge(g,w,{threshold:l,limit:u,excludeSlug:null,excludeDomain:null});c.innerHTML=j.map(({domain:T,slug:P,title:S,score:v})=>`<div class="sim-result">${ce(T,P,S,v)}</div>`).join("")+`<p class="sim-count">Top ${j.length} for "${o}"</p>`,d.textContent="",ee(t,{scored:j,query:o})}catch(g){d.textContent=`Error: ${g.message}`}finally{y.disabled=!1}}};y.addEventListener("click",r),h.addEventListener("keydown",o=>{o.key==="Enter"&&r()})}};typeof window<"u"&&(window.plugins=window.plugins||{},window.plugins.similarity={emit:Pt,bind:Ut,editor:(e,t)=>window.wiki.textEditor(e,t)},Ve());})();
128
+ ${S}`},{type:"markdown",id:v(),text:"# Reference Links"},...j.map(({domain:k,slug:B,title:z,score:Z})=>({type:"reference",id:v(),site:k,slug:B,title:z,text:`score ${Z.toFixed(3)}`}))],N=window.wiki.newPage({title:`${o} Results`,story:H,journal:[]});window.wiki.showResult(N,{$page:e.parents(".page")}),h.textContent=`${j.length} pages found`,ee(t,{scored:j,query:o})}catch(g){h.textContent=`Error: ${g.message}`}finally{y.disabled=!1}}};y.addEventListener("click",r),c.addEventListener("keydown",o=>{o.key==="Enter"&&r()})}else{let c=e.find(".sim-input")[0],y=e.find(".sim-btn")[0],d=e.find(".sim-results")[0],w=null;b?.scored&&(c.value=b.query||"",d.innerHTML=b.scored.map(({domain:o,slug:g,title:j,score:I})=>`<div class="sim-result">${de(o,g,j,I)}</div>`).join("")+`<p class="sim-count">Top ${b.scored.length} for "${b.query||""}"${G(b.ts)}</p>`,h.textContent=""),m&&!c.value&&(c.value=m.title),(async()=>{try{b||(h.textContent="Resolving domains\u2026"),w=await ye(await C,x);let o=w.reduce((g,j)=>g+j.pages.length,0);h.textContent=(m?`${J(m)} \xB7 `:"")+X("Search ready",o,w.length)}catch(o){h.textContent=`Load error: ${o.message}`}})();let r=async()=>{let o=c.value.trim();if(!(!o||!w)){y.disabled=!0,h.textContent="Embedding query\u2026",d.innerHTML="";try{let g=await ge(o,x),j=we(g,w,{threshold:l,limit:u,excludeSlug:null,excludeDomain:null});d.innerHTML=j.map(({domain:I,slug:P,title:v,score:S})=>`<div class="sim-result">${de(I,P,v,S)}</div>`).join("")+`<p class="sim-count">Top ${j.length} for "${o}"</p>`,h.textContent="",ee(t,{scored:j,query:o})}catch(g){h.textContent=`Error: ${g.message}`}finally{y.disabled=!1}}};y.addEventListener("click",r),c.addEventListener("keydown",o=>{o.key==="Enter"&&r()})}};typeof window<"u"&&(window.plugins=window.plugins||{},window.plugins.similarity={emit:Ft,bind:Nt,editor:(e,t)=>window.wiki.textEditor(e,t)},Ze());})();
124
129
  //# sourceMappingURL=similarity.js.map