@cloudflare/ai-search-snippet 0.0.37 → 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,142 +1,279 @@
1
- var P = Object.defineProperty;
2
- var U = (n, i, e) => i in n ? P(n, i, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[i] = e;
3
- var a = (n, i, e) => U(n, typeof i != "symbol" ? i + "" : i, e);
4
- const g = [
5
- "Searching...",
6
- "Digging through results...",
7
- "Scanning the knowledge base...",
8
- "Finding the best matches...",
9
- "Sifting through the data...",
10
- "Almost there...",
11
- "Looking far and wide...",
12
- "Connecting the dots...",
13
- "Rummaging through pages...",
14
- "Hunting down answers..."
15
- ];
16
- function $(n, i) {
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
+ // Shared
6
+ loadingAriaLabel: "Loading",
7
+ errorPrefix: "Error:",
8
+ missingApiUrlError: "The api-url attribute is required. Please provide a valid API URL.",
9
+ poweredBy: "Powered by",
10
+ poweredByLinkLabel: "Cloudflare AI Search",
11
+ // Search (shared between bar and modal)
12
+ placeholder: "Search...",
13
+ searchButtonLabel: "Search",
14
+ searchInputAriaLabel: "Search input",
15
+ searchResultsAriaLabel: "Search results",
16
+ emptyStateTitle: "Start Searching",
17
+ emptyStateDescription: "Enter a query to search for results",
18
+ modalEmptyStateDescription: "Start typing to search",
19
+ noResultsTitle: "No Results Found",
20
+ noResultsDescription: 'No results found for "{query}"',
21
+ modalNoResultsTitle: "No results found",
22
+ modalNoResultsDescription: 'No results for "{query}"',
23
+ resultsCount: "Found {n} result",
24
+ resultsCountPlural: "Found {n} results",
25
+ resultsCountOverflow: "Showing {n} of {total} results",
26
+ modalResultsCount: "{n} result",
27
+ modalResultsCountPlural: "{n} results",
28
+ modalResultsCountZero: "0 results",
29
+ modalResultsCountError: "Error",
30
+ seeMoreResults: "See more results",
31
+ // Modal-only
32
+ navigateHint: "Navigate",
33
+ selectHint: "Select",
34
+ closeHint: "Close",
35
+ // Chat (shared between bubble, page, and chat view)
36
+ chatTitle: "Chat",
37
+ chatPlaceholder: "Type a message...",
38
+ chatInputAriaLabel: "Chat message input",
39
+ sendButtonLabel: "Send",
40
+ sendButtonAriaLabel: "Send message",
41
+ chatEmptyTitle: "Start a Conversation",
42
+ chatEmptyDescription: "Send a message to begin chatting",
43
+ userAvatar: "U",
44
+ assistantAvatar: "AI",
45
+ unknownError: "Unknown error",
46
+ // Chat bubble
47
+ openChatAriaLabel: "Open chat",
48
+ clearHistoryAriaLabel: "Clear history",
49
+ minimizeAriaLabel: "Minimize",
50
+ closeAriaLabel: "Close",
51
+ // Chat page
52
+ historyTitle: "History",
53
+ newChatButton: "New Chat",
54
+ clearChatButton: "Clear Chat",
55
+ toggleSidebarTitle: "Toggle sidebar",
56
+ deleteChatTitle: "Delete chat",
57
+ noChatsYet: "No chats yet",
58
+ yesterday: "Yesterday",
59
+ // Relative timestamps (formatTimestamp)
60
+ justNow: "Just now",
61
+ minuteAgo: "{n} minute ago",
62
+ minutesAgo: "{n} minutes ago",
63
+ hourAgo: "{n} hour ago",
64
+ hoursAgo: "{n} hours ago",
65
+ // Cycling loading messages
66
+ loadingMessages: [
67
+ "Searching...",
68
+ "Digging through results...",
69
+ "Scanning the knowledge base...",
70
+ "Finding the best matches...",
71
+ "Sifting through the data...",
72
+ "Almost there...",
73
+ "Looking far and wide...",
74
+ "Connecting the dots...",
75
+ "Rummaging through pages...",
76
+ "Hunting down answers..."
77
+ ]
78
+ };
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
+ if (t != null) {
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 = T.loadingMessages));
88
+ continue;
89
+ }
90
+ typeof t == "string" && (i[e] = t);
91
+ }
92
+ }
93
+ return i;
94
+ }
95
+ function y(l, i = {}) {
96
+ return l.replace(/\{(\w+)\}/g, (e, t) => Object.hasOwn(i, t) ? String(i[t]) : e);
97
+ }
98
+ function k(l, i) {
99
+ if (!l) return null;
100
+ try {
101
+ const e = JSON.parse(l);
102
+ if (e === null || typeof e != "object" || Array.isArray(e))
103
+ throw new Error("translations must be a JSON object");
104
+ return e;
105
+ } catch (e) {
106
+ return console.error(`${i}: invalid translations attribute`, e), null;
107
+ }
108
+ }
109
+ const A = 2500;
110
+ function _(l, i) {
17
111
  let e;
18
112
  function t(...s) {
19
113
  clearTimeout(e), e = setTimeout(() => {
20
- n(...s);
114
+ l(...s);
21
115
  }, i);
22
116
  }
23
117
  return t.cancel = () => clearTimeout(e), t;
24
118
  }
