@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.
- package/dist/main.d.ts +412 -10
- package/dist/search-snippet.es.js +1203 -458
- package/dist/search-snippet.es.js.map +1 -1
- package/dist/search-snippet.umd.js +144 -127
- package/dist/search-snippet.umd.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,142 +1,279 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var a = (
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
114
|
+
l(...s);
|
|
21
115
|
}, i);
|
|
22
116
|
}
|
|
23
117
|
return t.cancel = () => clearTimeout(e), t;
|
|
24
118
|
}
|
|
25
|
-
function
|
|
119
|
+
function c(l) {
|
|
26
120
|
const i = document.createElement("div");
|
|
27
|
-
return i.textContent =
|
|
121
|
+
return i.textContent = l, i.innerHTML;
|
|
28
122
|
}
|
|
29
|
-
function
|
|
123
|
+
function V(l) {
|
|
30
124
|
try {
|
|
31
|
-
return decodeURI(
|
|
125
|
+
return decodeURI(l);
|
|
32
126
|
} catch {
|
|
33
|
-
return
|
|
127
|
+
return l;
|
|
34
128
|
}
|
|
35
129
|
}
|
|
36
|
-
function
|
|
37
|
-
return new DOMParser().parseFromString(
|
|
130
|
+
function I(l) {
|
|
131
|
+
return new DOMParser().parseFromString(l, "text/html").documentElement.textContent || "";
|
|
38
132
|
}
|
|
39
|
-
function
|
|
40
|
-
const i = new Date(
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
43
|
-
if (
|
|
44
|
-
const
|
|
45
|
-
return
|
|
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 (
|
|
48
|
-
const
|
|
49
|
-
return
|
|
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
|
|
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
|
|
59
|
-
return new Date(
|
|
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
|
|
65
|
-
return `${
|
|
158
|
+
function R(l = "id") {
|
|
159
|
+
return `${l}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
66
160
|
}
|
|
67
|
-
function
|
|
68
|
-
return
|
|
161
|
+
function m(l, i) {
|
|
162
|
+
return l !== null ? l : i;
|
|
69
163
|
}
|
|
70
|
-
function
|
|
71
|
-
return
|
|
164
|
+
function v(l, i) {
|
|
165
|
+
return l === null ? i : l === "true" || l === "";
|
|
72
166
|
}
|
|
73
|
-
function
|
|
74
|
-
if (
|
|
75
|
-
const e = Number.parseInt(
|
|
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
|
|
79
|
-
|
|
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
|
|
87
|
-
if (!
|
|
193
|
+
function S(l) {
|
|
194
|
+
if (!l)
|
|
88
195
|
throw new Error("API URL is required");
|
|
89
|
-
return new
|
|
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
|
|
92
|
-
return
|
|
228
|
+
function C(l) {
|
|
229
|
+
return l !== null && typeof l == "object" && !Array.isArray(l);
|
|
93
230
|
}
|
|
94
|
-
function
|
|
231
|
+
function K(...l) {
|
|
95
232
|
const i = {};
|
|
96
|
-
for (const e of
|
|
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
|
-
|
|
237
|
+
C(r) && C(s) ? i[t] = K(r, s) : i[t] = s;
|
|
101
238
|
}
|
|
102
239
|
return i;
|
|
103
240
|
}
|
|
104
|
-
function
|
|
105
|
-
if (!
|
|
106
|
-
return
|
|
241
|
+
function se(l, i) {
|
|
242
|
+
if (!C(i))
|
|
243
|
+
return l;
|
|
107
244
|
const e = new URLSearchParams();
|
|
108
|
-
for (const [
|
|
109
|
-
|
|
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
|
|
113
|
-
const s =
|
|
114
|
-
return `${r}${
|
|
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
|
|
117
|
-
if (!
|
|
253
|
+
function ie(l) {
|
|
254
|
+
if (!C(l))
|
|
118
255
|
return {};
|
|
119
256
|
const i = {};
|
|
120
|
-
for (const [e, t] of Object.entries(
|
|
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
|
|
125
|
-
return
|
|
261
|
+
function re(l) {
|
|
262
|
+
return C(l) ? l : void 0;
|
|
126
263
|
}
|
|
127
|
-
class
|
|
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",
|
|
135
|
-
return fetch(
|
|
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(
|
|
274
|
+
body: JSON.stringify(K(re(s?.body), i)),
|
|
138
275
|
headers: {
|
|
139
|
-
...
|
|
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
|
|
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 (!
|
|
170
|
-
throw new Error(`HTTP error! status: ${
|
|
171
|
-
if (!
|
|
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
|
|
174
|
-
if (
|
|
175
|
-
return
|
|
176
|
-
(
|
|
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:
|
|
179
|
-
title:
|
|
180
|
-
description:
|
|
181
|
-
timestamp:
|
|
182
|
-
url:
|
|
183
|
-
image:
|
|
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
|
-
...
|
|
186
|
-
instance_id:
|
|
322
|
+
...h.item.metadata,
|
|
323
|
+
instance_id: h.instance_id
|
|
187
324
|
}
|
|
188
325
|
})
|
|
189
326
|
);
|
|
190
|
-
throw
|
|
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
|
|
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 (!
|
|
211
|
-
throw new Error(`HTTP error! status: ${
|
|
212
|
-
if (!
|
|
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
|
|
215
|
-
const
|
|
351
|
+
let o = "";
|
|
352
|
+
const h = n.body.getReader(), d = new TextDecoder();
|
|
216
353
|
for (; ; ) {
|
|
217
|
-
const { done: u, value:
|
|
354
|
+
const { done: u, value: f } = await h.read();
|
|
218
355
|
if (u)
|
|
219
356
|
break;
|
|
220
|
-
const
|
|
221
|
-
|
|
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:
|
|
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 =
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
|
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>`,
|
|
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
|
-
`,
|
|
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
|
|
1045
|
-
let i =
|
|
1046
|
-
i =
|
|
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
|
|
1051
|
-
const
|
|
1052
|
-
if (
|
|
1053
|
-
const u =
|
|
1054
|
-
t.push(`<h${u}>${f
|
|
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 (
|
|
1393
|
+
if (o.match(/^---+$/)) {
|
|
1058
1394
|
t.push("<hr />");
|
|
1059
1395
|
continue;
|
|
1060
1396
|
}
|
|
1061
|
-
if (
|
|
1062
|
-
const u =
|
|
1063
|
-
t.push(`<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
|
|
1067
|
-
if (
|
|
1068
|
-
(!s || r !== "ul") && (s && t.push(`</${r}>`), t.push("<ul>"), s = !0, r = "ul"), t.push(`<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
|
|
1072
|
-
if (
|
|
1073
|
-
(!s || r !== "ol") && (s && t.push(`</${r}>`), t.push("<ol>"), s = !0, r = "ol"), t.push(`<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 = ""),
|
|
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>${
|
|
1416
|
+
t.push(`<p>${x(o)}</p>`);
|
|
1081
1417
|
}
|
|
1082
1418
|
return s && t.push(`</${r}>`), t.join(`
|
|
1083
1419
|
`);
|
|
1084
1420
|
}
|
|
1085
|
-
function
|
|
1086
|
-
let i =
|
|
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
|
|
1428
|
+
function ue(l) {
|
|
1093
1429
|
const i = {
|
|
1094
1430
|
"&": "&",
|
|
1095
1431
|
"<": "<",
|
|
@@ -1097,13 +1433,15 @@ function J(n) {
|
|
|
1097
1433
|
'"': """,
|
|
1098
1434
|
"'": "'"
|
|
1099
1435
|
};
|
|
1100
|
-
return
|
|
1436
|
+
return l.replace(/[&<>"']/g, (e) => i[e] || e);
|
|
1101
1437
|
}
|
|
1102
|
-
|
|
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
|
-
|
|
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="${
|
|
1143
|
-
aria-label="
|
|
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="
|
|
1148
|
-
<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
|
-
|
|
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(
|
|
1187
|
-
const
|
|
1188
|
-
id:
|
|
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(
|
|
1542
|
+
this.addMessage(n), this.currentStreamingMessageId = r, this.renderMessages(!0);
|
|
1194
1543
|
try {
|
|
1195
|
-
const
|
|
1196
|
-
let
|
|
1197
|
-
for await (const
|
|
1198
|
-
if (
|
|
1199
|
-
|
|
1200
|
-
else if (
|
|
1201
|
-
this.showErrorInMessage(
|
|
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
|
|
1205
|
-
|
|
1206
|
-
} catch (
|
|
1207
|
-
this.showErrorInMessage(
|
|
1208
|
-
|
|
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:
|
|
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 =
|
|
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.
|
|
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}`,
|
|
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 ${
|
|
1270
|
-
<div class="chat-message-avatar">${
|
|
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
|
-
${
|
|
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">${
|
|
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">${
|
|
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
|
-
*
|
|
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
|
-
|
|
1287
|
-
this.messagesContainer
|
|
1288
|
-
|
|
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>' :
|
|
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.
|
|
1299
|
-
|
|
1300
|
-
|
|
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
|
|
1331
|
-
class
|
|
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 [
|
|
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(
|
|
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"
|
|
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:
|
|
1362
|
-
placeholder:
|
|
1363
|
-
theme:
|
|
1364
|
-
hideBranding:
|
|
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 =
|
|
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 = `${
|
|
1382
|
-
${
|
|
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="
|
|
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
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
-
${
|
|
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
|
|
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
|
|
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(
|
|
1622
|
-
const
|
|
1623
|
-
class
|
|
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 [
|
|
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(
|
|
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"
|
|
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:
|
|
1656
|
-
placeholder:
|
|
1657
|
-
theme:
|
|
1658
|
-
hideBranding:
|
|
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 =
|
|
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 = `${
|
|
1676
|
-
${
|
|
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"
|
|
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
|
-
|
|
2537
|
+
${c(t.newChatButton)}
|
|
1966
2538
|
</button>
|
|
1967
2539
|
<div class="chat-list"></div>
|
|
1968
|
-
${
|
|
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="
|
|
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
|
|
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
|
-
|
|
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 = (
|
|
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"),
|
|
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 &&
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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((
|
|
2056
|
-
if (e
|
|
2057
|
-
|
|
2058
|
-
|
|
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:
|
|
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 =
|
|
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
|
|
2095
|
-
|
|
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
|
|
2101
|
-
|
|
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
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
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="
|
|
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(),
|
|
2132
|
-
return
|
|
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(
|
|
2162
|
-
const
|
|
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
|
-
`,
|
|
2533
|
-
class
|
|
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
|
-
"
|
|
3150
|
+
"disable-analytics",
|
|
3151
|
+
"request-options",
|
|
3152
|
+
"translations"
|
|
2566
3153
|
];
|
|
2567
3154
|
}
|
|
2568
3155
|
connectedCallback() {
|
|
2569
|
-
this.initializeClient(), this.render(), this.dispatchEvent(
|
|
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"
|
|
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:
|
|
2580
|
-
placeholder:
|
|
2581
|
-
maxResults:
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
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 =
|
|
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 = (
|
|
2618
|
-
this.debouncedSearch =
|
|
2619
|
-
|
|
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
|
|
2623
|
-
|
|
2624
|
-
${
|
|
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="${
|
|
2633
|
-
aria-label="
|
|
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="
|
|
2637
|
-
<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(
|
|
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:
|
|
3304
|
+
maxResults: t.maxResults || N,
|
|
2674
3305
|
request: this.getRequestOptions()
|
|
2675
|
-
}),
|
|
2676
|
-
this.displayResults(r, e,
|
|
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">${
|
|
2692
|
-
|
|
2693
|
-
|
|
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>` : "",
|
|
3329
|
+
</div>` : "", u = `
|
|
2697
3330
|
<div class="search-header">
|
|
2698
3331
|
<div class="search-count">
|
|
2699
|
-
${
|
|
3332
|
+
${c(d)}
|
|
2700
3333
|
</div>
|
|
2701
3334
|
${o}
|
|
2702
3335
|
</div>
|
|
2703
3336
|
<div class="search-results">
|
|
2704
|
-
${e.map((
|
|
3337
|
+
${e.map((f, L) => this.renderResult(f, L)).join("")}
|
|
2705
3338
|
</div>
|
|
2706
3339
|
${p}
|
|
2707
3340
|
`;
|
|
2708
|
-
this.resultsContainer.innerHTML =
|
|
3341
|
+
this.resultsContainer.innerHTML = u, this.attachResultHandlers();
|
|
2709
3342
|
}
|
|
2710
|
-
renderResult(e) {
|
|
2711
|
-
const
|
|
2712
|
-
${
|
|
2713
|
-
${
|
|
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="${
|
|
2717
|
-
${
|
|
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">${
|
|
2720
|
-
<div class="search-result-snippet">${
|
|
2721
|
-
${
|
|
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="${
|
|
2735
|
-
alt="${
|
|
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
|
-
|
|
3379
|
+
this.detachResultTrackingHandlers();
|
|
3380
|
+
const e = this.resultsContainer;
|
|
2747
3381
|
if (!e) return;
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
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"),
|
|
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
|
-
|
|
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="
|
|
2769
|
-
<div class="loading-text loading-text-animate">${
|
|
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
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
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
|
|
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"
|
|
3442
|
+
<div class="search-empty-title">${c(e.emptyStateTitle)}</div>
|
|
2791
3443
|
<div class="search-empty-description">
|
|
2792
|
-
|
|
3444
|
+
${c(e.emptyStateDescription)}
|
|
2793
3445
|
</div>
|
|
2794
3446
|
</div>
|
|
2795
|
-
|
|
3447
|
+
`;
|
|
2796
3448
|
}
|
|
2797
3449
|
showNoResultsState(e) {
|
|
2798
|
-
this.clearLoadingInterval(), this.resultsContainer
|
|
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"
|
|
3458
|
+
<div class="search-empty-title">${c(t.noResultsTitle)}</div>
|
|
2805
3459
|
<div class="search-empty-description">
|
|
2806
|
-
|
|
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
|
|
3466
|
+
if (this.clearLoadingInterval(), !this.resultsContainer) return;
|
|
3467
|
+
const t = this.resolvedTranslations;
|
|
3468
|
+
this.resultsContainer.innerHTML = `
|
|
2813
3469
|
<div class="error">
|
|
2814
|
-
<strong
|
|
3470
|
+
<strong>${c(t.errorPrefix)}</strong> ${c(e)}
|
|
2815
3471
|
</div>
|
|
2816
|
-
|
|
3472
|
+
`;
|
|
2817
3473
|
}
|
|
2818
3474
|
showMissingApiUrlError() {
|
|
2819
|
-
this.resultsContainer && this.showErrorState(
|
|
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(
|
|
2834
|
-
const
|
|
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
|
-
`,
|
|
3272
|
-
class
|
|
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
|
-
"
|
|
3978
|
+
"disable-analytics",
|
|
3979
|
+
"request-options",
|
|
3980
|
+
"translations"
|
|
3314
3981
|
];
|
|
3315
3982
|
}
|
|
3316
3983
|
connectedCallback() {
|
|
3317
|
-
this.initializeClient(), this.render(), this.attachGlobalKeyboardShortcut(), this.dispatchEvent(
|
|
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"
|
|
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:
|
|
3328
|
-
placeholder:
|
|
3329
|
-
maxResults:
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
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:
|
|
3335
|
-
showUrl:
|
|
3336
|
-
showDate:
|
|
3337
|
-
hideThumbnails:
|
|
3338
|
-
seeMore:
|
|
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 =
|
|
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 = (
|
|
3368
|
-
this.debouncedSearch =
|
|
3369
|
-
|
|
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
|
|
3373
|
-
|
|
3374
|
-
${
|
|
3375
|
-
const
|
|
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="${
|
|
3387
|
-
aria-label="
|
|
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="
|
|
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
|
|
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
|
|
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
|
|
4130
|
+
<span>${c(t.closeHint)}</span>
|
|
3414
4131
|
</div>
|
|
3415
4132
|
</div>
|
|
3416
|
-
${
|
|
4133
|
+
${n}
|
|
3417
4134
|
</div>
|
|
3418
4135
|
</div>
|
|
3419
|
-
`, this.shadow.innerHTML = "", this.shadow.appendChild(
|
|
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
|
-
|
|
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:
|
|
4205
|
+
maxResults: t.maxResults || U,
|
|
3489
4206
|
request: this.getRequestOptions()
|
|
3490
|
-
})
|
|
3491
|
-
this.results =
|
|
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((
|
|
3507
|
-
|
|
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 =
|
|
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),
|
|
3514
|
-
${s.showUrl && e.url ? `<span class="modal-result-url">${
|
|
3515
|
-
${
|
|
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="${
|
|
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-
|
|
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">${
|
|
3531
|
-
${e.description ? `<div class="modal-result-description">${
|
|
3532
|
-
${
|
|
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="${
|
|
3546
|
-
alt="${
|
|
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
|
-
|
|
4277
|
+
this.detachResultsContainerClick();
|
|
4278
|
+
const e = this.resultsContainer;
|
|
3558
4279
|
if (!e) return;
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
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
|
-
}),
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
}),
|
|
3569
|
-
const
|
|
3570
|
-
|
|
3571
|
-
const o =
|
|
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"),
|
|
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"
|
|
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
|
-
|
|
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="
|
|
3596
|
-
<div class="loading-text loading-text-animate">${
|
|
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 =
|
|
4335
|
+
`, this.footerCount && (this.footerCount.textContent = e[this.loadingMessageIndex]), this.startLoadingInterval();
|
|
3599
4336
|
}
|
|
3600
4337
|
startLoadingInterval() {
|
|
3601
4338
|
this.loadingMessageInterval = setInterval(() => {
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
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
|
|
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"
|
|
3618
|
-
<div class="modal-empty-description"
|
|
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 =
|
|
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
|
|
4363
|
+
if (this.clearLoadingInterval(), !this.resultsContainer) return;
|
|
4364
|
+
const t = this.resolvedTranslations;
|
|
4365
|
+
this.resultsContainer.innerHTML = `
|
|
3624
4366
|
<div class="error">
|
|
3625
|
-
<strong
|
|
4367
|
+
<strong>${c(t.errorPrefix)}</strong> ${c(e)}
|
|
3626
4368
|
</div>
|
|
3627
|
-
`, this.footerCount && (this.footerCount.textContent =
|
|
4369
|
+
`, this.footerCount && (this.footerCount.textContent = t.modalResultsCountError);
|
|
3628
4370
|
}
|
|
3629
4371
|
showMissingApiUrlError() {
|
|
3630
|
-
this.resultsContainer && this.showErrorState(
|
|
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(
|
|
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(
|
|
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(
|
|
4438
|
+
customElements.get(P) || customElements.define(P, we);
|
|
3697
4439
|
export {
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
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
|