wiki-plugin-similarity 0.5.0 → 0.5.1
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 +1 -1
- package/package.json +5 -4
- package/pages/about-similarity-plugin +89 -69
- package/server/embedder.js +23 -0
- package/server/ort-hooks.mjs +6 -0
- package/server/ort-shim.mjs +2 -0
package/client/similarity.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* wiki-plugin-similarity - 0.5.
|
|
1
|
+
/* wiki-plugin-similarity - 0.5.1 - Sat, 11 Jul 2026 15:13:27 GMT */
|
|
2
2
|
(()=>{var z={high:.78,medium:.68,low:.58},O=z.medium,F=10,N=t=>{let n=[],a=null,e=null,r="search",x=!1,y=!1,C=null,b=null,h=(s,d)=>s===d||s.startsWith(d)&&/^[\s:]/.test(s.slice(d.length)),w=(s,d)=>s.slice(d.length).replace(/^\s*:?\s*/,"").trim();for(let s of t.split(`
|
|
3
3
|
`)){let d=s.trim();if(!d||d.startsWith("#"))continue;let g=d.toUpperCase();if(h(g,"LIVE")){x=!0;continue}if(h(g,"AUTHOR")){!n.length&&r==="search"&&(r="author");continue}if(h(g,"REPORT")){r==="search"&&(r="report");continue}if(h(g,"KEYWORD")){r==="search"&&(r="keyword");continue}if(h(g,"BUILD")){r==="search"&&(r="build");continue}if(h(g,"FORCE")){y=!0;continue}if(h(g,"GHOST")){C=w(d,"GHOST"),r==="search"&&(r="ghost");continue}if(h(g,"BUTTON")){b=w(d,"BUTTON");continue}if(h(g,"LIST")){!n.length&&r==="search"&&(r="list");continue}if(h(g,"SIMILAR")){let S=w(g,"SIMILAR").toLowerCase();a=z[S]||O,!n.length&&r==="search"&&(r="similar");continue}if(h(g,"THRESHOLD")){let S=w(d,"THRESHOLD");a=z[S.toLowerCase()]??(parseFloat(S)||O);continue}if(h(g,"LIMIT")){e=parseInt(w(d,"LIMIT"))||F;continue}n.push(["PUBLIC","LOCAL","PRIVATE"].includes(g)?g:d)}return{mode:r,specs:n,threshold:a??O,limit:e??F,live:x,force:y,ghostUrl:C,label:b,thresholdSet:a!==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,""),U=new Map,Z=async(t,n)=>{if(U.has(t))return U.get(t);let a=`${n}/system/indexed-domains.json?pattern=${encodeURIComponent(t)}`,e=await fetch(a);if(!e.ok)throw new Error(`indexed-domains failed: ${e.status}`);let r=await e.json();return U.set(t,r),r},B=async(t,n)=>{t.length||(t=[window.location.hostname]);let a=new Set,e=[];for(let r of t)if(r==="*"||Y(r)||Q(r))for(let x of await Z(r,n))a.has(x.domain)||(a.add(x.domain),e.push(x));else a.has(r)||(a.add(r),e.push({domain:r,page_count:null}));return e},M=new Map,tt=t=>`${window.location.origin}/system/semantic-vectors.json?domain=${encodeURIComponent(t)}`,V=async t=>{if(M.has(t))return M.get(t);let n=await fetch(tt(t));if(!n.ok)return[];let a=await n.json();return M.set(t,a),a},H=async(t,n)=>{let a=await fetch(`${n}/system/embed.json?text=${encodeURIComponent(t)}`);if(!a.ok)throw new Error(`embed failed: ${a.status}`);return(await a.json()).vector},et=async(t,n)=>{let e=(await V(n)).find(r=>r.slug===t);return e?e.vector:null},P=(t,n,{threshold:a,limit:e,excludeSlug:r,excludeDomain:x})=>{let y=[];for(let{domain:C,pages:b}of n)for(let{slug:h,title:w,vector:s}of b){if(h===r&&C===x)continue;let d=0;for(let g=0;g<t.length;g++)d+=t[g]*s[g];d>=a&&y.push({domain:C,slug:h,title:w,score:d})}return y.sort((C,b)=>b.score-C.score),y.slice(0,e)},A=async(t,n)=>{let a=await B(t,n);return(await Promise.all(a.map(async({domain:r})=>({domain:r,pages:await V(r)})))).filter(r=>r.pages.length>0)},K=t=>`sim-cache-${t}`,nt=t=>{try{let n=JSON.parse(localStorage.getItem(K(t.id))||"null");return n?.text===(t.text||"")?n:null}catch{return null}},R=(t,n)=>{try{localStorage.setItem(K(t.id),JSON.stringify({text:t.text||"",ts:Date.now(),...n}))}catch{}},st=t=>{let n=Math.floor((Date.now()-t)/1e3);return n<60?`${n}s ago`:n<3600?`${Math.floor(n/60)}m ago`:n<86400?`${Math.floor(n/3600)}h ago`:`${Math.floor(n/86400)}d ago`},T=`
|
|
4
4
|
.sim-form { display:flex; gap:6px; margin-bottom:8px; }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-plugin-similarity",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Federated Wiki plugin
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "Federated Wiki plugin \u2014 semantic similarity search across wiki domains using prebuilt vector indexes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wiki",
|
|
7
7
|
"federated wiki",
|
|
@@ -43,9 +43,10 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@xenova/transformers": "^2.17.2",
|
|
46
|
-
"minisearch": "^7.2.0"
|
|
46
|
+
"minisearch": "^7.2.0",
|
|
47
|
+
"onnxruntime-web": "1.14.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"esbuild": "^0.25.0"
|
|
50
51
|
}
|
|
51
|
-
}
|
|
52
|
+
}
|
|
@@ -3,70 +3,66 @@
|
|
|
3
3
|
"story": [
|
|
4
4
|
{
|
|
5
5
|
"type": "markdown",
|
|
6
|
-
"id": "
|
|
7
|
-
"text": "The Similarity plugin adds
|
|
6
|
+
"id": "aec1dfcd5f2bc42b",
|
|
7
|
+
"text": "The Similarity plugin adds search to a Federated Wiki farm — semantic similarity over prebuilt vector indexes, ranked search reports, and galactic keyword search over each site's own MiniSearch index. One item type — **similarity** — with a line-oriented DSL, plus a server component that serves every search same-origin. Works on any host: embedding runs in-process (native ONNX where available, WASM everywhere else)."
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"type": "markdown",
|
|
11
|
-
"id": "
|
|
12
|
-
"text": "
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"type": "similarity",
|
|
16
|
-
"id": "ad3c41667c285efd",
|
|
17
|
-
"text": "SIMILAR: medium\nLIMIT: 5"
|
|
11
|
+
"id": "28edff984cc5334d",
|
|
12
|
+
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE):\n\n- (no keyword) — interactive semantic search form\n- `SIMILAR: high|medium|low` — ambient: pages similar to this page\n- `REPORT` — server-side ranked, stub-filtered, fork-bundled report\n- `KEYWORD` — galactic MiniSearch over live site-index.json files\n- `LIST` — table of indexed domains\n- `AUTHOR` — search results as a draggable ghost page\n- `BUILD` / `FORCE` — proxy to the farm indexer, where configured\n- `GHOST url` / `BUTTON label` — open any page-json as a ghost page\n- `THRESHOLD: 0.72` · `LIMIT: 8` · `LIVE` — tuning"
|
|
18
13
|
},
|
|
19
14
|
{
|
|
20
15
|
"type": "markdown",
|
|
21
|
-
"id": "
|
|
22
|
-
"text": "##
|
|
16
|
+
"id": "251793ca0dae4810",
|
|
17
|
+
"text": "## Try It\n\nSemantic search across the farm:"
|
|
23
18
|
},
|
|
24
19
|
{
|
|
25
|
-
"type": "
|
|
26
|
-
"id": "
|
|
27
|
-
"text": "
|
|
20
|
+
"type": "similarity",
|
|
21
|
+
"id": "86aeab8be16dd8e1",
|
|
22
|
+
"text": "*"
|
|
28
23
|
},
|
|
29
24
|
{
|
|
30
25
|
"type": "markdown",
|
|
31
|
-
"id": "
|
|
32
|
-
"text": "
|
|
26
|
+
"id": "b6a9be0145bae9bd",
|
|
27
|
+
"text": "Pages similar to this one:"
|
|
33
28
|
},
|
|
34
29
|
{
|
|
35
|
-
"type": "
|
|
36
|
-
"id": "
|
|
37
|
-
"text": "
|
|
30
|
+
"type": "similarity",
|
|
31
|
+
"id": "cf558a16395d7d8c",
|
|
32
|
+
"text": "SIMILAR: medium\nLIMIT: 5"
|
|
38
33
|
},
|
|
39
34
|
{
|
|
40
35
|
"type": "markdown",
|
|
41
|
-
"id": "
|
|
42
|
-
"text": "
|
|
36
|
+
"id": "1aceb7035c7537b2",
|
|
37
|
+
"text": "A ranked report (opens as a ghost page):"
|
|
43
38
|
},
|
|
44
39
|
{
|
|
45
|
-
"type": "
|
|
46
|
-
"id": "
|
|
47
|
-
"text": "
|
|
40
|
+
"type": "similarity",
|
|
41
|
+
"id": "59bfe1e3968743a1",
|
|
42
|
+
"text": "REPORT\nLIMIT: 5"
|
|
48
43
|
},
|
|
49
44
|
{
|
|
50
45
|
"type": "markdown",
|
|
51
|
-
"id": "
|
|
52
|
-
"text": "
|
|
46
|
+
"id": "31b81a31f675c4e1",
|
|
47
|
+
"text": "Keyword search over the sites' own indexes:"
|
|
53
48
|
},
|
|
54
49
|
{
|
|
55
|
-
"type": "
|
|
56
|
-
"id": "
|
|
57
|
-
"text": "
|
|
50
|
+
"type": "similarity",
|
|
51
|
+
"id": "0c3fb9298ba8fd23",
|
|
52
|
+
"text": "KEYWORD\nLIMIT: 5"
|
|
58
53
|
},
|
|
59
54
|
{
|
|
60
55
|
"type": "markdown",
|
|
61
|
-
"id": "
|
|
62
|
-
"text": "##
|
|
56
|
+
"id": "d9a96dfebb9d1692",
|
|
57
|
+
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n\nIndexes are `status/semantic-vectors.json` per domain — built by the farm indexer, distributed by sync, read from farm disk. Keyword search reads each site's per-edit `status/site-index.json`; no separate index is built. Query embedding is BAAI bge-small-en-v1.5, 384-dim, CLS-pooled, unit-normalised — byte-compatible with the index builder."
|
|
63
58
|
},
|
|
64
59
|
{
|
|
65
60
|
"type": "markdown",
|
|
66
|
-
"id": "
|
|
67
|
-
"text": "
|
|
61
|
+
"id": "21eb5354d2adb29e",
|
|
62
|
+
"text": "**Version:** 0.5.1\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub] — architecture, indexing, freshness."
|
|
68
63
|
}
|
|
69
64
|
],
|
|
65
|
+
"plugin": "similarity",
|
|
70
66
|
"journal": [
|
|
71
67
|
{
|
|
72
68
|
"type": "create",
|
|
@@ -75,44 +71,68 @@
|
|
|
75
71
|
"story": [
|
|
76
72
|
{
|
|
77
73
|
"type": "markdown",
|
|
78
|
-
"id": "
|
|
79
|
-
"text": "The Similarity plugin adds
|
|
74
|
+
"id": "aec1dfcd5f2bc42b",
|
|
75
|
+
"text": "The Similarity plugin adds search to a Federated Wiki farm — semantic similarity over prebuilt vector indexes, ranked search reports, and galactic keyword search over each site's own MiniSearch index. One item type — **similarity** — with a line-oriented DSL, plus a server component that serves every search same-origin. Works on any host: embedding runs in-process (native ONNX where available, WASM everywhere else)."
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"type": "markdown",
|
|
79
|
+
"id": "28edff984cc5334d",
|
|
80
|
+
"text": "## The DSL\n\nThe first ALL-CAPS keyword sets the mode; other lines are domain specs (globs, explicit domains, or the scope keywords PUBLIC / LOCAL / PRIVATE):\n\n- (no keyword) — interactive semantic search form\n- `SIMILAR: high|medium|low` — ambient: pages similar to this page\n- `REPORT` — server-side ranked, stub-filtered, fork-bundled report\n- `KEYWORD` — galactic MiniSearch over live site-index.json files\n- `LIST` — table of indexed domains\n- `AUTHOR` — search results as a draggable ghost page\n- `BUILD` / `FORCE` — proxy to the farm indexer, where configured\n- `GHOST url` / `BUTTON label` — open any page-json as a ghost page\n- `THRESHOLD: 0.72` · `LIMIT: 8` · `LIVE` — tuning"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"type": "markdown",
|
|
84
|
+
"id": "251793ca0dae4810",
|
|
85
|
+
"text": "## Try It\n\nSemantic search across the farm:"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"type": "similarity",
|
|
89
|
+
"id": "86aeab8be16dd8e1",
|
|
90
|
+
"text": "*"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"type": "markdown",
|
|
94
|
+
"id": "b6a9be0145bae9bd",
|
|
95
|
+
"text": "Pages similar to this one:"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"type": "similarity",
|
|
99
|
+
"id": "cf558a16395d7d8c",
|
|
100
|
+
"text": "SIMILAR: medium\nLIMIT: 5"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"type": "markdown",
|
|
104
|
+
"id": "1aceb7035c7537b2",
|
|
105
|
+
"text": "A ranked report (opens as a ghost page):"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"type": "similarity",
|
|
109
|
+
"id": "59bfe1e3968743a1",
|
|
110
|
+
"text": "REPORT\nLIMIT: 5"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"type": "markdown",
|
|
114
|
+
"id": "31b81a31f675c4e1",
|
|
115
|
+
"text": "Keyword search over the sites' own indexes:"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"type": "similarity",
|
|
119
|
+
"id": "0c3fb9298ba8fd23",
|
|
120
|
+
"text": "KEYWORD\nLIMIT: 5"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"type": "markdown",
|
|
124
|
+
"id": "d9a96dfebb9d1692",
|
|
125
|
+
"text": "## Server Routes\n\nAll same-origin, registered by the plugin's server component:\n\n- `GET /system/indexed-domains.json?pattern=`\n- `GET /system/semantic-vectors.json?domain=`\n- `GET /system/embed.json?text=`\n- `POST /system/search-report.json`\n- `GET /system/farm-search.json?q=&pattern=&limit=`\n- `GET /system/build-index.json?domains=` (proxied to the farm indexer)\n\nIndexes are `status/semantic-vectors.json` per domain — built by the farm indexer, distributed by sync, read from farm disk. Keyword search reads each site's per-edit `status/site-index.json`; no separate index is built. Query embedding is BAAI bge-small-en-v1.5, 384-dim, CLS-pooled, unit-normalised — byte-compatible with the index builder."
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"type": "markdown",
|
|
129
|
+
"id": "21eb5354d2adb29e",
|
|
130
|
+
"text": "**Version:** 0.5.1\n\n# Links\n\n- [https://github.com/Hitchhikers-Guide-to-the-Galaxy/wiki-plugin-similarity GitHub repository]\n- [https://www.npmjs.com/package/wiki-plugin-similarity npm package]\n- [https://plugin.fedwiki.club/similarity-plugin.html Plugin documentation]\n- [https://search.fedwiki.club/ Search hub] — architecture, indexing, freshness."
|
|
80
131
|
}
|
|
81
132
|
]
|
|
82
133
|
},
|
|
83
|
-
"date":
|
|
84
|
-
"
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
"type": "edit",
|
|
88
|
-
"id": "a3f7c8d2e1b94056",
|
|
89
|
-
"item": {
|
|
90
|
-
"type": "markdown",
|
|
91
|
-
"id": "a3f7c8d2e1b94056",
|
|
92
|
-
"text": "**Version:** 0.5.0 — server-side search: everything runs same-origin on the wiki host, no external API needed."
|
|
93
|
-
},
|
|
94
|
-
"date": 1783773290699
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
"type": "edit",
|
|
98
|
-
"id": "4f91a3b2d8e05c11",
|
|
99
|
-
"item": {
|
|
100
|
-
"type": "markdown",
|
|
101
|
-
"id": "4f91a3b2d8e05c11",
|
|
102
|
-
"text": "**Search** (default) — interactive form; results appear inline as scored links. Shift-click a result to append it at the end of the lineup instead of truncating.\n\n**Similar** — runs on page load using the current page's pre-computed vector (no embed call if already indexed). Ranked *Similar Pages* list.\n\n**List** — table of indexed domains with page counts; supports scopes, globs, and `LIMIT`.\n\n**Author** — same search, but opens a ghost page with `# Similar Pages` ([[wiki links]], deduplicated) and `# Reference Links` (all results as reference items with scores).\n\n**Report** — posts to the plugin's own `/system/search-report.json`: candidates from semantic scan ∪ title matching, stubs dropped, forks and near-duplicates bundled under the strongest copy with a one-click lineup link, ranked by semantic × richness × recency + title boost. Opens as a ghost page.\n\n**Keyword** — galactic MiniSearch via `/system/farm-search.json`: the server reads each site's own per-edit `site-index.json` — the indexes fedwiki already maintains — plus sitemap titles. No separate index is built. Opens as a ghost page.\n\n**Build** — a button that asks the farm indexer to rebuild semantic indexes. The wiki server never embeds page corpora itself; it proxies to `WIKI_INDEXER_URL` when configured and otherwise explains that indexing runs on the farm indexer. Add `FORCE` to re-embed fresh domains.\n\n**Ghost** — a generic button: `GHOST <url>` fetches any endpoint returning fedwiki page JSON and opens it as a ghost page. Caption set with `BUTTON <text>`."
|
|
103
|
-
},
|
|
104
|
-
"date": 1783773290699
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
"type": "edit",
|
|
108
|
-
"id": "4b1bbe95544548eb",
|
|
109
|
-
"item": {
|
|
110
|
-
"type": "markdown",
|
|
111
|
-
"id": "4b1bbe95544548eb",
|
|
112
|
-
"text": "Pages are pre-indexed as 384-dimensional embedding vectors (BAAI/bge-small-en-v1.5, unit-normalised) by the farm indexer, which writes `semantic-vectors.json` into each domain's `status/` folder. The plugin loads these indexes and scores them with a dot product — no remote search engine required.\n\nThe server component registers same-origin `/system` routes: `indexed-domains.json`, `semantic-vectors.json`, `embed.json` (query embedding **in-process** via transformers.js — the same model the indexes were built with, so scores match), `search-report.json` (the full ranked report pipeline), `farm-search.json` (keyword search over the sites' own per-edit MiniSearch indexes), and `build-index.json` (indexer proxy). Because every route runs on the wiki host itself, search works for any visitor — no HomeLab services needed. See [[Similarity Indexing]] for building indices and [[Build Semantic Index]] for triggering builds from the wiki."
|
|
113
|
-
},
|
|
114
|
-
"date": 1783773290699
|
|
134
|
+
"date": 1783782734556,
|
|
135
|
+
"provenance": "Authored by an AI agent — Claude Fable 5 via the [[fedwiki-page]] skill. [https://claude.com/claude-code Claude Code]"
|
|
115
136
|
}
|
|
116
|
-
]
|
|
117
|
-
"plugin": "similarity"
|
|
137
|
+
]
|
|
118
138
|
}
|
package/server/embedder.js
CHANGED
|
@@ -19,14 +19,37 @@ const MODEL = process.env.WIKI_EMBED_MODEL || 'Xenova/bge-small-en-v1.5'
|
|
|
19
19
|
const POOLING = process.env.WIKI_EMBED_POOLING || 'cls'
|
|
20
20
|
const QUANTIZED = process.env.WIKI_EMBED_QUANTIZED === '1'
|
|
21
21
|
|
|
22
|
+
// transformers.js statically imports onnxruntime-node, whose native binary
|
|
23
|
+
// needs glibc. On musl containers (node:alpine — the public farm) that import
|
|
24
|
+
// throws "Error loading shared library ld-linux-x86-64.so.2". Probe the
|
|
25
|
+
// native runtime with a CJS require first; if it can't load, register a
|
|
26
|
+
// module hook (Node >= 18.19) that redirects the onnxruntime-node import to
|
|
27
|
+
// onnxruntime-web — the WASM backend runs everywhere and benchmarks ~25ms a
|
|
28
|
+
// query. WIKI_EMBED_FORCE_WASM=1 forces the WASM path for testing.
|
|
29
|
+
|
|
30
|
+
const nativeOrtLoads = () => {
|
|
31
|
+
if (process.env.WIKI_EMBED_FORCE_WASM === '1') return false
|
|
32
|
+
try { require('onnxruntime-node'); return true } catch { return false }
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
let extractorPromise = null
|
|
23
36
|
|
|
24
37
|
const getExtractor = () => {
|
|
25
38
|
if (!extractorPromise) {
|
|
26
39
|
extractorPromise = (async () => {
|
|
40
|
+
if (!nativeOrtLoads()) {
|
|
41
|
+
const { register } = require('node:module')
|
|
42
|
+
if (typeof register !== 'function') {
|
|
43
|
+
throw new Error('native onnxruntime unavailable and Node lacks ' +
|
|
44
|
+
'module.register (need >= 18.19) for the WASM fallback')
|
|
45
|
+
}
|
|
46
|
+
register('./ort-hooks.mjs', require('node:url').pathToFileURL(__filename))
|
|
47
|
+
console.log('[wiki-plugin-similarity] native onnxruntime unavailable — using WASM backend')
|
|
48
|
+
}
|
|
27
49
|
// Dynamic import: @xenova/transformers is ESM-only; this file must stay CJS.
|
|
28
50
|
const { pipeline, env } = await import('@xenova/transformers')
|
|
29
51
|
if (process.env.WIKI_MODEL_CACHE) env.cacheDir = process.env.WIKI_MODEL_CACHE
|
|
52
|
+
if (env.backends?.onnx?.wasm) env.backends.onnx.wasm.numThreads = 1
|
|
30
53
|
return pipeline('feature-extraction', MODEL, { quantized: QUANTIZED })
|
|
31
54
|
})()
|
|
32
55
|
extractorPromise.catch(() => { extractorPromise = null }) // allow retry
|