jekyll-client-search 0.1.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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +121 -0
  3. data/LICENSE +22 -0
  4. data/NOTICE +49 -0
  5. data/README.developer.md +204 -0
  6. data/README.md +948 -0
  7. data/assets/adapters/elasticlunr.js +59 -0
  8. data/assets/adapters/minisearch.js +57 -0
  9. data/assets/adapters/semantic.js +154 -0
  10. data/assets/client-search-base.js +294 -0
  11. data/assets/client-search-related.js +176 -0
  12. data/assets/includes/related-articles.html +36 -0
  13. data/assets/layouts/post-with-related.html +54 -0
  14. data/assets/query-embedders/ollama-api.js +63 -0
  15. data/assets/query-embedders/transformers-worker.js +130 -0
  16. data/assets/query-embedders/transformers.js +223 -0
  17. data/docs/assets/icon-256.png +0 -0
  18. data/docs/assets/icon.svg +133 -0
  19. data/lib/jekyll/client_search/configuration.rb +152 -0
  20. data/lib/jekyll/client_search/configuration_accessors.rb +48 -0
  21. data/lib/jekyll/client_search/document_builder.rb +66 -0
  22. data/lib/jekyll/client_search/embedder_config_page.rb +14 -0
  23. data/lib/jekyll/client_search/embedding_configuration.rb +95 -0
  24. data/lib/jekyll/client_search/generator.rb +134 -0
  25. data/lib/jekyll/client_search/index_cache.rb +82 -0
  26. data/lib/jekyll/client_search/live_search_configuration.rb +70 -0
  27. data/lib/jekyll/client_search/ollama_embedding_adapter.rb +59 -0
  28. data/lib/jekyll/client_search/query_embedder_configuration.rb +127 -0
  29. data/lib/jekyll/client_search/related_analyzer.rb +152 -0
  30. data/lib/jekyll/client_search/related_configuration.rb +103 -0
  31. data/lib/jekyll/client_search/related_page.rb +14 -0
  32. data/lib/jekyll/client_search/related_tag.rb +76 -0
  33. data/lib/jekyll/client_search/runtime_config_page.rb +30 -0
  34. data/lib/jekyll/client_search/search_index_page.rb +15 -0
  35. data/lib/jekyll/client_search/search_tag.rb +100 -0
  36. data/lib/jekyll/client_search/tasks.rb +137 -0
  37. data/lib/jekyll/client_search/version.rb +7 -0
  38. data/lib/jekyll/client_search.rb +25 -0
  39. data/lib/jekyll-client-search.rb +3 -0
  40. metadata +104 -0