25
- function h(n) {
119
+ function c(l) {
26
120
  const i = document.createElement("div");
27
- return i.textContent = n, i.innerHTML;
121
+ return i.textContent = l, i.innerHTML;
28
122
  }
29
- function q(n) {
123
+ function V(l) {
30
124
  try {
31
- return decodeURI(n);
125
+ return decodeURI(l);
32
126
  } catch {
33
- return n;
127
+ return l;
34
128
  }
35
129
  }
36
- function E(n) {
37
- return new DOMParser().parseFromString(n, "text/html").documentElement.textContent || "";
130
+ function I(l) {
131
+ return new DOMParser().parseFromString(l, "text/html").documentElement.textContent || "";
38
132
  }
39
- function O(n) {
40
- const i = new Date(n), t = (/* @__PURE__ */ new Date()).getTime() - i.getTime();
41
- if (t < 6e4)
42
- return "Just now";
43
- if (t < 36e5) {
44
- const s = Math.floor(t / 6e4);
45
- return `${s} ${s === 1 ? "minute" : "minutes"} ago`;
133
+ function X(l, i) {
134
+ const e = g(i), t = new Date(l), r = (/* @__PURE__ */ new Date()).getTime() - t.getTime();
135
+ if (r < 6e4)
136
+ return e.justNow;
137
+ if (r < 36e5) {
138
+ const n = Math.floor(r / 6e4), o = n === 1 ? e.minuteAgo : e.minutesAgo;
139
+ return y(o, { n });
46
140
  }
47
- if (t < 864e5) {
48
- const s = Math.floor(t / 36e5);
49
- return `${s} ${s === 1 ? "hour" : "hours"} ago`;
141
+ if (r < 864e5) {
142
+ const n = Math.floor(r / 36e5), o = n === 1 ? e.hourAgo : e.hoursAgo;
143
+ return y(o, { n });
50
144
  }
51
- return i.toLocaleString(void 0, {
145
+ return t.toLocaleString(void 0, {
52
146
  month: "short",
53
147
  day: "numeric",
54
148
  hour: "2-digit",
55
149
  minute: "2-digit"
56
150
  });
57
151
  }
58
- function B(n) {
59
- return new Date(n).toLocaleDateString(void 0, {
152
+ function j(l) {
153
+ return new Date(l).toLocaleDateString(void 0, {
60
154
  month: "short",
61
155
  day: "numeric"
62
156
  });
63
157
  }
64
- function M(n = "id") {
65
- 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)}`;
66
160
  }
67
- function d(n, i) {
68
- return n !== null ? n : i;
161
+ function m(l, i) {
162
+ return l !== null ? l : i;
69
163
  }
70
- function m(n, i) {
71
- return n === null ? i : n === "true" || n === "";
164
+ function v(l, i) {
165
+ return l === null ? i : l === "true" || l === "";
72
166
  }
73
- function y(n, i) {
74
- if (n === null) return i;
75
- const e = Number.parseInt(n, 10);
167
+ function w(l, i) {
168
+ if (l === null) return i;
169
+ const e = Number.parseInt(l, 10);
76
170
  return Number.isNaN(e) ? i : e;
77
171
  }
78
- function v(n, i) {
79
- 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, {
80
187
  detail: i,
81
188
  bubbles: !0,
82
189
  composed: !0,
83
190
  cancelable: !0
84
191
  });
85
192
  }
86
- function x(n) {
87
- if (!n)
193
+ function S(l) {
194
+ if (!l)
88
195
  throw new Error("API URL is required");
89
- return new K(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
+ `) };
90
227
  }
91
- function w(n) {
92
- return n !== null && typeof n == "object" && !Array.isArray(n);
228
+ function C(l) {
229
+ return l !== null && typeof l == "object" && !Array.isArray(l);
93
230
  }
94
- function R(...n) {
231
+ function K(...l) {
95
232
  const i = {};
96
- for (const e of n)
233
+ for (const e of l)
97
234
  if (e)
98
235
  for (const [t, s] of Object.entries(e)) {
99
236
  const r = i[t];
100
- w(r) && w(s) ? i[t] = R(r, s) : i[t] = s;
237
+ C(r) && C(s) ? i[t] = K(r, s) : i[t] = s;
101
238
  }
102
239
  return i;
103
240
  }
104
- function D(n, i) {
105
- if (!w(i))
106
- return n;
241
+ function se(l, i) {
242
+ if (!C(i))
243
+ return l;
107
244
  const e = new URLSearchParams();
108
- for (const [l, p] of Object.entries(i))
109
- p != null && e.append(l, String(p));
245
+ for (const [h, d] of Object.entries(i))
246
+ d != null && e.append(h, String(d));
110
247
  const t = e.toString();
111
248
  if (!t)
112
- return n;
113
- const s = n.indexOf("#"), r = s === -1 ? n : n.slice(0, s), o = s === -1 ? "" : n.slice(s), c = r.includes("?") ? "&" : "?";
114
- 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}`;
115
252
  }
116
- function j(n) {
117
- if (!w(n))
253
+ function ie(l) {
254
+ if (!C(l))
118
255
  return {};
119
256
  const i = {};
120
- for (const [e, t] of Object.entries(n))
257
+ for (const [e, t] of Object.entries(l))
121
258
  t != null && (i[e] = String(t));
122
259
  return i;
123
260
  }
124
- function V(n) {
125
- return w(n) ? n : void 0;
261
+ function re(l) {
262
+ return C(l) ? l : void 0;
126
263
  }
127
- class K {
264
+ class ae {
128
265
  constructor(i) {
129
266
  a(this, "activeRequests", /* @__PURE__ */ new Map());
130
267
  a(this, "baseUrl");
131
268
  this.baseUrl = i.replace(/\/$/, "");
132
269
  }
133
270
  request(i, e, t, s) {
134
- const r = e === "search" ? "snippet-search" : "snippet-chat-completions", o = D(`${this.baseUrl}/${e}`, s?.queryParams);
135
- return fetch(o, {
271
+ const r = e === "search" ? "snippet-search" : "snippet-chat-completions", n = se(`${this.baseUrl}/${e}`, s?.queryParams);
272
+ return fetch(n, {
136
273
  method: "POST",
137
- body: JSON.stringify(R(V(s?.body), i)),
274
+ body: JSON.stringify(K(re(s?.body), i)),
138
275
  headers: {
139
- ...j(s?.headers),
276
+ ...ie(s?.headers),
140
277
  "Content-Type": "application/json",
141
278
  Accept: i.stream ? "text/event-stream" : "application/json",
142
279
  "cf-ai-search-source": r
@@ -151,7 +288,7 @@ class K {
151
288
  const t = this.generateRequestId(), s = new AbortController(), r = e.signal || s.signal;
152
289
  this.registerRequest(t, s);
153
290
  try {
154
- const o = await this.request(
291
+ const n = await this.request(
155
292
  {
156
293
  messages: [{ role: "user", content: i }],
157
294
  stream: !1,
@@ -166,28 +303,28 @@ class K {
166
303
  r,
167
304
  e.request
168
305
  );
169
- if (!o.ok)
170
- throw new Error(`HTTP error! status: ${o.status}`);
171
- if (!o.body)
306
+ if (!n.ok)
307
+ throw new Error(`HTTP error! status: ${n.status}`);
308
+ if (!n.body)
172
309
  throw new Error("Response body is empty");
173
- const c = await o.json();
174
- if (c.success && c.result)
175
- return c.result.chunks.map(
176
- (l) => ({
310
+ const o = await n.json();
311
+ if (o.success && o.result)
312
+ return o.result.chunks.map(
313
+ (h) => ({
177
314
  type: "result",
178
- id: l.id,
179
- title: E(l.item.metadata?.title),
180
- description: l.item.metadata?.description ? E(l.item.metadata?.description) : "",
181
- timestamp: l.item.timestamp ?? void 0,
182
- url: l.item.key,
183
- image: l.item.metadata?.image || void 0,
315
+ id: h.id,
316
+ title: I(h.item.metadata?.title),
317
+ description: h.item.metadata?.description ? I(h.item.metadata?.description) : "",
318
+ timestamp: h.item.timestamp ?? void 0,
319
+ url: h.item.key,
320
+ image: h.item.metadata?.image || void 0,
184
321
  metadata: {
185
- ...l.item.metadata,
186
- instance_id: l.instance_id
322
+ ...h.item.metadata,
323
+ instance_id: h.instance_id
187
324
  }
188
325
  })
189
326
  );
190
- 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");
191
328
  } finally {
192
329
  this.unregisterRequest(t);
193
330
  }
@@ -195,7 +332,7 @@ class K {
195
332
  async *searchStream(i, e = {}) {
196
333
  const t = this.generateRequestId(), s = new AbortController(), r = e.signal || s.signal;
197
334
  this.registerRequest(t, s);
198
- const o = await this.request(
335
+ const n = await this.request(
199
336
  {
200
337
  messages: [{ role: "user", content: i }],
201
338
  stream: !0,
@@ -207,24 +344,24 @@ class K {
207
344
  r,
208
345
  e.request
209
346
  );
210
- if (!o.ok)
211
- throw new Error(`HTTP error! status: ${o.status}`);
212
- if (!o.body)
347
+ if (!n.ok)
348
+ throw new Error(`HTTP error! status: ${n.status}`);
349
+ if (!n.body)
213
350
  throw new Error("Response body is empty");
214
- let c = "";
215
- const l = o.body.getReader(), p = new TextDecoder();
351
+ let o = "";
352
+ const h = n.body.getReader(), d = new TextDecoder();
216
353
  for (; ; ) {
217
- const { done: u, value: S } = await l.read();
354
+ const { done: u, value: f } = await h.read();
218
355
  if (u)
219
356
  break;
220
- const _ = p.decode(S, { stream: !0 });
221
- c += _;
357
+ const L = d.decode(f, { stream: !0 });
358
+ o += L;
222
359
  }
223
360
  yield {
224
361
  type: "result",
225
362
  id: "",
226
363
  title: "",
227
- description: c.replaceAll("data: ", "").trim().split(`
364
+ description: o.replaceAll("data: ", "").trim().split(`
228
365
 
229
366
  `).map((u) => JSON.parse(u)).map((u) => u.response).join(""),
230
367
  url: "",
@@ -232,22 +369,86 @@ class K {
232
369
  };
233
370
  }
234
371
  async *chat(i, e) {
235
- const t = new AbortController(), s = e?.signal || t.signal, r = await this.request(
236
- {
237
- messages: [{ role: "user", content: i }],
238
- stream: !1
239
- },
240
- "chat/completions",
241
- s
242
- );
243
- if (!r.ok)
244
- throw new Error(`HTTP error! status: ${r.status}`);
245
- 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)
246
387
  throw new Error("Response body is empty");
247
- yield {
248
- type: "text",
249
- 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;
250
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
+ }
251
452
  }
252
453
  /**
253
454
  * Cancels an active request by ID
@@ -286,10 +487,141 @@ class K {
286
487
  return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
287
488
  }
288
489
  }
289
- const G = `<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">
290
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"/>
291
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"/>
292
- </svg>`, F = "https://workers.cloudflare.com/product/ai-search", k = `Powered by <a href="${F}" target="_blank" rel="noopener noreferrer">Cloudflare AI Search ${G}</a>`, H = `
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 = `
293
625
  /* Chat container */
294
626
  .chat-container {
295
627
  display: flex;
@@ -332,6 +664,7 @@ const G = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.w
332
664
  gap: var(--search-snippet-spacing-sm);
333
665
  max-width: 85%;
334
666
  animation: slideIn var(--search-snippet-animation-duration) ease-out;
667
+ animation-fill-mode: both;
335
668
  }
336
669
 
337
670
  @keyframes slideIn {
@@ -402,6 +735,9 @@ const G = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.w
402
735
  border-radius: var(--search-snippet-border-radius);
403
736
  word-wrap: break-word;
404
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;
405
741
  }
406
742
 
407
743
  .chat-message-user .chat-message-bubble {
@@ -606,7 +942,7 @@ const G = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.w
606
942
  .chat-message-bubble a:hover {
607
943
  text-decoration: none;
608
944
  }
609
- `, C = `
945
+ `, M = `
610
946
  :host {
611
947
  /* Colors - Light Mode */
612
948
  --search-snippet-primary-color: #2563eb;
@@ -1041,55 +1377,55 @@ const G = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.w
1041
1377
  text-align: center;
1042
1378
  }
1043
1379
  `;
1044
- function Y(n) {
1045
- let i = n;
1046
- i = J(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>`);
1047
1383
  const e = i.split(`
1048
1384
  `), t = [];
1049
1385
  let s = !1, r = "";
1050
- for (let o = 0; o < e.length; o++) {
1051
- const c = e[o], l = c.match(/^(#{1,6})\s+(.+)$/);
1052
- if (l) {
1053
- const u = l[1].length, S = l[2];
1054
- t.push(`<h${u}>${f(S)}</h${u}>`);
1386
+ for (let n = 0; n < e.length; n++) {
1387
+ const o = e[n], h = o.match(/^(#{1,6})\s+(.+)$/);
1388
+ if (h) {
1389
+ const u = h[1].length, f = h[2];
1390
+ t.push(`<h${u}>${x(f)}</h${u}>`);
1055
1391
  continue;
1056
1392
  }
1057
- if (c.match(/^---+$/)) {
1393
+ if (o.match(/^---+$/)) {
1058
1394
  t.push("<hr />");
1059
1395
  continue;
1060
1396
  }
1061
- if (c.match(/^>\s+/)) {
1062
- const u = c.replace(/^>\s+/, "");
1063
- t.push(`<blockquote>${f(u)}</blockquote>`);
1397
+ if (o.match(/^>\s+/)) {
1398
+ const u = o.replace(/^>\s+/, "");
1399
+ t.push(`<blockquote>${x(u)}</blockquote>`);
1064
1400
  continue;
1065
1401
  }
1066
- const p = c.match(/^[-*]\s+(.+)$/);
1067
- if (p) {
1068
- (!s || r !== "ul") && (s && t.push(`</${r}>`), t.push("<ul>"), s = !0, r = "ul"), t.push(`<li>${f(p[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>`);
1069
1405
  continue;
1070
1406
  }
1071
- const b = c.match(/^\d+\.\s+(.+)$/);
1072
- if (b) {
1073
- (!s || r !== "ol") && (s && t.push(`</${r}>`), t.push("<ol>"), s = !0, r = "ol"), t.push(`<li>${f(b[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>`);
1074
1410
  continue;
1075
1411
  }
1076
- if (s && (t.push(`</${r}>`), s = !1, r = ""), c.trim() === "") {
1412
+ if (s && (t.push(`</${r}>`), s = !1, r = ""), o.trim() === "") {
1077
1413
  t.push("<br />");
1078
1414
  continue;
1079
1415
  }
1080
- t.push(`<p>${f(c)}</p>`);
1416
+ t.push(`<p>${x(o)}</p>`);
1081
1417
  }
1082
1418
  return s && t.push(`</${r}>`), t.join(`
1083
1419
  `);
1084
1420
  }
1085
- function f(n) {
1086
- let i = n;
1421
+ function x(l) {
1422
+ let i = l;
1087
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(
1088
1424
  /\[([^\]]+)\]\(([^)]+)\)/g,
1089
1425
  '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
1090
1426
  ), i;
1091
1427
  }
1092
- function J(n) {
1428
+ function ue(l) {
1093
1429
  const i = {
1094
1430
  "&": "&amp;",
1095
1431
  "<": "&lt;",
@@ -1097,13 +1433,15 @@ function J(n) {
1097
1433
  '"': "&quot;",
1098
1434
  "'": "&#39;"
1099
1435
  };
1100
- return n.replace(/[&<>"']/g, (e) => i[e] || e);
1436
+ return l.replace(/[&<>"']/g, (e) => i[e] || e);
1101
1437
  }
1102
- class N {
1438
+ const ge = 64;
1439
+ class Y {
1103
1440
  constructor(i, e, t) {
1104
1441
  a(this, "container");
1105
1442
  a(this, "client");
1106
1443
  a(this, "props");
1444
+ a(this, "translations");
1107
1445
  a(this, "inputElement", null);
1108
1446
  a(this, "messagesContainer", null);
1109
1447
  a(this, "sendButton", null);
@@ -1112,46 +1450,54 @@ class N {
1112
1450
  a(this, "currentStreamingMessageId", null);
1113
1451
  a(this, "loadingMessageInterval", null);
1114
1452
  a(this, "loadingMessageIndex", 0);
1453
+ a(this, "pendingScrollFrame", null);
1115
1454
  // Event handler references for cleanup
1116
1455
  a(this, "handleInputResize", null);
1117
1456
  a(this, "handleInputKeydown", null);
1118
1457
  a(this, "handleSendClick", null);
1119
- this.container = i, this.client = e, this.props = t, this.render(), this.attachEventListeners();
1458
+ this.container = i, this.client = e, this.props = t, this.translations = g(t.translations), this.render(), this.attachEventListeners();
1120
1459
  }
1121
1460
  /**
1122
1461
  * Render the chat interface
1123
1462
  */
1124
1463
  render() {
1464
+ const i = this.translations;
1125
1465
  this.container.innerHTML = `
1126
1466
  <div class="chat-container">
1127
1467
  <div class="chat-messages">
1128
- <div class="chat-empty">
1129
- <svg class="chat-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1130
- <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>
1131
- </svg>
1132
- <div class="chat-empty-title">Start a Conversation</div>
1133
- <div class="chat-empty-description">
1134
- Send a message to begin chatting
1135
- </div>
1136
- </div>
1468
+ ${this.renderEmptyStateHTML()}
1137
1469
  </div>
1138
1470
  <div class="chat-input-area">
1139
1471
  <div class="chat-input-wrapper">
1140
1472
  <textarea
1141
1473
  class="chat-input"
1142
- placeholder="${h(this.props.placeholder || "Type a message...")}"
1143
- aria-label="Chat message input"
1474
+ placeholder="${c(this.props.placeholder || i.chatPlaceholder)}"
1475
+ aria-label="${c(i.chatInputAriaLabel)}"
1144
1476
  style="height: 40px;"
1145
1477
  rows="1"
1146
1478
  ></textarea>
1147
- <button class="button chat-send-button" aria-label="Send message">
1148
- <span>Send</span>
1479
+ <button class="button chat-send-button" aria-label="${c(i.sendButtonAriaLabel)}">
1480
+ <span>${c(i.sendButtonLabel)}</span>
1149
1481
  </button>
1150
1482
  </div>
1151
1483
  </div>
1152
1484
  </div>
1153
1485
  `, this.messagesContainer = this.container.querySelector(".chat-messages"), this.inputElement = this.container.querySelector(".chat-input"), this.sendButton = this.container.querySelector(".chat-send-button");
1154
1486
  }
1487
+ renderEmptyStateHTML() {
1488
+ const i = this.translations;
1489
+ return `
1490
+ <div class="chat-empty">
1491
+ <svg class="chat-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
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>
1493
+ </svg>
1494
+ <div class="chat-empty-title">${c(i.chatEmptyTitle)}</div>
1495
+ <div class="chat-empty-description">
1496
+ ${c(i.chatEmptyDescription)}
1497
+ </div>
1498
+ </div>
1499
+ `;
1500
+ }
1155
1501
  /**
1156
1502
  * Attach event listeners
1157
1503
  */
@@ -1177,37 +1523,43 @@ class N {
1177
1523
  * Send a message
1178
1524
  */
1179
1525
  async sendMessage(i) {
1180
- const e = {
1181
- id: M("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"),
1182
1531
  role: "user",
1183
1532
  content: i,
1184
1533
  timestamp: Date.now()
1185
1534
  };
1186
- this.addMessage(e), this.renderMessages(!0), this.setStreamingState(!0);
1187
- const t = M("msg"), s = {
1188
- id: t,
1535
+ this.addMessage(s), this.renderMessages(!0), this.setStreamingState(!0);
1536
+ const r = R("msg"), n = {
1537
+ id: r,
1189
1538
  role: "assistant",
1190
1539
  content: "",
1191
1540
  timestamp: Date.now()
1192
1541
  };
1193
- this.addMessage(s), this.currentStreamingMessageId = t, this.renderMessages(!0);
1542
+ this.addMessage(n), this.currentStreamingMessageId = r, this.renderMessages(!0);
1194
1543
  try {
1195
- const r = this.client.chat(i);
1196
- let o = "";
1197
- for await (const l of r)
1198
- if (l.type === "text" && l.message)
1199
- o += l.message, this.updateStreamingMessage(t, o);
1200
- else if (l.type === "error") {
1201
- this.showErrorInMessage(t, l.message || "Unknown 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") {
1550
+ this.showErrorInMessage(
1551
+ r,
1552
+ p.message || this.translations.unknownError
1553
+ );
1202
1554
  break;
1203
1555
  }
1204
- const c = this.messages.findIndex((l) => l.id === t);
1205
- c !== -1 && (this.messages[c].content = o), this.container.dispatchEvent(v("message", { message: s }));
1206
- } catch (r) {
1207
- this.showErrorInMessage(t, r.message), this.container.dispatchEvent(
1208
- 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", {
1209
1561
  error: {
1210
- message: r.message,
1562
+ message: o.message,
1211
1563
  code: "CHAT_ERROR"
1212
1564
  }
1213
1565
  })
@@ -1216,6 +1568,16 @@ class N {
1216
1568
  this.setStreamingState(!1), this.renderMessages(), this.currentStreamingMessageId = null;
1217
1569
  }
1218
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
+ }
1219
1581
  /**
1220
1582
  * Add a message to the chat
1221
1583
  */
@@ -1223,81 +1585,138 @@ class N {
1223
1585
  this.messages.push(i), this.renderMessages();
1224
1586
  }
1225
1587
  /**
1226
- * 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.
1227
1595
  */
1228
1596
  updateStreamingMessage(i, e) {
1229
1597
  const t = this.messages.findIndex((s) => s.id === i);
1230
- 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;
1231
1625
  }
1232
1626
  /**
1233
1627
  * Show error in message
1234
1628
  */
1235
1629
  showErrorInMessage(i, e) {
1236
1630
  const t = this.messages.findIndex((s) => s.id === i);
1237
- t !== -1 && (this.messages[t].content = `Error: ${e}`, this.renderMessages());
1631
+ t !== -1 && (this.messages[t].content = `${this.translations.errorPrefix} ${e}`, this.renderMessages());
1238
1632
  }
1239
1633
  /**
1240
- * 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.
1241
1637
  */
1242
1638
  renderMessages(i = !1) {
1243
1639
  if (!this.messagesContainer) return;
1244
1640
  if (this.messages.length === 0) {
1245
- this.messagesContainer.innerHTML = `
1246
- <div class="chat-empty">
1247
- <svg class="chat-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1248
- <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
- </svg>
1250
- <div class="chat-empty-title">Start a Conversation</div>
1251
- <div class="chat-empty-description">
1252
- Send a message to begin chatting
1253
- </div>
1254
- </div>
1255
- `;
1641
+ this.messagesContainer.innerHTML = this.renderEmptyStateHTML();
1256
1642
  return;
1257
1643
  }
1258
1644
  const e = this.messages.map(
1259
1645
  (t) => this.renderMessage(t, i && t.id === this.currentStreamingMessageId)
1260
1646
  ).join("");
1261
- this.messagesContainer.innerHTML = e, this.scrollToBottom();
1647
+ this.messagesContainer.innerHTML = e, this.scheduleScrollToBottom();
1262
1648
  }
1263
1649
  /**
1264
1650
  * Render a single message
1265
1651
  */
1266
1652
  renderMessage(i, e = !1) {
1267
- const t = `chat-message-${i.role}`, s = i.role === "user" ? "U" : "AI";
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) : "";
1268
1654
  return `
1269
- <div class="chat-message ${t}">
1270
- <div class="chat-message-avatar">${s}</div>
1655
+ <div class="chat-message ${s}" data-message-id="${c(i.id)}">
1656
+ <div class="chat-message-avatar">${c(r)}</div>
1271
1657
  <div class="chat-message-content">
1272
1658
  <div class="chat-message-bubble">
1273
- ${i.content ? `<div class="chat-message-text">${Y(i.content)}</div>` : ""}
1274
- ${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">${g[this.loadingMessageIndex]}</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>` : ""}
1275
1661
  </div>
1276
1662
  <div class="chat-message-metadata">
1277
- <span class="chat-message-time">${O(i.timestamp)}</span>
1663
+ <span class="chat-message-time">${c(X(i.timestamp, this.translations))}</span>
1278
1664
  </div>
1279
1665
  </div>
1280
1666
  </div>
1281
1667
  `;
1282
1668
  }
1283
1669
  /**
1284
- * 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.
1285
1673
  */
1286
- scrollToBottom() {
1287
- this.messagesContainer && requestAnimationFrame(() => {
1288
- this.messagesContainer && (this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight);
1289
- });
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
+ }));
1290
1691
  }
1291
1692
  /**
1292
1693
  * Set streaming state
1293
1694
  */
1294
1695
  setStreamingState(i) {
1295
- this.isStreaming = i, this.inputElement && (this.inputElement.disabled = i), this.sendButton && (this.sendButton.disabled = i, this.sendButton.innerHTML = i ? '<div class="loading"></div>' : "<span>Send</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();
1296
1697
  }
1297
1698
  startLoadingMessages() {
1298
- this.loadingMessageIndex = Math.floor(Math.random() * g.length), this.loadingMessageInterval = setInterval(() => {
1299
- this.loadingMessageIndex = (this.loadingMessageIndex + 1) % g.length, this.isStreaming && this.renderMessages(!0);
1300
- }, 2500);
1699
+ this.clearLoadingMessages();
1700
+ const i = this.translations.loadingMessages;
1701
+ this.loadingMessageIndex = Math.floor(Math.random() * i.length), this.loadingMessageInterval = setInterval(() => {
1702
+ const e = this.translations.loadingMessages;
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;
1301
1720
  }
1302
1721
  clearLoadingMessages() {
1303
1722
  this.loadingMessageInterval && (clearInterval(this.loadingMessageInterval), this.loadingMessageInterval = null);
@@ -1320,15 +1739,28 @@ class N {
1320
1739
  setMessages(i) {
1321
1740
  this.messages = [...i], this.renderMessages();
1322
1741
  }
1742
+ /**
1743
+ * Update the props (e.g. placeholder / translations) and re-render.
1744
+ *
1745
+ * Safe to call at any time: existing messages and streaming state are
1746
+ * preserved; listeners on the old DOM are removed and re-attached to the
1747
+ * newly rendered elements.
1748
+ */
1749
+ setProps(i) {
1750
+ this.props = i, this.translations = g(i.translations), this.detachEventListeners(), this.render(), this.attachEventListeners(), this.renderMessages(this.isStreaming), this.isStreaming && this.setStreamingState(!0);
1751
+ }
1752
+ detachEventListeners() {
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;
1754
+ }
1323
1755
  /**
1324
1756
  * Destroy and cleanup
1325
1757
  */
1326
1758
  destroy() {
1327
- 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;
1328
1760
  }
1329
1761
  }
1330
- const L = "chat-bubble-snippet";
1331
- class W extends HTMLElement {
1762
+ const $ = "chat-bubble-snippet";
1763
+ class me extends HTMLElement {
1332
1764
  constructor() {
1333
1765
  super();
1334
1766
  a(this, "shadow");
@@ -1337,6 +1769,9 @@ class W extends HTMLElement {
1337
1769
  a(this, "container", null);
1338
1770
  a(this, "isExpanded", !1);
1339
1771
  a(this, "isMinimized", !1);
1772
+ a(this, "translationsOverride", null);
1773
+ a(this, "resolvedTranslations", g(null));
1774
+ a(this, "chatQueryRewriteOverride", null);
1340
1775
  // Event handler references for cleanup
1341
1776
  a(this, "handleBubbleClick", null);
1342
1777
  a(this, "handleCloseClick", null);
@@ -1345,25 +1780,85 @@ class W extends HTMLElement {
1345
1780
  this.shadow = this.attachShadow({ mode: "open" });
1346
1781
  }
1347
1782
  static get observedAttributes() {
1348
- return ["api-url", "placeholder", "theme", "hide-branding"];
1783
+ return [
1784
+ "api-url",
1785
+ "placeholder",
1786
+ "theme",
1787
+ "hide-branding",
1788
+ "translations",
1789
+ "chat-query-rewrite"
1790
+ ];
1349
1791
  }
1350
1792
  connectedCallback() {
1351
- this.render(), this.initializeClient(), this.dispatchEvent(v("ready", void 0));
1793
+ this.syncTranslationsFromAttribute(), this.render(), this.initializeClient(), this.dispatchEvent(b("ready", void 0));
1352
1794
  }
1353
1795
  disconnectedCallback() {
1354
1796
  this.cleanup();
1355
1797
  }
1356
1798
  attributeChangedCallback(e, t, s) {
1357
- t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" && this.updateTheme(s));
1799
+ t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerenderAfterTranslationsChange()));
1800
+ }
1801
+ /**
1802
+ * Get the current translations object.
1803
+ */
1804
+ get translations() {
1805
+ return this.translationsOverride;
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
+ }
1817
+ /**
1818
+ * Override any user-facing string. Omitted keys fall back to English defaults.
1819
+ */
1820
+ set translations(e) {
1821
+ this.translationsOverride = e ?? null, this.resolvedTranslations = g(this.translationsOverride), this.isConnected && this.rerenderAfterTranslationsChange();
1822
+ }
1823
+ syncTranslationsFromAttribute() {
1824
+ if (this.translationsOverride) {
1825
+ this.resolvedTranslations = g(this.translationsOverride);
1826
+ return;
1827
+ }
1828
+ const e = k(
1829
+ this.getAttribute("translations"),
1830
+ "ChatBubbleSnippet"
1831
+ );
1832
+ this.resolvedTranslations = g(e);
1833
+ }
1834
+ rerenderAfterTranslationsChange() {
1835
+ const e = this.isExpanded, t = this.isMinimized;
1836
+ this.removeEventListeners();
1837
+ const s = this.chatView ? this.shadow.querySelector(".chat-content") : null;
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"));
1839
+ const r = this.shadow.querySelector(".chat-window");
1840
+ if (this.chatView && s && r) {
1841
+ const n = r.querySelector(".chat-content");
1842
+ n ? r.replaceChild(s, n) : r.appendChild(s), this.chatView.setProps(this.getProps());
1843
+ } else e && this.initializeChatView();
1358
1844
  }
1359
1845
  getProps() {
1846
+ const e = this.resolvedTranslations;
1360
1847
  return {
1361
- apiUrl: d(this.getAttribute("api-url"), ""),
1362
- placeholder: d(this.getAttribute("placeholder"), "Type a message..."),
1363
- theme: d(this.getAttribute("theme"), "auto"),
1364
- hideBranding: m(this.getAttribute("hide-branding"), !1)
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()
1365
1854
  };
1366
1855
  }
1856
+ resolveChatQueryRewrite() {
1857
+ return this.chatQueryRewriteOverride !== null ? this.chatQueryRewriteOverride : F(
1858
+ this.getAttribute("chat-query-rewrite"),
1859
+ "ChatBubbleSnippet"
1860
+ );
1861
+ }
1367
1862
  initializeClient() {
1368
1863
  const e = this.getProps();
1369
1864
  if (!e.apiUrl) {
@@ -1371,15 +1866,15 @@ class W extends HTMLElement {
1371
1866
  return;
1372
1867
  }
1373
1868
  try {
1374
- this.client = x(e.apiUrl);
1869
+ this.client = S(e.apiUrl);
1375
1870
  } catch (t) {
1376
1871
  console.error("ChatBubbleSnippet:", t);
1377
1872
  }
1378
1873
  }
1379
1874
  render() {
1380
1875
  const e = document.createElement("style");
1381
- e.textContent = `${C}
1382
- ${H}
1876
+ e.textContent = `${M}
1877
+ ${G}
1383
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();
1384
1879
  }
1385
1880
  getBubbleStyles() {
@@ -1524,8 +2019,9 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1524
2019
  `;
1525
2020
  }
1526
2021
  getBaseHTML() {
2022
+ const e = this.getProps(), t = this.resolvedTranslations, s = e.hideBranding ? "" : `<div class="powered-by">${E}</div>`;
1527
2023
  return `
1528
- <button class="bubble-button" aria-label="Open chat">
2024
+ <button class="bubble-button" aria-label="${c(t.openChatAriaLabel)}">
1529
2025
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1530
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>
1531
2027
  </svg>
@@ -1536,21 +2032,21 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1536
2032
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1537
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>
1538
2034
  </svg>
1539
- <span>Chat</span>
2035
+ <span>${c(t.chatTitle)}</span>
1540
2036
  </div>
1541
2037
  <div class="chat-header-actions">
1542
- <button class="icon-button clear-button" aria-label="Clear history">
2038
+ <button class="icon-button clear-button" aria-label="${c(t.clearHistoryAriaLabel)}">
1543
2039
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1544
2040
  <polyline points="3 6 5 6 21 6"></polyline>
1545
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>
1546
2042
  </svg>
1547
2043
  </button>
1548
- <button class="icon-button minimize-button" aria-label="Minimize">
2044
+ <button class="icon-button minimize-button" aria-label="${c(t.minimizeAriaLabel)}">
1549
2045
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1550
2046
  <line x1="5" y1="12" x2="19" y2="12"></line>
1551
2047
  </svg>
1552
2048
  </button>
1553
- <button class="icon-button close-button" aria-label="Close">
2049
+ <button class="icon-button close-button" aria-label="${c(t.closeAriaLabel)}">
1554
2050
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1555
2051
  <line x1="18" y1="6" x2="6" y2="18"></line>
1556
2052
  <line x1="6" y1="6" x2="18" y2="18"></line>
@@ -1559,7 +2055,7 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1559
2055
  </div>
1560
2056
  </div>
1561
2057
  <div class="chat-content"></div>
1562
- ${this.getProps().hideBranding ? "" : `<div class="powered-by">${k}</div>`}
2058
+ ${s}
1563
2059
  </div>
1564
2060
  `;
1565
2061
  }
@@ -1591,15 +2087,16 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1591
2087
  const e = this.shadow.querySelector(".chat-content");
1592
2088
  if (!e) return;
1593
2089
  if (!this.client) {
2090
+ const s = this.resolvedTranslations;
1594
2091
  e.innerHTML = `
1595
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);">
1596
- <strong>Error:</strong> The <code>api-url</code> attribute is required. Please provide a valid API URL.
2093
+ <strong>${c(s.errorPrefix)}</strong> ${c(s.missingApiUrlError)}
1597
2094
  </div>
1598
2095
  `;
1599
2096
  return;
1600
2097
  }
1601
2098
  const t = this.getProps();
1602
- this.chatView = new N(e, this.client, t);
2099
+ this.chatView = new Y(e, this.client, t);
1603
2100
  }
1604
2101
  updateTheme(e) {
1605
2102
  (e === "light" || e === "dark" ? e : null) === null && this.hasAttribute("theme") && this.getAttribute("theme") !== "auto" && this.removeAttribute("theme");
@@ -1618,9 +2115,9 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1618
2115
  return this.chatView?.getMessages() || [];
1619
2116
  }
1620
2117
  }
1621
- customElements.get(L) || customElements.define(L, W);
1622
- const I = "chat-page-snippet", z = "chat-page-sessions";
1623
- class X extends HTMLElement {
2118
+ customElements.get($) || customElements.define($, me);
2119
+ const q = "chat-page-snippet", B = "chat-page-sessions";
2120
+ class ve extends HTMLElement {
1624
2121
  constructor() {
1625
2122
  super();
1626
2123
  a(this, "shadow");
@@ -1630,6 +2127,9 @@ class X extends HTMLElement {
1630
2127
  a(this, "sessions", []);
1631
2128
  a(this, "currentSessionId", null);
1632
2129
  a(this, "sidebarCollapsed", !1);
2130
+ a(this, "translationsOverride", null);
2131
+ a(this, "resolvedTranslations", g(null));
2132
+ a(this, "chatQueryRewriteOverride", null);
1633
2133
  // Event handler references for cleanup
1634
2134
  a(this, "handleClearClick", null);
1635
2135
  a(this, "handleNewChatClick", null);
@@ -1639,25 +2139,96 @@ class X extends HTMLElement {
1639
2139
  this.shadow = this.attachShadow({ mode: "open" }), this.loadSessions();
1640
2140
  }
1641
2141
  static get observedAttributes() {
1642
- return ["api-url", "placeholder", "theme", "hide-branding"];
2142
+ return [
2143
+ "api-url",
2144
+ "placeholder",
2145
+ "theme",
2146
+ "hide-branding",
2147
+ "translations",
2148
+ "chat-query-rewrite"
2149
+ ];
1643
2150
  }
1644
2151
  connectedCallback() {
1645
- 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));
1646
2153
  }
1647
2154
  disconnectedCallback() {
1648
2155
  this.saveCurrentSession(), this.cleanup();
1649
2156
  }
1650
2157
  attributeChangedCallback(e, t, s) {
1651
- t !== s && (e === "api-url" ? (this.initializeClient(), this.setupView()) : e === "theme" && this.updateTheme(s));
2158
+ t !== s && (e === "api-url" ? (this.initializeClient(), this.setupView()) : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerenderAfterTranslationsChange()));
2159
+ }
2160
+ /**
2161
+ * Get the current translations object.
2162
+ */
2163
+ get translations() {
2164
+ return this.translationsOverride;
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
+ }
2176
+ /**
2177
+ * Override any user-facing string. Omitted keys fall back to English defaults.
2178
+ */
2179
+ set translations(e) {
2180
+ this.translationsOverride = e ?? null, this.resolvedTranslations = g(this.translationsOverride), this.refreshDefaultSessionTitles(), this.isConnected && this.rerenderAfterTranslationsChange();
2181
+ }
2182
+ syncTranslationsFromAttribute() {
2183
+ if (this.translationsOverride) {
2184
+ this.resolvedTranslations = g(this.translationsOverride), this.refreshDefaultSessionTitles();
2185
+ return;
2186
+ }
2187
+ const e = k(this.getAttribute("translations"), "ChatPageSnippet");
2188
+ this.resolvedTranslations = g(e), this.refreshDefaultSessionTitles();
2189
+ }
2190
+ /**
2191
+ * Replace the stored title of any still-default-titled session with the
2192
+ * current `newChatButton` translation, so the sidebar reflects the active
2193
+ * language after a translations change.
2194
+ */
2195
+ refreshDefaultSessionTitles() {
2196
+ if (this.sessions.length === 0) return;
2197
+ const e = this.resolvedTranslations.newChatButton;
2198
+ let t = !1;
2199
+ for (const s of this.sessions)
2200
+ s.titleIsDefault && s.title !== e && (s.title = e, t = !0);
2201
+ t && this.saveSessions();
2202
+ }
2203
+ rerenderAfterTranslationsChange() {
2204
+ this.removeEventListeners();
2205
+ const e = this.chatView ? this.shadow.querySelector(".container") : null;
2206
+ e?.parentNode && e.parentNode.removeChild(e), this.render(), this.attachEventListeners(), this.renderChatList();
2207
+ const t = this.shadow.querySelector(".chat-page-content");
2208
+ if (this.chatView && e && t) {
2209
+ const s = t.querySelector(".container");
2210
+ s ? t.replaceChild(e, s) : t.appendChild(e), this.chatView.setProps(this.getProps()), this.handleMessageEvent = () => {
2211
+ this.saveCurrentSession(), this.updateSessionTitle(), this.renderChatList();
2212
+ }, e.addEventListener("message", this.handleMessageEvent);
2213
+ }
1652
2214
  }
1653
2215
  getProps() {
2216
+ const e = this.resolvedTranslations;
1654
2217
  return {
1655
- apiUrl: d(this.getAttribute("api-url"), ""),
1656
- placeholder: d(this.getAttribute("placeholder"), "Type a message..."),
1657
- theme: d(this.getAttribute("theme"), "auto"),
1658
- hideBranding: m(this.getAttribute("hide-branding"), !1)
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()
1659
2224
  };
1660
2225
  }
2226
+ resolveChatQueryRewrite() {
2227
+ return this.chatQueryRewriteOverride !== null ? this.chatQueryRewriteOverride : F(
2228
+ this.getAttribute("chat-query-rewrite"),
2229
+ "ChatPageSnippet"
2230
+ );
2231
+ }
1661
2232
  initializeClient() {
1662
2233
  const e = this.getProps();
1663
2234
  if (!e.apiUrl) {
@@ -1665,15 +2236,15 @@ class X extends HTMLElement {
1665
2236
  return;
1666
2237
  }
1667
2238
  try {
1668
- this.client = x(e.apiUrl);
2239
+ this.client = S(e.apiUrl);
1669
2240
  } catch (t) {
1670
2241
  console.error("ChatPageSnippet:", t);
1671
2242
  }
1672
2243
  }
1673
2244
  render() {
1674
2245
  const e = document.createElement("style");
1675
- e.textContent = `${C}
1676
- ${H}
2246
+ e.textContent = `${M}
2247
+ ${G}
1677
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();
1678
2249
  }
1679
2250
  getPageStyles() {
@@ -1953,24 +2524,25 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
1953
2524
  `;
1954
2525
  }
1955
2526
  getBaseHTML() {
2527
+ const e = this.getProps(), t = this.resolvedTranslations, s = e.hideBranding ? "" : `<div class="powered-by">${E}</div>`;
1956
2528
  return `
1957
2529
  <div class="chat-sidebar">
1958
2530
  <div class="sidebar-header">
1959
- <span class="sidebar-title">History</span>
2531
+ <span class="sidebar-title">${c(t.historyTitle)}</span>
1960
2532
  </div>
1961
2533
  <button class="new-chat-button">
1962
2534
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1963
2535
  <path d="M12 5v14M5 12h14"></path>
1964
2536
  </svg>
1965
- New Chat
2537
+ ${c(t.newChatButton)}
1966
2538
  </button>
1967
2539
  <div class="chat-list"></div>
1968
- ${this.getProps().hideBranding ? "" : `<div class="powered-by">${k}</div>`}
2540
+ ${s}
1969
2541
  </div>
1970
2542
  <div class="chat-main">
1971
2543
  <div class="chat-page-header">
1972
2544
  <div class="chat-page-header-left">
1973
- <button class="toggle-sidebar-button" title="Toggle sidebar">
2545
+ <button class="toggle-sidebar-button" title="${c(t.toggleSidebarTitle)}">
1974
2546
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1975
2547
  <path d="M3 12h18M3 6h18M3 18h18"></path>
1976
2548
  </svg>
@@ -1979,7 +2551,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
1979
2551
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1980
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>
1981
2553
  </svg>
1982
- <span>Chat</span>
2554
+ <span>${c(t.chatTitle)}</span>
1983
2555
  </div>
1984
2556
  </div>
1985
2557
  <div class="chat-page-header-actions">
@@ -1987,7 +2559,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
1987
2559
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1988
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>
1989
2561
  </svg>
1990
- Clear Chat
2562
+ ${c(t.clearChatButton)}
1991
2563
  </button>
1992
2564
  </div>
1993
2565
  </div>
@@ -1999,25 +2571,28 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
1999
2571
  }
2000
2572
  attachEventListeners() {
2001
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");
2002
- 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);
2003
2575
  }
2004
2576
  removeEventListeners() {
2005
- 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");
2006
- 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;
2007
2579
  }
2008
2580
  setupView() {
2009
2581
  const e = this.shadow.querySelector(".container");
2010
2582
  if (!this.client) {
2011
- e && (e.innerHTML = `
2583
+ if (e) {
2584
+ const s = this.resolvedTranslations;
2585
+ e.innerHTML = `
2012
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);">
2013
- <strong>Error:</strong> The <code>api-url</code> attribute is required. Please provide a valid API URL.
2587
+ <strong>${c(s.errorPrefix)}</strong> ${c(s.missingApiUrlError)}
2014
2588
  </div>
2015
- `);
2589
+ `;
2590
+ }
2016
2591
  return;
2017
2592
  }
2018
2593
  if (!e) return;
2019
2594
  const t = this.getProps();
2020
- if (this.chatView = new N(e, this.client, t), this.sessions.length === 0)
2595
+ if (this.chatView = new Y(e, this.client, t), this.sessions.length === 0)
2021
2596
  this.createNewChat();
2022
2597
  else {
2023
2598
  const s = this.sessions[0];
@@ -2032,7 +2607,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2032
2607
  }
2033
2608
  loadSessions() {
2034
2609
  try {
2035
- const e = localStorage.getItem(z);
2610
+ const e = localStorage.getItem(B);
2036
2611
  e && (this.sessions = JSON.parse(e), this.sessions.sort((t, s) => s.updatedAt - t.updatedAt));
2037
2612
  } catch (e) {
2038
2613
  console.error("Failed to load chat sessions:", e);
@@ -2040,7 +2615,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2040
2615
  }
2041
2616
  saveSessions() {
2042
2617
  try {
2043
- localStorage.setItem(z, JSON.stringify(this.sessions));
2618
+ localStorage.setItem(B, JSON.stringify(this.sessions));
2044
2619
  } catch (e) {
2045
2620
  console.error("Failed to save chat sessions:", e);
2046
2621
  }
@@ -2052,20 +2627,20 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2052
2627
  }
2053
2628
  updateSessionTitle() {
2054
2629
  if (!this.currentSessionId) return;
2055
- const e = this.sessions.find((t) => t.id === this.currentSessionId);
2056
- if (e && e.messages.length > 0 && e.title === "New Chat") {
2057
- const t = e.messages.find((s) => s.role === "user");
2058
- t && (e.title = t.content.slice(0, 50) + (t.content.length > 50 ? "..." : ""), this.saveSessions());
2059
- }
2630
+ const e = this.sessions.find((r) => r.id === this.currentSessionId);
2631
+ if (!e || e.messages.length === 0 || !(e.titleIsDefault ?? e.title === this.resolvedTranslations.newChatButton)) return;
2632
+ const s = e.messages.find((r) => r.role === "user");
2633
+ s && (e.title = s.content.slice(0, 50) + (s.content.length > 50 ? "..." : ""), e.titleIsDefault = !1, this.saveSessions());
2060
2634
  }
2061
2635
  createNewChat() {
2062
2636
  this.saveCurrentSession();
2063
2637
  const e = {
2064
2638
  id: this.generateSessionId(),
2065
- title: "New Chat",
2639
+ title: this.resolvedTranslations.newChatButton,
2066
2640
  messages: [],
2067
2641
  createdAt: Date.now(),
2068
- updatedAt: Date.now()
2642
+ updatedAt: Date.now(),
2643
+ titleIsDefault: !0
2069
2644
  };
2070
2645
  this.sessions.unshift(e), this.currentSessionId = e.id, this.saveSessions(), this.chatView?.clearMessages(), this.renderChatList();
2071
2646
  }
@@ -2082,7 +2657,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2082
2657
  clearCurrentChat() {
2083
2658
  if (!this.currentSessionId) return;
2084
2659
  const e = this.sessions.find((t) => t.id === this.currentSessionId);
2085
- e && (e.messages = [], e.title = "New Chat", e.updatedAt = Date.now(), this.saveSessions()), this.chatView?.clearMessages(), this.renderChatList();
2660
+ e && (e.messages = [], e.title = this.resolvedTranslations.newChatButton, e.titleIsDefault = !0, e.updatedAt = Date.now(), this.saveSessions()), this.chatView?.clearMessages(), this.renderChatList();
2086
2661
  }
2087
2662
  toggleSidebar() {
2088
2663
  this.sidebarCollapsed = !this.sidebarCollapsed, this.shadow.querySelector(".chat-sidebar")?.classList.toggle("collapsed", this.sidebarCollapsed);
@@ -2091,35 +2666,35 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2091
2666
  const t = e.target, s = t.closest(".chat-list-item-delete");
2092
2667
  if (s) {
2093
2668
  e.stopPropagation();
2094
- const o = s.getAttribute("data-session-id");
2095
- o && this.deleteSession(o);
2669
+ const n = s.getAttribute("data-session-id");
2670
+ n && this.deleteSession(n);
2096
2671
  return;
2097
2672
  }
2098
2673
  const r = t.closest(".chat-list-item");
2099
2674
  if (r) {
2100
- const o = r.getAttribute("data-session-id");
2101
- o && this.switchToSession(o);
2675
+ const n = r.getAttribute("data-session-id");
2676
+ n && this.switchToSession(n);
2102
2677
  }
2103
2678
  }
2104
2679
  renderChatList() {
2105
2680
  const e = this.shadow.querySelector(".chat-list");
2106
- if (e) {
2107
- if (this.sessions.length === 0) {
2108
- e.innerHTML = '<div class="chat-list-empty">No chats yet</div>';
2109
- return;
2110
- }
2111
- e.innerHTML = this.sessions.map((t) => this.renderChatListItem(t)).join("");
2681
+ if (!e) return;
2682
+ const t = this.resolvedTranslations;
2683
+ if (this.sessions.length === 0) {
2684
+ e.innerHTML = `<div class="chat-list-empty">${this.escapeHTML(t.noChatsYet)}</div>`;
2685
+ return;
2112
2686
  }
2687
+ e.innerHTML = this.sessions.map((s) => this.renderChatListItem(s)).join("");
2113
2688
  }
2114
2689
  renderChatListItem(e) {
2115
- const t = e.id === this.currentSessionId, s = this.formatDate(e.updatedAt);
2690
+ const t = e.id === this.currentSessionId, s = this.formatDate(e.updatedAt), r = this.resolvedTranslations.deleteChatTitle;
2116
2691
  return `
2117
2692
  <div class="chat-list-item ${t ? "active" : ""}" data-session-id="${e.id}">
2118
2693
  <div class="chat-list-item-content">
2119
2694
  <div class="chat-list-item-title">${this.escapeHTML(e.title)}</div>
2120
2695
  <div class="chat-list-item-date">${s}</div>
2121
2696
  </div>
2122
- <button class="chat-list-item-delete" data-session-id="${e.id}" title="Delete chat">
2697
+ <button class="chat-list-item-delete" data-session-id="${e.id}" title="${this.escapeHTML(r)}">
2123
2698
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2124
2699
  <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>
2125
2700
  </svg>
@@ -2128,8 +2703,8 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2128
2703
  `;
2129
2704
  }
2130
2705
  formatDate(e) {
2131
- const t = new Date(e), r = (/* @__PURE__ */ new Date()).getTime() - t.getTime(), o = Math.floor(r / (1e3 * 60 * 60 * 24));
2132
- return o === 0 ? t.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) : o === 1 ? "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" });
2133
2708
  }
2134
2709
  escapeHTML(e) {
2135
2710
  const t = document.createElement("div");
@@ -2158,8 +2733,8 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2158
2733
  return this.sessions.find((e) => e.id === this.currentSessionId) || null;
2159
2734
  }
2160
2735
  }
2161
- customElements.get(I) || customElements.define(I, X);
2162
- const Z = `
2736
+ customElements.get(q) || customElements.define(q, ve);
2737
+ const fe = `
2163
2738
  /* Search view states */
2164
2739
  .search-view {
2165
2740
  transition: var(--search-snippet-transition-slow);
@@ -2529,12 +3104,13 @@ a.search-result-item:focus-visible {
2529
3104
  border-radius: 2px;
2530
3105
  font-weight: var(--search-snippet-font-weight-medium);
2531
3106
  }
2532
- `, A = "search-bar-snippet", Q = 10, ee = 50;
2533
- class te extends HTMLElement {
3107
+ `, O = "search-bar-snippet", H = 10, N = 50;
3108
+ class be extends HTMLElement {
2534
3109
  constructor() {
2535
3110
  super();
2536
3111
  a(this, "shadow");
2537
3112
  a(this, "client", null);
3113
+ a(this, "stats", null);
2538
3114
  a(this, "container", null);
2539
3115
  a(this, "inputElement", null);
2540
3116
  a(this, "resultsContainer", null);
@@ -2543,11 +3119,19 @@ class te extends HTMLElement {
2543
3119
  a(this, "currentSearchController", null);
2544
3120
  a(this, "loadingMessageInterval", null);
2545
3121
  a(this, "loadingMessageIndex", 0);
3122
+ a(this, "translationsOverride", 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);
2546
3128
  // Event handler references for cleanup
2547
3129
  a(this, "handleInputChange", null);
2548
3130
  a(this, "handleInputKeydownEnter", null);
2549
3131
  a(this, "handleInputKeydownEscape", null);
2550
3132
  a(this, "handleSearchButtonClick", null);
3133
+ a(this, "handleResultClick", null);
3134
+ a(this, "handleSeeMoreClick", null);
2551
3135
  this.shadow = this.attachShadow({ mode: "open" });
2552
3136
  }
2553
3137
  static get observedAttributes() {
@@ -2555,6 +3139,7 @@ class te extends HTMLElement {
2555
3139
  "api-url",
2556
3140
  "placeholder",
2557
3141
  "max-results",
3142
+ "max-render-results",
2558
3143
  "debounce-ms",
2559
3144
  "theme",
2560
3145
  "hide-branding",
@@ -2562,30 +3147,73 @@ class te extends HTMLElement {
2562
3147
  "show-date",
2563
3148
  "hide-thumbnails",
2564
3149
  "see-more",
2565
- "request-options"
3150
+ "disable-analytics",
3151
+ "request-options",
3152
+ "translations"
2566
3153
  ];
2567
3154
  }
2568
3155
  connectedCallback() {
2569
- this.initializeClient(), this.render(), this.dispatchEvent(v("ready", void 0));
3156
+ this.syncTranslationsFromAttribute(), this.initializeClient(), this.render(), this.dispatchEvent(b("ready", void 0));
2570
3157
  }
2571
3158
  disconnectedCallback() {
2572
3159
  this.cleanup();
2573
3160
  }
2574
3161
  attributeChangedCallback(e, t, s) {
2575
- t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" && this.updateTheme(s));
3162
+ t !== s && (e === "api-url" || e === "disable-analytics" ? this.initializeClient() : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerender()));
3163
+ }
3164
+ /**
3165
+ * Get the current translations object. Mirrors the property getter.
3166
+ */
3167
+ get translations() {
3168
+ return this.translationsOverride;
3169
+ }
3170
+ /**
3171
+ * Override any user-facing string. Omitted keys fall back to English defaults.
3172
+ */
3173
+ set translations(e) {
3174
+ this.translationsOverride = e ?? null, this.resolvedTranslations = g(this.translationsOverride), this.isConnected && this.rerender();
3175
+ }
3176
+ /**
3177
+ * Re-render preserving the current query and re-running the search so
3178
+ * results remain visible after a translations change at runtime.
3179
+ */
3180
+ rerender() {
3181
+ const e = this.inputElement?.value ?? "";
3182
+ this.render(), e && this.inputElement && (this.inputElement.value = e, e.trim().length > 0 && this.performSearch(e.trim()));
3183
+ }
3184
+ syncTranslationsFromAttribute() {
3185
+ if (this.translationsOverride) {
3186
+ this.resolvedTranslations = g(this.translationsOverride);
3187
+ return;
3188
+ }
3189
+ const e = k(
3190
+ this.getAttribute("translations"),
3191
+ "SearchBarSnippet"
3192
+ );
3193
+ this.resolvedTranslations = g(e);
2576
3194
  }
2577
3195
  getProps() {
3196
+ const e = this.resolvedTranslations;
2578
3197
  return {
2579
- apiUrl: d(this.getAttribute("api-url"), ""),
2580
- placeholder: d(this.getAttribute("placeholder"), "Search..."),
2581
- maxResults: y(this.getAttribute("max-results"), 10),
2582
- debounceMs: y(this.getAttribute("debounce-ms"), 300),
2583
- theme: d(this.getAttribute("theme"), "auto"),
2584
- hideBranding: m(this.getAttribute("hide-branding"), !1),
2585
- showUrl: m(this.getAttribute("show-url"), !1),
2586
- showDate: m(this.getAttribute("show-date"), !1),
2587
- hideThumbnails: m(this.getAttribute("hide-thumbnails"), !1),
2588
- seeMore: d(this.getAttribute("see-more"), "")
3198
+ apiUrl: m(this.getAttribute("api-url"), ""),
3199
+ placeholder: m(this.getAttribute("placeholder"), e.placeholder),
3200
+ maxResults: w(
3201
+ this.getAttribute("max-results"),
3202
+ N
3203
+ ),
3204
+ maxRenderResults: w(
3205
+ this.getAttribute("max-render-results"),
3206
+ H
3207
+ ),
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),
3216
+ translations: this.translationsOverride ?? void 0
2589
3217
  };
2590
3218
  }
2591
3219
  getRequestOptions() {
@@ -2604,24 +3232,27 @@ class te extends HTMLElement {
2604
3232
  initializeClient() {
2605
3233
  const e = this.getProps();
2606
3234
  if (!e.apiUrl) {
2607
- 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();
2608
3236
  return;
2609
3237
  }
2610
3238
  try {
2611
- this.client = x(e.apiUrl);
3239
+ this.client = S(e.apiUrl), this.destroyStatsClient(), e.disableAnalytics || (this.stats = new Q(e.apiUrl));
2612
3240
  } catch (t) {
2613
3241
  console.error("SearchBarSnippet:", t);
2614
3242
  }
2615
3243
  }
3244
+ destroyStatsClient() {
3245
+ this.stats && (this.stats.destroy(), this.stats = null);
3246
+ }
2616
3247
  render() {
2617
- const e = this.getProps(), t = (r) => this.performSearch(r);
2618
- this.debouncedSearch = $(
2619
- t,
3248
+ const e = this.getProps(), t = this.resolvedTranslations, s = (n) => this.performSearch(n);
3249
+ this.debouncedSearch = _(
3250
+ s,
2620
3251
  e.debounceMs || 400
2621
3252
  );
2622
- const s = document.createElement("style");
2623
- s.textContent = `${C}
2624
- ${Z}`, this.container = document.createElement("div"), this.container.className = "container", this.container.innerHTML = `
3253
+ const r = document.createElement("style");
3254
+ r.textContent = `${M}
3255
+ ${fe}`, this.container = document.createElement("div"), this.container.className = "container", this.container.innerHTML = `
2625
3256
  <div class="search-view">
2626
3257
  <div class="search-input-wrapper">
2627
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>
@@ -2629,12 +3260,12 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2629
3260
  type="text"
2630
3261
  name="search-input"
2631
3262
  class="search-input"
2632
- placeholder="${h(e.placeholder || "Search...")}"
2633
- aria-label="Search input"
3263
+ placeholder="${c(e.placeholder || t.placeholder)}"
3264
+ aria-label="${c(t.searchInputAriaLabel)}"
2634
3265
  autocomplete="off"
2635
3266
  />
2636
- <button class="button search-submit-button" aria-label="Search">
2637
- <span>Search</span>
3267
+ <button class="button search-submit-button" aria-label="${c(t.searchButtonLabel)}">
3268
+ <span>${c(t.searchButtonLabel)}</span>
2638
3269
  </button>
2639
3270
  </div>
2640
3271
  <div class="search-content">
@@ -2643,7 +3274,7 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2643
3274
  </div>
2644
3275
  </div>
2645
3276
  </div>
2646
- `, this.shadow.innerHTML = "", this.shadow.appendChild(s), this.shadow.appendChild(this.container), this.inputElement = this.container.querySelector(".search-input"), this.resultsContainer = this.container.querySelector(".search-results-wrapper"), this.searchButton = this.container.querySelector(".search-submit-button"), this.attachEventListeners(), this.client || this.showMissingApiUrlError();
3277
+ `, this.shadow.innerHTML = "", this.shadow.appendChild(r), this.shadow.appendChild(this.container), this.inputElement = this.container.querySelector(".search-input"), this.resultsContainer = this.container.querySelector(".search-results-wrapper"), this.searchButton = this.container.querySelector(".search-submit-button"), this.attachEventListeners(), this.client || this.showMissingApiUrlError();
2647
3278
  }
2648
3279
  attachEventListeners() {
2649
3280
  this.inputElement && (this.handleInputChange = (e) => {
@@ -2668,12 +3299,12 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2668
3299
  }
2669
3300
  this.currentSearchController && (this.currentSearchController.abort(), this.currentSearchController = null), this.currentSearchController = new AbortController(), this.showLoadingState();
2670
3301
  try {
2671
- const t = await this.client.search(e, {
3302
+ const t = this.getProps(), s = await this.client.search(e, {
2672
3303
  signal: this.currentSearchController.signal,
2673
- maxResults: ee,
3304
+ maxResults: t.maxResults || N,
2674
3305
  request: this.getRequestOptions()
2675
- }), s = this.getProps(), r = t.slice(0, s.maxResults || Q);
2676
- this.displayResults(r, e, t.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);
2677
3308
  } catch (t) {
2678
3309
  if (t.name === "AbortError")
2679
3310
  return;
@@ -2688,37 +3319,39 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2688
3319
  this.showNoResultsState(t);
2689
3320
  return;
2690
3321
  }
2691
- const r = this.getProps(), o = r.hideBranding ? "" : `<div class="powered-by-inline">${k}</div>`, c = s > e.length, l = c ? `Showing ${e.length} of ${s} results` : `Found ${s} result${s === 1 ? "" : "s"}`, p = r.seeMore && c ? `<div class="search-footer">
2692
- <a href="${h(r.seeMore + encodeURIComponent(t))}" class="search-see-more">
2693
- <span>See more results</span>
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, {
3323
+ n: s
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>
2694
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>
2695
3328
  </a>
2696
- </div>` : "", b = `
3329
+ </div>` : "", u = `
2697
3330
  <div class="search-header">
2698
3331
  <div class="search-count">
2699
- ${l}
3332
+ ${c(d)}
2700
3333
  </div>
2701
3334
  ${o}
2702
3335
  </div>
2703
3336
  <div class="search-results">
2704
- ${e.map((u) => this.renderResult(u)).join("")}
3337
+ ${e.map((f, L) => this.renderResult(f, L)).join("")}
2705
3338
  </div>
2706
3339
  ${p}
2707
3340
  `;
2708
- this.resultsContainer.innerHTML = b, this.attachResultHandlers();
3341
+ this.resultsContainer.innerHTML = u, this.attachResultHandlers();
2709
3342
  }
2710
- renderResult(e) {
2711
- const t = this.getProps(), s = t.hideThumbnails ? "" : this.renderResultImage(e.image, e.title), r = e.url ? h(e.url) : "#", o = e.url ? h(q(e.url)) : "", c = t.showDate && e.timestamp !== void 0 ? `<div class="search-result-date">${h(B(e.timestamp))}</div>` : "", l = t.showUrl && e.url || c ? `<div class="search-result-metadata">
2712
- ${t.showUrl && e.url ? `<span class="search-result-url">${o}</span>` : '<span class="search-result-url search-result-url-empty"></span>'}
2713
- ${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}
2714
3347
  </div>` : "";
2715
3348
  return `
2716
- <a href="${r}" class="search-result-item" data-result-id="${h(e.url || "")}">
2717
- ${s}
3349
+ <a href="${n}" class="search-result-item" data-index="${t}" data-result-id="${c(e.id || "")}">
3350
+ ${r}
2718
3351
  <div class="search-result-content">
2719
- <div class="search-result-title">${h(e.title || "")}</div>
2720
- <div class="search-result-snippet">${h(e.description || "")}</div>
2721
- ${l}
3352
+ <div class="search-result-title">${c(e.title || "")}</div>
3353
+ <div class="search-result-snippet">${c(e.description || "")}</div>
3354
+ ${d}
2722
3355
  </div>
2723
3356
  </a>
2724
3357
  `;
@@ -2731,8 +3364,8 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2731
3364
  <div class="search-result-image-placeholder" style="display: none;">${s}</div>
2732
3365
  <img
2733
3366
  class="search-result-image"
2734
- src="${h(e)}"
2735
- alt="${h(t)}"
3367
+ src="${c(e)}"
3368
+ alt="${c(t)}"
2736
3369
  loading="lazy"
2737
3370
  />
2738
3371
  </div>
@@ -2743,95 +3376,118 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2743
3376
  `;
2744
3377
  }
2745
3378
  attachResultHandlers() {
2746
- const e = this.container?.querySelectorAll(".search-result-item");
3379
+ this.detachResultTrackingHandlers();
3380
+ const e = this.resultsContainer;
2747
3381
  if (!e) return;
2748
- for (const s of e)
2749
- s.getAttribute("href") === "#" && s.addEventListener("click", (o) => {
2750
- o.preventDefault();
2751
- });
2752
- this.container?.querySelectorAll(".search-result-image")?.forEach((s) => {
2753
- s.addEventListener("load", () => {
2754
- s.classList.add("loaded"), s.closest(".search-result-image-container")?.querySelector(".search-result-image-loading")?.remove();
2755
- }), s.addEventListener("error", () => {
2756
- const r = s.closest(".search-result-image-container");
2757
- r?.querySelector(".search-result-image-loading")?.remove();
2758
- 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(
2759
3399
  ".search-result-image-placeholder"
2760
3400
  );
2761
- o && (o.style.display = "flex"), s.style.display = "none";
3401
+ o && (o.style.display = "flex"), r.style.display = "none";
2762
3402
  });
2763
3403
  });
2764
3404
  }
3405
+ detachResultTrackingHandlers() {
3406
+ const e = this.resultsContainer;
3407
+ e && this.handleResultClick && e.removeEventListener("click", this.handleResultClick), this.handleResultClick = null, this.handleSeeMoreClick = null;
3408
+ }
2765
3409
  showLoadingState() {
2766
- this.resultsContainer && (this.clearLoadingInterval(), this.loadingMessageIndex = Math.floor(Math.random() * g.length), this.resultsContainer.innerHTML = `
3410
+ if (!this.resultsContainer) return;
3411
+ this.clearLoadingInterval();
3412
+ const e = this.resolvedTranslations.loadingMessages;
3413
+ this.loadingMessageIndex = Math.floor(Math.random() * e.length);
3414
+ const t = this.resolvedTranslations;
3415
+ this.resultsContainer.innerHTML = `
2767
3416
  <div class="search-loading">
2768
- <div class="loading" aria-label="Loading"></div>
2769
- <div class="loading-text loading-text-animate">${g[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>
2770
3419
  </div>
2771
- `, this.startLoadingInterval());
3420
+ `, this.startLoadingInterval();
2772
3421
  }
2773
3422
  startLoadingInterval() {
2774
3423
  this.loadingMessageInterval = setInterval(() => {
2775
- this.loadingMessageIndex = (this.loadingMessageIndex + 1) % g.length;
2776
- const e = this.resultsContainer?.querySelector(".loading-text");
2777
- e && (e.classList.remove("loading-text-animate"), e.offsetWidth, e.textContent = g[this.loadingMessageIndex], e.classList.add("loading-text-animate"));
2778
- }, 2500);
3424
+ const e = this.resolvedTranslations.loadingMessages;
3425
+ this.loadingMessageIndex = (this.loadingMessageIndex + 1) % e.length;
3426
+ const t = this.resultsContainer?.querySelector(".loading-text");
3427
+ t && (t.classList.remove("loading-text-animate"), t.offsetWidth, t.textContent = e[this.loadingMessageIndex], t.classList.add("loading-text-animate"));
3428
+ }, A);
2779
3429
  }
2780
3430
  clearLoadingInterval() {
2781
3431
  this.loadingMessageInterval && (clearInterval(this.loadingMessageInterval), this.loadingMessageInterval = null);
2782
3432
  }
2783
3433
  showEmptyState() {
2784
- this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = `
3434
+ if (this.clearLoadingInterval(), !this.resultsContainer) return;
3435
+ const e = this.resolvedTranslations;
3436
+ this.resultsContainer.innerHTML = `
2785
3437
  <div class="search-empty">
2786
3438
  <svg class="search-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2787
3439
  <circle cx="11" cy="11" r="8"></circle>
2788
3440
  <path d="m21 21-4.35-4.35"></path>
2789
3441
  </svg>
2790
- <div class="search-empty-title">Start Searching</div>
3442
+ <div class="search-empty-title">${c(e.emptyStateTitle)}</div>
2791
3443
  <div class="search-empty-description">
2792
- Enter a query to search for results
3444
+ ${c(e.emptyStateDescription)}
2793
3445
  </div>
2794
3446
  </div>
2795
- `);
3447
+ `;
2796
3448
  }
2797
3449
  showNoResultsState(e) {
2798
- this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = `
3450
+ if (this.clearLoadingInterval(), !this.resultsContainer) return;
3451
+ const t = this.resolvedTranslations;
3452
+ this.resultsContainer.innerHTML = `
2799
3453
  <div class="search-empty">
2800
3454
  <svg class="search-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2801
3455
  <circle cx="11" cy="11" r="8"></circle>
2802
3456
  <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
2803
3457
  </svg>
2804
- <div class="search-empty-title">No Results Found</div>
3458
+ <div class="search-empty-title">${c(t.noResultsTitle)}</div>
2805
3459
  <div class="search-empty-description">
2806
- No results found for "${h(e)}"
3460
+ ${c(y(t.noResultsDescription, { query: e }))}
2807
3461
  </div>
2808
3462
  </div>
2809
- `);
3463
+ `;
2810
3464
  }
2811
3465
  showErrorState(e) {
2812
- this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = `
3466
+ if (this.clearLoadingInterval(), !this.resultsContainer) return;
3467
+ const t = this.resolvedTranslations;
3468
+ this.resultsContainer.innerHTML = `
2813
3469
  <div class="error">
2814
- <strong>Error:</strong> ${h(e)}
3470
+ <strong>${c(t.errorPrefix)}</strong> ${c(e)}
2815
3471
  </div>
2816
- `);
3472
+ `;
2817
3473
  }
2818
3474
  showMissingApiUrlError() {
2819
- this.resultsContainer && this.showErrorState("The api-url attribute is required. Please provide a valid API URL.");
3475
+ this.resultsContainer && this.showErrorState(this.resolvedTranslations.missingApiUrlError);
2820
3476
  }
2821
3477
  updateTheme(e) {
2822
3478
  const t = e === "light" || e === "dark" || e === "auto" ? e : "auto";
2823
3479
  t === "auto" ? this.removeAttribute("theme") : this.setAttribute("theme", t);
2824
3480
  }
2825
3481
  cleanup() {
2826
- 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;
2827
3483
  }
2828
3484
  // Public API
2829
3485
  async search(e) {
2830
3486
  await this.performSearch(e);
2831
3487
  }
2832
3488
  }
2833
- customElements.get(A) || customElements.define(A, te);
2834
- const se = `
3489
+ customElements.get(O) || customElements.define(O, be);
3490
+ const ye = `
2835
3491
  /* Modal backdrop */
2836
3492
  .modal-backdrop {
2837
3493
  position: fixed;
@@ -3268,12 +3924,13 @@ a.modal-result-item:focus-visible {
3268
3924
  .modal-container.open {
3269
3925
  animation: modal-slide-in var(--search-snippet-transition) ease-out;
3270
3926
  }
3271
- `, T = "search-modal-snippet", ie = 10, re = 50;
3272
- class ae extends HTMLElement {
3927
+ `, P = "search-modal-snippet", D = 10, U = 50;
3928
+ class we extends HTMLElement {
3273
3929
  constructor() {
3274
3930
  super();
3275
3931
  a(this, "shadow");
3276
3932
  a(this, "client", null);
3933
+ a(this, "stats", null);
3277
3934
  a(this, "backdrop", null);
3278
3935
  a(this, "modal", null);
3279
3936
  a(this, "inputElement", null);
@@ -3286,11 +3943,18 @@ class ae extends HTMLElement {
3286
3943
  a(this, "currentSearchController", null);
3287
3944
  a(this, "loadingMessageInterval", null);
3288
3945
  a(this, "loadingMessageIndex", 0);
3946
+ a(this, "translationsOverride", 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);
3289
3952
  // Event handler references for cleanup
3290
3953
  a(this, "handleGlobalKeydown", null);
3291
3954
  a(this, "handleInputChange", null);
3292
3955
  a(this, "handleInputKeydown", null);
3293
3956
  a(this, "handleBackdropClick", null);
3957
+ a(this, "handleResultsContainerClick", null);
3294
3958
  // Scroll lock state
3295
3959
  a(this, "savedBodyStyles", null);
3296
3960
  a(this, "savedHtmlOverflow", null);
@@ -3301,6 +3965,7 @@ class ae extends HTMLElement {
3301
3965
  "api-url",
3302
3966
  "placeholder",
3303
3967
  "max-results",
3968
+ "max-render-results",
3304
3969
  "theme",
3305
3970
  "shortcut",
3306
3971
  "use-meta-key",
@@ -3310,32 +3975,81 @@ class ae extends HTMLElement {
3310
3975
  "show-date",
3311
3976
  "hide-thumbnails",
3312
3977
  "see-more",
3313
- "request-options"
3978
+ "disable-analytics",
3979
+ "request-options",
3980
+ "translations"
3314
3981
  ];
3315
3982
  }
3316
3983
  connectedCallback() {
3317
- 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));
3318
3985
  }
3319
3986
  disconnectedCallback() {
3320
3987
  this.cleanup();
3321
3988
  }
3322
3989
  attributeChangedCallback(e, t, s) {
3323
- t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" && this.updateTheme(s));
3990
+ t !== s && (e === "api-url" || e === "disable-analytics" ? this.initializeClient() : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerender()));
3991
+ }
3992
+ /**
3993
+ * Get the current translations object.
3994
+ */
3995
+ get translations() {
3996
+ return this.translationsOverride;
3997
+ }
3998
+ /**
3999
+ * Override any user-facing string. Omitted keys fall back to English defaults.
4000
+ */
4001
+ set translations(e) {
4002
+ this.translationsOverride = e ?? null, this.resolvedTranslations = g(this.translationsOverride), this.isConnected && this.rerender();
4003
+ }
4004
+ /**
4005
+ * Re-render while preserving open state and the current query. Results are
4006
+ * re-fetched so the list reflects the updated translation strings around
4007
+ * them (counts, footer hints, etc.). Selection resets to none — the same
4008
+ * behavior as the immediate post-search state.
4009
+ */
4010
+ rerender() {
4011
+ const e = this.isOpen, t = this.inputElement?.value ?? "";
4012
+ this.isOpen = !1, this.render(), e && (this.isOpen = !0, this.backdrop?.classList.add("open"), this.modal?.classList.add("open"), requestAnimationFrame(() => {
4013
+ requestAnimationFrame(() => {
4014
+ this.inputElement?.focus();
4015
+ });
4016
+ })), t && this.inputElement && (this.inputElement.value = t, t.trim().length > 0 && this.performSearch(t.trim()));
4017
+ }
4018
+ syncTranslationsFromAttribute() {
4019
+ if (this.translationsOverride) {
4020
+ this.resolvedTranslations = g(this.translationsOverride);
4021
+ return;
4022
+ }
4023
+ const e = k(
4024
+ this.getAttribute("translations"),
4025
+ "SearchModalSnippet"
4026
+ );
4027
+ this.resolvedTranslations = g(e);
3324
4028
  }
3325
4029
  getProps() {
4030
+ const e = this.resolvedTranslations;
3326
4031
  return {
3327
- apiUrl: d(this.getAttribute("api-url"), ""),
3328
- placeholder: d(this.getAttribute("placeholder"), "Search..."),
3329
- maxResults: y(this.getAttribute("max-results"), 10),
3330
- debounceMs: y(this.getAttribute("debounce-ms"), 300),
3331
- theme: d(this.getAttribute("theme"), "auto"),
3332
- shortcut: d(this.getAttribute("shortcut"), "k"),
4032
+ apiUrl: m(this.getAttribute("api-url"), ""),
4033
+ placeholder: m(this.getAttribute("placeholder"), e.placeholder),
4034
+ maxResults: w(
4035
+ this.getAttribute("max-results"),
4036
+ U
4037
+ ),
4038
+ maxRenderResults: w(
4039
+ this.getAttribute("max-render-results"),
4040
+ D
4041
+ ),
4042
+ debounceMs: w(this.getAttribute("debounce-ms"), 300),
4043
+ theme: m(this.getAttribute("theme"), "auto"),
4044
+ shortcut: m(this.getAttribute("shortcut"), "k"),
3333
4045
  useMetaKey: this.getAttribute("use-meta-key") !== "false",
3334
- hideBranding: m(this.getAttribute("hide-branding"), !1),
3335
- showUrl: m(this.getAttribute("show-url"), !1),
3336
- showDate: m(this.getAttribute("show-date"), !1),
3337
- hideThumbnails: m(this.getAttribute("hide-thumbnails"), !1),
3338
- seeMore: d(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),
4052
+ translations: this.translationsOverride ?? void 0
3339
4053
  };
3340
4054
  }
3341
4055
  getRequestOptions() {
@@ -3354,25 +4068,28 @@ class ae extends HTMLElement {
3354
4068
  initializeClient() {
3355
4069
  const e = this.getProps();
3356
4070
  if (!e.apiUrl) {
3357
- 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();
3358
4072
  return;
3359
4073
  }
3360
4074
  try {
3361
- this.client = x(e.apiUrl);
4075
+ this.client = S(e.apiUrl), this.destroyStatsClient(), e.disableAnalytics || (this.stats = new Q(e.apiUrl));
3362
4076
  } catch (t) {
3363
4077
  console.error("SearchModalSnippet:", t);
3364
4078
  }
3365
4079
  }
4080
+ destroyStatsClient() {
4081
+ this.stats && (this.stats.destroy(), this.stats = null);
4082
+ }
3366
4083
  render() {
3367
- const e = this.getProps(), t = (c) => this.performSearch(c);
3368
- this.debouncedSearch = $(
3369
- t,
4084
+ const e = this.getProps(), t = this.resolvedTranslations, s = (h) => this.performSearch(h);
4085
+ this.debouncedSearch = _(
4086
+ s,
3370
4087
  e.debounceMs || 300
3371
4088
  );
3372
- const s = document.createElement("style");
3373
- s.textContent = `${C}
3374
- ${se}`;
3375
- const r = e.hideBranding ? "" : `<div class="powered-by-inline">${k}</div>`, o = document.createElement("div");
4089
+ const r = document.createElement("style");
4090
+ r.textContent = `${M}
4091
+ ${ye}`;
4092
+ const n = e.hideBranding ? "" : `<div class="powered-by-inline">${E}</div>`, o = document.createElement("div");
3376
4093
  o.innerHTML = `
3377
4094
  <div class="modal-backdrop" role="presentation"></div>
3378
4095
  <div class="modal-container" role="dialog" aria-modal="true" aria-labelledby="modal-title">
@@ -3383,8 +4100,8 @@ ${se}`;
3383
4100
  <input
3384
4101
  type="text"
3385
4102
  class="modal-search-input"
3386
- placeholder="${h(e.placeholder || "Search...")}"
3387
- aria-label="Search"
4103
+ placeholder="${c(e.placeholder || t.placeholder)}"
4104
+ aria-label="${c(t.searchButtonLabel)}"
3388
4105
  aria-autocomplete="list"
3389
4106
  aria-controls="modal-results-list"
3390
4107
  aria-expanded="false"
@@ -3393,7 +4110,7 @@ ${se}`;
3393
4110
  />
3394
4111
  </div>
3395
4112
  <div class="modal-content">
3396
- <div class="modal-results" id="modal-results-list" role="listbox" aria-label="Search results">
4113
+ <div class="modal-results" id="modal-results-list" role="listbox" aria-label="${c(t.searchResultsAriaLabel)}">
3397
4114
  ${this.renderEmptyState()}
3398
4115
  </div>
3399
4116
  </div>
@@ -3402,21 +4119,21 @@ ${se}`;
3402
4119
  <div class="modal-footer-hint">
3403
4120
  <kbd class="modal-kbd">↑</kbd>
3404
4121
  <kbd class="modal-kbd">↓</kbd>
3405
- <span>Navigate</span>
4122
+ <span>${c(t.navigateHint)}</span>
3406
4123
  </div>
3407
4124
  <div class="modal-footer-hint">
3408
4125
  <kbd class="modal-kbd">↵</kbd>
3409
- <span>Select</span>
4126
+ <span>${c(t.selectHint)}</span>
3410
4127
  </div>
3411
4128
  <div class="modal-footer-hint">
3412
4129
  <kbd class="modal-kbd">Esc</kbd>
3413
- <span>Close</span>
4130
+ <span>${c(t.closeHint)}</span>
3414
4131
  </div>
3415
4132
  </div>
3416
- ${r}
4133
+ ${n}
3417
4134
  </div>
3418
4135
  </div>
3419
- `, this.shadow.innerHTML = "", this.shadow.appendChild(s), 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();
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();
3420
4137
  }
3421
4138
  attachGlobalKeyboardShortcut() {
3422
4139
  const e = this.getProps(), t = e.shortcut?.toLowerCase() || "k";
@@ -3466,7 +4183,7 @@ ${se}`;
3466
4183
  }
3467
4184
  const e = this.results[this.activeIndex];
3468
4185
  this.dispatchEvent(
3469
- v("result-select", {
4186
+ b("result-select", {
3470
4187
  result: e,
3471
4188
  index: this.activeIndex
3472
4189
  })
@@ -3483,12 +4200,12 @@ ${se}`;
3483
4200
  }
3484
4201
  this.currentSearchController && (this.currentSearchController.abort(), this.currentSearchController = null), this.currentSearchController = new AbortController(), this.showLoadingState();
3485
4202
  try {
3486
- const t = await this.client.search(e, {
4203
+ const t = this.getProps(), s = await this.client.search(e, {
3487
4204
  signal: this.currentSearchController.signal,
3488
- maxResults: re,
4205
+ maxResults: t.maxResults || U,
3489
4206
  request: this.getRequestOptions()
3490
- }), s = this.getProps();
3491
- this.results = t.slice(0, s.maxResults || ie), this.activeIndex = this.results.length > 0 ? 0 : -1, this.displayResults(this.results, e, t.length);
4207
+ });
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);
3492
4209
  } catch (t) {
3493
4210
  if (t.name === "AbortError")
3494
4211
  return;
@@ -3503,33 +4220,36 @@ ${se}`;
3503
4220
  this.showNoResultsState(t);
3504
4221
  return;
3505
4222
  }
3506
- const r = this.getProps(), o = e.map((b, u) => this.renderResult(b, u)).join(""), c = s > e.length, l = c ? `Showing ${e.length} of ${s} results` : `${s} result${s === 1 ? "" : "s"}`, p = r.seeMore && c ? `<a href="${h(r.seeMore + encodeURIComponent(t))}" class="modal-see-more">
3507
- <span>See more results</span>
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, {
4224
+ n: s
4225
+ }), p = r.seeMore && h ? `<a href="${c(r.seeMore + encodeURIComponent(t))}" class="modal-see-more">
4226
+ <span>${c(n.seeMoreResults)}</span>
3508
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>
3509
4228
  </a>` : "";
3510
- this.resultsContainer.innerHTML = o + p, this.footerCount && (this.footerCount.textContent = l), 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();
3511
4230
  }
3512
4231
  renderResult(e, t) {
3513
- const s = this.getProps(), r = s.hideThumbnails ? "" : this.renderResultImage(e.image, e.title), o = e.url ? h(e.url) : "#", c = e.url ? h(q(e.url)) : "", l = s.showDate && e.timestamp !== void 0 ? `<div class="modal-result-date">${h(B(e.timestamp))}</div>` : "", p = s.showUrl && e.url || l ? `<div class="modal-result-metadata">
3514
- ${s.showUrl && e.url ? `<span class="modal-result-url">${c}</span>` : '<span class="modal-result-url modal-result-url-empty"></span>'}
3515
- ${l}
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>'}
4234
+ ${h}
3516
4235
  </div>` : "";
3517
4236
  return `
3518
4237
  <a
3519
- href="${o}"
4238
+ href="${n}"
3520
4239
  class="modal-result-item${t === this.activeIndex ? " active" : ""}"
3521
4240
  role="option"
3522
4241
  id="result-${t}"
3523
4242
  aria-selected="${t === this.activeIndex}"
3524
4243
  tabindex="-1"
3525
4244
  data-index="${t}"
3526
- data-url="${h(e.url || "")}"
4245
+ data-result-id="${c(e.id || "")}"
4246
+ data-url="${c(e.url || "")}"
3527
4247
  >
3528
4248
  ${r}
3529
4249
  <div class="modal-result-content">
3530
- <div class="modal-result-title">${h(e.title || "")}</div>
3531
- ${e.description ? `<div class="modal-result-description">${h(e.description)}</div>` : ""}
3532
- ${p}
4250
+ <div class="modal-result-title">${c(e.title || "")}</div>
4251
+ ${e.description ? `<div class="modal-result-description">${c(e.description)}</div>` : ""}
4252
+ ${d}
3533
4253
  </div>
3534
4254
  </a>
3535
4255
  `;
@@ -3542,8 +4262,8 @@ ${se}`;
3542
4262
  <div class="modal-result-image-placeholder" style="display: none;">${s}</div>
3543
4263
  <img
3544
4264
  class="modal-result-image"
3545
- src="${h(e)}"
3546
- alt="${h(t)}"
4265
+ src="${c(e)}"
4266
+ alt="${c(t)}"
3547
4267
  loading="lazy"
3548
4268
  />
3549
4269
  </div>
@@ -3554,35 +4274,49 @@ ${se}`;
3554
4274
  `;
3555
4275
  }
3556
4276
  attachResultHandlers() {
3557
- const e = this.resultsContainer?.querySelectorAll(".modal-result-item");
4277
+ this.detachResultsContainerClick();
4278
+ const e = this.resultsContainer;
3558
4279
  if (!e) return;
3559
- e.forEach((s, r) => {
3560
- s.getAttribute("href") === "#" && s.addEventListener("click", (c) => {
3561
- c.preventDefault();
3562
- }), s.addEventListener("mouseenter", () => {
3563
- 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();
3564
4294
  });
3565
- }), this.resultsContainer?.querySelectorAll(".modal-result-image")?.forEach((s) => {
3566
- s.addEventListener("load", () => {
3567
- s.classList.add("loaded"), s.closest(".modal-result-image-container")?.querySelector(".modal-result-image-loading")?.remove();
3568
- }), s.addEventListener("error", () => {
3569
- const r = s.closest(".modal-result-image-container");
3570
- r?.querySelector(".modal-result-image-loading")?.remove();
3571
- 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(
3572
4302
  ".modal-result-image-placeholder"
3573
4303
  );
3574
- o && (o.style.display = "flex"), s.style.display = "none";
4304
+ o && (o.style.display = "flex"), r.style.display = "none";
3575
4305
  });
3576
4306
  });
3577
4307
  }
4308
+ detachResultsContainerClick() {
4309
+ this.resultsContainer && this.handleResultsContainerClick && this.resultsContainer.removeEventListener("click", this.handleResultsContainerClick), this.handleResultsContainerClick = null;
4310
+ }
3578
4311
  renderEmptyState() {
4312
+ const e = this.resolvedTranslations;
3579
4313
  return `
3580
4314
  <div class="modal-empty">
3581
4315
  <svg class="modal-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3582
4316
  <circle cx="11" cy="11" r="8"></circle>
3583
4317
  <path d="m21 21-4.35-4.35"></path>
3584
4318
  </svg>
3585
- <div class="modal-empty-description">Start typing to search</div>
4319
+ <div class="modal-empty-description">${c(e.modalEmptyStateDescription)}</div>
3586
4320
  </div>
3587
4321
  `;
3588
4322
  }
@@ -3590,44 +4324,52 @@ ${se}`;
3590
4324
  this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = this.renderEmptyState(), this.footerCount && (this.footerCount.textContent = ""), this.inputElement && this.inputElement.setAttribute("aria-expanded", "false"));
3591
4325
  }
3592
4326
  showLoadingState() {
3593
- this.resultsContainer && (this.clearLoadingInterval(), this.loadingMessageIndex = Math.floor(Math.random() * g.length), this.resultsContainer.innerHTML = `
4327
+ if (!this.resultsContainer) return;
4328
+ this.clearLoadingInterval();
4329
+ const e = this.resolvedTranslations.loadingMessages, t = this.resolvedTranslations;
4330
+ this.loadingMessageIndex = Math.floor(Math.random() * e.length), this.resultsContainer.innerHTML = `
3594
4331
  <div class="modal-loading">
3595
- <div class="loading" aria-label="Loading"></div>
3596
- <div class="loading-text loading-text-animate">${g[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>
3597
4334
  </div>
3598
- `, this.footerCount && (this.footerCount.textContent = g[this.loadingMessageIndex]), this.startLoadingInterval());
4335
+ `, this.footerCount && (this.footerCount.textContent = e[this.loadingMessageIndex]), this.startLoadingInterval();
3599
4336
  }
3600
4337
  startLoadingInterval() {
3601
4338
  this.loadingMessageInterval = setInterval(() => {
3602
- this.loadingMessageIndex = (this.loadingMessageIndex + 1) % g.length;
3603
- const e = this.resultsContainer?.querySelector(".loading-text");
3604
- e && (e.classList.remove("loading-text-animate"), e.offsetWidth, e.textContent = g[this.loadingMessageIndex], e.classList.add("loading-text-animate")), this.footerCount && (this.footerCount.textContent = g[this.loadingMessageIndex]);
3605
- }, 2500);
4339
+ const e = this.resolvedTranslations.loadingMessages;
4340
+ this.loadingMessageIndex = (this.loadingMessageIndex + 1) % e.length;
4341
+ const t = this.resultsContainer?.querySelector(".loading-text");
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]);
4343
+ }, A);
3606
4344
  }
3607
4345
  clearLoadingInterval() {
3608
4346
  this.loadingMessageInterval && (clearInterval(this.loadingMessageInterval), this.loadingMessageInterval = null);
3609
4347
  }
3610
4348
  showNoResultsState(e) {
3611
- this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = `
4349
+ if (this.clearLoadingInterval(), !this.resultsContainer) return;
4350
+ const t = this.resolvedTranslations;
4351
+ this.resultsContainer.innerHTML = `
3612
4352
  <div class="modal-empty">
3613
4353
  <svg class="modal-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3614
4354
  <circle cx="11" cy="11" r="8"></circle>
3615
4355
  <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
3616
4356
  </svg>
3617
- <div class="modal-empty-title">No results found</div>
3618
- <div class="modal-empty-description">No results for "${h(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>
3619
4359
  </div>
3620
- `, this.footerCount && (this.footerCount.textContent = "0 results"), this.inputElement && this.inputElement.setAttribute("aria-expanded", "false"));
4360
+ `, this.footerCount && (this.footerCount.textContent = t.modalResultsCountZero), this.inputElement && this.inputElement.setAttribute("aria-expanded", "false");
3621
4361
  }
3622
4362
  showErrorState(e) {
3623
- this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = `
4363
+ if (this.clearLoadingInterval(), !this.resultsContainer) return;
4364
+ const t = this.resolvedTranslations;
4365
+ this.resultsContainer.innerHTML = `
3624
4366
  <div class="error">
3625
- <strong>Error:</strong> ${h(e)}
4367
+ <strong>${c(t.errorPrefix)}</strong> ${c(e)}
3626
4368
  </div>
3627
- `, this.footerCount && (this.footerCount.textContent = "Error"));
4369
+ `, this.footerCount && (this.footerCount.textContent = t.modalResultsCountError);
3628
4370
  }
3629
4371
  showMissingApiUrlError() {
3630
- this.resultsContainer && this.showErrorState("The api-url attribute is required. Please provide a valid API URL.");
4372
+ this.resultsContainer && this.showErrorState(this.resolvedTranslations.missingApiUrlError);
3631
4373
  }
3632
4374
  updateTheme(e) {
3633
4375
  const t = e === "light" || e === "dark" || e === "auto" ? e : "auto";
@@ -3649,7 +4391,7 @@ ${se}`;
3649
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;
3650
4392
  }
3651
4393
  cleanup() {
3652
- 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();
3653
4395
  }
3654
4396
  // Public API
3655
4397
  /**
@@ -3660,13 +4402,13 @@ ${se}`;
3660
4402
  requestAnimationFrame(() => {
3661
4403
  this.inputElement?.focus();
3662
4404
  });
3663
- }), this.lockBodyScroll(), this.dispatchEvent(v("open", void 0)));
4405
+ }), this.lockBodyScroll(), this.dispatchEvent(b("open", void 0)));
3664
4406
  }
3665
4407
  /**
3666
4408
  * Close the search modal
3667
4409
  */
3668
4410
  close() {
3669
- 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)));
3670
4412
  }
3671
4413
  /**
3672
4414
  * Toggle the search modal open/closed
@@ -3693,13 +4435,16 @@ ${se}`;
3693
4435
  return this.isOpen;
3694
4436
  }
3695
4437
  }
3696
- customElements.get(T) || customElements.define(T, ae);
4438
+ customElements.get(P) || customElements.define(P, we);
3697
4439
  export {
3698
- K as AISearchClient,
3699
- W as ChatBubbleSnippet,
3700
- X as ChatPageSnippet,
3701
- te as SearchBarSnippet,
3702
- ae as SearchModalSnippet,
3703
- te as default
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
3704
4449
  };
3705
4450
  //# sourceMappingURL=search-snippet.es.js.map