@cloudflare/ai-search-snippet 0.0.38 → 0.0.39

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,7 +1,7 @@
1
- var G = Object.defineProperty;
2
- var Y = (n, i, e) => i in n ? G(n, i, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[i] = e;
3
- var a = (n, i, e) => Y(n, typeof i != "symbol" ? i + "" : i, e);
4
- const L = {
1
+ var W = Object.defineProperty;
2
+ var J = (l, i, e) => i in l ? W(l, i, { enumerable: !0, configurable: !0, writable: !0, value: e }) : l[i] = e;
3
+ var a = (l, i, e) => J(l, typeof i != "symbol" ? i + "" : i, e);
4
+ const T = {
5
5
  // Shared
6
6
  loadingAriaLabel: "Loading",
7
7
  errorPrefix: "Error:",
@@ -76,15 +76,15 @@ const L = {
76
76
  "Hunting down answers..."
77
77
  ]
78
78
  };
79
- function d(n) {
80
- if (!n || typeof n != "object")
81
- return L;
82
- const i = { ...L };
83
- for (const e of Object.keys(n)) {
84
- const t = n[e];
79
+ function g(l) {
80
+ if (!l || typeof l != "object")
81
+ return T;
82
+ const i = { ...T };
83
+ for (const e of Object.keys(l)) {
84
+ const t = l[e];
85
85
  if (t != null) {
86
86
  if (e === "loadingMessages") {
87
- Array.isArray(t) && t.length > 0 && (i.loadingMessages = t.filter((s) => typeof s == "string"), i.loadingMessages.length === 0 && (i.loadingMessages = L.loadingMessages));
87
+ Array.isArray(t) && t.length > 0 && (i.loadingMessages = t.filter((s) => typeof s == "string"), i.loadingMessages.length === 0 && (i.loadingMessages = T.loadingMessages));
88
88
  continue;
89
89
  }
90
90
  typeof t == "string" && (i[e] = t);
@@ -92,13 +92,13 @@ function d(n) {
92
92
  }
93
93
  return i;
94
94
  }
95
- function b(n, i = {}) {
96
- return n.replace(/\{(\w+)\}/g, (e, t) => Object.hasOwn(i, t) ? String(i[t]) : e);
95
+ function y(l, i = {}) {
96
+ return l.replace(/\{(\w+)\}/g, (e, t) => Object.hasOwn(i, t) ? String(i[t]) : e);
97
97
  }
98
- function k(n, i) {
99
- if (!n) return null;
98
+ function k(l, i) {
99
+ if (!l) return null;
100
100
  try {
101
- const e = JSON.parse(n);
101
+ const e = JSON.parse(l);
102
102
  if (e === null || typeof e != "object" || Array.isArray(e))
103
103
  throw new Error("translations must be a JSON object");
104
104
  return e;
@@ -106,41 +106,41 @@ function k(n, i) {
106
106
  return console.error(`${i}: invalid translations attribute`, e), null;
107
107
  }
108
108
  }
109
- const T = 2500;
110
- function P(n, i) {
109
+ const A = 2500;
110
+ function _(l, i) {
111
111
  let e;
112
112
  function t(...s) {
113
113
  clearTimeout(e), e = setTimeout(() => {
114
- n(...s);
114
+ l(...s);
115
115
  }, i);
116
116
  }
117
117
  return t.cancel = () => clearTimeout(e), t;
118
118
  }
119
- function l(n) {
119
+ function c(l) {
120
120
  const i = document.createElement("div");
121
- return i.textContent = n, i.innerHTML;
121
+ return i.textContent = l, i.innerHTML;
122
122
  }
123
- function U(n) {
123
+ function V(l) {
124
124
  try {
125
- return decodeURI(n);
125
+ return decodeURI(l);
126
126
  } catch {
127
- return n;
127
+ return l;
128
128
  }
129
129
  }
130
- function A(n) {
131
- return new DOMParser().parseFromString(n, "text/html").documentElement.textContent || "";
130
+ function I(l) {
131
+ return new DOMParser().parseFromString(l, "text/html").documentElement.textContent || "";
132
132
  }
133
- function J(n, i) {
134
- const e = d(i), t = new Date(n), r = (/* @__PURE__ */ new Date()).getTime() - t.getTime();
133
+ function X(l, i) {
134
+ const e = g(i), t = new Date(l), r = (/* @__PURE__ */ new Date()).getTime() - t.getTime();
135
135
  if (r < 6e4)
136
136
  return e.justNow;
137
137
  if (r < 36e5) {
138
- const o = Math.floor(r / 6e4), c = o === 1 ? e.minuteAgo : e.minutesAgo;
139
- return b(c, { n: o });
138
+ const n = Math.floor(r / 6e4), o = n === 1 ? e.minuteAgo : e.minutesAgo;
139
+ return y(o, { n });
140
140
  }
141
141
  if (r < 864e5) {
142
- const o = Math.floor(r / 36e5), c = o === 1 ? e.hourAgo : e.hoursAgo;
143
- return b(c, { n: o });
142
+ const n = Math.floor(r / 36e5), o = n === 1 ? e.hourAgo : e.hoursAgo;
143
+ return y(o, { n });
144
144
  }
145
145
  return t.toLocaleString(void 0, {
146
146
  month: "short",
@@ -149,88 +149,131 @@ function J(n, i) {
149
149
  minute: "2-digit"
150
150
  });
151
151
  }
152
- function j(n) {
153
- return new Date(n).toLocaleDateString(void 0, {
152
+ function j(l) {
153
+ return new Date(l).toLocaleDateString(void 0, {
154
154
  month: "short",
155
155
  day: "numeric"
156
156
  });
157
157
  }
158
- function I(n = "id") {
159
- return `${n}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
158
+ function R(l = "id") {
159
+ return `${l}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
160
160
  }
161
- function p(n, i) {
162
- return n !== null ? n : i;
161
+ function m(l, i) {
162
+ return l !== null ? l : i;
163
163
  }
164
- function m(n, i) {
165
- return n === null ? i : n === "true" || n === "";
164
+ function v(l, i) {
165
+ return l === null ? i : l === "true" || l === "";
166
166
  }
167
- function y(n, i) {
168
- if (n === null) return i;
169
- const e = Number.parseInt(n, 10);
167
+ function w(l, i) {
168
+ if (l === null) return i;
169
+ const e = Number.parseInt(l, 10);
170
170
  return Number.isNaN(e) ? i : e;
171
171
  }
172
- function v(n, i) {
173
- return new CustomEvent(n, {
172
+ function F(l, i) {
173
+ if (!(l === null || l === ""))
174
+ try {
175
+ const e = JSON.parse(l);
176
+ if (e === null || typeof e != "object" || Array.isArray(e))
177
+ throw new Error("chat-query-rewrite must be a JSON object");
178
+ const t = e, s = {};
179
+ return typeof t.enabled == "boolean" && (s.enabled = t.enabled), typeof t.model == "string" && (s.model = t.model), typeof t.rewritePrompt == "string" && (s.rewritePrompt = t.rewritePrompt), s;
180
+ } catch (e) {
181
+ console.error(`${i}: invalid chat-query-rewrite attribute`, e);
182
+ return;
183
+ }
184
+ }
185
+ function b(l, i) {
186
+ return new CustomEvent(l, {
174
187
  detail: i,
175
188
  bubbles: !0,
176
189
  composed: !0,
177
190
  cancelable: !0
178
191
  });
179
192
  }
180
- function S(n) {
181
- if (!n)
193
+ function S(l) {
194
+ if (!l)
182
195
  throw new Error("API URL is required");
183
- return new Q(n);
196
+ return new ae(l);
197
+ }
198
+ const Z = "@cf/meta/llama-3.3-70b-instruct-fp8-fast", ee = `You rewrite a multi-turn chat into a single standalone search query for a retrieval system.
199
+
200
+ Inputs: the full conversation in \`messages\`. The final user message is the one to answer; earlier messages are context only.
201
+
202
+ Rules:
203
+ - Output ONLY the rewritten query as plain text. No preamble, no quotes, no markdown, no explanation.
204
+ - Resolve pronouns and references (it, that, they, the second one, the previous one, etc.) using prior turns.
205
+ - Inline any entities, names, versions, products, or constraints from earlier turns that the final message depends on.
206
+ - Preserve the user's original language and terminology. Do not translate.
207
+ - Do not invent facts, sources, dates, or details not present in the conversation.
208
+ - If the final user message is already fully self-contained, return it unchanged (modulo trivial cleanup).
209
+ - Drop greetings, thanks, and meta questions about the assistant itself; keep only the information need.
210
+ - Keep it concise — a search query, not a sentence. Aim for under 200 characters when possible.
211
+
212
+ Return only the rewritten query.`;
213
+ function te(l) {
214
+ let i = "message";
215
+ const e = [];
216
+ for (const t of l.split(`
217
+ `)) {
218
+ const s = t.endsWith("\r") ? t.slice(0, -1) : t;
219
+ if (s === "" || s.startsWith(":"))
220
+ continue;
221
+ const r = s.indexOf(":"), n = r === -1 ? s : s.slice(0, r);
222
+ let o = r === -1 ? "" : s.slice(r + 1);
223
+ o.startsWith(" ") && (o = o.slice(1)), n === "event" ? i = o : n === "data" && e.push(o);
224
+ }
225
+ return e.length === 0 ? null : { event: i, data: e.join(`
226
+ `) };
184
227
  }
185
- function C(n) {
186
- return n !== null && typeof n == "object" && !Array.isArray(n);
228
+ function C(l) {
229
+ return l !== null && typeof l == "object" && !Array.isArray(l);
187
230
  }
188
- function _(...n) {
231
+ function K(...l) {
189
232
  const i = {};
190
- for (const e of n)
233
+ for (const e of l)
191
234
  if (e)
192
235
  for (const [t, s] of Object.entries(e)) {
193
236
  const r = i[t];
194
- C(r) && C(s) ? i[t] = _(r, s) : i[t] = s;
237
+ C(r) && C(s) ? i[t] = K(r, s) : i[t] = s;
195
238
  }
196
239
  return i;
197
240
  }
198
- function W(n, i) {
241
+ function se(l, i) {
199
242
  if (!C(i))
200
- return n;
243
+ return l;
201
244
  const e = new URLSearchParams();
202
- for (const [h, u] of Object.entries(i))
203
- u != null && e.append(h, String(u));
245
+ for (const [h, d] of Object.entries(i))
246
+ d != null && e.append(h, String(d));
204
247
  const t = e.toString();
205
248
  if (!t)
206
- return n;
207
- const s = n.indexOf("#"), r = s === -1 ? n : n.slice(0, s), o = s === -1 ? "" : n.slice(s), c = r.includes("?") ? "&" : "?";
208
- return `${r}${c}${t}${o}`;
249
+ return l;
250
+ const s = l.indexOf("#"), r = s === -1 ? l : l.slice(0, s), n = s === -1 ? "" : l.slice(s), o = r.includes("?") ? "&" : "?";
251
+ return `${r}${o}${t}${n}`;
209
252
  }
210
- function Z(n) {
211
- if (!C(n))
253
+ function ie(l) {
254
+ if (!C(l))
212
255
  return {};
213
256
  const i = {};
214
- for (const [e, t] of Object.entries(n))
257
+ for (const [e, t] of Object.entries(l))
215
258
  t != null && (i[e] = String(t));
216
259
  return i;
217
260
  }
218
- function X(n) {
219
- return C(n) ? n : void 0;
261
+ function re(l) {
262
+ return C(l) ? l : void 0;
220
263
  }
221
- class Q {
264
+ class ae {
222
265
  constructor(i) {
223
266
  a(this, "activeRequests", /* @__PURE__ */ new Map());
224
267
  a(this, "baseUrl");
225
268
  this.baseUrl = i.replace(/\/$/, "");
226
269
  }
227
270
  request(i, e, t, s) {
228
- const r = e === "search" ? "snippet-search" : "snippet-chat-completions", o = W(`${this.baseUrl}/${e}`, s?.queryParams);
229
- return fetch(o, {
271
+ const r = e === "search" ? "snippet-search" : "snippet-chat-completions", n = se(`${this.baseUrl}/${e}`, s?.queryParams);
272
+ return fetch(n, {
230
273
  method: "POST",
231
- body: JSON.stringify(_(X(s?.body), i)),
274
+ body: JSON.stringify(K(re(s?.body), i)),
232
275
  headers: {
233
- ...Z(s?.headers),
276
+ ...ie(s?.headers),
234
277
  "Content-Type": "application/json",
235
278
  Accept: i.stream ? "text/event-stream" : "application/json",
236
279
  "cf-ai-search-source": r
@@ -245,7 +288,7 @@ class Q {
245
288
  const t = this.generateRequestId(), s = new AbortController(), r = e.signal || s.signal;
246
289
  this.registerRequest(t, s);
247
290
  try {
248
- const o = await this.request(
291
+ const n = await this.request(
249
292
  {
250
293
  messages: [{ role: "user", content: i }],
251
294
  stream: !1,
@@ -260,18 +303,18 @@ class Q {
260
303
  r,
261
304
  e.request
262
305
  );
263
- if (!o.ok)
264
- throw new Error(`HTTP error! status: ${o.status}`);
265
- if (!o.body)
306
+ if (!n.ok)
307
+ throw new Error(`HTTP error! status: ${n.status}`);
308
+ if (!n.body)
266
309
  throw new Error("Response body is empty");
267
- const c = await o.json();
268
- if (c.success && c.result)
269
- return c.result.chunks.map(
310
+ const o = await n.json();
311
+ if (o.success && o.result)
312
+ return o.result.chunks.map(
270
313
  (h) => ({
271
314
  type: "result",
272
315
  id: h.id,
273
- title: A(h.item.metadata?.title),
274
- description: h.item.metadata?.description ? A(h.item.metadata?.description) : "",
316
+ title: I(h.item.metadata?.title),
317
+ description: h.item.metadata?.description ? I(h.item.metadata?.description) : "",
275
318
  timestamp: h.item.timestamp ?? void 0,
276
319
  url: h.item.key,
277
320
  image: h.item.metadata?.image || void 0,
@@ -281,7 +324,7 @@ class Q {
281
324
  }
282
325
  })
283
326
  );
284
- throw c.success === !1 ? new Error(c.error) : new Error("Unknown error");
327
+ throw o.success === !1 ? new Error(o.error) : new Error("Unknown error");
285
328
  } finally {
286
329
  this.unregisterRequest(t);
287
330
  }
@@ -289,7 +332,7 @@ class Q {
289
332
  async *searchStream(i, e = {}) {
290
333
  const t = this.generateRequestId(), s = new AbortController(), r = e.signal || s.signal;
291
334
  this.registerRequest(t, s);
292
- const o = await this.request(
335
+ const n = await this.request(
293
336
  {
294
337
  messages: [{ role: "user", content: i }],
295
338
  stream: !0,
@@ -301,47 +344,111 @@ class Q {
301
344
  r,
302
345
  e.request
303
346
  );
304
- if (!o.ok)
305
- throw new Error(`HTTP error! status: ${o.status}`);
306
- if (!o.body)
347
+ if (!n.ok)
348
+ throw new Error(`HTTP error! status: ${n.status}`);
349
+ if (!n.body)
307
350
  throw new Error("Response body is empty");
308
- let c = "";
309
- const h = o.body.getReader(), u = new TextDecoder();
351
+ let o = "";
352
+ const h = n.body.getReader(), d = new TextDecoder();
310
353
  for (; ; ) {
311
- const { done: g, value: w } = await h.read();
312
- if (g)
354
+ const { done: u, value: f } = await h.read();
355
+ if (u)
313
356
  break;
314
- const F = u.decode(w, { stream: !0 });
315
- c += F;
357
+ const L = d.decode(f, { stream: !0 });
358
+ o += L;
316
359
  }
317
360
  yield {
318
361
  type: "result",
319
362
  id: "",
320
363
  title: "",
321
- description: c.replaceAll("data: ", "").trim().split(`
364
+ description: o.replaceAll("data: ", "").trim().split(`
322
365
 
323
- `).map((g) => JSON.parse(g)).map((g) => g.response).join(""),
366
+ `).map((u) => JSON.parse(u)).map((u) => u.response).join(""),
324
367
  url: "",
325
368
  metadata: {}
326
369
  };
327
370
  }
328
371
  async *chat(i, e) {
329
- const t = new AbortController(), s = e?.signal || t.signal, r = await this.request(
330
- {
331
- messages: [{ role: "user", content: i }],
332
- stream: !1
333
- },
334
- "chat/completions",
335
- s
336
- );
337
- if (!r.ok)
338
- throw new Error(`HTTP error! status: ${r.status}`);
339
- if (!r.body)
372
+ const t = new AbortController(), s = e?.signal || t.signal, r = e?.stream ?? !0, o = { messages: [...e?.history ?? [], { role: "user", content: i }], stream: r };
373
+ if (e?.queryRewrite) {
374
+ const d = typeof e.queryRewrite == "object" ? e.queryRewrite : {};
375
+ o.ai_search_options = {
376
+ query_rewrite: {
377
+ enabled: !0,
378
+ model: d.model ?? Z,
379
+ rewrite_prompt: d.rewritePrompt ?? ee
380
+ }
381
+ };
382
+ }
383
+ const h = await this.request(o, "chat/completions", s);
384
+ if (!h.ok)
385
+ throw new Error(`HTTP error! status: ${h.status}`);
386
+ if (!h.body)
340
387
  throw new Error("Response body is empty");
341
- yield {
342
- type: "text",
343
- message: (await r.json()).choices.map((c) => c.message.content).join("")
388
+ if (!r) {
389
+ yield {
390
+ type: "text",
391
+ message: (await h.json()).choices.map((p) => p.message.content).join("")
392
+ };
393
+ return;
394
+ }
395
+ yield* this.parseChatStream(h.body);
396
+ }
397
+ /**
398
+ * Consume an SSE stream from the chat/completions endpoint and yield one
399
+ * ChatTextResponse per non-empty content delta. Discards `event: chunks`
400
+ * (RAG sources) and the `[DONE]` sentinel; tolerates malformed individual
401
+ * frames without aborting the whole stream.
402
+ */
403
+ async *parseChatStream(i) {
404
+ const e = i.getReader(), t = new TextDecoder();
405
+ let s = "";
406
+ const r = (n) => {
407
+ const o = te(n);
408
+ if (!o || o.event === "chunks")
409
+ return null;
410
+ if (o.data === "[DONE]")
411
+ return "done";
412
+ try {
413
+ const d = JSON.parse(o.data).choices?.[0]?.delta?.content;
414
+ if (typeof d == "string" && d.length > 0)
415
+ return { type: "text", message: d };
416
+ } catch (h) {
417
+ console.error("AISearchClient: failed to parse SSE chat chunk", h);
418
+ }
419
+ return null;
344
420
  };
421
+ try {
422
+ for (; ; ) {
423
+ const { done: n, value: o } = await e.read();
424
+ if (n)
425
+ break;
426
+ s += t.decode(o, { stream: !0 });
427
+ let h = s.indexOf(`
428
+
429
+ `);
430
+ for (; h !== -1; ) {
431
+ const d = s.slice(0, h);
432
+ s = s.slice(h + 2);
433
+ const p = r(d);
434
+ if (p === "done")
435
+ return;
436
+ p && (yield p), h = s.indexOf(`
437
+
438
+ `);
439
+ }
440
+ }
441
+ if (s += t.decode(), s.trim().length > 0) {
442
+ const n = r(s);
443
+ n && n !== "done" && (yield n);
444
+ }
445
+ } catch (n) {
446
+ if (n.name === "AbortError")
447
+ return;
448
+ throw n;
449
+ } finally {
450
+ e.releaseLock();
451
+ }
345
452
  }
346
453
  /**
347
454
  * Cancels an active request by ID
@@ -380,10 +487,141 @@ class Q {
380
487
  return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
381
488
  }
382
489
  }
383
- const ee = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.w3.org/2000/svg" aria-label="Cloudflare" role="img">
490
+ const ne = "0.0.39", oe = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.w3.org/2000/svg" aria-label="Cloudflare" role="img">
384
491
  <path fill="#f38020" d="m280.8395,183.31456c11,-26 -4,-38 -19,-38l-148,-2c-4,0 -4,-6 1,-7l150,-2c17,-1 37,-15 43,-33c0,0 10,-21 9,-24a97,97 0 0 0 -187,-11c-38,-25 -78,9 -69,46c-48,3 -65,46 -60,72c0,1 1,2 3,2l274,0c1,0 3,-1 3,-3z"/>
385
492
  <path fill="#faae40" d="m330.8395,81.31456c-4,0 -6,-1 -7,1l-5,21c-5,16 3,30 20,31l32,2c4,0 4,6 -1,7l-33,1c-36,4 -46,39 -46,39c0,2 0,3 2,3l113,0l3,-2a81,81 0 0 0 -78,-103"/>
386
- </svg>`, te = "https://workers.cloudflare.com/product/ai-search", E = `Powered by <a href="${te}" target="_blank" rel="noopener noreferrer">Cloudflare AI Search ${ee}</a>`, V = `
493
+ </svg>`, le = "https://workers.cloudflare.com/product/ai-search", E = `Powered by <a href="${le}" target="_blank" rel="noopener noreferrer">Cloudflare AI Search ${oe}</a>`, ce = 2e4, he = 20, de = "/stats";
494
+ function z() {
495
+ return typeof document < "u" && typeof window < "u";
496
+ }
497
+ class Q {
498
+ constructor(i, e = {}) {
499
+ a(this, "baseUrl");
500
+ a(this, "endpoint");
501
+ a(this, "snippetVersion");
502
+ a(this, "flushIntervalMs");
503
+ a(this, "maxBufferSize");
504
+ a(this, "buffer", []);
505
+ a(this, "flushTimer", null);
506
+ a(this, "destroyed", !1);
507
+ a(this, "boundUnloadHandler");
508
+ a(this, "boundVisibilityHandler");
509
+ this.baseUrl = i.replace(/\/$/, ""), this.endpoint = e.endpoint ?? de, this.snippetVersion = e.snippetVersion ?? ne, this.flushIntervalMs = e.flushIntervalMs ?? ce, this.maxBufferSize = Math.max(1, e.maxBufferSize ?? he), this.boundUnloadHandler = () => this.flushBeacon(), this.boundVisibilityHandler = () => {
510
+ typeof document < "u" && document.visibilityState === "hidden" && this.flushBeacon();
511
+ }, z() && (window.addEventListener("pagehide", this.boundUnloadHandler), document.addEventListener("visibilitychange", this.boundVisibilityHandler));
512
+ }
513
+ /**
514
+ * Record a completed search (no click).
515
+ */
516
+ trackSearch(i, e) {
517
+ this.track({
518
+ inputQuery: i,
519
+ snippetVersion: this.snippetVersion,
520
+ totalResult: e
521
+ });
522
+ }
523
+ /**
524
+ * Record a click on a specific result.
525
+ */
526
+ trackClick(i, e, t, s) {
527
+ this.track({
528
+ inputQuery: i,
529
+ snippetVersion: this.snippetVersion,
530
+ totalResult: e,
531
+ clickedResultId: t,
532
+ clickPosition: s,
533
+ clickViewMore: !1
534
+ });
535
+ }
536
+ /**
537
+ * Record a click on the "See more" link.
538
+ */
539
+ trackViewMore(i, e) {
540
+ this.track({
541
+ inputQuery: i,
542
+ snippetVersion: this.snippetVersion,
543
+ totalResult: e,
544
+ clickViewMore: !0
545
+ });
546
+ }
547
+ /**
548
+ * Buffer a pre-built event. Higher-level `track*` helpers call this.
549
+ */
550
+ track(i) {
551
+ if (!this.destroyed) {
552
+ if (this.buffer.push(i), this.buffer.length >= this.maxBufferSize) {
553
+ this.flush();
554
+ return;
555
+ }
556
+ this.scheduleFlush();
557
+ }
558
+ }
559
+ /**
560
+ * Force an immediate flush using `fetch` with `keepalive: true`.
561
+ * Returns synchronously; network errors are swallowed.
562
+ */
563
+ flush() {
564
+ const i = this.drainBuffer();
565
+ if (i.length === 0)
566
+ return;
567
+ const e = JSON.stringify({ events: i });
568
+ fetch(this.buildUrl(), {
569
+ method: "POST",
570
+ headers: { "Content-Type": "application/json" },
571
+ body: e,
572
+ keepalive: !0
573
+ }).catch((t) => {
574
+ console.log(t);
575
+ });
576
+ }
577
+ /**
578
+ * Flush path optimized for page-unload. Prefers `navigator.sendBeacon`
579
+ * when available; falls back to `fetch({ keepalive: true })`.
580
+ */
581
+ flushBeacon() {
582
+ const i = this.drainBuffer();
583
+ if (i.length === 0)
584
+ return;
585
+ const e = JSON.stringify({ events: i }), t = this.buildUrl();
586
+ if (typeof navigator < "u" && typeof navigator.sendBeacon == "function")
587
+ try {
588
+ const s = new Blob([e], { type: "application/json" });
589
+ if (navigator.sendBeacon(t, s))
590
+ return;
591
+ } catch {
592
+ }
593
+ typeof fetch < "u" && fetch(t, {
594
+ method: "POST",
595
+ headers: { "Content-Type": "application/json" },
596
+ body: e,
597
+ keepalive: !0
598
+ }).catch(() => {
599
+ });
600
+ }
601
+ /**
602
+ * Remove unload listeners, clear timers, and flush anything still buffered.
603
+ * Call from the host component's `disconnectedCallback`.
604
+ */
605
+ destroy() {
606
+ this.destroyed || (this.destroyed = !0, this.flushTimer !== null && (clearTimeout(this.flushTimer), this.flushTimer = null), z() && (window.removeEventListener("pagehide", this.boundUnloadHandler), document.removeEventListener("visibilitychange", this.boundVisibilityHandler)), this.flushBeacon());
607
+ }
608
+ scheduleFlush() {
609
+ this.flushTimer !== null || this.destroyed || (this.flushTimer = setTimeout(() => {
610
+ this.flushTimer = null, this.flush();
611
+ }, this.flushIntervalMs));
612
+ }
613
+ drainBuffer() {
614
+ if (this.flushTimer !== null && (clearTimeout(this.flushTimer), this.flushTimer = null), this.buffer.length === 0)
615
+ return [];
616
+ const i = this.buffer;
617
+ return this.buffer = [], i;
618
+ }
619
+ buildUrl() {
620
+ const i = this.endpoint.startsWith("/") ? this.endpoint : `/${this.endpoint}`;
621
+ return `${this.baseUrl}${i}`;
622
+ }
623
+ }
624
+ const G = `
387
625
  /* Chat container */
388
626
  .chat-container {
389
627
  display: flex;
@@ -426,6 +664,7 @@ const ee = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.
426
664
  gap: var(--search-snippet-spacing-sm);
427
665
  max-width: 85%;
428
666
  animation: slideIn var(--search-snippet-animation-duration) ease-out;
667
+ animation-fill-mode: both;
429
668
  }
430
669
 
431
670
  @keyframes slideIn {
@@ -496,6 +735,9 @@ const ee = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.
496
735
  border-radius: var(--search-snippet-border-radius);
497
736
  word-wrap: break-word;
498
737
  overflow-wrap: break-word;
738
+ /* Isolate layout cost of bubble height changes during streaming so
739
+ siblings don't reflow. */
740
+ contain: layout style;
499
741
  }
500
742
 
501
743
  .chat-message-user .chat-message-bubble {
@@ -1135,55 +1377,55 @@ const ee = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.
1135
1377
  text-align: center;
1136
1378
  }
1137
1379
  `;
1138
- function se(n) {
1139
- let i = n;
1140
- i = ie(i), i = i.replace(/```([\s\S]*?)```/g, (o, c) => `<pre><code>${c.trim()}</code></pre>`);
1380
+ function pe(l) {
1381
+ let i = l;
1382
+ i = ue(i), i = i.replace(/```([\s\S]*?)```/g, (n, o) => `<pre><code>${o.trim()}</code></pre>`);
1141
1383
  const e = i.split(`
1142
1384
  `), t = [];
1143
1385
  let s = !1, r = "";
1144
- for (let o = 0; o < e.length; o++) {
1145
- const c = e[o], h = c.match(/^(#{1,6})\s+(.+)$/);
1386
+ for (let n = 0; n < e.length; n++) {
1387
+ const o = e[n], h = o.match(/^(#{1,6})\s+(.+)$/);
1146
1388
  if (h) {
1147
- const g = h[1].length, w = h[2];
1148
- t.push(`<h${g}>${x(w)}</h${g}>`);
1389
+ const u = h[1].length, f = h[2];
1390
+ t.push(`<h${u}>${x(f)}</h${u}>`);
1149
1391
  continue;
1150
1392
  }
1151
- if (c.match(/^---+$/)) {
1393
+ if (o.match(/^---+$/)) {
1152
1394
  t.push("<hr />");
1153
1395
  continue;
1154
1396
  }
1155
- if (c.match(/^>\s+/)) {
1156
- const g = c.replace(/^>\s+/, "");
1157
- t.push(`<blockquote>${x(g)}</blockquote>`);
1397
+ if (o.match(/^>\s+/)) {
1398
+ const u = o.replace(/^>\s+/, "");
1399
+ t.push(`<blockquote>${x(u)}</blockquote>`);
1158
1400
  continue;
1159
1401
  }
1160
- const u = c.match(/^[-*]\s+(.+)$/);
1161
- if (u) {
1162
- (!s || r !== "ul") && (s && t.push(`</${r}>`), t.push("<ul>"), s = !0, r = "ul"), t.push(`<li>${x(u[1])}</li>`);
1402
+ const d = o.match(/^[-*]\s+(.+)$/);
1403
+ if (d) {
1404
+ (!s || r !== "ul") && (s && t.push(`</${r}>`), t.push("<ul>"), s = !0, r = "ul"), t.push(`<li>${x(d[1])}</li>`);
1163
1405
  continue;
1164
1406
  }
1165
- const f = c.match(/^\d+\.\s+(.+)$/);
1166
- if (f) {
1167
- (!s || r !== "ol") && (s && t.push(`</${r}>`), t.push("<ol>"), s = !0, r = "ol"), t.push(`<li>${x(f[1])}</li>`);
1407
+ const p = o.match(/^\d+\.\s+(.+)$/);
1408
+ if (p) {
1409
+ (!s || r !== "ol") && (s && t.push(`</${r}>`), t.push("<ol>"), s = !0, r = "ol"), t.push(`<li>${x(p[1])}</li>`);
1168
1410
  continue;
1169
1411
  }
1170
- if (s && (t.push(`</${r}>`), s = !1, r = ""), c.trim() === "") {
1412
+ if (s && (t.push(`</${r}>`), s = !1, r = ""), o.trim() === "") {
1171
1413
  t.push("<br />");
1172
1414
  continue;
1173
1415
  }
1174
- t.push(`<p>${x(c)}</p>`);
1416
+ t.push(`<p>${x(o)}</p>`);
1175
1417
  }
1176
1418
  return s && t.push(`</${r}>`), t.join(`
1177
1419
  `);
1178
1420
  }
1179
- function x(n) {
1180
- let i = n;
1421
+ function x(l) {
1422
+ let i = l;
1181
1423
  return i = i.replace(/`([^`]+)`/g, "<code>$1</code>"), i = i.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>"), i = i.replace(/___(.+?)___/g, "<strong><em>$1</em></strong>"), i = i.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>"), i = i.replace(/__(.+?)__/g, "<strong>$1</strong>"), i = i.replace(/\*(.+?)\*/g, "<em>$1</em>"), i = i.replace(/_(.+?)_/g, "<em>$1</em>"), i = i.replace(
1182
1424
  /\[([^\]]+)\]\(([^)]+)\)/g,
1183
1425
  '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
1184
1426
  ), i;
1185
1427
  }
1186
- function ie(n) {
1428
+ function ue(l) {
1187
1429
  const i = {
1188
1430
  "&": "&amp;",
1189
1431
  "<": "&lt;",
@@ -1191,9 +1433,10 @@ function ie(n) {
1191
1433
  '"': "&quot;",
1192
1434
  "'": "&#39;"
1193
1435
  };
1194
- return n.replace(/[&<>"']/g, (e) => i[e] || e);
1436
+ return l.replace(/[&<>"']/g, (e) => i[e] || e);
1195
1437
  }
1196
- class K {
1438
+ const ge = 64;
1439
+ class Y {
1197
1440
  constructor(i, e, t) {
1198
1441
  a(this, "container");
1199
1442
  a(this, "client");
@@ -1207,11 +1450,12 @@ class K {
1207
1450
  a(this, "currentStreamingMessageId", null);
1208
1451
  a(this, "loadingMessageInterval", null);
1209
1452
  a(this, "loadingMessageIndex", 0);
1453
+ a(this, "pendingScrollFrame", null);
1210
1454
  // Event handler references for cleanup
1211
1455
  a(this, "handleInputResize", null);
1212
1456
  a(this, "handleInputKeydown", null);
1213
1457
  a(this, "handleSendClick", null);
1214
- this.container = i, this.client = e, this.props = t, this.translations = d(t.translations), this.render(), this.attachEventListeners();
1458
+ this.container = i, this.client = e, this.props = t, this.translations = g(t.translations), this.render(), this.attachEventListeners();
1215
1459
  }
1216
1460
  /**
1217
1461
  * Render the chat interface
@@ -1227,13 +1471,13 @@ class K {
1227
1471
  <div class="chat-input-wrapper">
1228
1472
  <textarea
1229
1473
  class="chat-input"
1230
- placeholder="${l(this.props.placeholder || i.chatPlaceholder)}"
1231
- aria-label="${l(i.chatInputAriaLabel)}"
1474
+ placeholder="${c(this.props.placeholder || i.chatPlaceholder)}"
1475
+ aria-label="${c(i.chatInputAriaLabel)}"
1232
1476
  style="height: 40px;"
1233
1477
  rows="1"
1234
1478
  ></textarea>
1235
- <button class="button chat-send-button" aria-label="${l(i.sendButtonAriaLabel)}">
1236
- <span>${l(i.sendButtonLabel)}</span>
1479
+ <button class="button chat-send-button" aria-label="${c(i.sendButtonAriaLabel)}">
1480
+ <span>${c(i.sendButtonLabel)}</span>
1237
1481
  </button>
1238
1482
  </div>
1239
1483
  </div>
@@ -1247,9 +1491,9 @@ class K {
1247
1491
  <svg class="chat-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1248
1492
  <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1249
1493
  </svg>
1250
- <div class="chat-empty-title">${l(i.chatEmptyTitle)}</div>
1494
+ <div class="chat-empty-title">${c(i.chatEmptyTitle)}</div>
1251
1495
  <div class="chat-empty-description">
1252
- ${l(i.chatEmptyDescription)}
1496
+ ${c(i.chatEmptyDescription)}
1253
1497
  </div>
1254
1498
  </div>
1255
1499
  `;
@@ -1279,40 +1523,43 @@ class K {
1279
1523
  * Send a message
1280
1524
  */
1281
1525
  async sendMessage(i) {
1282
- const e = {
1283
- id: I("msg"),
1526
+ const e = this.messages.map((o) => ({
1527
+ role: o.role,
1528
+ content: o.content
1529
+ })), t = this.resolveQueryRewriteOption(e), s = {
1530
+ id: R("msg"),
1284
1531
  role: "user",
1285
1532
  content: i,
1286
1533
  timestamp: Date.now()
1287
1534
  };
1288
- this.addMessage(e), this.renderMessages(!0), this.setStreamingState(!0);
1289
- const t = I("msg"), s = {
1290
- id: t,
1535
+ this.addMessage(s), this.renderMessages(!0), this.setStreamingState(!0);
1536
+ const r = R("msg"), n = {
1537
+ id: r,
1291
1538
  role: "assistant",
1292
1539
  content: "",
1293
1540
  timestamp: Date.now()
1294
1541
  };
1295
- this.addMessage(s), this.currentStreamingMessageId = t, this.renderMessages(!0);
1542
+ this.addMessage(n), this.currentStreamingMessageId = r, this.renderMessages(!0);
1296
1543
  try {
1297
- const r = this.client.chat(i);
1298
- let o = "";
1299
- for await (const h of r)
1300
- if (h.type === "text" && h.message)
1301
- o += h.message, this.updateStreamingMessage(t, o);
1302
- else if (h.type === "error") {
1544
+ const o = this.client.chat(i, { history: e, queryRewrite: t });
1545
+ let h = "";
1546
+ for await (const p of o)
1547
+ if (p.type === "text" && p.message)
1548
+ h += p.message, this.updateStreamingMessage(r, h);
1549
+ else if (p.type === "error") {
1303
1550
  this.showErrorInMessage(
1304
- t,
1305
- h.message || this.translations.unknownError
1551
+ r,
1552
+ p.message || this.translations.unknownError
1306
1553
  );
1307
1554
  break;
1308
1555
  }
1309
- const c = this.messages.findIndex((h) => h.id === t);
1310
- c !== -1 && (this.messages[c].content = o), this.container.dispatchEvent(v("message", { message: s }));
1311
- } catch (r) {
1312
- this.showErrorInMessage(t, r.message), this.container.dispatchEvent(
1313
- v("error", {
1556
+ const d = this.messages.findIndex((p) => p.id === r);
1557
+ d !== -1 && (this.messages[d].content = h), this.container.dispatchEvent(b("message", { message: n }));
1558
+ } catch (o) {
1559
+ this.showErrorInMessage(r, o.message), this.container.dispatchEvent(
1560
+ b("error", {
1314
1561
  error: {
1315
- message: r.message,
1562
+ message: o.message,
1316
1563
  code: "CHAT_ERROR"
1317
1564
  }
1318
1565
  })
@@ -1321,6 +1568,16 @@ class K {
1321
1568
  this.setStreamingState(!1), this.renderMessages(), this.currentStreamingMessageId = null;
1322
1569
  }
1323
1570
  }
1571
+ /**
1572
+ * Resolve `chatQueryRewrite` from props into the value passed to
1573
+ * `AISearchClient.chat`. Query rewriting is gated on having at least one
1574
+ * prior turn — there is nothing to rewrite against on the first message.
1575
+ * `chatQueryRewrite.enabled === false` opts out unconditionally.
1576
+ */
1577
+ resolveQueryRewriteOption(i) {
1578
+ const e = this.props.chatQueryRewrite;
1579
+ return e?.enabled === !1 || i.length === 0 ? !1 : e && (e.model !== void 0 || e.rewritePrompt !== void 0) ? { model: e.model, rewritePrompt: e.rewritePrompt } : !0;
1580
+ }
1324
1581
  /**
1325
1582
  * Add a message to the chat
1326
1583
  */
@@ -1328,11 +1585,43 @@ class K {
1328
1585
  this.messages.push(i), this.renderMessages();
1329
1586
  }
1330
1587
  /**
1331
- * Update streaming message content
1588
+ * Update streaming message content.
1589
+ *
1590
+ * During streaming this performs a surgical DOM update on the streaming
1591
+ * bubble only — no full message-list re-render. Content is written as plain
1592
+ * text (escaped via textContent) to avoid markdown structural flips that
1593
+ * cause height jumps. Markdown is applied once the stream completes via the
1594
+ * final `renderMessages()` call in `sendMessage`'s `finally` block.
1332
1595
  */
1333
1596
  updateStreamingMessage(i, e) {
1334
1597
  const t = this.messages.findIndex((s) => s.id === i);
1335
- t !== -1 && (this.messages[t].content = e, this.renderMessages(!0));
1598
+ t !== -1 && (this.messages[t].content = e, this.updateStreamingMessageDOM(i, e) || this.renderMessages(!0));
1599
+ }
1600
+ /**
1601
+ * Surgically update the streaming bubble's text node. Returns true on
1602
+ * success, false if the target nodes weren't found (caller should fall back
1603
+ * to a full re-render).
1604
+ */
1605
+ updateStreamingMessageDOM(i, e) {
1606
+ if (!this.messagesContainer)
1607
+ return !1;
1608
+ const t = this.messagesContainer.querySelector(
1609
+ `[data-message-id="${CSS.escape(i)}"]`
1610
+ );
1611
+ if (!t)
1612
+ return !1;
1613
+ const s = t.querySelector(".chat-message-bubble");
1614
+ if (!s)
1615
+ return !1;
1616
+ const r = this.isNearBottom();
1617
+ let n = s.querySelector(".chat-message-text");
1618
+ if (!n) {
1619
+ const o = document.createElement("div");
1620
+ o.className = "chat-message-text";
1621
+ const h = s.querySelector(".chat-streaming");
1622
+ h ? s.insertBefore(o, h) : s.appendChild(o), n = o;
1623
+ }
1624
+ return n.textContent = e, r && this.scheduleScrollToBottom(), !0;
1336
1625
  }
1337
1626
  /**
1338
1627
  * Show error in message
@@ -1342,7 +1631,9 @@ class K {
1342
1631
  t !== -1 && (this.messages[t].content = `${this.translations.errorPrefix} ${e}`, this.renderMessages());
1343
1632
  }
1344
1633
  /**
1345
- * Render all messages
1634
+ * Render all messages. Forces a scroll to bottom regardless of current
1635
+ * scroll position; called only on full-list mutations (add, clear, set,
1636
+ * end-of-stream final render, prop changes), not on per-token updates.
1346
1637
  */
1347
1638
  renderMessages(i = !1) {
1348
1639
  if (!this.messagesContainer) return;
@@ -1353,49 +1644,79 @@ class K {
1353
1644
  const e = this.messages.map(
1354
1645
  (t) => this.renderMessage(t, i && t.id === this.currentStreamingMessageId)
1355
1646
  ).join("");
1356
- this.messagesContainer.innerHTML = e, this.scrollToBottom();
1647
+ this.messagesContainer.innerHTML = e, this.scheduleScrollToBottom();
1357
1648
  }
1358
1649
  /**
1359
1650
  * Render a single message
1360
1651
  */
1361
1652
  renderMessage(i, e = !1) {
1362
- const t = this.translations, s = `chat-message-${i.role}`, r = i.role === "user" ? t.userAvatar : t.assistantAvatar, o = t.loadingMessages[this.loadingMessageIndex] ?? "";
1653
+ const t = this.translations, s = `chat-message-${i.role}`, r = i.role === "user" ? t.userAvatar : t.assistantAvatar, n = t.loadingMessages[this.loadingMessageIndex] ?? "", o = i.content ? e ? c(i.content) : pe(i.content) : "";
1363
1654
  return `
1364
- <div class="chat-message ${s}">
1365
- <div class="chat-message-avatar">${l(r)}</div>
1655
+ <div class="chat-message ${s}" data-message-id="${c(i.id)}">
1656
+ <div class="chat-message-avatar">${c(r)}</div>
1366
1657
  <div class="chat-message-content">
1367
1658
  <div class="chat-message-bubble">
1368
- ${i.content ? `<div class="chat-message-text">${se(i.content)}</div>` : ""}
1369
- ${e ? `<div class="chat-streaming"><span class="chat-streaming-dot"></span><span class="chat-streaming-dot"></span><span class="chat-streaming-dot"></span><span class="loading-text">${l(o)}</span></div>` : ""}
1659
+ ${o ? `<div class="chat-message-text">${o}</div>` : ""}
1660
+ ${e ? `<div class="chat-streaming"><span class="chat-streaming-dot"></span><span class="chat-streaming-dot"></span><span class="chat-streaming-dot"></span><span class="loading-text">${c(n)}</span></div>` : ""}
1370
1661
  </div>
1371
1662
  <div class="chat-message-metadata">
1372
- <span class="chat-message-time">${l(J(i.timestamp, this.translations))}</span>
1663
+ <span class="chat-message-time">${c(X(i.timestamp, this.translations))}</span>
1373
1664
  </div>
1374
1665
  </div>
1375
1666
  </div>
1376
1667
  `;
1377
1668
  }
1378
1669
  /**
1379
- * Scroll to bottom of messages
1670
+ * True when the user is within `BOTTOM_FOLLOW_THRESHOLD_PX` of the bottom
1671
+ * of the messages list. Used to decide whether to auto-follow the stream
1672
+ * or leave the user where they scrolled.
1380
1673
  */
1381
- scrollToBottom() {
1382
- this.messagesContainer && requestAnimationFrame(() => {
1383
- this.messagesContainer && (this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight);
1384
- });
1674
+ isNearBottom() {
1675
+ const i = this.messagesContainer;
1676
+ return i ? i.scrollHeight - i.scrollTop - i.clientHeight < ge : !0;
1677
+ }
1678
+ /**
1679
+ * Schedule a single scroll-to-bottom on the next animation frame. Multiple
1680
+ * calls within the same frame are coalesced into one DOM write. The "should
1681
+ * I scroll?" decision is gated by callers — `updateStreamingMessageDOM`
1682
+ * only calls this when the user is near the bottom; full re-renders always
1683
+ * call it.
1684
+ */
1685
+ scheduleScrollToBottom() {
1686
+ this.messagesContainer && this.pendingScrollFrame === null && (this.pendingScrollFrame = requestAnimationFrame(() => {
1687
+ this.pendingScrollFrame = null;
1688
+ const i = this.messagesContainer;
1689
+ i && (i.scrollTop = i.scrollHeight);
1690
+ }));
1385
1691
  }
1386
1692
  /**
1387
1693
  * Set streaming state
1388
1694
  */
1389
1695
  setStreamingState(i) {
1390
- this.isStreaming = i, this.inputElement && (this.inputElement.disabled = i), this.sendButton && (this.sendButton.disabled = i, this.sendButton.innerHTML = i ? '<div class="loading"></div>' : `<span>${l(this.translations.sendButtonLabel)}</span>`), i ? this.startLoadingMessages() : this.clearLoadingMessages();
1696
+ this.isStreaming = i, this.inputElement && (this.inputElement.disabled = i), this.sendButton && (this.sendButton.disabled = i, this.sendButton.innerHTML = i ? '<div class="loading"></div>' : `<span>${c(this.translations.sendButtonLabel)}</span>`), i ? this.startLoadingMessages() : this.clearLoadingMessages();
1391
1697
  }
1392
1698
  startLoadingMessages() {
1393
1699
  this.clearLoadingMessages();
1394
1700
  const i = this.translations.loadingMessages;
1395
1701
  this.loadingMessageIndex = Math.floor(Math.random() * i.length), this.loadingMessageInterval = setInterval(() => {
1396
1702
  const e = this.translations.loadingMessages;
1397
- this.loadingMessageIndex = (this.loadingMessageIndex + 1) % e.length, this.isStreaming && this.renderMessages(!0);
1398
- }, T);
1703
+ if (this.loadingMessageIndex = (this.loadingMessageIndex + 1) % e.length, !this.isStreaming) return;
1704
+ this.updateLoadingTextDOM(e[this.loadingMessageIndex] ?? "") || this.renderMessages(!0);
1705
+ }, A);
1706
+ }
1707
+ /**
1708
+ * Update the rotating loading-text label inside the currently-streaming
1709
+ * bubble only. Returns true on success, false if the target wasn't found.
1710
+ */
1711
+ updateLoadingTextDOM(i) {
1712
+ if (!this.messagesContainer || !this.currentStreamingMessageId)
1713
+ return !1;
1714
+ const e = this.messagesContainer.querySelector(
1715
+ `[data-message-id="${CSS.escape(this.currentStreamingMessageId)}"]`
1716
+ );
1717
+ if (!e) return !1;
1718
+ const t = e.querySelector(".chat-streaming .loading-text");
1719
+ return t ? (t.textContent = i, !0) : !1;
1399
1720
  }
1400
1721
  clearLoadingMessages() {
1401
1722
  this.loadingMessageInterval && (clearInterval(this.loadingMessageInterval), this.loadingMessageInterval = null);
@@ -1426,7 +1747,7 @@ class K {
1426
1747
  * newly rendered elements.
1427
1748
  */
1428
1749
  setProps(i) {
1429
- this.props = i, this.translations = d(i.translations), this.detachEventListeners(), this.render(), this.attachEventListeners(), this.renderMessages(this.isStreaming), this.isStreaming && this.setStreamingState(!0);
1750
+ this.props = i, this.translations = g(i.translations), this.detachEventListeners(), this.render(), this.attachEventListeners(), this.renderMessages(this.isStreaming), this.isStreaming && this.setStreamingState(!0);
1430
1751
  }
1431
1752
  detachEventListeners() {
1432
1753
  this.inputElement && (this.handleInputResize && this.inputElement.removeEventListener("input", this.handleInputResize), this.handleInputKeydown && this.inputElement.removeEventListener("keydown", this.handleInputKeydown)), this.sendButton && this.handleSendClick && this.sendButton.removeEventListener("click", this.handleSendClick), this.handleInputResize = null, this.handleInputKeydown = null, this.handleSendClick = null;
@@ -1435,11 +1756,11 @@ class K {
1435
1756
  * Destroy and cleanup
1436
1757
  */
1437
1758
  destroy() {
1438
- this.clearLoadingMessages(), this.isStreaming && this.client.cancelAllRequests(), this.inputElement && (this.handleInputResize && this.inputElement.removeEventListener("input", this.handleInputResize), this.handleInputKeydown && this.inputElement.removeEventListener("keydown", this.handleInputKeydown)), this.sendButton && this.handleSendClick && this.sendButton.removeEventListener("click", this.handleSendClick), this.handleInputResize = null, this.handleInputKeydown = null, this.handleSendClick = null;
1759
+ this.clearLoadingMessages(), this.pendingScrollFrame !== null && (cancelAnimationFrame(this.pendingScrollFrame), this.pendingScrollFrame = null), this.isStreaming && this.client.cancelAllRequests(), this.inputElement && (this.handleInputResize && this.inputElement.removeEventListener("input", this.handleInputResize), this.handleInputKeydown && this.inputElement.removeEventListener("keydown", this.handleInputKeydown)), this.sendButton && this.handleSendClick && this.sendButton.removeEventListener("click", this.handleSendClick), this.handleInputResize = null, this.handleInputKeydown = null, this.handleSendClick = null;
1439
1760
  }
1440
1761
  }
1441
- const z = "chat-bubble-snippet";
1442
- class re extends HTMLElement {
1762
+ const $ = "chat-bubble-snippet";
1763
+ class me extends HTMLElement {
1443
1764
  constructor() {
1444
1765
  super();
1445
1766
  a(this, "shadow");
@@ -1449,7 +1770,8 @@ class re extends HTMLElement {
1449
1770
  a(this, "isExpanded", !1);
1450
1771
  a(this, "isMinimized", !1);
1451
1772
  a(this, "translationsOverride", null);
1452
- a(this, "resolvedTranslations", d(null));
1773
+ a(this, "resolvedTranslations", g(null));
1774
+ a(this, "chatQueryRewriteOverride", null);
1453
1775
  // Event handler references for cleanup
1454
1776
  a(this, "handleBubbleClick", null);
1455
1777
  a(this, "handleCloseClick", null);
@@ -1458,10 +1780,17 @@ class re extends HTMLElement {
1458
1780
  this.shadow = this.attachShadow({ mode: "open" });
1459
1781
  }
1460
1782
  static get observedAttributes() {
1461
- return ["api-url", "placeholder", "theme", "hide-branding", "translations"];
1783
+ return [
1784
+ "api-url",
1785
+ "placeholder",
1786
+ "theme",
1787
+ "hide-branding",
1788
+ "translations",
1789
+ "chat-query-rewrite"
1790
+ ];
1462
1791
  }
1463
1792
  connectedCallback() {
1464
- this.syncTranslationsFromAttribute(), this.render(), this.initializeClient(), this.dispatchEvent(v("ready", void 0));
1793
+ this.syncTranslationsFromAttribute(), this.render(), this.initializeClient(), this.dispatchEvent(b("ready", void 0));
1465
1794
  }
1466
1795
  disconnectedCallback() {
1467
1796
  this.cleanup();
@@ -1475,22 +1804,32 @@ class re extends HTMLElement {
1475
1804
  get translations() {
1476
1805
  return this.translationsOverride;
1477
1806
  }
1807
+ /**
1808
+ * Override AI Search query rewriting on subsequent chat turns. Setting
1809
+ * `null` falls back to parsing the `chat-query-rewrite` attribute.
1810
+ */
1811
+ get chatQueryRewrite() {
1812
+ return this.chatQueryRewriteOverride;
1813
+ }
1814
+ set chatQueryRewrite(e) {
1815
+ this.chatQueryRewriteOverride = e ?? null, this.chatView && this.chatView.setProps(this.getProps());
1816
+ }
1478
1817
  /**
1479
1818
  * Override any user-facing string. Omitted keys fall back to English defaults.
1480
1819
  */
1481
1820
  set translations(e) {
1482
- this.translationsOverride = e ?? null, this.resolvedTranslations = d(this.translationsOverride), this.isConnected && this.rerenderAfterTranslationsChange();
1821
+ this.translationsOverride = e ?? null, this.resolvedTranslations = g(this.translationsOverride), this.isConnected && this.rerenderAfterTranslationsChange();
1483
1822
  }
1484
1823
  syncTranslationsFromAttribute() {
1485
1824
  if (this.translationsOverride) {
1486
- this.resolvedTranslations = d(this.translationsOverride);
1825
+ this.resolvedTranslations = g(this.translationsOverride);
1487
1826
  return;
1488
1827
  }
1489
1828
  const e = k(
1490
1829
  this.getAttribute("translations"),
1491
1830
  "ChatBubbleSnippet"
1492
1831
  );
1493
- this.resolvedTranslations = d(e);
1832
+ this.resolvedTranslations = g(e);
1494
1833
  }
1495
1834
  rerenderAfterTranslationsChange() {
1496
1835
  const e = this.isExpanded, t = this.isMinimized;
@@ -1499,20 +1838,27 @@ class re extends HTMLElement {
1499
1838
  s?.parentNode && s.parentNode.removeChild(s), this.render(), e && (this.shadow.querySelector(".bubble-button")?.classList.add("hidden"), this.shadow.querySelector(".chat-window")?.classList.add("expanded"), t && this.shadow.querySelector(".chat-window")?.classList.add("minimized"));
1500
1839
  const r = this.shadow.querySelector(".chat-window");
1501
1840
  if (this.chatView && s && r) {
1502
- const o = r.querySelector(".chat-content");
1503
- o ? r.replaceChild(s, o) : r.appendChild(s), this.chatView.setProps(this.getProps());
1841
+ const n = r.querySelector(".chat-content");
1842
+ n ? r.replaceChild(s, n) : r.appendChild(s), this.chatView.setProps(this.getProps());
1504
1843
  } else e && this.initializeChatView();
1505
1844
  }
1506
1845
  getProps() {
1507
1846
  const e = this.resolvedTranslations;
1508
1847
  return {
1509
- apiUrl: p(this.getAttribute("api-url"), ""),
1510
- placeholder: p(this.getAttribute("placeholder"), e.chatPlaceholder),
1511
- theme: p(this.getAttribute("theme"), "auto"),
1512
- hideBranding: m(this.getAttribute("hide-branding"), !1),
1513
- translations: this.translationsOverride ?? void 0
1848
+ apiUrl: m(this.getAttribute("api-url"), ""),
1849
+ placeholder: m(this.getAttribute("placeholder"), e.chatPlaceholder),
1850
+ theme: m(this.getAttribute("theme"), "auto"),
1851
+ hideBranding: v(this.getAttribute("hide-branding"), !1),
1852
+ translations: this.translationsOverride ?? void 0,
1853
+ chatQueryRewrite: this.resolveChatQueryRewrite()
1514
1854
  };
1515
1855
  }
1856
+ resolveChatQueryRewrite() {
1857
+ return this.chatQueryRewriteOverride !== null ? this.chatQueryRewriteOverride : F(
1858
+ this.getAttribute("chat-query-rewrite"),
1859
+ "ChatBubbleSnippet"
1860
+ );
1861
+ }
1516
1862
  initializeClient() {
1517
1863
  const e = this.getProps();
1518
1864
  if (!e.apiUrl) {
@@ -1528,7 +1874,7 @@ class re extends HTMLElement {
1528
1874
  render() {
1529
1875
  const e = document.createElement("style");
1530
1876
  e.textContent = `${M}
1531
- ${V}
1877
+ ${G}
1532
1878
  ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this.container.className = "chat-bubble-widget", this.container.innerHTML = this.getBaseHTML(), this.shadow.innerHTML = "", this.shadow.appendChild(e), this.shadow.appendChild(this.container), this.attachEventListeners();
1533
1879
  }
1534
1880
  getBubbleStyles() {
@@ -1675,7 +2021,7 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1675
2021
  getBaseHTML() {
1676
2022
  const e = this.getProps(), t = this.resolvedTranslations, s = e.hideBranding ? "" : `<div class="powered-by">${E}</div>`;
1677
2023
  return `
1678
- <button class="bubble-button" aria-label="${l(t.openChatAriaLabel)}">
2024
+ <button class="bubble-button" aria-label="${c(t.openChatAriaLabel)}">
1679
2025
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1680
2026
  <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1681
2027
  </svg>
@@ -1686,21 +2032,21 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1686
2032
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1687
2033
  <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1688
2034
  </svg>
1689
- <span>${l(t.chatTitle)}</span>
2035
+ <span>${c(t.chatTitle)}</span>
1690
2036
  </div>
1691
2037
  <div class="chat-header-actions">
1692
- <button class="icon-button clear-button" aria-label="${l(t.clearHistoryAriaLabel)}">
2038
+ <button class="icon-button clear-button" aria-label="${c(t.clearHistoryAriaLabel)}">
1693
2039
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1694
2040
  <polyline points="3 6 5 6 21 6"></polyline>
1695
2041
  <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
1696
2042
  </svg>
1697
2043
  </button>
1698
- <button class="icon-button minimize-button" aria-label="${l(t.minimizeAriaLabel)}">
2044
+ <button class="icon-button minimize-button" aria-label="${c(t.minimizeAriaLabel)}">
1699
2045
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1700
2046
  <line x1="5" y1="12" x2="19" y2="12"></line>
1701
2047
  </svg>
1702
2048
  </button>
1703
- <button class="icon-button close-button" aria-label="${l(t.closeAriaLabel)}">
2049
+ <button class="icon-button close-button" aria-label="${c(t.closeAriaLabel)}">
1704
2050
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1705
2051
  <line x1="18" y1="6" x2="6" y2="18"></line>
1706
2052
  <line x1="6" y1="6" x2="18" y2="18"></line>
@@ -1744,13 +2090,13 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1744
2090
  const s = this.resolvedTranslations;
1745
2091
  e.innerHTML = `
1746
2092
  <div style="padding: 16px; color: var(--search-snippet-error-color, #ef4444); font-family: var(--search-snippet-font-family, sans-serif); font-size: var(--search-snippet-font-size-base, 14px);">
1747
- <strong>${l(s.errorPrefix)}</strong> ${l(s.missingApiUrlError)}
2093
+ <strong>${c(s.errorPrefix)}</strong> ${c(s.missingApiUrlError)}
1748
2094
  </div>
1749
2095
  `;
1750
2096
  return;
1751
2097
  }
1752
2098
  const t = this.getProps();
1753
- this.chatView = new K(e, this.client, t);
2099
+ this.chatView = new Y(e, this.client, t);
1754
2100
  }
1755
2101
  updateTheme(e) {
1756
2102
  (e === "light" || e === "dark" ? e : null) === null && this.hasAttribute("theme") && this.getAttribute("theme") !== "auto" && this.removeAttribute("theme");
@@ -1769,9 +2115,9 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1769
2115
  return this.chatView?.getMessages() || [];
1770
2116
  }
1771
2117
  }
1772
- customElements.get(z) || customElements.define(z, re);
1773
- const $ = "chat-page-snippet", R = "chat-page-sessions";
1774
- class ae extends HTMLElement {
2118
+ customElements.get($) || customElements.define($, me);
2119
+ const q = "chat-page-snippet", B = "chat-page-sessions";
2120
+ class ve extends HTMLElement {
1775
2121
  constructor() {
1776
2122
  super();
1777
2123
  a(this, "shadow");
@@ -1782,7 +2128,8 @@ class ae extends HTMLElement {
1782
2128
  a(this, "currentSessionId", null);
1783
2129
  a(this, "sidebarCollapsed", !1);
1784
2130
  a(this, "translationsOverride", null);
1785
- a(this, "resolvedTranslations", d(null));
2131
+ a(this, "resolvedTranslations", g(null));
2132
+ a(this, "chatQueryRewriteOverride", null);
1786
2133
  // Event handler references for cleanup
1787
2134
  a(this, "handleClearClick", null);
1788
2135
  a(this, "handleNewChatClick", null);
@@ -1792,10 +2139,17 @@ class ae extends HTMLElement {
1792
2139
  this.shadow = this.attachShadow({ mode: "open" }), this.loadSessions();
1793
2140
  }
1794
2141
  static get observedAttributes() {
1795
- return ["api-url", "placeholder", "theme", "hide-branding", "translations"];
2142
+ return [
2143
+ "api-url",
2144
+ "placeholder",
2145
+ "theme",
2146
+ "hide-branding",
2147
+ "translations",
2148
+ "chat-query-rewrite"
2149
+ ];
1796
2150
  }
1797
2151
  connectedCallback() {
1798
- this.syncTranslationsFromAttribute(), this.render(), this.initializeClient(), this.setupView(), this.dispatchEvent(v("ready", void 0));
2152
+ this.syncTranslationsFromAttribute(), this.render(), this.initializeClient(), this.setupView(), this.dispatchEvent(b("ready", void 0));
1799
2153
  }
1800
2154
  disconnectedCallback() {
1801
2155
  this.saveCurrentSession(), this.cleanup();
@@ -1809,19 +2163,29 @@ class ae extends HTMLElement {
1809
2163
  get translations() {
1810
2164
  return this.translationsOverride;
1811
2165
  }
2166
+ /**
2167
+ * Override AI Search query rewriting on subsequent chat turns. Setting
2168
+ * `null` falls back to parsing the `chat-query-rewrite` attribute.
2169
+ */
2170
+ get chatQueryRewrite() {
2171
+ return this.chatQueryRewriteOverride;
2172
+ }
2173
+ set chatQueryRewrite(e) {
2174
+ this.chatQueryRewriteOverride = e ?? null, this.chatView && this.chatView.setProps(this.getProps());
2175
+ }
1812
2176
  /**
1813
2177
  * Override any user-facing string. Omitted keys fall back to English defaults.
1814
2178
  */
1815
2179
  set translations(e) {
1816
- this.translationsOverride = e ?? null, this.resolvedTranslations = d(this.translationsOverride), this.refreshDefaultSessionTitles(), this.isConnected && this.rerenderAfterTranslationsChange();
2180
+ this.translationsOverride = e ?? null, this.resolvedTranslations = g(this.translationsOverride), this.refreshDefaultSessionTitles(), this.isConnected && this.rerenderAfterTranslationsChange();
1817
2181
  }
1818
2182
  syncTranslationsFromAttribute() {
1819
2183
  if (this.translationsOverride) {
1820
- this.resolvedTranslations = d(this.translationsOverride), this.refreshDefaultSessionTitles();
2184
+ this.resolvedTranslations = g(this.translationsOverride), this.refreshDefaultSessionTitles();
1821
2185
  return;
1822
2186
  }
1823
2187
  const e = k(this.getAttribute("translations"), "ChatPageSnippet");
1824
- this.resolvedTranslations = d(e), this.refreshDefaultSessionTitles();
2188
+ this.resolvedTranslations = g(e), this.refreshDefaultSessionTitles();
1825
2189
  }
1826
2190
  /**
1827
2191
  * Replace the stored title of any still-default-titled session with the
@@ -1851,13 +2215,20 @@ class ae extends HTMLElement {
1851
2215
  getProps() {
1852
2216
  const e = this.resolvedTranslations;
1853
2217
  return {
1854
- apiUrl: p(this.getAttribute("api-url"), ""),
1855
- placeholder: p(this.getAttribute("placeholder"), e.chatPlaceholder),
1856
- theme: p(this.getAttribute("theme"), "auto"),
1857
- hideBranding: m(this.getAttribute("hide-branding"), !1),
1858
- translations: this.translationsOverride ?? void 0
2218
+ apiUrl: m(this.getAttribute("api-url"), ""),
2219
+ placeholder: m(this.getAttribute("placeholder"), e.chatPlaceholder),
2220
+ theme: m(this.getAttribute("theme"), "auto"),
2221
+ hideBranding: v(this.getAttribute("hide-branding"), !1),
2222
+ translations: this.translationsOverride ?? void 0,
2223
+ chatQueryRewrite: this.resolveChatQueryRewrite()
1859
2224
  };
1860
2225
  }
2226
+ resolveChatQueryRewrite() {
2227
+ return this.chatQueryRewriteOverride !== null ? this.chatQueryRewriteOverride : F(
2228
+ this.getAttribute("chat-query-rewrite"),
2229
+ "ChatPageSnippet"
2230
+ );
2231
+ }
1861
2232
  initializeClient() {
1862
2233
  const e = this.getProps();
1863
2234
  if (!e.apiUrl) {
@@ -1873,7 +2244,7 @@ class ae extends HTMLElement {
1873
2244
  render() {
1874
2245
  const e = document.createElement("style");
1875
2246
  e.textContent = `${M}
1876
- ${V}
2247
+ ${G}
1877
2248
  ${this.getPageStyles()}`, this.container = document.createElement("div"), this.container.className = "chat-page-container", this.container.innerHTML = this.getBaseHTML(), this.shadow.innerHTML = "", this.shadow.appendChild(e), this.shadow.appendChild(this.container), this.attachEventListeners();
1878
2249
  }
1879
2250
  getPageStyles() {
@@ -2157,13 +2528,13 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2157
2528
  return `
2158
2529
  <div class="chat-sidebar">
2159
2530
  <div class="sidebar-header">
2160
- <span class="sidebar-title">${l(t.historyTitle)}</span>
2531
+ <span class="sidebar-title">${c(t.historyTitle)}</span>
2161
2532
  </div>
2162
2533
  <button class="new-chat-button">
2163
2534
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2164
2535
  <path d="M12 5v14M5 12h14"></path>
2165
2536
  </svg>
2166
- ${l(t.newChatButton)}
2537
+ ${c(t.newChatButton)}
2167
2538
  </button>
2168
2539
  <div class="chat-list"></div>
2169
2540
  ${s}
@@ -2171,7 +2542,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2171
2542
  <div class="chat-main">
2172
2543
  <div class="chat-page-header">
2173
2544
  <div class="chat-page-header-left">
2174
- <button class="toggle-sidebar-button" title="${l(t.toggleSidebarTitle)}">
2545
+ <button class="toggle-sidebar-button" title="${c(t.toggleSidebarTitle)}">
2175
2546
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2176
2547
  <path d="M3 12h18M3 6h18M3 18h18"></path>
2177
2548
  </svg>
@@ -2180,7 +2551,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2180
2551
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2181
2552
  <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
2182
2553
  </svg>
2183
- <span>${l(t.chatTitle)}</span>
2554
+ <span>${c(t.chatTitle)}</span>
2184
2555
  </div>
2185
2556
  </div>
2186
2557
  <div class="chat-page-header-actions">
@@ -2188,7 +2559,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2188
2559
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2189
2560
  <path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
2190
2561
  </svg>
2191
- ${l(t.clearChatButton)}
2562
+ ${c(t.clearChatButton)}
2192
2563
  </button>
2193
2564
  </div>
2194
2565
  </div>
@@ -2200,11 +2571,11 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2200
2571
  }
2201
2572
  attachEventListeners() {
2202
2573
  const e = this.shadow.querySelector(".clear-button"), t = this.shadow.querySelector(".new-chat-button"), s = this.shadow.querySelector(".toggle-sidebar-button"), r = this.shadow.querySelector(".chat-list");
2203
- this.handleClearClick = () => this.clearCurrentChat(), this.handleNewChatClick = () => this.createNewChat(), this.handleToggleSidebarClick = () => this.toggleSidebar(), this.handleChatListClick = (o) => this.onChatListClick(o), e?.addEventListener("click", this.handleClearClick), t?.addEventListener("click", this.handleNewChatClick), s?.addEventListener("click", this.handleToggleSidebarClick), r?.addEventListener("click", this.handleChatListClick);
2574
+ this.handleClearClick = () => this.clearCurrentChat(), this.handleNewChatClick = () => this.createNewChat(), this.handleToggleSidebarClick = () => this.toggleSidebar(), this.handleChatListClick = (n) => this.onChatListClick(n), e?.addEventListener("click", this.handleClearClick), t?.addEventListener("click", this.handleNewChatClick), s?.addEventListener("click", this.handleToggleSidebarClick), r?.addEventListener("click", this.handleChatListClick);
2204
2575
  }
2205
2576
  removeEventListeners() {
2206
- const e = this.shadow.querySelector(".clear-button"), t = this.shadow.querySelector(".new-chat-button"), s = this.shadow.querySelector(".toggle-sidebar-button"), r = this.shadow.querySelector(".chat-list"), o = this.shadow.querySelector(".container");
2207
- this.handleClearClick && e?.removeEventListener("click", this.handleClearClick), this.handleNewChatClick && t?.removeEventListener("click", this.handleNewChatClick), this.handleToggleSidebarClick && s?.removeEventListener("click", this.handleToggleSidebarClick), this.handleChatListClick && r?.removeEventListener("click", this.handleChatListClick), this.handleMessageEvent && o && o.removeEventListener("message", this.handleMessageEvent), this.handleClearClick = null, this.handleNewChatClick = null, this.handleToggleSidebarClick = null, this.handleChatListClick = null, this.handleMessageEvent = null;
2577
+ const e = this.shadow.querySelector(".clear-button"), t = this.shadow.querySelector(".new-chat-button"), s = this.shadow.querySelector(".toggle-sidebar-button"), r = this.shadow.querySelector(".chat-list"), n = this.shadow.querySelector(".container");
2578
+ this.handleClearClick && e?.removeEventListener("click", this.handleClearClick), this.handleNewChatClick && t?.removeEventListener("click", this.handleNewChatClick), this.handleToggleSidebarClick && s?.removeEventListener("click", this.handleToggleSidebarClick), this.handleChatListClick && r?.removeEventListener("click", this.handleChatListClick), this.handleMessageEvent && n && n.removeEventListener("message", this.handleMessageEvent), this.handleClearClick = null, this.handleNewChatClick = null, this.handleToggleSidebarClick = null, this.handleChatListClick = null, this.handleMessageEvent = null;
2208
2579
  }
2209
2580
  setupView() {
2210
2581
  const e = this.shadow.querySelector(".container");
@@ -2213,7 +2584,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2213
2584
  const s = this.resolvedTranslations;
2214
2585
  e.innerHTML = `
2215
2586
  <div style="padding: 16px; color: var(--search-snippet-error-color, #ef4444); font-family: var(--search-snippet-font-family, sans-serif); font-size: var(--search-snippet-font-size-base, 14px);">
2216
- <strong>${l(s.errorPrefix)}</strong> ${l(s.missingApiUrlError)}
2587
+ <strong>${c(s.errorPrefix)}</strong> ${c(s.missingApiUrlError)}
2217
2588
  </div>
2218
2589
  `;
2219
2590
  }
@@ -2221,7 +2592,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2221
2592
  }
2222
2593
  if (!e) return;
2223
2594
  const t = this.getProps();
2224
- if (this.chatView = new K(e, this.client, t), this.sessions.length === 0)
2595
+ if (this.chatView = new Y(e, this.client, t), this.sessions.length === 0)
2225
2596
  this.createNewChat();
2226
2597
  else {
2227
2598
  const s = this.sessions[0];
@@ -2236,7 +2607,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2236
2607
  }
2237
2608
  loadSessions() {
2238
2609
  try {
2239
- const e = localStorage.getItem(R);
2610
+ const e = localStorage.getItem(B);
2240
2611
  e && (this.sessions = JSON.parse(e), this.sessions.sort((t, s) => s.updatedAt - t.updatedAt));
2241
2612
  } catch (e) {
2242
2613
  console.error("Failed to load chat sessions:", e);
@@ -2244,7 +2615,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2244
2615
  }
2245
2616
  saveSessions() {
2246
2617
  try {
2247
- localStorage.setItem(R, JSON.stringify(this.sessions));
2618
+ localStorage.setItem(B, JSON.stringify(this.sessions));
2248
2619
  } catch (e) {
2249
2620
  console.error("Failed to save chat sessions:", e);
2250
2621
  }
@@ -2295,14 +2666,14 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2295
2666
  const t = e.target, s = t.closest(".chat-list-item-delete");
2296
2667
  if (s) {
2297
2668
  e.stopPropagation();
2298
- const o = s.getAttribute("data-session-id");
2299
- o && this.deleteSession(o);
2669
+ const n = s.getAttribute("data-session-id");
2670
+ n && this.deleteSession(n);
2300
2671
  return;
2301
2672
  }
2302
2673
  const r = t.closest(".chat-list-item");
2303
2674
  if (r) {
2304
- const o = r.getAttribute("data-session-id");
2305
- o && this.switchToSession(o);
2675
+ const n = r.getAttribute("data-session-id");
2676
+ n && this.switchToSession(n);
2306
2677
  }
2307
2678
  }
2308
2679
  renderChatList() {
@@ -2332,8 +2703,8 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2332
2703
  `;
2333
2704
  }
2334
2705
  formatDate(e) {
2335
- const t = new Date(e), r = (/* @__PURE__ */ new Date()).getTime() - t.getTime(), o = Math.floor(r / (1e3 * 60 * 60 * 24));
2336
- return o === 0 ? t.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) : o === 1 ? this.resolvedTranslations.yesterday : o < 7 ? t.toLocaleDateString(void 0, { weekday: "long" }) : t.toLocaleDateString(void 0, { month: "short", day: "numeric" });
2706
+ const t = new Date(e), r = (/* @__PURE__ */ new Date()).getTime() - t.getTime(), n = Math.floor(r / (1e3 * 60 * 60 * 24));
2707
+ return n === 0 ? t.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) : n === 1 ? this.resolvedTranslations.yesterday : n < 7 ? t.toLocaleDateString(void 0, { weekday: "long" }) : t.toLocaleDateString(void 0, { month: "short", day: "numeric" });
2337
2708
  }
2338
2709
  escapeHTML(e) {
2339
2710
  const t = document.createElement("div");
@@ -2362,8 +2733,8 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2362
2733
  return this.sessions.find((e) => e.id === this.currentSessionId) || null;
2363
2734
  }
2364
2735
  }
2365
- customElements.get($) || customElements.define($, ae);
2366
- const ne = `
2736
+ customElements.get(q) || customElements.define(q, ve);
2737
+ const fe = `
2367
2738
  /* Search view states */
2368
2739
  .search-view {
2369
2740
  transition: var(--search-snippet-transition-slow);
@@ -2733,12 +3104,13 @@ a.search-result-item:focus-visible {
2733
3104
  border-radius: 2px;
2734
3105
  font-weight: var(--search-snippet-font-weight-medium);
2735
3106
  }
2736
- `, q = "search-bar-snippet", B = 10, H = 50;
2737
- class oe extends HTMLElement {
3107
+ `, O = "search-bar-snippet", H = 10, N = 50;
3108
+ class be extends HTMLElement {
2738
3109
  constructor() {
2739
3110
  super();
2740
3111
  a(this, "shadow");
2741
3112
  a(this, "client", null);
3113
+ a(this, "stats", null);
2742
3114
  a(this, "container", null);
2743
3115
  a(this, "inputElement", null);
2744
3116
  a(this, "resultsContainer", null);
@@ -2748,12 +3120,18 @@ class oe extends HTMLElement {
2748
3120
  a(this, "loadingMessageInterval", null);
2749
3121
  a(this, "loadingMessageIndex", 0);
2750
3122
  a(this, "translationsOverride", null);
2751
- a(this, "resolvedTranslations", d(null));
3123
+ a(this, "resolvedTranslations", g(null));
3124
+ // Analytics context: captures the most recently completed search so that
3125
+ // click / view-more events report the same query + total.
3126
+ a(this, "lastSearchQuery", "");
3127
+ a(this, "lastSearchTotal", 0);
2752
3128
  // Event handler references for cleanup
2753
3129
  a(this, "handleInputChange", null);
2754
3130
  a(this, "handleInputKeydownEnter", null);
2755
3131
  a(this, "handleInputKeydownEscape", null);
2756
3132
  a(this, "handleSearchButtonClick", null);
3133
+ a(this, "handleResultClick", null);
3134
+ a(this, "handleSeeMoreClick", null);
2757
3135
  this.shadow = this.attachShadow({ mode: "open" });
2758
3136
  }
2759
3137
  static get observedAttributes() {
@@ -2769,18 +3147,19 @@ class oe extends HTMLElement {
2769
3147
  "show-date",
2770
3148
  "hide-thumbnails",
2771
3149
  "see-more",
3150
+ "disable-analytics",
2772
3151
  "request-options",
2773
3152
  "translations"
2774
3153
  ];
2775
3154
  }
2776
3155
  connectedCallback() {
2777
- this.syncTranslationsFromAttribute(), this.initializeClient(), this.render(), this.dispatchEvent(v("ready", void 0));
3156
+ this.syncTranslationsFromAttribute(), this.initializeClient(), this.render(), this.dispatchEvent(b("ready", void 0));
2778
3157
  }
2779
3158
  disconnectedCallback() {
2780
3159
  this.cleanup();
2781
3160
  }
2782
3161
  attributeChangedCallback(e, t, s) {
2783
- t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerender()));
3162
+ t !== s && (e === "api-url" || e === "disable-analytics" ? this.initializeClient() : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerender()));
2784
3163
  }
2785
3164
  /**
2786
3165
  * Get the current translations object. Mirrors the property getter.
@@ -2792,7 +3171,7 @@ class oe extends HTMLElement {
2792
3171
  * Override any user-facing string. Omitted keys fall back to English defaults.
2793
3172
  */
2794
3173
  set translations(e) {
2795
- this.translationsOverride = e ?? null, this.resolvedTranslations = d(this.translationsOverride), this.isConnected && this.rerender();
3174
+ this.translationsOverride = e ?? null, this.resolvedTranslations = g(this.translationsOverride), this.isConnected && this.rerender();
2796
3175
  }
2797
3176
  /**
2798
3177
  * Re-render preserving the current query and re-running the search so
@@ -2804,35 +3183,36 @@ class oe extends HTMLElement {
2804
3183
  }
2805
3184
  syncTranslationsFromAttribute() {
2806
3185
  if (this.translationsOverride) {
2807
- this.resolvedTranslations = d(this.translationsOverride);
3186
+ this.resolvedTranslations = g(this.translationsOverride);
2808
3187
  return;
2809
3188
  }
2810
3189
  const e = k(
2811
3190
  this.getAttribute("translations"),
2812
3191
  "SearchBarSnippet"
2813
3192
  );
2814
- this.resolvedTranslations = d(e);
3193
+ this.resolvedTranslations = g(e);
2815
3194
  }
2816
3195
  getProps() {
2817
3196
  const e = this.resolvedTranslations;
2818
3197
  return {
2819
- apiUrl: p(this.getAttribute("api-url"), ""),
2820
- placeholder: p(this.getAttribute("placeholder"), e.placeholder),
2821
- maxResults: y(
3198
+ apiUrl: m(this.getAttribute("api-url"), ""),
3199
+ placeholder: m(this.getAttribute("placeholder"), e.placeholder),
3200
+ maxResults: w(
2822
3201
  this.getAttribute("max-results"),
2823
- H
3202
+ N
2824
3203
  ),
2825
- maxRenderResults: y(
3204
+ maxRenderResults: w(
2826
3205
  this.getAttribute("max-render-results"),
2827
- B
3206
+ H
2828
3207
  ),
2829
- debounceMs: y(this.getAttribute("debounce-ms"), 300),
2830
- theme: p(this.getAttribute("theme"), "auto"),
2831
- hideBranding: m(this.getAttribute("hide-branding"), !1),
2832
- showUrl: m(this.getAttribute("show-url"), !1),
2833
- showDate: m(this.getAttribute("show-date"), !1),
2834
- hideThumbnails: m(this.getAttribute("hide-thumbnails"), !1),
2835
- seeMore: p(this.getAttribute("see-more"), ""),
3208
+ debounceMs: w(this.getAttribute("debounce-ms"), 300),
3209
+ theme: m(this.getAttribute("theme"), "auto"),
3210
+ hideBranding: v(this.getAttribute("hide-branding"), !1),
3211
+ showUrl: v(this.getAttribute("show-url"), !1),
3212
+ showDate: v(this.getAttribute("show-date"), !1),
3213
+ hideThumbnails: v(this.getAttribute("hide-thumbnails"), !1),
3214
+ seeMore: m(this.getAttribute("see-more"), ""),
3215
+ disableAnalytics: v(this.getAttribute("disable-analytics"), !1),
2836
3216
  translations: this.translationsOverride ?? void 0
2837
3217
  };
2838
3218
  }
@@ -2852,24 +3232,27 @@ class oe extends HTMLElement {
2852
3232
  initializeClient() {
2853
3233
  const e = this.getProps();
2854
3234
  if (!e.apiUrl) {
2855
- console.error("SearchBarSnippet: api-url attribute is required"), this.client = null, this.showMissingApiUrlError();
3235
+ console.error("SearchBarSnippet: api-url attribute is required"), this.client = null, this.destroyStatsClient(), this.showMissingApiUrlError();
2856
3236
  return;
2857
3237
  }
2858
3238
  try {
2859
- this.client = S(e.apiUrl);
3239
+ this.client = S(e.apiUrl), this.destroyStatsClient(), e.disableAnalytics || (this.stats = new Q(e.apiUrl));
2860
3240
  } catch (t) {
2861
3241
  console.error("SearchBarSnippet:", t);
2862
3242
  }
2863
3243
  }
3244
+ destroyStatsClient() {
3245
+ this.stats && (this.stats.destroy(), this.stats = null);
3246
+ }
2864
3247
  render() {
2865
- const e = this.getProps(), t = this.resolvedTranslations, s = (o) => this.performSearch(o);
2866
- this.debouncedSearch = P(
3248
+ const e = this.getProps(), t = this.resolvedTranslations, s = (n) => this.performSearch(n);
3249
+ this.debouncedSearch = _(
2867
3250
  s,
2868
3251
  e.debounceMs || 400
2869
3252
  );
2870
3253
  const r = document.createElement("style");
2871
3254
  r.textContent = `${M}
2872
- ${ne}`, this.container = document.createElement("div"), this.container.className = "container", this.container.innerHTML = `
3255
+ ${fe}`, this.container = document.createElement("div"), this.container.className = "container", this.container.innerHTML = `
2873
3256
  <div class="search-view">
2874
3257
  <div class="search-input-wrapper">
2875
3258
  <svg xmlns="http://www.w3.org/2000/svg" class="search-icon" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor"><path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>
@@ -2877,12 +3260,12 @@ ${ne}`, this.container = document.createElement("div"), this.container.className
2877
3260
  type="text"
2878
3261
  name="search-input"
2879
3262
  class="search-input"
2880
- placeholder="${l(e.placeholder || t.placeholder)}"
2881
- aria-label="${l(t.searchInputAriaLabel)}"
3263
+ placeholder="${c(e.placeholder || t.placeholder)}"
3264
+ aria-label="${c(t.searchInputAriaLabel)}"
2882
3265
  autocomplete="off"
2883
3266
  />
2884
- <button class="button search-submit-button" aria-label="${l(t.searchButtonLabel)}">
2885
- <span>${l(t.searchButtonLabel)}</span>
3267
+ <button class="button search-submit-button" aria-label="${c(t.searchButtonLabel)}">
3268
+ <span>${c(t.searchButtonLabel)}</span>
2886
3269
  </button>
2887
3270
  </div>
2888
3271
  <div class="search-content">
@@ -2918,10 +3301,10 @@ ${ne}`, this.container = document.createElement("div"), this.container.className
2918
3301
  try {
2919
3302
  const t = this.getProps(), s = await this.client.search(e, {
2920
3303
  signal: this.currentSearchController.signal,
2921
- maxResults: t.maxResults || H,
3304
+ maxResults: t.maxResults || N,
2922
3305
  request: this.getRequestOptions()
2923
- }), r = s.slice(0, t.maxRenderResults || B);
2924
- this.displayResults(r, e, s.length);
3306
+ }), r = s.slice(0, t.maxRenderResults || H);
3307
+ this.lastSearchQuery = e, this.lastSearchTotal = s.length, this.stats?.trackSearch(e, s.length), this.displayResults(r, e, s.length);
2925
3308
  } catch (t) {
2926
3309
  if (t.name === "AbortError")
2927
3310
  return;
@@ -2936,39 +3319,39 @@ ${ne}`, this.container = document.createElement("div"), this.container.className
2936
3319
  this.showNoResultsState(t);
2937
3320
  return;
2938
3321
  }
2939
- const r = this.getProps(), o = this.resolvedTranslations, c = r.hideBranding ? "" : `<div class="powered-by-inline">${E}</div>`, h = s > e.length, u = h ? b(o.resultsCountOverflow, { n: e.length, total: s }) : b(s === 1 ? o.resultsCount : o.resultsCountPlural, {
3322
+ const r = this.getProps(), n = this.resolvedTranslations, o = r.hideBranding ? "" : `<div class="powered-by-inline">${E}</div>`, h = s > e.length, d = h ? y(n.resultsCountOverflow, { n: e.length, total: s }) : y(s === 1 ? n.resultsCount : n.resultsCountPlural, {
2940
3323
  n: s
2941
- }), f = r.seeMore && h ? `<div class="search-footer">
2942
- <a href="${l(r.seeMore + encodeURIComponent(t))}" class="search-see-more">
2943
- <span>${l(o.seeMoreResults)}</span>
3324
+ }), p = r.seeMore && h ? `<div class="search-footer">
3325
+ <a href="${c(r.seeMore + encodeURIComponent(t))}" class="search-see-more">
3326
+ <span>${c(n.seeMoreResults)}</span>
2944
3327
  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
2945
3328
  </a>
2946
- </div>` : "", g = `
3329
+ </div>` : "", u = `
2947
3330
  <div class="search-header">
2948
3331
  <div class="search-count">
2949
- ${l(u)}
3332
+ ${c(d)}
2950
3333
  </div>
2951
- ${c}
3334
+ ${o}
2952
3335
  </div>
2953
3336
  <div class="search-results">
2954
- ${e.map((w) => this.renderResult(w)).join("")}
3337
+ ${e.map((f, L) => this.renderResult(f, L)).join("")}
2955
3338
  </div>
2956
- ${f}
3339
+ ${p}
2957
3340
  `;
2958
- this.resultsContainer.innerHTML = g, this.attachResultHandlers();
3341
+ this.resultsContainer.innerHTML = u, this.attachResultHandlers();
2959
3342
  }
2960
- renderResult(e) {
2961
- const t = this.getProps(), s = t.hideThumbnails ? "" : this.renderResultImage(e.image, e.title), r = e.url ? l(e.url) : "#", o = e.url ? l(U(e.url)) : "", c = t.showDate && e.timestamp !== void 0 ? `<div class="search-result-date">${l(j(e.timestamp))}</div>` : "", h = t.showUrl && e.url || c ? `<div class="search-result-metadata">
2962
- ${t.showUrl && e.url ? `<span class="search-result-url">${o}</span>` : '<span class="search-result-url search-result-url-empty"></span>'}
2963
- ${c}
3343
+ renderResult(e, t) {
3344
+ const s = this.getProps(), r = s.hideThumbnails ? "" : this.renderResultImage(e.image, e.title), n = e.url ? c(e.url) : "#", o = e.url ? c(V(e.url)) : "", h = s.showDate && e.timestamp !== void 0 ? `<div class="search-result-date">${c(j(e.timestamp))}</div>` : "", d = s.showUrl && e.url || h ? `<div class="search-result-metadata">
3345
+ ${s.showUrl && e.url ? `<span class="search-result-url">${o}</span>` : '<span class="search-result-url search-result-url-empty"></span>'}
3346
+ ${h}
2964
3347
  </div>` : "";
2965
3348
  return `
2966
- <a href="${r}" class="search-result-item" data-result-id="${l(e.url || "")}">
2967
- ${s}
3349
+ <a href="${n}" class="search-result-item" data-index="${t}" data-result-id="${c(e.id || "")}">
3350
+ ${r}
2968
3351
  <div class="search-result-content">
2969
- <div class="search-result-title">${l(e.title || "")}</div>
2970
- <div class="search-result-snippet">${l(e.description || "")}</div>
2971
- ${h}
3352
+ <div class="search-result-title">${c(e.title || "")}</div>
3353
+ <div class="search-result-snippet">${c(e.description || "")}</div>
3354
+ ${d}
2972
3355
  </div>
2973
3356
  </a>
2974
3357
  `;
@@ -2981,8 +3364,8 @@ ${ne}`, this.container = document.createElement("div"), this.container.className
2981
3364
  <div class="search-result-image-placeholder" style="display: none;">${s}</div>
2982
3365
  <img
2983
3366
  class="search-result-image"
2984
- src="${l(e)}"
2985
- alt="${l(t)}"
3367
+ src="${c(e)}"
3368
+ alt="${c(t)}"
2986
3369
  loading="lazy"
2987
3370
  />
2988
3371
  </div>
@@ -2993,25 +3376,36 @@ ${ne}`, this.container = document.createElement("div"), this.container.className
2993
3376
  `;
2994
3377
  }
2995
3378
  attachResultHandlers() {
2996
- const e = this.container?.querySelectorAll(".search-result-item");
3379
+ this.detachResultTrackingHandlers();
3380
+ const e = this.resultsContainer;
2997
3381
  if (!e) return;
2998
- for (const s of e)
2999
- s.getAttribute("href") === "#" && s.addEventListener("click", (o) => {
3000
- o.preventDefault();
3001
- });
3002
- this.container?.querySelectorAll(".search-result-image")?.forEach((s) => {
3003
- s.addEventListener("load", () => {
3004
- s.classList.add("loaded"), s.closest(".search-result-image-container")?.querySelector(".search-result-image-loading")?.remove();
3005
- }), s.addEventListener("error", () => {
3006
- const r = s.closest(".search-result-image-container");
3007
- r?.querySelector(".search-result-image-loading")?.remove();
3008
- const o = r?.querySelector(
3382
+ this.handleResultClick = (r) => {
3383
+ const o = r.target?.closest(".search-result-item");
3384
+ if (!o) return;
3385
+ o.getAttribute("href") === "#" && r.preventDefault();
3386
+ const d = o.getAttribute("data-index"), p = o.getAttribute("data-result-id") ?? "", u = d !== null ? Number.parseInt(d, 10) : Number.NaN;
3387
+ !Number.isNaN(u) && p && this.stats?.trackClick(this.lastSearchQuery, this.lastSearchTotal, p, u);
3388
+ }, e.addEventListener("click", this.handleResultClick);
3389
+ const t = e.querySelector(".search-see-more");
3390
+ t && (this.handleSeeMoreClick = () => {
3391
+ this.stats?.trackViewMore(this.lastSearchQuery, this.lastSearchTotal);
3392
+ }, t.addEventListener("click", this.handleSeeMoreClick)), this.container?.querySelectorAll(".search-result-image")?.forEach((r) => {
3393
+ r.addEventListener("load", () => {
3394
+ r.classList.add("loaded"), r.closest(".search-result-image-container")?.querySelector(".search-result-image-loading")?.remove();
3395
+ }), r.addEventListener("error", () => {
3396
+ const n = r.closest(".search-result-image-container");
3397
+ n?.querySelector(".search-result-image-loading")?.remove();
3398
+ const o = n?.querySelector(
3009
3399
  ".search-result-image-placeholder"
3010
3400
  );
3011
- o && (o.style.display = "flex"), s.style.display = "none";
3401
+ o && (o.style.display = "flex"), r.style.display = "none";
3012
3402
  });
3013
3403
  });
3014
3404
  }
3405
+ detachResultTrackingHandlers() {
3406
+ const e = this.resultsContainer;
3407
+ e && this.handleResultClick && e.removeEventListener("click", this.handleResultClick), this.handleResultClick = null, this.handleSeeMoreClick = null;
3408
+ }
3015
3409
  showLoadingState() {
3016
3410
  if (!this.resultsContainer) return;
3017
3411
  this.clearLoadingInterval();
@@ -3020,8 +3414,8 @@ ${ne}`, this.container = document.createElement("div"), this.container.className
3020
3414
  const t = this.resolvedTranslations;
3021
3415
  this.resultsContainer.innerHTML = `
3022
3416
  <div class="search-loading">
3023
- <div class="loading" aria-label="${l(t.loadingAriaLabel)}"></div>
3024
- <div class="loading-text loading-text-animate">${l(e[this.loadingMessageIndex])}</div>
3417
+ <div class="loading" aria-label="${c(t.loadingAriaLabel)}"></div>
3418
+ <div class="loading-text loading-text-animate">${c(e[this.loadingMessageIndex])}</div>
3025
3419
  </div>
3026
3420
  `, this.startLoadingInterval();
3027
3421
  }
@@ -3031,7 +3425,7 @@ ${ne}`, this.container = document.createElement("div"), this.container.className
3031
3425
  this.loadingMessageIndex = (this.loadingMessageIndex + 1) % e.length;
3032
3426
  const t = this.resultsContainer?.querySelector(".loading-text");
3033
3427
  t && (t.classList.remove("loading-text-animate"), t.offsetWidth, t.textContent = e[this.loadingMessageIndex], t.classList.add("loading-text-animate"));
3034
- }, T);
3428
+ }, A);
3035
3429
  }
3036
3430
  clearLoadingInterval() {
3037
3431
  this.loadingMessageInterval && (clearInterval(this.loadingMessageInterval), this.loadingMessageInterval = null);
@@ -3045,9 +3439,9 @@ ${ne}`, this.container = document.createElement("div"), this.container.className
3045
3439
  <circle cx="11" cy="11" r="8"></circle>
3046
3440
  <path d="m21 21-4.35-4.35"></path>
3047
3441
  </svg>
3048
- <div class="search-empty-title">${l(e.emptyStateTitle)}</div>
3442
+ <div class="search-empty-title">${c(e.emptyStateTitle)}</div>
3049
3443
  <div class="search-empty-description">
3050
- ${l(e.emptyStateDescription)}
3444
+ ${c(e.emptyStateDescription)}
3051
3445
  </div>
3052
3446
  </div>
3053
3447
  `;
@@ -3061,9 +3455,9 @@ ${ne}`, this.container = document.createElement("div"), this.container.className
3061
3455
  <circle cx="11" cy="11" r="8"></circle>
3062
3456
  <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
3063
3457
  </svg>
3064
- <div class="search-empty-title">${l(t.noResultsTitle)}</div>
3458
+ <div class="search-empty-title">${c(t.noResultsTitle)}</div>
3065
3459
  <div class="search-empty-description">
3066
- ${l(b(t.noResultsDescription, { query: e }))}
3460
+ ${c(y(t.noResultsDescription, { query: e }))}
3067
3461
  </div>
3068
3462
  </div>
3069
3463
  `;
@@ -3073,7 +3467,7 @@ ${ne}`, this.container = document.createElement("div"), this.container.className
3073
3467
  const t = this.resolvedTranslations;
3074
3468
  this.resultsContainer.innerHTML = `
3075
3469
  <div class="error">
3076
- <strong>${l(t.errorPrefix)}</strong> ${l(e)}
3470
+ <strong>${c(t.errorPrefix)}</strong> ${c(e)}
3077
3471
  </div>
3078
3472
  `;
3079
3473
  }
@@ -3085,15 +3479,15 @@ ${ne}`, this.container = document.createElement("div"), this.container.className
3085
3479
  t === "auto" ? this.removeAttribute("theme") : this.setAttribute("theme", t);
3086
3480
  }
3087
3481
  cleanup() {
3088
- this.clearLoadingInterval(), this.currentSearchController && (this.currentSearchController.abort(), this.currentSearchController = null), this.client && this.client.cancelAllRequests(), this.inputElement && (this.handleInputChange && this.inputElement.removeEventListener("input", this.handleInputChange), this.handleInputKeydownEnter && this.inputElement.removeEventListener("keydown", this.handleInputKeydownEnter), this.handleInputKeydownEscape && window.removeEventListener("keydown", this.handleInputKeydownEscape)), this.searchButton && this.handleSearchButtonClick && this.searchButton.removeEventListener("click", this.handleSearchButtonClick), this.handleInputChange = null, this.handleInputKeydownEnter = null, this.handleInputKeydownEscape = null, this.handleSearchButtonClick = null;
3482
+ this.clearLoadingInterval(), this.currentSearchController && (this.currentSearchController.abort(), this.currentSearchController = null), this.client && this.client.cancelAllRequests(), this.destroyStatsClient(), this.inputElement && (this.handleInputChange && this.inputElement.removeEventListener("input", this.handleInputChange), this.handleInputKeydownEnter && this.inputElement.removeEventListener("keydown", this.handleInputKeydownEnter), this.handleInputKeydownEscape && window.removeEventListener("keydown", this.handleInputKeydownEscape)), this.searchButton && this.handleSearchButtonClick && this.searchButton.removeEventListener("click", this.handleSearchButtonClick), this.detachResultTrackingHandlers(), this.handleInputChange = null, this.handleInputKeydownEnter = null, this.handleInputKeydownEscape = null, this.handleSearchButtonClick = null;
3089
3483
  }
3090
3484
  // Public API
3091
3485
  async search(e) {
3092
3486
  await this.performSearch(e);
3093
3487
  }
3094
3488
  }
3095
- customElements.get(q) || customElements.define(q, oe);
3096
- const le = `
3489
+ customElements.get(O) || customElements.define(O, be);
3490
+ const ye = `
3097
3491
  /* Modal backdrop */
3098
3492
  .modal-backdrop {
3099
3493
  position: fixed;
@@ -3530,12 +3924,13 @@ a.modal-result-item:focus-visible {
3530
3924
  .modal-container.open {
3531
3925
  animation: modal-slide-in var(--search-snippet-transition) ease-out;
3532
3926
  }
3533
- `, O = "search-modal-snippet", D = 10, N = 50;
3534
- class ce extends HTMLElement {
3927
+ `, P = "search-modal-snippet", D = 10, U = 50;
3928
+ class we extends HTMLElement {
3535
3929
  constructor() {
3536
3930
  super();
3537
3931
  a(this, "shadow");
3538
3932
  a(this, "client", null);
3933
+ a(this, "stats", null);
3539
3934
  a(this, "backdrop", null);
3540
3935
  a(this, "modal", null);
3541
3936
  a(this, "inputElement", null);
@@ -3549,12 +3944,17 @@ class ce extends HTMLElement {
3549
3944
  a(this, "loadingMessageInterval", null);
3550
3945
  a(this, "loadingMessageIndex", 0);
3551
3946
  a(this, "translationsOverride", null);
3552
- a(this, "resolvedTranslations", d(null));
3947
+ a(this, "resolvedTranslations", g(null));
3948
+ // Analytics context: populated by performSearch so click/view-more events
3949
+ // can reuse the query and total from the most recent result set.
3950
+ a(this, "lastSearchQuery", "");
3951
+ a(this, "lastSearchTotal", 0);
3553
3952
  // Event handler references for cleanup
3554
3953
  a(this, "handleGlobalKeydown", null);
3555
3954
  a(this, "handleInputChange", null);
3556
3955
  a(this, "handleInputKeydown", null);
3557
3956
  a(this, "handleBackdropClick", null);
3957
+ a(this, "handleResultsContainerClick", null);
3558
3958
  // Scroll lock state
3559
3959
  a(this, "savedBodyStyles", null);
3560
3960
  a(this, "savedHtmlOverflow", null);
@@ -3575,18 +3975,19 @@ class ce extends HTMLElement {
3575
3975
  "show-date",
3576
3976
  "hide-thumbnails",
3577
3977
  "see-more",
3978
+ "disable-analytics",
3578
3979
  "request-options",
3579
3980
  "translations"
3580
3981
  ];
3581
3982
  }
3582
3983
  connectedCallback() {
3583
- this.syncTranslationsFromAttribute(), this.initializeClient(), this.render(), this.attachGlobalKeyboardShortcut(), this.dispatchEvent(v("ready", void 0));
3984
+ this.syncTranslationsFromAttribute(), this.initializeClient(), this.render(), this.attachGlobalKeyboardShortcut(), this.dispatchEvent(b("ready", void 0));
3584
3985
  }
3585
3986
  disconnectedCallback() {
3586
3987
  this.cleanup();
3587
3988
  }
3588
3989
  attributeChangedCallback(e, t, s) {
3589
- t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerender()));
3990
+ t !== s && (e === "api-url" || e === "disable-analytics" ? this.initializeClient() : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerender()));
3590
3991
  }
3591
3992
  /**
3592
3993
  * Get the current translations object.
@@ -3598,7 +3999,7 @@ class ce extends HTMLElement {
3598
3999
  * Override any user-facing string. Omitted keys fall back to English defaults.
3599
4000
  */
3600
4001
  set translations(e) {
3601
- this.translationsOverride = e ?? null, this.resolvedTranslations = d(this.translationsOverride), this.isConnected && this.rerender();
4002
+ this.translationsOverride = e ?? null, this.resolvedTranslations = g(this.translationsOverride), this.isConnected && this.rerender();
3602
4003
  }
3603
4004
  /**
3604
4005
  * Re-render while preserving open state and the current query. Results are
@@ -3616,37 +4017,38 @@ class ce extends HTMLElement {
3616
4017
  }
3617
4018
  syncTranslationsFromAttribute() {
3618
4019
  if (this.translationsOverride) {
3619
- this.resolvedTranslations = d(this.translationsOverride);
4020
+ this.resolvedTranslations = g(this.translationsOverride);
3620
4021
  return;
3621
4022
  }
3622
4023
  const e = k(
3623
4024
  this.getAttribute("translations"),
3624
4025
  "SearchModalSnippet"
3625
4026
  );
3626
- this.resolvedTranslations = d(e);
4027
+ this.resolvedTranslations = g(e);
3627
4028
  }
3628
4029
  getProps() {
3629
4030
  const e = this.resolvedTranslations;
3630
4031
  return {
3631
- apiUrl: p(this.getAttribute("api-url"), ""),
3632
- placeholder: p(this.getAttribute("placeholder"), e.placeholder),
3633
- maxResults: y(
4032
+ apiUrl: m(this.getAttribute("api-url"), ""),
4033
+ placeholder: m(this.getAttribute("placeholder"), e.placeholder),
4034
+ maxResults: w(
3634
4035
  this.getAttribute("max-results"),
3635
- N
4036
+ U
3636
4037
  ),
3637
- maxRenderResults: y(
4038
+ maxRenderResults: w(
3638
4039
  this.getAttribute("max-render-results"),
3639
4040
  D
3640
4041
  ),
3641
- debounceMs: y(this.getAttribute("debounce-ms"), 300),
3642
- theme: p(this.getAttribute("theme"), "auto"),
3643
- shortcut: p(this.getAttribute("shortcut"), "k"),
4042
+ debounceMs: w(this.getAttribute("debounce-ms"), 300),
4043
+ theme: m(this.getAttribute("theme"), "auto"),
4044
+ shortcut: m(this.getAttribute("shortcut"), "k"),
3644
4045
  useMetaKey: this.getAttribute("use-meta-key") !== "false",
3645
- hideBranding: m(this.getAttribute("hide-branding"), !1),
3646
- showUrl: m(this.getAttribute("show-url"), !1),
3647
- showDate: m(this.getAttribute("show-date"), !1),
3648
- hideThumbnails: m(this.getAttribute("hide-thumbnails"), !1),
3649
- seeMore: p(this.getAttribute("see-more"), ""),
4046
+ hideBranding: v(this.getAttribute("hide-branding"), !1),
4047
+ showUrl: v(this.getAttribute("show-url"), !1),
4048
+ showDate: v(this.getAttribute("show-date"), !1),
4049
+ hideThumbnails: v(this.getAttribute("hide-thumbnails"), !1),
4050
+ seeMore: m(this.getAttribute("see-more"), ""),
4051
+ disableAnalytics: v(this.getAttribute("disable-analytics"), !1),
3650
4052
  translations: this.translationsOverride ?? void 0
3651
4053
  };
3652
4054
  }
@@ -3666,26 +4068,29 @@ class ce extends HTMLElement {
3666
4068
  initializeClient() {
3667
4069
  const e = this.getProps();
3668
4070
  if (!e.apiUrl) {
3669
- console.error("SearchModalSnippet: api-url attribute is required"), this.client = null, this.showMissingApiUrlError();
4071
+ console.error("SearchModalSnippet: api-url attribute is required"), this.client = null, this.destroyStatsClient(), this.showMissingApiUrlError();
3670
4072
  return;
3671
4073
  }
3672
4074
  try {
3673
- this.client = S(e.apiUrl);
4075
+ this.client = S(e.apiUrl), this.destroyStatsClient(), e.disableAnalytics || (this.stats = new Q(e.apiUrl));
3674
4076
  } catch (t) {
3675
4077
  console.error("SearchModalSnippet:", t);
3676
4078
  }
3677
4079
  }
4080
+ destroyStatsClient() {
4081
+ this.stats && (this.stats.destroy(), this.stats = null);
4082
+ }
3678
4083
  render() {
3679
4084
  const e = this.getProps(), t = this.resolvedTranslations, s = (h) => this.performSearch(h);
3680
- this.debouncedSearch = P(
4085
+ this.debouncedSearch = _(
3681
4086
  s,
3682
4087
  e.debounceMs || 300
3683
4088
  );
3684
4089
  const r = document.createElement("style");
3685
4090
  r.textContent = `${M}
3686
- ${le}`;
3687
- const o = e.hideBranding ? "" : `<div class="powered-by-inline">${E}</div>`, c = document.createElement("div");
3688
- c.innerHTML = `
4091
+ ${ye}`;
4092
+ const n = e.hideBranding ? "" : `<div class="powered-by-inline">${E}</div>`, o = document.createElement("div");
4093
+ o.innerHTML = `
3689
4094
  <div class="modal-backdrop" role="presentation"></div>
3690
4095
  <div class="modal-container" role="dialog" aria-modal="true" aria-labelledby="modal-title">
3691
4096
  <div class="modal-header">
@@ -3695,8 +4100,8 @@ ${le}`;
3695
4100
  <input
3696
4101
  type="text"
3697
4102
  class="modal-search-input"
3698
- placeholder="${l(e.placeholder || t.placeholder)}"
3699
- aria-label="${l(t.searchButtonLabel)}"
4103
+ placeholder="${c(e.placeholder || t.placeholder)}"
4104
+ aria-label="${c(t.searchButtonLabel)}"
3700
4105
  aria-autocomplete="list"
3701
4106
  aria-controls="modal-results-list"
3702
4107
  aria-expanded="false"
@@ -3705,7 +4110,7 @@ ${le}`;
3705
4110
  />
3706
4111
  </div>
3707
4112
  <div class="modal-content">
3708
- <div class="modal-results" id="modal-results-list" role="listbox" aria-label="${l(t.searchResultsAriaLabel)}">
4113
+ <div class="modal-results" id="modal-results-list" role="listbox" aria-label="${c(t.searchResultsAriaLabel)}">
3709
4114
  ${this.renderEmptyState()}
3710
4115
  </div>
3711
4116
  </div>
@@ -3714,21 +4119,21 @@ ${le}`;
3714
4119
  <div class="modal-footer-hint">
3715
4120
  <kbd class="modal-kbd">↑</kbd>
3716
4121
  <kbd class="modal-kbd">↓</kbd>
3717
- <span>${l(t.navigateHint)}</span>
4122
+ <span>${c(t.navigateHint)}</span>
3718
4123
  </div>
3719
4124
  <div class="modal-footer-hint">
3720
4125
  <kbd class="modal-kbd">↵</kbd>
3721
- <span>${l(t.selectHint)}</span>
4126
+ <span>${c(t.selectHint)}</span>
3722
4127
  </div>
3723
4128
  <div class="modal-footer-hint">
3724
4129
  <kbd class="modal-kbd">Esc</kbd>
3725
- <span>${l(t.closeHint)}</span>
4130
+ <span>${c(t.closeHint)}</span>
3726
4131
  </div>
3727
4132
  </div>
3728
- ${o}
4133
+ ${n}
3729
4134
  </div>
3730
4135
  </div>
3731
- `, this.shadow.innerHTML = "", this.shadow.appendChild(r), this.shadow.appendChild(c), this.backdrop = this.shadow.querySelector(".modal-backdrop"), this.modal = this.shadow.querySelector(".modal-container"), this.inputElement = this.shadow.querySelector(".modal-search-input"), this.resultsContainer = this.shadow.querySelector(".modal-results"), this.footerCount = this.shadow.querySelector(".modal-results-count"), this.attachEventListeners(), this.client || this.showMissingApiUrlError();
4136
+ `, this.shadow.innerHTML = "", this.shadow.appendChild(r), this.shadow.appendChild(o), this.backdrop = this.shadow.querySelector(".modal-backdrop"), this.modal = this.shadow.querySelector(".modal-container"), this.inputElement = this.shadow.querySelector(".modal-search-input"), this.resultsContainer = this.shadow.querySelector(".modal-results"), this.footerCount = this.shadow.querySelector(".modal-results-count"), this.attachEventListeners(), this.client || this.showMissingApiUrlError();
3732
4137
  }
3733
4138
  attachGlobalKeyboardShortcut() {
3734
4139
  const e = this.getProps(), t = e.shortcut?.toLowerCase() || "k";
@@ -3778,7 +4183,7 @@ ${le}`;
3778
4183
  }
3779
4184
  const e = this.results[this.activeIndex];
3780
4185
  this.dispatchEvent(
3781
- v("result-select", {
4186
+ b("result-select", {
3782
4187
  result: e,
3783
4188
  index: this.activeIndex
3784
4189
  })
@@ -3797,10 +4202,10 @@ ${le}`;
3797
4202
  try {
3798
4203
  const t = this.getProps(), s = await this.client.search(e, {
3799
4204
  signal: this.currentSearchController.signal,
3800
- maxResults: t.maxResults || N,
4205
+ maxResults: t.maxResults || U,
3801
4206
  request: this.getRequestOptions()
3802
4207
  });
3803
- this.results = s.slice(0, t.maxRenderResults || D), this.activeIndex = this.results.length > 0 ? 0 : -1, this.displayResults(this.results, e, s.length);
4208
+ this.results = s.slice(0, t.maxRenderResults || D), this.activeIndex = this.results.length > 0 ? 0 : -1, this.lastSearchQuery = e, this.lastSearchTotal = s.length, this.stats?.trackSearch(e, s.length), this.displayResults(this.results, e, s.length);
3804
4209
  } catch (t) {
3805
4210
  if (t.name === "AbortError")
3806
4211
  return;
@@ -3815,35 +4220,36 @@ ${le}`;
3815
4220
  this.showNoResultsState(t);
3816
4221
  return;
3817
4222
  }
3818
- const r = this.getProps(), o = this.resolvedTranslations, c = e.map((g, w) => this.renderResult(g, w)).join(""), h = s > e.length, u = h ? b(o.resultsCountOverflow, { n: e.length, total: s }) : b(s === 1 ? o.modalResultsCount : o.modalResultsCountPlural, {
4223
+ const r = this.getProps(), n = this.resolvedTranslations, o = e.map((u, f) => this.renderResult(u, f)).join(""), h = s > e.length, d = h ? y(n.resultsCountOverflow, { n: e.length, total: s }) : y(s === 1 ? n.modalResultsCount : n.modalResultsCountPlural, {
3819
4224
  n: s
3820
- }), f = r.seeMore && h ? `<a href="${l(r.seeMore + encodeURIComponent(t))}" class="modal-see-more">
3821
- <span>${l(o.seeMoreResults)}</span>
4225
+ }), p = r.seeMore && h ? `<a href="${c(r.seeMore + encodeURIComponent(t))}" class="modal-see-more">
4226
+ <span>${c(n.seeMoreResults)}</span>
3822
4227
  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
3823
4228
  </a>` : "";
3824
- this.resultsContainer.innerHTML = c + f, this.footerCount && (this.footerCount.textContent = u), this.inputElement && this.inputElement.setAttribute("aria-expanded", "true"), this.attachResultHandlers(), this.updateActiveResult();
4229
+ this.resultsContainer.innerHTML = o + p, this.footerCount && (this.footerCount.textContent = d), this.inputElement && this.inputElement.setAttribute("aria-expanded", "true"), this.attachResultHandlers(), this.updateActiveResult();
3825
4230
  }
3826
4231
  renderResult(e, t) {
3827
- const s = this.getProps(), r = s.hideThumbnails ? "" : this.renderResultImage(e.image, e.title), o = e.url ? l(e.url) : "#", c = e.url ? l(U(e.url)) : "", h = s.showDate && e.timestamp !== void 0 ? `<div class="modal-result-date">${l(j(e.timestamp))}</div>` : "", u = s.showUrl && e.url || h ? `<div class="modal-result-metadata">
3828
- ${s.showUrl && e.url ? `<span class="modal-result-url">${c}</span>` : '<span class="modal-result-url modal-result-url-empty"></span>'}
4232
+ const s = this.getProps(), r = s.hideThumbnails ? "" : this.renderResultImage(e.image, e.title), n = e.url ? c(e.url) : "#", o = e.url ? c(V(e.url)) : "", h = s.showDate && e.timestamp !== void 0 ? `<div class="modal-result-date">${c(j(e.timestamp))}</div>` : "", d = s.showUrl && e.url || h ? `<div class="modal-result-metadata">
4233
+ ${s.showUrl && e.url ? `<span class="modal-result-url">${o}</span>` : '<span class="modal-result-url modal-result-url-empty"></span>'}
3829
4234
  ${h}
3830
4235
  </div>` : "";
3831
4236
  return `
3832
4237
  <a
3833
- href="${o}"
4238
+ href="${n}"
3834
4239
  class="modal-result-item${t === this.activeIndex ? " active" : ""}"
3835
4240
  role="option"
3836
4241
  id="result-${t}"
3837
4242
  aria-selected="${t === this.activeIndex}"
3838
4243
  tabindex="-1"
3839
4244
  data-index="${t}"
3840
- data-url="${l(e.url || "")}"
4245
+ data-result-id="${c(e.id || "")}"
4246
+ data-url="${c(e.url || "")}"
3841
4247
  >
3842
4248
  ${r}
3843
4249
  <div class="modal-result-content">
3844
- <div class="modal-result-title">${l(e.title || "")}</div>
3845
- ${e.description ? `<div class="modal-result-description">${l(e.description)}</div>` : ""}
3846
- ${u}
4250
+ <div class="modal-result-title">${c(e.title || "")}</div>
4251
+ ${e.description ? `<div class="modal-result-description">${c(e.description)}</div>` : ""}
4252
+ ${d}
3847
4253
  </div>
3848
4254
  </a>
3849
4255
  `;
@@ -3856,8 +4262,8 @@ ${le}`;
3856
4262
  <div class="modal-result-image-placeholder" style="display: none;">${s}</div>
3857
4263
  <img
3858
4264
  class="modal-result-image"
3859
- src="${l(e)}"
3860
- alt="${l(t)}"
4265
+ src="${c(e)}"
4266
+ alt="${c(t)}"
3861
4267
  loading="lazy"
3862
4268
  />
3863
4269
  </div>
@@ -3868,27 +4274,40 @@ ${le}`;
3868
4274
  `;
3869
4275
  }
3870
4276
  attachResultHandlers() {
3871
- const e = this.resultsContainer?.querySelectorAll(".modal-result-item");
4277
+ this.detachResultsContainerClick();
4278
+ const e = this.resultsContainer;
3872
4279
  if (!e) return;
3873
- e.forEach((s, r) => {
3874
- s.getAttribute("href") === "#" && s.addEventListener("click", (c) => {
3875
- c.preventDefault();
3876
- }), s.addEventListener("mouseenter", () => {
3877
- this.activeIndex = r, this.updateActiveResult();
4280
+ this.handleResultsContainerClick = (r) => {
4281
+ const n = r.target;
4282
+ if (!n) return;
4283
+ const o = n.closest(".modal-result-item");
4284
+ if (o) {
4285
+ o.getAttribute("href") === "#" && r.preventDefault();
4286
+ const p = o.getAttribute("data-index"), u = o.getAttribute("data-result-id") ?? "", f = p !== null ? Number.parseInt(p, 10) : Number.NaN;
4287
+ !Number.isNaN(f) && u && this.stats?.trackClick(this.lastSearchQuery, this.lastSearchTotal, u, f);
4288
+ return;
4289
+ }
4290
+ n.closest(".modal-see-more") && this.stats?.trackViewMore(this.lastSearchQuery, this.lastSearchTotal);
4291
+ }, e.addEventListener("click", this.handleResultsContainerClick), e.querySelectorAll(".modal-result-item").forEach((r, n) => {
4292
+ r.addEventListener("mouseenter", () => {
4293
+ this.activeIndex = n, this.updateActiveResult();
3878
4294
  });
3879
- }), this.resultsContainer?.querySelectorAll(".modal-result-image")?.forEach((s) => {
3880
- s.addEventListener("load", () => {
3881
- s.classList.add("loaded"), s.closest(".modal-result-image-container")?.querySelector(".modal-result-image-loading")?.remove();
3882
- }), s.addEventListener("error", () => {
3883
- const r = s.closest(".modal-result-image-container");
3884
- r?.querySelector(".modal-result-image-loading")?.remove();
3885
- const o = r?.querySelector(
4295
+ }), e.querySelectorAll(".modal-result-image").forEach((r) => {
4296
+ r.addEventListener("load", () => {
4297
+ r.classList.add("loaded"), r.closest(".modal-result-image-container")?.querySelector(".modal-result-image-loading")?.remove();
4298
+ }), r.addEventListener("error", () => {
4299
+ const n = r.closest(".modal-result-image-container");
4300
+ n?.querySelector(".modal-result-image-loading")?.remove();
4301
+ const o = n?.querySelector(
3886
4302
  ".modal-result-image-placeholder"
3887
4303
  );
3888
- o && (o.style.display = "flex"), s.style.display = "none";
4304
+ o && (o.style.display = "flex"), r.style.display = "none";
3889
4305
  });
3890
4306
  });
3891
4307
  }
4308
+ detachResultsContainerClick() {
4309
+ this.resultsContainer && this.handleResultsContainerClick && this.resultsContainer.removeEventListener("click", this.handleResultsContainerClick), this.handleResultsContainerClick = null;
4310
+ }
3892
4311
  renderEmptyState() {
3893
4312
  const e = this.resolvedTranslations;
3894
4313
  return `
@@ -3897,7 +4316,7 @@ ${le}`;
3897
4316
  <circle cx="11" cy="11" r="8"></circle>
3898
4317
  <path d="m21 21-4.35-4.35"></path>
3899
4318
  </svg>
3900
- <div class="modal-empty-description">${l(e.modalEmptyStateDescription)}</div>
4319
+ <div class="modal-empty-description">${c(e.modalEmptyStateDescription)}</div>
3901
4320
  </div>
3902
4321
  `;
3903
4322
  }
@@ -3910,8 +4329,8 @@ ${le}`;
3910
4329
  const e = this.resolvedTranslations.loadingMessages, t = this.resolvedTranslations;
3911
4330
  this.loadingMessageIndex = Math.floor(Math.random() * e.length), this.resultsContainer.innerHTML = `
3912
4331
  <div class="modal-loading">
3913
- <div class="loading" aria-label="${l(t.loadingAriaLabel)}"></div>
3914
- <div class="loading-text loading-text-animate">${l(e[this.loadingMessageIndex])}</div>
4332
+ <div class="loading" aria-label="${c(t.loadingAriaLabel)}"></div>
4333
+ <div class="loading-text loading-text-animate">${c(e[this.loadingMessageIndex])}</div>
3915
4334
  </div>
3916
4335
  `, this.footerCount && (this.footerCount.textContent = e[this.loadingMessageIndex]), this.startLoadingInterval();
3917
4336
  }
@@ -3921,7 +4340,7 @@ ${le}`;
3921
4340
  this.loadingMessageIndex = (this.loadingMessageIndex + 1) % e.length;
3922
4341
  const t = this.resultsContainer?.querySelector(".loading-text");
3923
4342
  t && (t.classList.remove("loading-text-animate"), t.offsetWidth, t.textContent = e[this.loadingMessageIndex], t.classList.add("loading-text-animate")), this.footerCount && (this.footerCount.textContent = e[this.loadingMessageIndex]);
3924
- }, T);
4343
+ }, A);
3925
4344
  }
3926
4345
  clearLoadingInterval() {
3927
4346
  this.loadingMessageInterval && (clearInterval(this.loadingMessageInterval), this.loadingMessageInterval = null);
@@ -3935,8 +4354,8 @@ ${le}`;
3935
4354
  <circle cx="11" cy="11" r="8"></circle>
3936
4355
  <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
3937
4356
  </svg>
3938
- <div class="modal-empty-title">${l(t.modalNoResultsTitle)}</div>
3939
- <div class="modal-empty-description">${l(b(t.modalNoResultsDescription, { query: e }))}</div>
4357
+ <div class="modal-empty-title">${c(t.modalNoResultsTitle)}</div>
4358
+ <div class="modal-empty-description">${c(y(t.modalNoResultsDescription, { query: e }))}</div>
3940
4359
  </div>
3941
4360
  `, this.footerCount && (this.footerCount.textContent = t.modalResultsCountZero), this.inputElement && this.inputElement.setAttribute("aria-expanded", "false");
3942
4361
  }
@@ -3945,7 +4364,7 @@ ${le}`;
3945
4364
  const t = this.resolvedTranslations;
3946
4365
  this.resultsContainer.innerHTML = `
3947
4366
  <div class="error">
3948
- <strong>${l(t.errorPrefix)}</strong> ${l(e)}
4367
+ <strong>${c(t.errorPrefix)}</strong> ${c(e)}
3949
4368
  </div>
3950
4369
  `, this.footerCount && (this.footerCount.textContent = t.modalResultsCountError);
3951
4370
  }
@@ -3972,7 +4391,7 @@ ${le}`;
3972
4391
  document.documentElement.style.overflow = this.savedHtmlOverflow || "", document.body.style.overflow = this.savedBodyStyles.overflow, document.body.style.position = this.savedBodyStyles.position, document.body.style.top = this.savedBodyStyles.top, document.body.style.width = this.savedBodyStyles.width, document.body.style.scrollbarGutter = this.savedBodyStyles.scrollbarGutter || "", window.scrollTo(0, e), this.savedBodyStyles = null, this.savedHtmlOverflow = null;
3973
4392
  }
3974
4393
  cleanup() {
3975
- this.clearLoadingInterval(), this.currentSearchController && (this.currentSearchController.abort(), this.currentSearchController = null), this.handleGlobalKeydown && (document.removeEventListener("keydown", this.handleGlobalKeydown), this.handleGlobalKeydown = null), this.inputElement && (this.handleInputChange && this.inputElement.removeEventListener("input", this.handleInputChange), this.handleInputKeydown && this.inputElement.removeEventListener("keydown", this.handleInputKeydown)), this.backdrop && this.handleBackdropClick && this.backdrop.removeEventListener("click", this.handleBackdropClick), this.handleInputChange = null, this.handleInputKeydown = null, this.handleBackdropClick = null, this.client && this.client.cancelAllRequests();
4394
+ this.clearLoadingInterval(), this.currentSearchController && (this.currentSearchController.abort(), this.currentSearchController = null), this.handleGlobalKeydown && (document.removeEventListener("keydown", this.handleGlobalKeydown), this.handleGlobalKeydown = null), this.inputElement && (this.handleInputChange && this.inputElement.removeEventListener("input", this.handleInputChange), this.handleInputKeydown && this.inputElement.removeEventListener("keydown", this.handleInputKeydown)), this.backdrop && this.handleBackdropClick && this.backdrop.removeEventListener("click", this.handleBackdropClick), this.detachResultsContainerClick(), this.handleInputChange = null, this.handleInputKeydown = null, this.handleBackdropClick = null, this.destroyStatsClient(), this.client && this.client.cancelAllRequests();
3976
4395
  }
3977
4396
  // Public API
3978
4397
  /**
@@ -3983,13 +4402,13 @@ ${le}`;
3983
4402
  requestAnimationFrame(() => {
3984
4403
  this.inputElement?.focus();
3985
4404
  });
3986
- }), this.lockBodyScroll(), this.dispatchEvent(v("open", void 0)));
4405
+ }), this.lockBodyScroll(), this.dispatchEvent(b("open", void 0)));
3987
4406
  }
3988
4407
  /**
3989
4408
  * Close the search modal
3990
4409
  */
3991
4410
  close() {
3992
- this.isOpen && (this.isOpen = !1, this.backdrop?.classList.remove("open"), this.modal?.classList.remove("open"), this.inputElement && (this.inputElement.value = ""), this.results = [], this.activeIndex = -1, this.showEmptyState(), this.unlockBodyScroll(), this.dispatchEvent(v("close", void 0)));
4411
+ this.isOpen && (this.isOpen = !1, this.backdrop?.classList.remove("open"), this.modal?.classList.remove("open"), this.inputElement && (this.inputElement.value = ""), this.results = [], this.activeIndex = -1, this.showEmptyState(), this.unlockBodyScroll(), this.dispatchEvent(b("close", void 0)));
3993
4412
  }
3994
4413
  /**
3995
4414
  * Toggle the search modal open/closed
@@ -4016,15 +4435,16 @@ ${le}`;
4016
4435
  return this.isOpen;
4017
4436
  }
4018
4437
  }
4019
- customElements.get(O) || customElements.define(O, ce);
4438
+ customElements.get(P) || customElements.define(P, we);
4020
4439
  export {
4021
- Q as AISearchClient,
4022
- re as ChatBubbleSnippet,
4023
- ae as ChatPageSnippet,
4024
- L as DEFAULT_TRANSLATIONS,
4025
- oe as SearchBarSnippet,
4026
- ce as SearchModalSnippet,
4027
- oe as default,
4028
- d as mergeTranslations
4440
+ ae as AISearchClient,
4441
+ me as ChatBubbleSnippet,
4442
+ ve as ChatPageSnippet,
4443
+ T as DEFAULT_TRANSLATIONS,
4444
+ be as SearchBarSnippet,
4445
+ we as SearchModalSnippet,
4446
+ Q as StatsClient,
4447
+ be as default,
4448
+ g as mergeTranslations
4029
4449
  };
4030
4450
  //# sourceMappingURL=search-snippet.es.js.map