@@ -0,0 +1,176 @@
1
+ (function () {
2
+ "use strict";
3
+
4
+ function safeUrl(value) {
5
+ try {
6
+ var url = new URL(value, window.location.origin);
7
+ if (url.origin === window.location.origin &&
8
+ ["http:", "https:"].includes(url.protocol)) {
9
+ return url.pathname + url.search + url.hash;
10
+ }
11
+ } catch (_error) {
12
+ return "#";
13
+ }
14
+ return "#";
15
+ }
16
+
17
+ function formatDate(timestamp) {
18
+ if (!timestamp) {
19
+ return "";
20
+ }
21
+ try {
22
+ return new Date(timestamp * 1000).toLocaleDateString(undefined, {
23
+ year: "numeric", month: "short", day: "numeric"
24
+ });
25
+ } catch (_error) {
26
+ return "";
27
+ }
28
+ }
29
+
30
+ function appendMeta(parent, className, text) {
31
+ if (!text) {
32
+ return;
33
+ }
34
+ var span = document.createElement("span");
35
+ span.className = className;
36
+ span.textContent = text;
37
+ parent.appendChild(span);
38
+ }
39
+
40
+ function defaultRenderItem(item) {
41
+ var entry = document.createElement("li");
42
+ entry.className = "related-article-item";
43
+
44
+ var link = document.createElement("a");
45
+ link.href = safeUrl(item.url);
46
+ link.textContent = item.title || item.url;
47
+ link.className = "related-article-link";
48
+ entry.appendChild(link);
49
+
50
+ var metaParts = [];
51
+ if (item.date_timestamp) {
52
+ var dateSpan = document.createElement("span");
53
+ dateSpan.className = "related-article-date";
54
+ dateSpan.textContent = formatDate(item.date_timestamp);
55
+ metaParts.push(dateSpan);
56
+ }
57
+ if (Array.isArray(item.shared_tags) && item.shared_tags.length) {
58
+ appendMeta(entry, "related-article-tags", item.shared_tags.join(", "));
59
+ }
60
+ if (Array.isArray(item.shared_categories) && item.shared_categories.length) {
61
+ appendMeta(entry, "related-article-categories", item.shared_categories.join(", "));
62
+ }
63
+ if (metaParts.length) {
64
+ var meta = document.createElement("div");
65
+ meta.className = "related-article-meta";
66
+ metaParts.forEach(function (el) { meta.appendChild(el); });
67
+ entry.appendChild(meta);
68
+ }
69
+
70
+ if (item.excerpt) {
71
+ var excerpt = document.createElement("p");
72
+ excerpt.className = "related-article-excerpt";
73
+ excerpt.textContent = item.excerpt;
74
+ entry.appendChild(excerpt);
75
+ }
76
+
77
+ return entry;
78
+ }
79
+
80
+ function sortItems(items, sortOrder) {
81
+ if (sortOrder === "date") {
82
+ items.sort(function (a, b) {
83
+ return (b.date_timestamp || 0) - (a.date_timestamp || 0) ||
84
+ String(a.title).localeCompare(String(b.title));
85
+ });
86
+ } else {
87
+ items.sort(function (a, b) {
88
+ return (b.score || 0) - (a.score || 0) ||
89
+ String(a.title).localeCompare(String(b.title));
90
+ });
91
+ }
92
+ }
93
+
94
+ function render(container, relations, sortOrder, options) {
95
+ var items = relations.slice();
96
+ if (typeof options.filter === "function") {
97
+ items = items.filter(function (item, index, array) {
98
+ return options.filter(item, index, array);
99
+ });
100
+ }
101
+ sortItems(items, sortOrder);
102
+
103
+ container.replaceChildren();
104
+ if (items.length === 0) {
105
+ return;
106
+ }
107
+
108
+ var heading = document.createElement("h2");
109
+ heading.textContent = "Related articles";
110
+ var list = document.createElement("ul");
111
+ list.className = "related-articles-list";
112
+
113
+ var renderItem = options.renderItem || defaultRenderItem;
114
+ items.forEach(function (item) {
115
+ var entry = renderItem(item, document);
116
+ if (entry) {
117
+ list.appendChild(entry);
118
+ }
119
+ });
120
+ container.append(heading, list);
121
+ }
122
+
123
+ function resolveSort(container, sortControl, fallback) {
124
+ if (sortControl) {
125
+ return sortControl.value || fallback;
126
+ }
127
+ if (container.dataset.relatedSort) {
128
+ return container.dataset.relatedSort;
129
+ }
130
+ return fallback;
131
+ }
132
+
133
+ window.ClientSearchRelated = {
134
+ run: function (options) {
135
+ options = Object.assign({
136
+ container: "#related-articles",
137
+ sortControl: "#related-sort",
138
+ relationsUrl: window.clientSearchConfig && window.clientSearchConfig.relatedUrl,
139
+ currentUrl: window.location.pathname,
140
+ sort: (window.clientSearchConfig && window.clientSearchConfig.relatedSort) || "relevance",
141
+ renderItem: null,
142
+ filter: null
143
+ }, options || {});
144
+ var container = document.querySelector(options.container);
145
+ if (!container || !options.relationsUrl) {
146
+ return Promise.resolve();
147
+ }
148
+
149
+ return fetch(options.relationsUrl, { headers: { Accept: "application/json" } })
150
+ .then(function (response) {
151
+ if (!response.ok) {
152
+ throw new Error("Unable to load related articles");
153
+ }
154
+ return response.json();
155
+ })
156
+ .then(function (data) {
157
+ var relations = (data.relations && data.relations[options.currentUrl]) || [];
158
+ var sortControl = document.querySelector(options.sortControl);
159
+ var sort = resolveSort(container, sortControl, options.sort);
160
+ render(container, relations, sort, options);
161
+ if (sortControl) {
162
+ sortControl.addEventListener("change", function () {
163
+ render(container, relations, sortControl.value, options);
164
+ });
165
+ }
166
+ })
167
+ .catch(function () {
168
+ container.replaceChildren();
169
+ });
170
+ }
171
+ };
172
+
173
+ if (document.querySelector("#related-articles")) {
174
+ window.ClientSearchRelated.run();
175
+ }
176
+ }());
@@ -0,0 +1,36 @@
1
+ ---
2
+ # Reference include for related articles.
3
+ #
4
+ # Copy this file into your site's _includes/ directory:
5
+ # cp $(bundle show jekyll-client-search)/assets/includes/related-articles.html _includes/
6
+ #
7
+ # Then add one line to your post layout:
8
+ # {% include related-articles.html %}
9
+ #
10
+ # Or use the Liquid tag directly (no file copy needed):
11
+ # {% related_articles %}
12
+ #
13
+ # Available parameters via include variables:
14
+ # sort: "relevance" (default) or "date"
15
+ # scripts: true (default) to load runtime scripts, false to skip
16
+ ---
17
+
18
+ {% assign related_sort = include.sort | default: "relevance" %}
19
+ {% assign include_scripts = include.scripts | default: true %}
20
+ {% if include_scripts == false or include_scripts == "false" %}
21
+ {% assign include_scripts = false %}
22
+ {% endif %}
23
+
24
+ <section class="related-articles-section">
25
+ <label for="related-sort">Sort related articles</label>
26
+ <select id="related-sort">
27
+ <option value="relevance">Most related</option>
28
+ <option value="date">Newest</option>
29
+ </select>
30
+ <div id="related-articles" data-related-sort="{{ related_sort }}"></div>
31
+ </section>
32
+
33
+ {% if include_scripts %}
34
+ <script src="{{ '/assets/search-runtime-config.js' | relative_url }}"></script>
35
+ <script src="{{ '/assets/client-search-related.js' | relative_url }}"></script>
36
+ {% endif %}
@@ -0,0 +1,54 @@
1
+ ---
2
+ # Reference post layout with related articles.
3
+ #
4
+ # Copy this file into your site's _layouts/ directory and adapt it:
5
+ # cp $(bundle show jekyll-client-search)/assets/layouts/post-with-related.html _layouts/
6
+ #
7
+ # Then use it in front matter:
8
+ # layout: post-with-related
9
+ #
10
+ # This is a minimal starting point. Most sites already have a post layout
11
+ # they like — in that case, just add the {% related_articles %} tag or
12
+ # {% include related-articles.html %} to your existing layout instead.
13
+ ---
14
+ <!doctype html>
15
+ <html lang="{{ page.lang | default: site.lang | default: 'en' }}">
16
+ <head>
17
+ <meta charset="utf-8">
18
+ <meta name="viewport" content="width=device-width, initial-scale=1">
19
+ <title>{{ page.title | default: site.title }}</title>
20
+ </head>
21
+ <body>
22
+ <header>
23
+ <a href="{{ '/' | relative_url }}">{{ site.title }}</a>
24
+ </header>
25
+
26
+ <main>
27
+ <article>
28
+ <h1>{{ page.title }}</h1>
29
+ {% if page.date %}
30
+ <p class="post-date">
31
+ Published: {{ page.date | date: '%b %-d, %Y' }}
32
+ {% if page.author %} by {{ page.author }}{% endif %}
33
+ </p>
34
+ {% endif %}
35
+
36
+ {{ content }}
37
+
38
+ {% if page.tags and page.tags.size > 0 %}
39
+ <div class="tags">
40
+ {% for tag in page.tags %}
41
+ <span class="tag">{{ tag }}</span>
42
+ {% endfor %}
43
+ </div>
44
+ {% endif %}
45
+ </article>
46
+
47
+ {% related_articles %}
48
+ </main>
49
+
50
+ <footer>
51
+ <p>&copy; {{ site.time | date: '%Y' }} {{ site.title }}</p>
52
+ </footer>
53
+ </body>
54
+ </html>
@@ -0,0 +1,63 @@
1
+ (function () {
2
+ "use strict";
3
+
4
+ var config = window.ClientSearchEmbedderConfig || {};
5
+ var model = config.model || config.buildModel || "embeddinggemma:300m";
6
+ var apiUrl = config.apiUrl || "http://localhost:11434/api/embed";
7
+ var activeController = null;
8
+
9
+ function reportStatus(message) {
10
+ window.dispatchEvent(new CustomEvent("client-search:status", {
11
+ detail: { message: message }
12
+ }));
13
+ }
14
+
15
+ window.ClientSearchQueryEmbedder = async function (query) {
16
+ if (activeController) {
17
+ activeController.abort();
18
+ }
19
+ var controller = new AbortController();
20
+ var timeoutMs = Number.isInteger(config.timeoutMs) ? config.timeoutMs : 30000;
21
+ var timedOut = false;
22
+ var timer = setTimeout(function () {
23
+ timedOut = true;
24
+ controller.abort();
25
+ }, timeoutMs);
26
+ activeController = controller;
27
+ reportStatus("Contacting semantic search…");
28
+
29
+ try {
30
+ var response = await fetch(apiUrl, {
31
+ method: "POST",
32
+ headers: { "Content-Type": "application/json" },
33
+ body: JSON.stringify({
34
+ model: model,
35
+ input: (config.queryPrefix || "") + query
36
+ }),
37
+ signal: controller.signal
38
+ });
39
+
40
+ if (!response.ok) {
41
+ throw new Error("Ollama API returned " + response.status);
42
+ }
43
+
44
+ var data = await response.json();
45
+ var embedding = data.embeddings && data.embeddings[0];
46
+ if (!embedding || embedding.length === 0) {
47
+ throw new Error("Ollama API returned empty embedding");
48
+ }
49
+
50
+ return embedding;
51
+ } catch (error) {
52
+ if (timedOut) {
53
+ throw new Error("Ollama API request timed out");
54
+ }
55
+ throw error;
56
+ } finally {
57
+ clearTimeout(timer);
58
+ if (activeController === controller) {
59
+ activeController = null;
60
+ }
61
+ }
62
+ };
63
+ }());
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+
3
+ var runtimePromise = null;
4
+ var modelPromise = null;
5
+ var activeConfig = null;
6
+ var inferenceQueue = Promise.resolve();
7
+ var latestRequestId = null;
8
+
9
+ function reportStatus(message) {
10
+ self.postMessage({ type: "status", message: message });
11
+ }
12
+
13
+ function configureRuntime(runtime) {
14
+ if (activeConfig.modelBaseUrl && runtime.env) {
15
+ runtime.env.localModelPath = activeConfig.modelBaseUrl;
16
+ runtime.env.allowRemoteModels = false;
17
+ }
18
+ if (activeConfig.wasmBaseUrl && runtime.env && runtime.env.backends &&
19
+ runtime.env.backends.onnx && runtime.env.backends.onnx.wasm) {
20
+ runtime.env.backends.onnx.wasm.wasmPaths = activeConfig.wasmBaseUrl;
21
+ }
22
+ return runtime;
23
+ }
24
+
25
+ function retry(operation) {
26
+ var attempts = Number.isInteger(activeConfig.retryAttempts) ? activeConfig.retryAttempts : 1;
27
+ return operation().catch(function retryAfterFailure(error) {
28
+ if (attempts <= 0) {
29
+ throw error;
30
+ }
31
+ attempts -= 1;
32
+ return new Promise(function (resolve) {
33
+ setTimeout(resolve, 1000);
34
+ }).then(operation).catch(retryAfterFailure);
35
+ });
36
+ }
37
+
38
+ function progress(update) {
39
+ if (update && Number.isFinite(update.progress)) {
40
+ reportStatus("Loading semantic model… " + Math.round(update.progress) + "%");
41
+ } else {
42
+ reportStatus("Loading semantic model…");
43
+ }
44
+ }
45
+
46
+ function loadRuntime() {
47
+ if (self.ClientSearchTransformers) {
48
+ return Promise.resolve(configureRuntime(self.ClientSearchTransformers));
49
+ }
50
+ if (!runtimePromise) {
51
+ runtimePromise = retry(function () {
52
+ return import(
53
+ activeConfig.libraryUrl ||
54
+ "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1"
55
+ ).then(configureRuntime);
56
+ }).catch(function (error) {
57
+ runtimePromise = null;
58
+ throw error;
59
+ });
60
+ }
61
+ return runtimePromise;
62
+ }
63
+
64
+ function loadModel() {
65
+ if (!modelPromise) {
66
+ reportStatus("Loading semantic model…");
67
+ modelPromise = retry(function () {
68
+ return loadRuntime().then(async function (runtime) {
69
+ var modelId = activeConfig.model || "onnx-community/embeddinggemma-300m-ONNX";
70
+ var tokenizer = await runtime.AutoTokenizer.from_pretrained(modelId, {
71
+ progress_callback: progress
72
+ });
73
+ var options = {
74
+ dtype: activeConfig.dtype || "q8",
75
+ progress_callback: progress
76
+ };
77
+ if (activeConfig.device) {
78
+ options.device = activeConfig.device;
79
+ }
80
+ var model = await runtime.AutoModel.from_pretrained(modelId, options);
81
+ return { tokenizer: tokenizer, model: model };
82
+ });
83
+ }).catch(function (error) {
84
+ modelPromise = null;
85
+ throw error;
86
+ });
87
+ }
88
+ return modelPromise;
89
+ }
90
+
91
+ async function embed(query) {
92
+ var loaded = await loadModel();
93
+ reportStatus("Embedding query…");
94
+ var input = (activeConfig.queryPrefix || "") + query;
95
+ var tokens = await loaded.tokenizer([input], {
96
+ padding: true,
97
+ truncation: true,
98
+ max_length: Number.isInteger(activeConfig.maxTokens) ? activeConfig.maxTokens : 512
99
+ });
100
+ var output = await loaded.model(tokens);
101
+ var embedding = output.sentence_embedding;
102
+ if (!embedding || !embedding.data) {
103
+ throw new Error("Transformers model returned no sentence embedding");
104
+ }
105
+ return Array.from(embedding.data);
106
+ }
107
+
108
+ self.addEventListener("message", function (event) {
109
+ var message = event.data || {};
110
+ activeConfig = activeConfig || message.config || {};
111
+ latestRequestId = message.id;
112
+ inferenceQueue = inferenceQueue.then(function () {
113
+ if (message.id !== latestRequestId) {
114
+ return null;
115
+ }
116
+ return embed(message.query);
117
+ }).then(function (embedding) {
118
+ if (embedding && message.id === latestRequestId) {
119
+ self.postMessage({ type: "result", id: message.id, embedding: embedding });
120
+ }
121
+ }, function (error) {
122
+ if (message.id === latestRequestId) {
123
+ self.postMessage({
124
+ type: "error",
125
+ id: message.id,
126
+ error: error && error.message ? error.message : String(error)
127
+ });
128
+ }
129
+ });
130
+ });
@@ -0,0 +1,223 @@
1
+ (function () {
2
+ "use strict";
3
+
4
+ var config = window.ClientSearchEmbedderConfig || {};
5
+ var modelId = config.model || "onnx-community/embeddinggemma-300m-ONNX";
6
+ var runtimePromise = null;
7
+ var modelPromise = null;
8
+ var worker = null;
9
+ var workerUnavailable = false;
10
+ var workerRequests = new Map();
11
+ var nextWorkerRequestId = 1;
12
+ var currentScriptUrl = document.currentScript && document.currentScript.src;
13
+
14
+ function reportStatus(message) {
15
+ window.dispatchEvent(new CustomEvent("client-search:status", {
16
+ detail: { message: message }
17
+ }));
18
+ }
19
+
20
+ function configureRuntime(runtime) {
21
+ if (config.modelBaseUrl && runtime.env) {
22
+ runtime.env.localModelPath = config.modelBaseUrl;
23
+ runtime.env.allowRemoteModels = false;
24
+ }
25
+ if (config.wasmBaseUrl && runtime.env && runtime.env.backends &&
26
+ runtime.env.backends.onnx && runtime.env.backends.onnx.wasm) {
27
+ runtime.env.backends.onnx.wasm.wasmPaths = config.wasmBaseUrl;
28
+ }
29
+ return runtime;
30
+ }
31
+
32
+ function retry(operation) {
33
+ var attempts = Number.isInteger(config.retryAttempts) ? config.retryAttempts : 1;
34
+ return operation().catch(function retryAfterFailure(error) {
35
+ if (attempts <= 0) {
36
+ throw error;
37
+ }
38
+ attempts -= 1;
39
+ return new Promise(function (resolve) {
40
+ setTimeout(resolve, 1000);
41
+ }).then(operation).catch(retryAfterFailure);
42
+ });
43
+ }
44
+
45
+ function withTimeout(promise) {
46
+ var timeoutMs = Number.isInteger(config.timeoutMs) ? config.timeoutMs : 300000;
47
+ return new Promise(function (resolve, reject) {
48
+ var timer = setTimeout(function () {
49
+ reject(new Error("Transformers model loading timed out"));
50
+ }, timeoutMs);
51
+ promise.then(function (value) {
52
+ clearTimeout(timer);
53
+ resolve(value);
54
+ }, function (error) {
55
+ clearTimeout(timer);
56
+ reject(error);
57
+ });
58
+ });
59
+ }
60
+
61
+ function progress(update) {
62
+ if (update && Number.isFinite(update.progress)) {
63
+ reportStatus("Loading semantic model… " + Math.round(update.progress) + "%");
64
+ } else {
65
+ reportStatus("Loading semantic model…");
66
+ }
67
+ }
68
+
69
+ function loadRuntime() {
70
+ if (window.ClientSearchTransformers) {
71
+ return Promise.resolve(configureRuntime(window.ClientSearchTransformers));
72
+ }
73
+ if (!runtimePromise) {
74
+ runtimePromise = retry(function () {
75
+ return import(
76
+ config.libraryUrl ||
77
+ "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1"
78
+ ).then(configureRuntime);
79
+ }).catch(function (error) {
80
+ runtimePromise = null;
81
+ throw error;
82
+ });
83
+ }
84
+ return runtimePromise;
85
+ }
86
+
87
+ function loadModel() {
88
+ if (!modelPromise) {
89
+ reportStatus("Loading semantic model…");
90
+ modelPromise = retry(function () {
91
+ return loadRuntime().then(async function (runtime) {
92
+ var tokenizer = await runtime.AutoTokenizer.from_pretrained(modelId, {
93
+ progress_callback: progress
94
+ });
95
+ var options = {
96
+ dtype: config.dtype || "q8",
97
+ progress_callback: progress
98
+ };
99
+ if (config.device) {
100
+ options.device = config.device;
101
+ }
102
+ var model = await runtime.AutoModel.from_pretrained(modelId, options);
103
+ return { tokenizer: tokenizer, model: model };
104
+ });
105
+ }).catch(function (error) {
106
+ modelPromise = null;
107
+ throw error;
108
+ });
109
+ }
110
+ return withTimeout(modelPromise);
111
+ }
112
+
113
+ async function embedInline(query) {
114
+ var loaded = await loadModel();
115
+ reportStatus("Embedding query…");
116
+ var input = (config.queryPrefix || "") + query;
117
+ var tokens = await loaded.tokenizer([input], {
118
+ padding: true,
119
+ truncation: true,
120
+ max_length: Number.isInteger(config.maxTokens) ? config.maxTokens : 512
121
+ });
122
+ var output = await loaded.model(tokens);
123
+ var embedding = output.sentence_embedding;
124
+ if (!embedding || !embedding.data) {
125
+ throw new Error("Transformers model returned no sentence embedding");
126
+ }
127
+ return Array.from(embedding.data);
128
+ }
129
+
130
+ function workerUrl() {
131
+ if (config.workerUrl) {
132
+ return config.workerUrl;
133
+ }
134
+ if (currentScriptUrl) {
135
+ return new URL("transformers-worker.js", currentScriptUrl).href;
136
+ }
137
+ return "/assets/query-embedders/transformers-worker.js";
138
+ }
139
+
140
+ function rejectWorkerRequests(error, useFallback) {
141
+ workerRequests.forEach(function (request) {
142
+ clearTimeout(request.timer);
143
+ if (useFallback) {
144
+ embedInline(request.query).then(request.resolve, request.reject);
145
+ } else {
146
+ request.reject(error);
147
+ }
148
+ });
149
+ workerRequests.clear();
150
+ }
151
+
152
+ function createWorker() {
153
+ if (worker || workerUnavailable || config.worker === false || typeof Worker !== "function") {
154
+ return worker;
155
+ }
156
+ try {
157
+ worker = new Worker(workerUrl(), { type: "module" });
158
+ worker.addEventListener("message", function (event) {
159
+ var message = event.data || {};
160
+ if (message.type === "status") {
161
+ reportStatus(message.message);
162
+ return;
163
+ }
164
+ var request = workerRequests.get(message.id);
165
+ if (!request) {
166
+ return;
167
+ }
168
+ clearTimeout(request.timer);
169
+ workerRequests.delete(message.id);
170
+ if (message.type === "result") {
171
+ request.resolve(message.embedding);
172
+ } else {
173
+ request.reject(new Error(message.error || "Transformers worker failed"));
174
+ }
175
+ });
176
+ worker.addEventListener("error", function () {
177
+ workerUnavailable = true;
178
+ worker.terminate();
179
+ worker = null;
180
+ rejectWorkerRequests(new Error("Transformers worker failed"), true);
181
+ });
182
+ } catch (_error) {
183
+ workerUnavailable = true;
184
+ worker = null;
185
+ }
186
+ return worker;
187
+ }
188
+
189
+ function abortObsoleteWorkerRequests() {
190
+ workerRequests.forEach(function (request) {
191
+ clearTimeout(request.timer);
192
+ var error = new Error("Obsolete semantic query");
193
+ error.name = "AbortError";
194
+ request.reject(error);
195
+ });
196
+ workerRequests.clear();
197
+ }
198
+
199
+ function embedWithWorker(query) {
200
+ var activeWorker = createWorker();
201
+ if (!activeWorker) {
202
+ return embedInline(query);
203
+ }
204
+ abortObsoleteWorkerRequests();
205
+ return new Promise(function (resolve, reject) {
206
+ var id = nextWorkerRequestId++;
207
+ var timeoutMs = Number.isInteger(config.timeoutMs) ? config.timeoutMs : 300000;
208
+ var timer = setTimeout(function () {
209
+ workerRequests.delete(id);
210
+ reject(new Error("Transformers worker timed out"));
211
+ }, timeoutMs);
212
+ workerRequests.set(id, {
213
+ query: query,
214
+ resolve: resolve,
215
+ reject: reject,
216
+ timer: timer
217
+ });
218
+ activeWorker.postMessage({ id: id, query: query, config: config });
219
+ });
220
+ }
221
+
222
+ window.ClientSearchQueryEmbedder = embedWithWorker;
223
+ }());
Binary file