@blinq_ai/widget 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,3207 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ BlinqWidget: () => BlinqWidget,
34
+ createBlinqWidget: () => createBlinqWidget,
35
+ init: () => init,
36
+ initFromConfig: () => initFromConfig
37
+ });
38
+ module.exports = __toCommonJS(src_exports);
39
+
40
+ // src/widget-core.ts
41
+ var React2 = __toESM(require("react"), 1);
42
+ var import_client = require("react-dom/client");
43
+
44
+ // src/page-capabilities.ts
45
+ var MAX_VISIBLE_TEXT_LENGTH = 2500;
46
+ var MAX_ACTIONABLE_HINTS = 30;
47
+ var MAX_SEMANTIC_TOOLS = 40;
48
+ function isBrowser() {
49
+ return typeof window !== "undefined" && typeof document !== "undefined";
50
+ }
51
+ function selectedText() {
52
+ if (!isBrowser()) {
53
+ return "";
54
+ }
55
+ return window.getSelection()?.toString().trim() ?? "";
56
+ }
57
+ function visibleText() {
58
+ if (!isBrowser()) {
59
+ return "";
60
+ }
61
+ return document.body.innerText.replace(/\s+/g, " ").trim().slice(0, MAX_VISIBLE_TEXT_LENGTH);
62
+ }
63
+ function normalizedText(value) {
64
+ return (value ?? "").replace(/\s+/g, " ").trim();
65
+ }
66
+ function normalizedWords(...values) {
67
+ return Array.from(
68
+ new Set(
69
+ values.flatMap((value) => normalizedText(value).toLowerCase().split(/[^a-zа-я0-9]+/i)).filter((part) => part.length >= 2)
70
+ )
71
+ );
72
+ }
73
+ function escapeSelectorValue(value) {
74
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
75
+ return CSS.escape(value);
76
+ }
77
+ return value.replace(/["\\]/g, "\\$&");
78
+ }
79
+ function nthOfType(element) {
80
+ let index = 1;
81
+ let sibling = element.previousElementSibling;
82
+ while (sibling) {
83
+ if (sibling.tagName === element.tagName) {
84
+ index += 1;
85
+ }
86
+ sibling = sibling.previousElementSibling;
87
+ }
88
+ return index;
89
+ }
90
+ function buildPathSelector(element) {
91
+ const segments = [];
92
+ let current = element;
93
+ while (current && segments.length < 4 && current !== document.body) {
94
+ const tag = current.tagName.toLowerCase();
95
+ segments.unshift(`${tag}:nth-of-type(${nthOfType(current)})`);
96
+ current = current.parentElement;
97
+ }
98
+ return segments.length > 0 ? `body > ${segments.join(" > ")}` : null;
99
+ }
100
+ function selectorForElement(element) {
101
+ if (!element || !(element instanceof Element)) {
102
+ return null;
103
+ }
104
+ if (element.id) {
105
+ return `#${escapeSelectorValue(element.id)}`;
106
+ }
107
+ const dataTestId = element.getAttribute("data-testid");
108
+ if (dataTestId) {
109
+ return `[data-testid="${escapeSelectorValue(dataTestId)}"]`;
110
+ }
111
+ const name = element.getAttribute("name");
112
+ if (name) {
113
+ return `${element.tagName.toLowerCase()}[name="${escapeSelectorValue(name)}"]`;
114
+ }
115
+ const ariaLabel = element.getAttribute("aria-label");
116
+ if (ariaLabel) {
117
+ return `${element.tagName.toLowerCase()}[aria-label="${escapeSelectorValue(ariaLabel)}"]`;
118
+ }
119
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLButtonElement) {
120
+ const placeholder = element.getAttribute("placeholder");
121
+ if (placeholder) {
122
+ return `${element.tagName.toLowerCase()}[placeholder="${escapeSelectorValue(placeholder)}"]`;
123
+ }
124
+ }
125
+ return buildPathSelector(element);
126
+ }
127
+ function contextTextForElement(element) {
128
+ const contextualContainer = element.closest("li, tr, [role='listitem'], [data-testid], article, section, form") ?? element.parentElement;
129
+ if (!contextualContainer) {
130
+ return void 0;
131
+ }
132
+ const text = normalizedText(contextualContainer.textContent);
133
+ if (!text) {
134
+ return void 0;
135
+ }
136
+ return text.slice(0, 240);
137
+ }
138
+ function hintFromElement(element) {
139
+ const selector = selectorForElement(element);
140
+ if (!selector) {
141
+ return null;
142
+ }
143
+ const formSelector = selectorForElement(element.closest("form"));
144
+ const textContent = normalizedText(element.textContent);
145
+ const contextText = contextTextForElement(element);
146
+ if (element instanceof HTMLAnchorElement && element.href) {
147
+ return {
148
+ type: "link",
149
+ label: textContent || element.getAttribute("aria-label") || element.href,
150
+ selector,
151
+ href: element.href,
152
+ form_selector: formSelector ?? void 0,
153
+ context_text: contextText
154
+ };
155
+ }
156
+ if (element instanceof HTMLTextAreaElement) {
157
+ return {
158
+ type: "textarea",
159
+ label: textContent || element.getAttribute("aria-label") || void 0,
160
+ selector,
161
+ placeholder: element.placeholder || void 0,
162
+ form_selector: formSelector ?? void 0,
163
+ context_text: contextText
164
+ };
165
+ }
166
+ if (element instanceof HTMLInputElement) {
167
+ const type = element.type === "submit" ? "submit" : "input";
168
+ return {
169
+ type,
170
+ label: textContent || element.value || element.getAttribute("aria-label") || void 0,
171
+ selector,
172
+ placeholder: element.placeholder || void 0,
173
+ form_selector: formSelector ?? void 0,
174
+ context_text: contextText,
175
+ input_type: element.type || void 0
176
+ };
177
+ }
178
+ if (element instanceof HTMLButtonElement || element.getAttribute("role") === "button") {
179
+ return {
180
+ type: element instanceof HTMLButtonElement && element.type === "submit" ? "submit" : "button",
181
+ label: textContent || element.getAttribute("aria-label") || void 0,
182
+ selector,
183
+ form_selector: formSelector ?? void 0,
184
+ context_text: contextText
185
+ };
186
+ }
187
+ return null;
188
+ }
189
+ function collectActionableElements() {
190
+ if (!isBrowser()) {
191
+ return [];
192
+ }
193
+ const elements = Array.from(
194
+ document.querySelectorAll("button, a[href], input:not([type='hidden']), textarea, [role='button']")
195
+ );
196
+ const hints = [];
197
+ for (const element of elements) {
198
+ const hint = hintFromElement(element);
199
+ if (!hint) {
200
+ continue;
201
+ }
202
+ hints.push(hint);
203
+ if (hints.length >= MAX_ACTIONABLE_HINTS) {
204
+ break;
205
+ }
206
+ }
207
+ return hints;
208
+ }
209
+ function slugPart(value) {
210
+ return normalizedText(value).toLowerCase().replace(/[^a-zа-я0-9]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 48);
211
+ }
212
+ function hashValue(value) {
213
+ let hash = 0;
214
+ for (let index = 0; index < value.length; index += 1) {
215
+ hash = hash * 31 + value.charCodeAt(index) >>> 0;
216
+ }
217
+ return hash.toString(36);
218
+ }
219
+ function makeToolName(kind, seed) {
220
+ const slug = slugPart(seed) || "target";
221
+ return `blinq.semantic.${kind}.${slug}.${hashValue(`${kind}:${seed}`)}`;
222
+ }
223
+ function looksLikeAddLabel(value) {
224
+ const text = value.toLowerCase();
225
+ return /(добав|add|create|new|созда|қос|енгіз)/.test(text);
226
+ }
227
+ function looksLikeDeleteLabel(value) {
228
+ const text = value.toLowerCase();
229
+ return /(удал|delete|remove|өшір|жой)/.test(text);
230
+ }
231
+ function looksLikeClearCompletedLabel(value) {
232
+ const text = value.toLowerCase();
233
+ return /(очист|clear|тазала).*(выполн|completed|аяқталған)|(completed).*(clear|remove)/.test(text);
234
+ }
235
+ function looksLikeGenericActionLabel(value) {
236
+ const text = value.toLowerCase();
237
+ return /(open|view|edit|archive|start|run|save|send|submit|launch|continue|next|apply|confirm|перейд|открой|посмотр|измен|архив|запуст|сохран|отправ|продолж|далее|примен|подтверд|аш|өт|сақта|жібер|жалғастыр|таңда)/.test(
238
+ text
239
+ );
240
+ }
241
+ function buttonLikeText(element) {
242
+ if (element instanceof HTMLInputElement) {
243
+ return normalizedText(element.value || element.getAttribute("aria-label"));
244
+ }
245
+ return normalizedText(element.textContent || element.getAttribute("aria-label"));
246
+ }
247
+ function deriveItemTitle(container) {
248
+ const cloned = container.cloneNode(true);
249
+ cloned.querySelectorAll("button, a, [role='button']").forEach((node) => node.remove());
250
+ const text = normalizedText(cloned.textContent);
251
+ if (text) {
252
+ return text.slice(0, 120);
253
+ }
254
+ return normalizedText(container.textContent).slice(0, 120);
255
+ }
256
+ function collectForms() {
257
+ if (!isBrowser()) {
258
+ return [];
259
+ }
260
+ const forms = Array.from(document.querySelectorAll("form")).slice(0, 8);
261
+ const collected = [];
262
+ for (const form of forms) {
263
+ const selector = selectorForElement(form);
264
+ if (!selector) {
265
+ continue;
266
+ }
267
+ const field = form.querySelector(
268
+ "input:not([type='hidden']):not([type='checkbox']):not([type='radio']):not([type='submit']), textarea"
269
+ );
270
+ const submit = form.querySelector(
271
+ "button[type='submit'], input[type='submit'], button:not([type])"
272
+ );
273
+ const fieldSelector = selectorForElement(field);
274
+ const submitSelector = selectorForElement(submit);
275
+ const fieldLabel = normalizedText(field?.getAttribute("aria-label")) || normalizedText(field?.getAttribute("placeholder")) || normalizedText(field?.getAttribute("name"));
276
+ const submitLabel = submit ? buttonLikeText(submit) : "";
277
+ const title = submitLabel || fieldLabel || normalizedText(form.getAttribute("aria-label")) || selector;
278
+ collected.push({
279
+ selector,
280
+ title,
281
+ context_text: contextTextForElement(form),
282
+ field_selector: fieldSelector ?? void 0,
283
+ field_label: fieldLabel || void 0,
284
+ submit_selector: submitSelector ?? void 0,
285
+ submit_label: submitLabel || void 0
286
+ });
287
+ }
288
+ return collected;
289
+ }
290
+ function collectListItems() {
291
+ if (!isBrowser()) {
292
+ return [];
293
+ }
294
+ const candidates = Array.from(
295
+ document.querySelectorAll("li, [role='listitem'], tr, [data-testid='todo-item']")
296
+ ).slice(0, 20);
297
+ const items = [];
298
+ for (const container of candidates) {
299
+ const selector = selectorForElement(container);
300
+ if (!selector) {
301
+ continue;
302
+ }
303
+ const contextText = normalizedText(container.textContent);
304
+ if (!contextText) {
305
+ continue;
306
+ }
307
+ const actions = [];
308
+ const toggleControl = container.querySelector("input[type='checkbox']");
309
+ const toggleSelector = selectorForElement(toggleControl);
310
+ if (toggleSelector) {
311
+ actions.push({
312
+ kind: "toggle-item",
313
+ selector: toggleSelector,
314
+ label: "\u041E\u0442\u043C\u0435\u0442\u0438\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443",
315
+ target_summary: contextText.slice(0, 160)
316
+ });
317
+ }
318
+ const deleteButton = Array.from(
319
+ container.querySelectorAll(
320
+ "button, a, input[type='button'], input[type='submit']"
321
+ )
322
+ ).find((element) => looksLikeDeleteLabel(buttonLikeText(element)));
323
+ const deleteSelector = selectorForElement(deleteButton ?? null);
324
+ if (deleteSelector) {
325
+ actions.push({
326
+ kind: "delete-item",
327
+ selector: deleteSelector,
328
+ label: buttonLikeText(deleteButton ?? container) || "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442",
329
+ target_summary: contextText.slice(0, 160)
330
+ });
331
+ }
332
+ const openLink = Array.from(container.querySelectorAll("a[href]"))[0];
333
+ const openSelector = selectorForElement(openLink);
334
+ if (openSelector && openLink?.href) {
335
+ actions.push({
336
+ kind: "open-link",
337
+ selector: openSelector,
338
+ href: openLink.href,
339
+ label: buttonLikeText(openLink) || openLink.href,
340
+ target_summary: contextText.slice(0, 160)
341
+ });
342
+ }
343
+ if (actions.length === 0) {
344
+ continue;
345
+ }
346
+ items.push({
347
+ selector,
348
+ title: deriveItemTitle(container),
349
+ context_text: contextText.slice(0, 160),
350
+ actions
351
+ });
352
+ }
353
+ return items;
354
+ }
355
+ function collectSemanticTools(actionableElements, pageGraph) {
356
+ const tools = [];
357
+ const usedSelectors = /* @__PURE__ */ new Set();
358
+ const usedHrefs = /* @__PURE__ */ new Set();
359
+ for (const form of pageGraph.forms) {
360
+ if (form.field_selector && (form.submit_selector || form.selector)) {
361
+ const submitSeed = `${form.selector}:${form.submit_selector ?? form.title}`;
362
+ if (looksLikeAddLabel(form.submit_label || form.title)) {
363
+ tools.push({
364
+ name: makeToolName("add-item", submitSeed),
365
+ kind: "add-item",
366
+ title: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442",
367
+ description: `\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043D\u043E\u0432\u044B\u0439 \u044D\u043B\u0435\u043C\u0435\u043D\u0442 \u0447\u0435\u0440\u0435\u0437 ${form.title}.`,
368
+ target_summary: form.context_text || form.title,
369
+ keywords: normalizedWords(form.title, form.field_label, form.submit_label, "add create new \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u049B\u043E\u0441 \u0435\u043D\u0433\u0456\u0437"),
370
+ input_schema: {
371
+ type: "object",
372
+ properties: {
373
+ value: { type: "string" }
374
+ },
375
+ required: ["value"]
376
+ },
377
+ metadata: {
378
+ field_selector: form.field_selector,
379
+ submit_selector: form.submit_selector,
380
+ form_selector: form.selector,
381
+ field_label: form.field_label
382
+ }
383
+ });
384
+ }
385
+ if (form.submit_selector) {
386
+ usedSelectors.add(form.submit_selector);
387
+ tools.push({
388
+ name: makeToolName("submit-form", submitSeed),
389
+ kind: "submit-form",
390
+ title: form.submit_label ? `\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C: ${form.submit_label}` : "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0444\u043E\u0440\u043C\u0443",
391
+ description: `\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0444\u043E\u0440\u043C\u0443 ${form.title}.`,
392
+ target_summary: form.context_text || form.title,
393
+ keywords: normalizedWords(form.title, form.submit_label, "submit send save \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0441\u0430\u049B\u0442\u0430 \u0436\u0456\u0431\u0435\u0440"),
394
+ metadata: {
395
+ selector: form.selector,
396
+ submit_selector: form.submit_selector,
397
+ form_selector: form.selector
398
+ }
399
+ });
400
+ }
401
+ usedSelectors.add(form.selector);
402
+ if (form.field_selector) {
403
+ usedSelectors.add(form.field_selector);
404
+ }
405
+ }
406
+ }
407
+ for (const item of pageGraph.list_items) {
408
+ for (const action of item.actions) {
409
+ const seed = `${item.selector}:${action.kind}:${action.selector ?? action.href ?? item.title}`;
410
+ if (action.kind === "delete-item" && action.selector) {
411
+ usedSelectors.add(action.selector);
412
+ tools.push({
413
+ name: makeToolName("delete-item", seed),
414
+ kind: "delete-item",
415
+ title: `\u0423\u0434\u0430\u043B\u0438\u0442\u044C: ${item.title}`,
416
+ description: `\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442 ${item.title}.`,
417
+ target_summary: item.context_text,
418
+ keywords: normalizedWords(item.title, item.context_text, action.label, "delete remove \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u04E9\u0448\u0456\u0440 \u0436\u043E\u0439"),
419
+ metadata: {
420
+ selector: action.selector,
421
+ item_title: item.title,
422
+ item_context: item.context_text
423
+ }
424
+ });
425
+ }
426
+ if (action.kind === "toggle-item" && action.selector) {
427
+ usedSelectors.add(action.selector);
428
+ tools.push({
429
+ name: makeToolName("toggle-item", seed),
430
+ kind: "toggle-item",
431
+ title: `\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0441\u0442\u0430\u0442\u0443\u0441: ${item.title}`,
432
+ description: `\u041F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F \u0434\u043B\u044F ${item.title}.`,
433
+ target_summary: item.context_text,
434
+ keywords: normalizedWords(item.title, item.context_text, "toggle complete check uncheck \u043E\u0442\u043C\u0435\u0442\u0438\u0442\u044C \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u0431\u0435\u043B\u0433\u0456\u043B\u0435 \u0430\u044F\u049B\u0442\u0430"),
435
+ metadata: {
436
+ selector: action.selector,
437
+ item_title: item.title,
438
+ item_context: item.context_text
439
+ }
440
+ });
441
+ }
442
+ if (action.kind === "open-link" && action.href) {
443
+ if (action.selector) {
444
+ usedSelectors.add(action.selector);
445
+ }
446
+ usedHrefs.add(action.href);
447
+ tools.push({
448
+ name: makeToolName("open-link", seed),
449
+ kind: "open-link",
450
+ title: `\u041E\u0442\u043A\u0440\u044B\u0442\u044C: ${item.title}`,
451
+ description: `\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 \u0434\u043B\u044F ${item.title}.`,
452
+ target_summary: item.context_text,
453
+ keywords: normalizedWords(item.title, item.context_text, action.label, action.href, "open link \u043F\u0435\u0440\u0435\u0439\u0442\u0438 \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0430\u0448 \u04E9\u0442\u0443"),
454
+ metadata: {
455
+ href: action.href,
456
+ selector: action.selector
457
+ }
458
+ });
459
+ }
460
+ }
461
+ }
462
+ for (const hint of actionableElements) {
463
+ const label = normalizedText(hint.label);
464
+ const contextText = normalizedText(hint.context_text);
465
+ const selector = hint.selector;
466
+ if (usedSelectors.has(selector)) {
467
+ continue;
468
+ }
469
+ if (hint.type === "button" && label && looksLikeClearCompletedLabel(`${label} ${hint.context_text ?? ""}`)) {
470
+ usedSelectors.add(selector);
471
+ tools.push({
472
+ name: makeToolName("clear-completed", `${selector}:${label}`),
473
+ kind: "clear-completed",
474
+ title: label,
475
+ description: `\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u044B\u0435 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u044B \u0447\u0435\u0440\u0435\u0437 ${label}.`,
476
+ target_summary: hint.context_text || label,
477
+ keywords: normalizedWords(label, hint.context_text, "clear completed \u043E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u044B\u0435 \u0442\u0430\u0437\u0430\u043B\u0430\u0443 \u0430\u044F\u049B\u0442\u0430\u043B\u0493\u0430\u043D"),
478
+ metadata: {
479
+ selector
480
+ }
481
+ });
482
+ }
483
+ if (hint.type === "link" && hint.href) {
484
+ if (usedHrefs.has(hint.href)) {
485
+ continue;
486
+ }
487
+ usedSelectors.add(selector);
488
+ usedHrefs.add(hint.href);
489
+ tools.push({
490
+ name: makeToolName("open-link", `${selector}:${hint.href}`),
491
+ kind: "open-link",
492
+ title: label || hint.href,
493
+ description: `\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 ${label || hint.href}.`,
494
+ target_summary: hint.context_text || label || hint.href,
495
+ keywords: normalizedWords(label, hint.href, hint.context_text, "open link \u043F\u0435\u0440\u0435\u0439\u0442\u0438 \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0430\u0448 \u04E9\u0442\u0443"),
496
+ metadata: {
497
+ href: hint.href,
498
+ selector
499
+ }
500
+ });
501
+ }
502
+ if (hint.type === "input" && hint.input_type === "checkbox") {
503
+ usedSelectors.add(selector);
504
+ tools.push({
505
+ name: makeToolName("toggle-item", `${selector}:${label}:${contextText}`),
506
+ kind: "toggle-item",
507
+ title: label || "\u041F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0444\u043B\u0430\u0436\u043E\u043A",
508
+ description: `\u041F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0447\u0435\u043A\u0431\u043E\u043A\u0441 \u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0430\u0442\u0435\u043B\u044C ${label || selector}.`,
509
+ target_summary: contextText || label || selector,
510
+ keywords: normalizedWords(label, contextText, "toggle complete check uncheck enable disable \u0432\u043A\u043B\u044E\u0447\u0438 \u0432\u044B\u043A\u043B\u044E\u0447\u0438 \u043E\u0442\u043C\u0435\u0442\u044C \u0441\u043D\u0438\u043C\u0438"),
511
+ metadata: {
512
+ selector,
513
+ item_title: label || contextText || selector,
514
+ item_context: contextText || label || selector
515
+ }
516
+ });
517
+ }
518
+ if (hint.type === "submit" && (hint.form_selector || selector)) {
519
+ usedSelectors.add(selector);
520
+ if (hint.form_selector) {
521
+ usedSelectors.add(hint.form_selector);
522
+ }
523
+ tools.push({
524
+ name: makeToolName("submit-form", `${hint.form_selector ?? selector}:${label || contextText}`),
525
+ kind: "submit-form",
526
+ title: label || "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0444\u043E\u0440\u043C\u0443",
527
+ description: `\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0444\u043E\u0440\u043C\u0443 \u0447\u0435\u0440\u0435\u0437 ${label || selector}.`,
528
+ target_summary: contextText || label || selector,
529
+ keywords: normalizedWords(label, contextText, "submit send save \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0441\u0430\u049B\u0442\u0430 \u0436\u0456\u0431\u0435\u0440"),
530
+ metadata: {
531
+ selector: hint.form_selector || selector,
532
+ submit_selector: selector,
533
+ form_selector: hint.form_selector || selector
534
+ }
535
+ });
536
+ }
537
+ if (hint.type === "button" && label && !looksLikeDeleteLabel(`${label} ${contextText}`) && !looksLikeClearCompletedLabel(`${label} ${contextText}`) && looksLikeGenericActionLabel(`${label} ${contextText}`)) {
538
+ usedSelectors.add(selector);
539
+ tools.push({
540
+ name: makeToolName("activate-element", `${selector}:${label}:${contextText}`),
541
+ kind: "activate-element",
542
+ title: label,
543
+ description: `\u0412\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B: ${label}.`,
544
+ target_summary: contextText || label,
545
+ keywords: normalizedWords(label, contextText, "click press activate \u043D\u0430\u0436\u043C\u0438 \u043A\u043B\u0438\u043A\u043D\u0438 \u0431\u0430\u0441 \u0442\u0430\u04A3\u0434\u0430"),
546
+ metadata: {
547
+ selector,
548
+ label
549
+ }
550
+ });
551
+ }
552
+ }
553
+ return tools.slice(0, MAX_SEMANTIC_TOOLS);
554
+ }
555
+ function collectNormalizedPageContext() {
556
+ const actionableElements = collectActionableElements();
557
+ const pageGraph = {
558
+ forms: collectForms(),
559
+ list_items: collectListItems(),
560
+ standalone_actions: actionableElements.filter(
561
+ (hint) => hint.type === "button" || hint.type === "submit" || hint.type === "input" && hint.input_type === "checkbox"
562
+ )
563
+ };
564
+ const semanticTools = collectSemanticTools(actionableElements, pageGraph);
565
+ return {
566
+ url: isBrowser() ? window.location.href : "",
567
+ title: isBrowser() ? document.title : "",
568
+ selection: selectedText(),
569
+ visible_text: visibleText(),
570
+ actionable_elements: actionableElements,
571
+ page_graph: pageGraph,
572
+ semantic_tools: semanticTools
573
+ };
574
+ }
575
+
576
+ // src/page-tools.ts
577
+ function isBrowser2() {
578
+ return typeof window !== "undefined" && typeof document !== "undefined";
579
+ }
580
+ function selectedText2() {
581
+ if (!isBrowser2()) {
582
+ return "";
583
+ }
584
+ return window.getSelection()?.toString().trim() ?? "";
585
+ }
586
+ function normalizedText2(value) {
587
+ return (value ?? "").replace(/\s+/g, " ").trim();
588
+ }
589
+ function setFormFieldValue(target, value) {
590
+ const prototype = target instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
591
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, "value");
592
+ if (descriptor?.set) {
593
+ descriptor.set.call(target, value);
594
+ return;
595
+ }
596
+ target.value = value;
597
+ }
598
+ function associatedForm(target) {
599
+ return target instanceof HTMLFormElement ? target : target.closest("form");
600
+ }
601
+ function submitAssociatedForm(target) {
602
+ const form = associatedForm(target);
603
+ if (!form) {
604
+ throw new Error("\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0444\u043E\u0440\u043C\u0430 \u0434\u043B\u044F \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u043E\u0433\u043E \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u0430");
605
+ }
606
+ const isSubmitter = target instanceof HTMLButtonElement || target instanceof HTMLInputElement && target.type === "submit";
607
+ if (typeof form.requestSubmit === "function") {
608
+ if (isSubmitter) {
609
+ form.requestSubmit(target);
610
+ } else {
611
+ form.requestSubmit();
612
+ }
613
+ return;
614
+ }
615
+ const submitEvent = new Event("submit", { bubbles: true, cancelable: true });
616
+ const shouldContinue = form.dispatchEvent(submitEvent);
617
+ if (shouldContinue) {
618
+ form.submit();
619
+ }
620
+ }
621
+ async function waitForDomSettle() {
622
+ await new Promise((resolve) => window.setTimeout(resolve, 80));
623
+ }
624
+ function bodyTextIncludes(query) {
625
+ if (!isBrowser2()) {
626
+ return false;
627
+ }
628
+ const haystack = normalizedText2(document.body.innerText).toLowerCase();
629
+ return haystack.includes(normalizedText2(query).toLowerCase());
630
+ }
631
+ async function verifyFormSubmission(target) {
632
+ const form = associatedForm(target);
633
+ if (!form) {
634
+ throw new Error("\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0444\u043E\u0440\u043C\u0430 \u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430");
635
+ }
636
+ const successSelector = normalizedText2(form.dataset.blinqSuccessSelector);
637
+ const successText = normalizedText2(form.dataset.blinqSuccessText);
638
+ if (successSelector) {
639
+ const successTarget = document.querySelector(successSelector);
640
+ if (!successTarget) {
641
+ throw new Error(`\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u044D\u043B\u0435\u043C\u0435\u043D\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F: ${successSelector}`);
642
+ }
643
+ const content = normalizedText2(successTarget.textContent || successTarget.innerText);
644
+ if (successText && !content.toLowerCase().includes(successText.toLowerCase())) {
645
+ throw new Error("\u0424\u043E\u0440\u043C\u0430 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0430, \u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u044F \u043D\u0435 \u043F\u043E\u044F\u0432\u0438\u043B\u043E\u0441\u044C");
646
+ }
647
+ return {
648
+ submitted: true,
649
+ verified: true,
650
+ success_selector: successSelector,
651
+ success_text: content
652
+ };
653
+ }
654
+ if (successText) {
655
+ if (!bodyTextIncludes(successText)) {
656
+ throw new Error("\u0424\u043E\u0440\u043C\u0430 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0430, \u043D\u043E \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u044B\u0439 \u0442\u0435\u043A\u0441\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D");
657
+ }
658
+ return {
659
+ submitted: true,
660
+ verified: true,
661
+ success_text: successText
662
+ };
663
+ }
664
+ return { submitted: true };
665
+ }
666
+ function findTarget(selector) {
667
+ const target = document.querySelector(selector);
668
+ if (!target) {
669
+ throw new Error(`\u042D\u043B\u0435\u043C\u0435\u043D\u0442 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${selector}`);
670
+ }
671
+ return target;
672
+ }
673
+ function isDuplicateNativeToolError(error) {
674
+ if (!(error instanceof Error)) {
675
+ return false;
676
+ }
677
+ const message = error.message.toLowerCase();
678
+ return message.includes("duplicate tool name") || message.includes("already registered") || message.includes("already exists");
679
+ }
680
+ function metadataString(descriptor, key) {
681
+ const value = descriptor.metadata?.[key];
682
+ return typeof value === "string" ? value : "";
683
+ }
684
+ function buildSemanticToolDefinition(descriptor) {
685
+ const summary = descriptor.target_summary || descriptor.description;
686
+ if (descriptor.kind === "add-item") {
687
+ return {
688
+ capability: {
689
+ name: descriptor.name,
690
+ description: descriptor.description,
691
+ read_only: false
692
+ },
693
+ inputSchema: descriptor.input_schema,
694
+ describeTarget: () => summary,
695
+ execute: async (input) => {
696
+ const fieldSelector = metadataString(descriptor, "field_selector");
697
+ const submitSelector = metadataString(descriptor, "submit_selector");
698
+ const formSelector = metadataString(descriptor, "form_selector");
699
+ const value = String(input.value ?? "").trim();
700
+ if (!fieldSelector || !value) {
701
+ throw new Error("\u0414\u043B\u044F \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0438\u044F \u043D\u0443\u0436\u0435\u043D \u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440 \u043F\u043E\u043B\u044F \u0438 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435");
702
+ }
703
+ const fieldTarget = findTarget(fieldSelector);
704
+ fieldTarget.focus();
705
+ setFormFieldValue(fieldTarget, value);
706
+ fieldTarget.dispatchEvent(new Event("input", { bubbles: true }));
707
+ fieldTarget.dispatchEvent(new Event("change", { bubbles: true }));
708
+ const submitTarget = submitSelector ? findTarget(submitSelector) : formSelector ? findTarget(formSelector) : fieldTarget;
709
+ submitAssociatedForm(submitTarget);
710
+ await waitForDomSettle();
711
+ if (!bodyTextIncludes(value)) {
712
+ throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C, \u0447\u0442\u043E \u044D\u043B\u0435\u043C\u0435\u043D\u0442 "${value}" \u043F\u043E\u044F\u0432\u0438\u043B\u0441\u044F \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435`);
713
+ }
714
+ return { success: true, value, verified: true };
715
+ }
716
+ };
717
+ }
718
+ if (descriptor.kind === "delete-item") {
719
+ return {
720
+ capability: {
721
+ name: descriptor.name,
722
+ description: descriptor.description,
723
+ read_only: false
724
+ },
725
+ describeTarget: () => summary,
726
+ execute: async () => {
727
+ const selector = metadataString(descriptor, "selector");
728
+ const itemTitle = metadataString(descriptor, "item_title") || summary;
729
+ findTarget(selector).click();
730
+ await waitForDomSettle();
731
+ if (itemTitle && bodyTextIncludes(itemTitle)) {
732
+ throw new Error(`\u042D\u043B\u0435\u043C\u0435\u043D\u0442 "${itemTitle}" \u0432\u0441\u0451 \u0435\u0449\u0451 \u0432\u0438\u0434\u0435\u043D \u043F\u043E\u0441\u043B\u0435 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F`);
733
+ }
734
+ return { success: true, verified: true };
735
+ }
736
+ };
737
+ }
738
+ if (descriptor.kind === "toggle-item") {
739
+ return {
740
+ capability: {
741
+ name: descriptor.name,
742
+ description: descriptor.description,
743
+ read_only: false
744
+ },
745
+ describeTarget: () => summary,
746
+ execute: async () => {
747
+ const selector = metadataString(descriptor, "selector");
748
+ const target = findTarget(selector);
749
+ if (!(target instanceof HTMLInputElement) || target.type !== "checkbox") {
750
+ throw new Error("\u0414\u043B\u044F \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u043D\u0443\u0436\u0435\u043D \u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440 \u0447\u0435\u043A\u0431\u043E\u043A\u0441\u0430");
751
+ }
752
+ const before = target.checked;
753
+ target.click();
754
+ await waitForDomSettle();
755
+ if (target.checked === before) {
756
+ throw new Error("\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0447\u0435\u043A\u0431\u043E\u043A\u0441\u0430 \u043D\u0435 \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u043E\u0441\u044C");
757
+ }
758
+ return { success: true, checked: target.checked, verified: true };
759
+ }
760
+ };
761
+ }
762
+ if (descriptor.kind === "clear-completed") {
763
+ return {
764
+ capability: {
765
+ name: descriptor.name,
766
+ description: descriptor.description,
767
+ read_only: false
768
+ },
769
+ describeTarget: () => summary,
770
+ execute: async () => {
771
+ const selector = metadataString(descriptor, "selector");
772
+ const checkedBefore = document.querySelectorAll("input[type='checkbox']:checked").length;
773
+ findTarget(selector).click();
774
+ await waitForDomSettle();
775
+ const checkedAfter = document.querySelectorAll("input[type='checkbox']:checked").length;
776
+ if (checkedAfter >= checkedBefore && checkedBefore > 0) {
777
+ throw new Error("\u0412\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u044B\u0435 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u044B \u043D\u0435 \u0431\u044B\u043B\u0438 \u043E\u0447\u0438\u0449\u0435\u043D\u044B");
778
+ }
779
+ return { success: true, checked_before: checkedBefore, checked_after: checkedAfter, verified: true };
780
+ }
781
+ };
782
+ }
783
+ if (descriptor.kind === "open-link") {
784
+ return {
785
+ capability: {
786
+ name: descriptor.name,
787
+ description: descriptor.description,
788
+ read_only: false
789
+ },
790
+ describeTarget: () => summary,
791
+ execute: async () => {
792
+ const href = metadataString(descriptor, "href");
793
+ if (!href) {
794
+ throw new Error("\u0414\u043B\u044F \u043E\u0442\u043A\u0440\u044B\u0442\u0438\u044F \u0441\u0441\u044B\u043B\u043A\u0438 \u043D\u0443\u0436\u0435\u043D \u0430\u0434\u0440\u0435\u0441 href");
795
+ }
796
+ return { success: true, href, deferred_navigation: true };
797
+ }
798
+ };
799
+ }
800
+ if (descriptor.kind === "submit-form") {
801
+ return {
802
+ capability: {
803
+ name: descriptor.name,
804
+ description: descriptor.description,
805
+ read_only: false
806
+ },
807
+ describeTarget: () => summary,
808
+ execute: async () => {
809
+ const selector = metadataString(descriptor, "form_selector") || metadataString(descriptor, "selector");
810
+ const target = findTarget(selector);
811
+ submitAssociatedForm(target);
812
+ await waitForDomSettle();
813
+ return { success: true, selector, ...await verifyFormSubmission(target) };
814
+ }
815
+ };
816
+ }
817
+ if (descriptor.kind === "activate-element") {
818
+ return {
819
+ capability: {
820
+ name: descriptor.name,
821
+ description: descriptor.description,
822
+ read_only: false
823
+ },
824
+ describeTarget: () => summary,
825
+ execute: async () => {
826
+ const selector = metadataString(descriptor, "selector");
827
+ findTarget(selector).click();
828
+ return { success: true, selector };
829
+ }
830
+ };
831
+ }
832
+ return null;
833
+ }
834
+ function detectNativeWebMcpSupport() {
835
+ if (!isBrowser2() || !window.isSecureContext) {
836
+ return false;
837
+ }
838
+ const navigatorWithModelContext = navigator;
839
+ return typeof navigatorWithModelContext.modelContext?.registerTool === "function";
840
+ }
841
+ var PageToolRegistry = class {
842
+ confirmationHandler;
843
+ tools = /* @__PURE__ */ new Map();
844
+ nativeRegistrations = [];
845
+ latestPageContext = null;
846
+ nativeToolsActive = false;
847
+ highlightLayer = null;
848
+ highlightTimer = null;
849
+ constructor(confirmationHandler) {
850
+ this.confirmationHandler = confirmationHandler;
851
+ this.refreshToolCatalog();
852
+ }
853
+ buildBaseTools() {
854
+ const tools = /* @__PURE__ */ new Map();
855
+ tools.set("blinq.read_visible_page_context", {
856
+ capability: {
857
+ name: "blinq.read_visible_page_context",
858
+ description: "\u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\u0439 URL, \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B, \u0432\u0438\u0434\u0438\u043C\u044B\u0439 \u0442\u0435\u043A\u0441\u0442 \u0438 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439.",
859
+ read_only: true
860
+ },
861
+ execute: async () => ({ page_context: this.collectPageContext() })
862
+ });
863
+ tools.set("blinq.read_selection", {
864
+ capability: {
865
+ name: "blinq.read_selection",
866
+ description: "\u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u0442\u0435\u043A\u0441\u0442, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0441\u0435\u0439\u0447\u0430\u0441 \u0432\u044B\u0434\u0435\u043B\u0435\u043D \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435.",
867
+ read_only: true
868
+ },
869
+ execute: async () => ({ selection: selectedText2() })
870
+ });
871
+ tools.set("blinq.highlight_element", {
872
+ capability: {
873
+ name: "blinq.highlight_element",
874
+ description: "\u041F\u043E\u0434\u0441\u0432\u0435\u0442\u0438\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u043F\u043E CSS-\u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440\u0443, \u0447\u0442\u043E\u0431\u044B \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044E, \u043E \u0447\u0451\u043C \u0438\u0434\u0451\u0442 \u0440\u0435\u0447\u044C.",
875
+ read_only: true
876
+ },
877
+ inputSchema: {
878
+ type: "object",
879
+ properties: {
880
+ selector: { type: "string" },
881
+ label: { type: "string" }
882
+ },
883
+ required: ["selector"]
884
+ },
885
+ describeTarget: (input) => `\u041F\u043E\u0434\u0441\u0432\u0435\u0442\u0438\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442: ${String(input.selector ?? "")}`,
886
+ execute: async (input) => {
887
+ const selector = String(input.selector ?? "");
888
+ const label = String(input.label ?? "").trim() || void 0;
889
+ const highlighted = this.highlightTargets([{ selector, label }]);
890
+ if (!highlighted) {
891
+ throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u0434\u0441\u0432\u0435\u0442\u0438\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442: ${selector}`);
892
+ }
893
+ return { success: true, selector, label: label ?? null };
894
+ }
895
+ });
896
+ tools.set("blinq.scroll_to_element", {
897
+ capability: {
898
+ name: "blinq.scroll_to_element",
899
+ description: "\u041F\u0440\u043E\u043A\u0440\u0443\u0442\u0438\u0442\u044C \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443 \u043A \u043F\u0435\u0440\u0432\u043E\u043C\u0443 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u0443 \u043F\u043E CSS-\u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440\u0443.",
900
+ read_only: false
901
+ },
902
+ inputSchema: { type: "object", properties: { selector: { type: "string" } }, required: ["selector"] },
903
+ describeTarget: (input) => `\u041F\u0440\u043E\u043A\u0440\u0443\u0442\u043A\u0430 \u043A \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u0443: ${String(input.selector ?? "")}`,
904
+ execute: async (input) => {
905
+ const selector = String(input.selector ?? "");
906
+ const target = findTarget(selector);
907
+ target.scrollIntoView({ behavior: "smooth", block: "center" });
908
+ return { success: true, selector };
909
+ }
910
+ });
911
+ tools.set("blinq.click_element", {
912
+ capability: {
913
+ name: "blinq.click_element",
914
+ description: "\u041D\u0430\u0436\u0430\u0442\u044C \u043F\u0435\u0440\u0432\u0443\u044E \u043F\u043E\u0434\u0445\u043E\u0434\u044F\u0449\u0443\u044E \u043A\u043D\u043E\u043F\u043A\u0443, \u0441\u0441\u044B\u043B\u043A\u0443 \u0438\u043B\u0438 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u044D\u043B\u0435\u043C\u0435\u043D\u0442.",
915
+ read_only: false
916
+ },
917
+ inputSchema: { type: "object", properties: { selector: { type: "string" } }, required: ["selector"] },
918
+ describeTarget: (input) => `\u041D\u0430\u0436\u0430\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442: ${String(input.selector ?? "")}`,
919
+ execute: async (input) => {
920
+ const selector = String(input.selector ?? "");
921
+ const target = findTarget(selector);
922
+ const isSubmitButton = target instanceof HTMLButtonElement && target.type === "submit";
923
+ const isSubmitInput = target instanceof HTMLInputElement && target.type === "submit";
924
+ if (isSubmitButton || isSubmitInput) {
925
+ submitAssociatedForm(target);
926
+ await waitForDomSettle();
927
+ return { success: true, selector, ...await verifyFormSubmission(target) };
928
+ }
929
+ target.click();
930
+ await waitForDomSettle();
931
+ return { success: true, selector };
932
+ }
933
+ });
934
+ tools.set("blinq.fill_field", {
935
+ capability: {
936
+ name: "blinq.fill_field",
937
+ description: "\u0417\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043F\u043E\u043B\u0435 \u0432\u0432\u043E\u0434\u0430 \u0438\u043B\u0438 textarea \u043F\u043E CSS-\u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440\u0443.",
938
+ read_only: false
939
+ },
940
+ inputSchema: {
941
+ type: "object",
942
+ properties: {
943
+ selector: { type: "string" },
944
+ value: { type: "string" }
945
+ },
946
+ required: ["selector", "value"]
947
+ },
948
+ describeTarget: (input) => `\u0417\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u044C ${String(input.selector ?? "")} \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435\u043C "${String(input.value ?? "")}"`,
949
+ execute: async (input) => {
950
+ const selector = String(input.selector ?? "");
951
+ const value = String(input.value ?? "");
952
+ const target = findTarget(selector);
953
+ target.focus();
954
+ setFormFieldValue(target, value);
955
+ target.dispatchEvent(new Event("input", { bubbles: true }));
956
+ target.dispatchEvent(new Event("change", { bubbles: true }));
957
+ await waitForDomSettle();
958
+ return { success: true, selector, value, verified: target.value === value };
959
+ }
960
+ });
961
+ tools.set("blinq.submit_form", {
962
+ capability: {
963
+ name: "blinq.submit_form",
964
+ description: "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0431\u043B\u0438\u0436\u0430\u0439\u0448\u0443\u044E \u0444\u043E\u0440\u043C\u0443 \u0434\u043B\u044F \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u043E\u0433\u043E \u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440\u0430.",
965
+ read_only: false
966
+ },
967
+ inputSchema: { type: "object", properties: { selector: { type: "string" } }, required: ["selector"] },
968
+ describeTarget: (input) => `\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0444\u043E\u0440\u043C\u0443 \u0440\u044F\u0434\u043E\u043C \u0441 ${String(input.selector ?? "")}`,
969
+ execute: async (input) => {
970
+ const selector = String(input.selector ?? "");
971
+ const target = findTarget(selector);
972
+ submitAssociatedForm(target);
973
+ await waitForDomSettle();
974
+ return { success: true, selector, ...await verifyFormSubmission(target) };
975
+ }
976
+ });
977
+ tools.set("blinq.open_link", {
978
+ capability: {
979
+ name: "blinq.open_link",
980
+ description: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C URL \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435.",
981
+ read_only: false
982
+ },
983
+ inputSchema: { type: "object", properties: { href: { type: "string" } }, required: ["href"] },
984
+ describeTarget: (input) => `\u041E\u0442\u043A\u0440\u044B\u0442\u044C ${String(input.href ?? "")}`,
985
+ execute: async (input) => {
986
+ const href = String(input.href ?? "");
987
+ if (!href) {
988
+ throw new Error("\u041D\u0443\u0436\u0435\u043D \u0430\u0434\u0440\u0435\u0441 \u0441\u0441\u044B\u043B\u043A\u0438");
989
+ }
990
+ return { success: true, href, deferred_navigation: true };
991
+ }
992
+ });
993
+ return tools;
994
+ }
995
+ refreshToolCatalog() {
996
+ const pageContext = collectNormalizedPageContext();
997
+ const tools = this.buildBaseTools();
998
+ for (const descriptor of pageContext.semantic_tools) {
999
+ const tool = buildSemanticToolDefinition(descriptor);
1000
+ if (tool) {
1001
+ tools.set(descriptor.name, tool);
1002
+ }
1003
+ }
1004
+ this.tools.clear();
1005
+ for (const [name, tool] of tools) {
1006
+ this.tools.set(name, tool);
1007
+ }
1008
+ this.latestPageContext = pageContext;
1009
+ return pageContext;
1010
+ }
1011
+ async onMutatingToolSuccess() {
1012
+ this.refreshToolCatalog();
1013
+ if (this.nativeToolsActive) {
1014
+ try {
1015
+ await this.registerNativeTools();
1016
+ } catch {
1017
+ this.nativeToolsActive = false;
1018
+ }
1019
+ }
1020
+ }
1021
+ collectPageContext() {
1022
+ return this.refreshToolCatalog();
1023
+ }
1024
+ getToolCapabilities() {
1025
+ this.refreshToolCatalog();
1026
+ return Array.from(this.tools.values()).map((tool) => tool.capability);
1027
+ }
1028
+ clearHighlights() {
1029
+ if (this.highlightTimer !== null) {
1030
+ window.clearTimeout(this.highlightTimer);
1031
+ this.highlightTimer = null;
1032
+ }
1033
+ this.highlightLayer?.remove();
1034
+ this.highlightLayer = null;
1035
+ }
1036
+ ensureHighlightLayer() {
1037
+ if (this.highlightLayer) {
1038
+ return this.highlightLayer;
1039
+ }
1040
+ const layer = document.createElement("div");
1041
+ layer.setAttribute("data-blinq-highlight-layer", "true");
1042
+ Object.assign(layer.style, {
1043
+ position: "fixed",
1044
+ inset: "0",
1045
+ pointerEvents: "none",
1046
+ zIndex: "2147483646"
1047
+ });
1048
+ document.body.appendChild(layer);
1049
+ this.highlightLayer = layer;
1050
+ return layer;
1051
+ }
1052
+ highlightTargets(targets, options) {
1053
+ if (!isBrowser2() || targets.length === 0) {
1054
+ return false;
1055
+ }
1056
+ this.clearHighlights();
1057
+ const layer = this.ensureHighlightLayer();
1058
+ let highlightedCount = 0;
1059
+ for (const target of targets) {
1060
+ if (!target.selector) {
1061
+ continue;
1062
+ }
1063
+ let element = null;
1064
+ try {
1065
+ element = document.querySelector(target.selector);
1066
+ } catch {
1067
+ element = null;
1068
+ }
1069
+ if (!element) {
1070
+ continue;
1071
+ }
1072
+ const rect = element.getBoundingClientRect();
1073
+ if (rect.width <= 0 || rect.height <= 0) {
1074
+ continue;
1075
+ }
1076
+ const frame = document.createElement("div");
1077
+ Object.assign(frame.style, {
1078
+ position: "fixed",
1079
+ left: `${Math.max(rect.left - 6, 4)}px`,
1080
+ top: `${Math.max(rect.top - 6, 4)}px`,
1081
+ width: `${Math.max(rect.width + 12, 18)}px`,
1082
+ height: `${Math.max(rect.height + 12, 18)}px`,
1083
+ border: "2px solid rgba(17, 17, 17, 0.92)",
1084
+ borderRadius: "16px",
1085
+ background: "rgba(17, 17, 17, 0.05)",
1086
+ boxShadow: "0 0 0 1px rgba(255, 255, 255, 0.85), 0 18px 38px rgba(17, 17, 17, 0.14)",
1087
+ opacity: "0",
1088
+ transform: "scale(0.985)",
1089
+ transition: "opacity 180ms ease, transform 180ms ease"
1090
+ });
1091
+ const glow = document.createElement("div");
1092
+ Object.assign(glow.style, {
1093
+ position: "absolute",
1094
+ inset: "-8px",
1095
+ borderRadius: "20px",
1096
+ border: "1px solid rgba(17, 17, 17, 0.14)",
1097
+ animation: "blinq-highlight-pulse 1.2s ease-in-out infinite"
1098
+ });
1099
+ frame.appendChild(glow);
1100
+ if (target.label) {
1101
+ const badge = document.createElement("div");
1102
+ badge.textContent = target.label;
1103
+ Object.assign(badge.style, {
1104
+ position: "absolute",
1105
+ top: "-14px",
1106
+ left: "10px",
1107
+ maxWidth: "min(220px, 70vw)",
1108
+ padding: "4px 10px",
1109
+ borderRadius: "999px",
1110
+ background: "#111111",
1111
+ color: "#ffffff",
1112
+ fontFamily: '"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
1113
+ fontSize: "11px",
1114
+ fontWeight: "600",
1115
+ lineHeight: "1.3",
1116
+ whiteSpace: "nowrap",
1117
+ overflow: "hidden",
1118
+ textOverflow: "ellipsis",
1119
+ boxShadow: "0 8px 18px rgba(17, 17, 17, 0.16)"
1120
+ });
1121
+ frame.appendChild(badge);
1122
+ }
1123
+ layer.appendChild(frame);
1124
+ window.requestAnimationFrame(() => {
1125
+ frame.style.opacity = "1";
1126
+ frame.style.transform = "scale(1)";
1127
+ });
1128
+ highlightedCount += 1;
1129
+ }
1130
+ if (!document.getElementById("blinq-highlight-style")) {
1131
+ const style = document.createElement("style");
1132
+ style.id = "blinq-highlight-style";
1133
+ style.textContent = `
1134
+ @keyframes blinq-highlight-pulse {
1135
+ 0%, 100% { opacity: 0.55; transform: scale(1); }
1136
+ 50% { opacity: 1; transform: scale(1.02); }
1137
+ }
1138
+ `;
1139
+ document.head.appendChild(style);
1140
+ }
1141
+ if (highlightedCount === 0) {
1142
+ this.clearHighlights();
1143
+ return false;
1144
+ }
1145
+ this.highlightTimer = window.setTimeout(() => {
1146
+ this.clearHighlights();
1147
+ }, options?.durationMs ?? 2200);
1148
+ return true;
1149
+ }
1150
+ canExecuteWriteTools() {
1151
+ return isBrowser2();
1152
+ }
1153
+ async executeTool(request) {
1154
+ let tool = this.tools.get(request.tool_name);
1155
+ if (!tool) {
1156
+ this.refreshToolCatalog();
1157
+ tool = this.tools.get(request.tool_name);
1158
+ }
1159
+ if (!tool) {
1160
+ return {
1161
+ status: "error",
1162
+ error: `\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442: ${request.tool_name}`
1163
+ };
1164
+ }
1165
+ const displayName = request.display_name ?? tool.capability.name;
1166
+ const targetSummary = request.target_summary ?? tool.describeTarget?.(request.arguments) ?? tool.capability.description;
1167
+ const requiresConfirmation = request.requires_confirmation ?? !tool.capability.read_only;
1168
+ if (!tool.capability.read_only && requiresConfirmation) {
1169
+ const confirmed = await this.confirmationHandler({
1170
+ displayName,
1171
+ targetSummary,
1172
+ arguments: request.arguments
1173
+ });
1174
+ if (!confirmed) {
1175
+ return {
1176
+ status: "cancelled",
1177
+ error: "\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435"
1178
+ };
1179
+ }
1180
+ }
1181
+ try {
1182
+ const result = await tool.execute(request.arguments);
1183
+ if (!tool.capability.read_only) {
1184
+ await this.onMutatingToolSuccess();
1185
+ }
1186
+ return { status: "success", result };
1187
+ } catch (error) {
1188
+ return {
1189
+ status: "error",
1190
+ error: error instanceof Error ? error.message : "\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435"
1191
+ };
1192
+ }
1193
+ }
1194
+ unregisterNativeTools() {
1195
+ while (this.nativeRegistrations.length > 0) {
1196
+ this.nativeRegistrations.pop()?.abort();
1197
+ }
1198
+ this.nativeToolsActive = false;
1199
+ }
1200
+ async registerNativeTools() {
1201
+ const navigatorWithModelContext = navigator;
1202
+ const modelContext = navigatorWithModelContext.modelContext;
1203
+ if (!window.isSecureContext || !modelContext?.registerTool) {
1204
+ return false;
1205
+ }
1206
+ this.refreshToolCatalog();
1207
+ this.unregisterNativeTools();
1208
+ for (const [toolName, tool] of this.tools) {
1209
+ const controller = new AbortController();
1210
+ try {
1211
+ modelContext.registerTool(
1212
+ {
1213
+ name: toolName,
1214
+ description: tool.capability.description,
1215
+ inputSchema: tool.inputSchema,
1216
+ annotations: {
1217
+ readOnlyHint: tool.capability.read_only
1218
+ },
1219
+ execute: async (input) => {
1220
+ const outcome = await this.executeTool({
1221
+ tool_name: toolName,
1222
+ display_name: tool.capability.name,
1223
+ arguments: input
1224
+ });
1225
+ return outcome;
1226
+ }
1227
+ },
1228
+ { signal: controller.signal }
1229
+ );
1230
+ this.nativeRegistrations.push(controller);
1231
+ } catch (error) {
1232
+ controller.abort();
1233
+ if (isDuplicateNativeToolError(error)) {
1234
+ this.nativeToolsActive = true;
1235
+ continue;
1236
+ }
1237
+ throw error;
1238
+ }
1239
+ }
1240
+ this.nativeToolsActive = true;
1241
+ return true;
1242
+ }
1243
+ destroy() {
1244
+ this.clearHighlights();
1245
+ this.unregisterNativeTools();
1246
+ }
1247
+ };
1248
+
1249
+ // src/embed-shell.tsx
1250
+ var React = __toESM(require("react"), 1);
1251
+ var import_react = require("@assistant-ui/react");
1252
+ var import_jsx_runtime = require("react/jsx-runtime");
1253
+ function cn(...values) {
1254
+ return values.filter(Boolean).join(" ");
1255
+ }
1256
+ function buttonClassName(variant = "default", size = "default") {
1257
+ const variantClass = variant === "outline" ? "blinq-button-outline" : variant === "ghost" ? "blinq-button-ghost" : variant === "destructive" ? "blinq-button-destructive" : "blinq-button-default";
1258
+ const sizeClass = size === "sm" ? "blinq-button-sm" : size === "icon" ? "blinq-button-icon" : size === "icon-sm" ? "blinq-button-icon-sm" : "blinq-button-md";
1259
+ return cn("blinq-button", variantClass, sizeClass);
1260
+ }
1261
+ function badgeClassName(variant = "default") {
1262
+ if (variant === "outline") {
1263
+ return "blinq-badge-pill blinq-badge-pill-outline";
1264
+ }
1265
+ if (variant === "soft") {
1266
+ return "blinq-badge-pill blinq-badge-pill-soft";
1267
+ }
1268
+ if (variant === "accent") {
1269
+ return "blinq-badge-pill blinq-badge-pill-accent";
1270
+ }
1271
+ return "blinq-badge-pill blinq-badge-pill-default";
1272
+ }
1273
+ function Button({
1274
+ className,
1275
+ variant,
1276
+ size,
1277
+ ...props
1278
+ }) {
1279
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: cn(buttonClassName(variant, size), className), ...props });
1280
+ }
1281
+ function Input({ className, ...props }) {
1282
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: cn("blinq-input", className), ...props });
1283
+ }
1284
+ function Card({ className, ...props }) {
1285
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("blinq-card", className), ...props });
1286
+ }
1287
+ function CardHeader({ className, ...props }) {
1288
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("blinq-card-header", className), ...props });
1289
+ }
1290
+ function CardTitle({ className, ...props }) {
1291
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("blinq-card-title", className), ...props });
1292
+ }
1293
+ function CardDescription({ className, ...props }) {
1294
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("blinq-card-description", className), ...props });
1295
+ }
1296
+ function CardContent({ className, ...props }) {
1297
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("blinq-card-content", className), ...props });
1298
+ }
1299
+ function Badge({
1300
+ className,
1301
+ variant,
1302
+ ...props
1303
+ }) {
1304
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn(badgeClassName(variant), className), ...props });
1305
+ }
1306
+ function LauncherIcon() {
1307
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1308
+ "svg",
1309
+ {
1310
+ viewBox: "0 0 24 24",
1311
+ width: "18",
1312
+ height: "18",
1313
+ fill: "none",
1314
+ "aria-hidden": "true",
1315
+ className: "blinq-launcher-agent",
1316
+ children: [
1317
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "blinq-agent-orbit", cx: "12", cy: "12", r: "9" }),
1318
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "blinq-agent-spark", cx: "18.6", cy: "8.2", r: "1.2" }),
1319
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1320
+ "path",
1321
+ {
1322
+ className: "blinq-agent-antenna",
1323
+ d: "M12 4.8V6.8",
1324
+ stroke: "currentColor",
1325
+ strokeWidth: "1.6",
1326
+ strokeLinecap: "round"
1327
+ }
1328
+ ),
1329
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "blinq-agent-antenna-dot", cx: "12", cy: "4.1", r: "1.1", fill: "currentColor" }),
1330
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1331
+ "rect",
1332
+ {
1333
+ className: "blinq-agent-head",
1334
+ x: "7",
1335
+ y: "7.4",
1336
+ width: "10",
1337
+ height: "9.2",
1338
+ rx: "3.2",
1339
+ fill: "currentColor"
1340
+ }
1341
+ ),
1342
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "blinq-agent-eye", cx: "10.2", cy: "11.3", r: "0.9", fill: "#111111" }),
1343
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "blinq-agent-eye", cx: "13.8", cy: "11.3", r: "0.9", fill: "#111111" }),
1344
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1345
+ "path",
1346
+ {
1347
+ className: "blinq-agent-mouth",
1348
+ d: "M10 14.2c.7.55 1.38.82 2 .82s1.3-.27 2-.82",
1349
+ stroke: "#111111",
1350
+ strokeWidth: "1.3",
1351
+ strokeLinecap: "round"
1352
+ }
1353
+ ),
1354
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1355
+ "path",
1356
+ {
1357
+ className: "blinq-agent-neck",
1358
+ d: "M10.2 17.1h3.6",
1359
+ stroke: "currentColor",
1360
+ strokeWidth: "1.4",
1361
+ strokeLinecap: "round"
1362
+ }
1363
+ )
1364
+ ]
1365
+ }
1366
+ );
1367
+ }
1368
+ function CollapseIcon() {
1369
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 12h12", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" }) });
1370
+ }
1371
+ function MicrophoneIcon({ listening }) {
1372
+ if (listening) {
1373
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "7", y: "7", width: "10", height: "10", rx: "2.5", fill: "currentColor" }) });
1374
+ }
1375
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", "aria-hidden": "true", children: [
1376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1377
+ "path",
1378
+ {
1379
+ d: "M12 3.75a2.75 2.75 0 0 1 2.75 2.75v5.25a2.75 2.75 0 1 1-5.5 0V6.5A2.75 2.75 0 0 1 12 3.75Z",
1380
+ fill: "currentColor"
1381
+ }
1382
+ ),
1383
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1384
+ "path",
1385
+ {
1386
+ d: "M7.5 10.5a.75.75 0 0 1 .75.75a3.75 3.75 0 1 0 7.5 0a.75.75 0 0 1 1.5 0A5.25 5.25 0 0 1 12.75 16.4V18.5H15a.75.75 0 0 1 0 1.5H9a.75.75 0 0 1 0-1.5h2.25v-2.1A5.25 5.25 0 0 1 6.75 11.25a.75.75 0 0 1 .75-.75Z",
1387
+ fill: "currentColor"
1388
+ }
1389
+ )
1390
+ ] });
1391
+ }
1392
+ function SendIcon() {
1393
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1394
+ "path",
1395
+ {
1396
+ d: "M5.5 12h11M12.5 5l6.5 7-6.5 7",
1397
+ stroke: "currentColor",
1398
+ strokeWidth: "1.8",
1399
+ strokeLinecap: "round",
1400
+ strokeLinejoin: "round"
1401
+ }
1402
+ ) });
1403
+ }
1404
+ function ThinkingSparkIcon() {
1405
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "blinq-thinking-dots", "aria-hidden": "true", children: [
1406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
1407
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
1408
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {})
1409
+ ] });
1410
+ }
1411
+ function isAssistantMessageRunning(status) {
1412
+ return status?.type === "running";
1413
+ }
1414
+ function itemToThreadMessage(item) {
1415
+ if (item.type === "message") {
1416
+ return {
1417
+ id: item.id,
1418
+ role: item.role,
1419
+ status: item.role === "assistant" ? item.status === "done" ? { type: "complete", reason: "stop" } : { type: "running" } : void 0,
1420
+ content: item.text ? [{ type: "text", text: item.text }] : [],
1421
+ metadata: {
1422
+ custom: {
1423
+ blinqKind: "message"
1424
+ }
1425
+ }
1426
+ };
1427
+ }
1428
+ if (item.type === "plan") {
1429
+ return {
1430
+ id: item.id,
1431
+ role: "assistant",
1432
+ status: item.status === "completed" ? { type: "complete", reason: "stop" } : item.status === "failed" ? { type: "incomplete", reason: "error" } : { type: "running" },
1433
+ content: [{ type: "data", name: "blinq-plan", data: item }],
1434
+ metadata: {
1435
+ custom: {
1436
+ blinqKind: "plan"
1437
+ }
1438
+ }
1439
+ };
1440
+ }
1441
+ return {
1442
+ id: item.id,
1443
+ role: "assistant",
1444
+ status: item.state === "approved" ? { type: "running" } : { type: "complete", reason: "stop" },
1445
+ content: [{ type: "data", name: "blinq-action", data: item }],
1446
+ metadata: {
1447
+ custom: {
1448
+ blinqKind: "action"
1449
+ }
1450
+ }
1451
+ };
1452
+ }
1453
+ function conversationToThreadMessages(items) {
1454
+ return items.map(itemToThreadMessage);
1455
+ }
1456
+ function ThreadMessageBubble({
1457
+ accentColor,
1458
+ onApproveAction,
1459
+ onDeclineAction,
1460
+ onApprovePlan,
1461
+ onDeclinePlan
1462
+ }) {
1463
+ const role = (0, import_react.useMessage)((state) => state.role);
1464
+ const messageId = (0, import_react.useMessage)((state) => state.id);
1465
+ const status = (0, import_react.useMessage)((state) => state.status);
1466
+ const content = (0, import_react.useMessage)((state) => state.content);
1467
+ const isDataOnly = Array.isArray(content) && content.length === 1 && typeof content[0] !== "string" && content[0]?.type === "data";
1468
+ const messageText = React.useMemo(
1469
+ () => content.filter((part) => typeof part !== "string" && part.type === "text").map((part) => part.text).join(""),
1470
+ [content]
1471
+ );
1472
+ const [animateFreshText, setAnimateFreshText] = React.useState(false);
1473
+ const [hasAnimatedStreamingReveal, setHasAnimatedStreamingReveal] = React.useState(false);
1474
+ React.useEffect(() => {
1475
+ setAnimateFreshText(false);
1476
+ setHasAnimatedStreamingReveal(false);
1477
+ }, [messageId]);
1478
+ React.useEffect(() => {
1479
+ if (role !== "assistant" || isDataOnly || !isAssistantMessageRunning(status) || !messageText.trim() || hasAnimatedStreamingReveal) {
1480
+ return;
1481
+ }
1482
+ setAnimateFreshText(true);
1483
+ setHasAnimatedStreamingReveal(true);
1484
+ const timeoutId = window.setTimeout(() => {
1485
+ setAnimateFreshText(false);
1486
+ }, 220);
1487
+ return () => window.clearTimeout(timeoutId);
1488
+ }, [role, isDataOnly, messageText, hasAnimatedStreamingReveal]);
1489
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1490
+ import_react.MessagePrimitive.Root,
1491
+ {
1492
+ className: "blinq-message",
1493
+ "data-role": role,
1494
+ "data-kind": isDataOnly ? "artifact" : "bubble",
1495
+ "data-status": status?.type ?? "complete",
1496
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.MessagePrimitive.Parts, { children: ({ part }) => {
1497
+ if (part.type === "text") {
1498
+ if (!part.text) {
1499
+ return null;
1500
+ }
1501
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1502
+ "div",
1503
+ {
1504
+ className: cn(
1505
+ "blinq-message-copy",
1506
+ role === "assistant" && animateFreshText && "blinq-message-copy-reveal"
1507
+ ),
1508
+ children: part.text
1509
+ }
1510
+ );
1511
+ }
1512
+ if (part.type === "data" && part.name === "blinq-action") {
1513
+ const actionItem = part.data;
1514
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1515
+ ActionCard,
1516
+ {
1517
+ item: actionItem,
1518
+ accentColor,
1519
+ onApprove: () => onApproveAction(actionItem.id),
1520
+ onDecline: () => onDeclineAction(actionItem.id)
1521
+ }
1522
+ );
1523
+ }
1524
+ if (part.type === "data" && part.name === "blinq-plan") {
1525
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1526
+ PlanCard,
1527
+ {
1528
+ item: part.data,
1529
+ onApprove: onApprovePlan,
1530
+ onDecline: onDeclinePlan
1531
+ }
1532
+ );
1533
+ }
1534
+ return null;
1535
+ } })
1536
+ }
1537
+ );
1538
+ }
1539
+ function ThinkingPlaceholder() {
1540
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-message blinq-thinking-card", "data-role": "assistant", "data-kind": "bubble", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-thinking", children: [
1541
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThinkingSparkIcon, {}),
1542
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "\u0414\u0443\u043C\u0430\u044E \u043D\u0430\u0434 \u043E\u0442\u0432\u0435\u0442\u043E\u043C" })
1543
+ ] }) });
1544
+ }
1545
+ function formatRecordingDuration(durationMs) {
1546
+ const totalSeconds = Math.max(0, Math.floor(durationMs / 1e3));
1547
+ const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, "0");
1548
+ const seconds = String(totalSeconds % 60).padStart(2, "0");
1549
+ return `${minutes}:${seconds}`;
1550
+ }
1551
+ function VoiceWaveform({
1552
+ durationMs,
1553
+ samples
1554
+ }) {
1555
+ const waveform = samples.length > 0 ? samples : [0.18];
1556
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-recording-input", role: "status", "aria-live": "polite", children: [
1557
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-recording-time", children: formatRecordingDuration(durationMs) }),
1558
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-recording-waveform", "aria-hidden": "true", children: waveform.map((sample, index) => {
1559
+ const scale = Math.max(0.16, Math.min(1, sample));
1560
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1561
+ "span",
1562
+ {
1563
+ className: "blinq-recording-waveform-bar",
1564
+ style: { transform: `scaleY(${scale})` }
1565
+ },
1566
+ `voice-wave-${index}`
1567
+ );
1568
+ }) })
1569
+ ] });
1570
+ }
1571
+ function ActionCard({
1572
+ item,
1573
+ accentColor,
1574
+ onApprove,
1575
+ onDecline
1576
+ }) {
1577
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1578
+ Card,
1579
+ {
1580
+ className: "blinq-action-card",
1581
+ style: item.state === "approved" ? { borderColor: "rgba(17, 17, 17, 0.22)" } : item.state === "declined" ? { borderColor: "rgba(17, 17, 17, 0.12)" } : void 0,
1582
+ children: [
1583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, { children: [
1584
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, { children: item.displayName }),
1585
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: item.targetSummary })
1586
+ ] }),
1587
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, { children: [
1588
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "blinq-action-details", children: [
1589
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0434\u0435\u0442\u0430\u043B\u0438 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F" }),
1590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "blinq-code", children: JSON.stringify(item.arguments, null, 2) })
1591
+ ] }),
1592
+ item.state !== "pending" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-action-state", "data-state": item.state, children: item.state === "approved" ? "\u0412\u044B\u043F\u043E\u043B\u043D\u044F\u044E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435" : "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E" }) : null,
1593
+ item.state === "pending" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-actions", children: [
1594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, { type: "button", variant: "outline", onClick: onDecline, children: "\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C" }),
1595
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1596
+ Button,
1597
+ {
1598
+ type: "button",
1599
+ onClick: onApprove,
1600
+ style: {
1601
+ background: accentColor
1602
+ },
1603
+ children: "\u0412\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C"
1604
+ }
1605
+ )
1606
+ ] }) : null
1607
+ ] })
1608
+ ]
1609
+ }
1610
+ );
1611
+ }
1612
+ function PlanCard({
1613
+ item,
1614
+ onApprove,
1615
+ onDecline
1616
+ }) {
1617
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { className: "blinq-action-card blinq-plan-card", children: [
1618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
1619
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1620
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, { children: "\u041F\u043B\u0430\u043D" }),
1621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: item.goal })
1622
+ ] }),
1623
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: item.status === "completed" ? "default" : item.status === "failed" ? "outline" : "soft", children: item.status === "completed" ? "\u0413\u043E\u0442\u043E\u0432\u043E" : item.status === "failed" ? "\u041E\u0448\u0438\u0431\u043A\u0430" : item.status === "awaiting_navigation" ? "\u041F\u0435\u0440\u0435\u0445\u043E\u0434" : "\u0412 \u0440\u0430\u0431\u043E\u0442\u0435" })
1624
+ ] }) }),
1625
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, { className: "space-y-3", children: [
1626
+ item.progressLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-action-state", children: item.progressLabel }) : null,
1627
+ item.statusReason ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-card-description", children: item.statusReason }) : null,
1628
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "space-y-2", children: item.steps.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1629
+ "div",
1630
+ {
1631
+ className: "blinq-plan-step",
1632
+ "data-current": index === item.currentStepIndex ? "true" : "false",
1633
+ "data-status": step.status,
1634
+ children: [
1635
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "blinq-plan-step-index", children: index + 1 }),
1636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-plan-step-copy", children: [
1637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: step.title }),
1638
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-plan-step-meta", children: step.status === "completed" ? "\u0412\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043E" : step.status === "failed" ? "\u041E\u0448\u0438\u0431\u043A\u0430" : step.status === "cancelled" ? "\u041E\u0442\u043C\u0435\u043D\u0435\u043D\u043E" : index === item.currentStepIndex ? "\u0422\u0435\u043A\u0443\u0449\u0438\u0439 \u0448\u0430\u0433" : "\u041E\u0436\u0438\u0434\u0430\u0435\u0442" })
1639
+ ] })
1640
+ ]
1641
+ },
1642
+ step.id
1643
+ )) }),
1644
+ item.approvalState === "pending" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-actions", children: [
1645
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, { type: "button", variant: "outline", onClick: onDecline, children: "\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C" }),
1646
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, { type: "button", onClick: onApprove, children: "\u0412\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C" })
1647
+ ] }) : null
1648
+ ] })
1649
+ ] });
1650
+ }
1651
+ function AssistantConversationThread({
1652
+ items,
1653
+ showThinkingIndicator,
1654
+ accentColor,
1655
+ onApproveAction,
1656
+ onDeclineAction,
1657
+ onApprovePlan,
1658
+ onDeclinePlan
1659
+ }) {
1660
+ const messages = React.useMemo(() => conversationToThreadMessages(items), [items]);
1661
+ const runtime = (0, import_react.useExternalStoreRuntime)({
1662
+ messages,
1663
+ isRunning: messages.some((message) => message.role === "assistant" && message.status?.type === "running"),
1664
+ convertMessage: (message) => message,
1665
+ onNew: async () => void 0
1666
+ });
1667
+ const scrollRef = React.useRef(null);
1668
+ React.useEffect(() => {
1669
+ if (!scrollRef.current) {
1670
+ return;
1671
+ }
1672
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
1673
+ }, [items]);
1674
+ const assistantMessageComponent = React.useMemo(
1675
+ () => function AssistantMessageComponent() {
1676
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1677
+ ThreadMessageBubble,
1678
+ {
1679
+ accentColor,
1680
+ onApproveAction,
1681
+ onDeclineAction,
1682
+ onApprovePlan,
1683
+ onDeclinePlan
1684
+ }
1685
+ );
1686
+ },
1687
+ [accentColor, onApproveAction, onDeclineAction, onApprovePlan, onDeclinePlan]
1688
+ );
1689
+ const userMessageComponent = React.useMemo(
1690
+ () => function UserMessageComponent() {
1691
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1692
+ ThreadMessageBubble,
1693
+ {
1694
+ accentColor,
1695
+ onApproveAction,
1696
+ onDeclineAction,
1697
+ onApprovePlan,
1698
+ onDeclinePlan
1699
+ }
1700
+ );
1701
+ },
1702
+ [accentColor, onApproveAction, onDeclineAction, onApprovePlan, onDeclinePlan]
1703
+ );
1704
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.AssistantRuntimeProvider, { runtime, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.ThreadPrimitive.Root, { className: "blinq-thread", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.ThreadPrimitive.Viewport, { ref: scrollRef, className: "blinq-scroll blinq-thread-viewport", children: [
1705
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1706
+ import_react.ThreadPrimitive.Messages,
1707
+ {
1708
+ components: {
1709
+ UserMessage: userMessageComponent,
1710
+ AssistantMessage: assistantMessageComponent
1711
+ }
1712
+ }
1713
+ ),
1714
+ showThinkingIndicator ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThinkingPlaceholder, {}) : null
1715
+ ] }) }) });
1716
+ }
1717
+ function WidgetEmbedShell({
1718
+ state,
1719
+ onOpen,
1720
+ onCollapse,
1721
+ onInputChange,
1722
+ onSubmit,
1723
+ onToggleVoice,
1724
+ onApproveAction,
1725
+ onDeclineAction,
1726
+ onApprovePlan,
1727
+ onDeclinePlan
1728
+ }) {
1729
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1730
+ "div",
1731
+ {
1732
+ className: "blinq-shell",
1733
+ "data-side": state.side,
1734
+ style: { ["--blinq-accent"]: state.accentColor },
1735
+ children: [
1736
+ !state.minimized ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-panel", children: [
1737
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-header", children: [
1738
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-header-row", children: [
1739
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-header-copy", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-heading", children: state.assistantName }) }),
1740
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1741
+ Button,
1742
+ {
1743
+ type: "button",
1744
+ size: "icon-sm",
1745
+ variant: "ghost",
1746
+ "aria-label": "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0438\u0434\u0436\u0435\u0442",
1747
+ title: "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0438\u0434\u0436\u0435\u0442",
1748
+ onClick: onCollapse,
1749
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollapseIcon, {})
1750
+ }
1751
+ )
1752
+ ] }),
1753
+ state.statusText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-status-line", children: state.statusText }) : null
1754
+ ] }),
1755
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1756
+ AssistantConversationThread,
1757
+ {
1758
+ items: state.items,
1759
+ showThinkingIndicator: state.showThinkingIndicator,
1760
+ accentColor: state.accentColor,
1761
+ onApproveAction,
1762
+ onDeclineAction,
1763
+ onApprovePlan,
1764
+ onDeclinePlan
1765
+ }
1766
+ ),
1767
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1768
+ "form",
1769
+ {
1770
+ className: "blinq-composer",
1771
+ onSubmit: (event) => {
1772
+ event.preventDefault();
1773
+ onSubmit();
1774
+ },
1775
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-composer-controls", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-input-shell", children: [
1776
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1777
+ Button,
1778
+ {
1779
+ type: "button",
1780
+ size: "icon-sm",
1781
+ variant: state.voiceListening ? "destructive" : "ghost",
1782
+ className: "blinq-input-mic",
1783
+ "aria-label": state.voiceListening ? "\u041E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0433\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434" : "\u041D\u0430\u0447\u0430\u0442\u044C \u0433\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434",
1784
+ title: !state.voiceSupported ? "\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0432 \u044D\u0442\u043E\u043C \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435" : state.voiceListening ? "\u041E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0437\u0430\u043F\u0438\u0441\u044C" : "\u041D\u0430\u0447\u0430\u0442\u044C \u0433\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434",
1785
+ disabled: !state.voiceSupported || state.isSending,
1786
+ onClick: onToggleVoice,
1787
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MicrophoneIcon, { listening: state.voiceListening })
1788
+ }
1789
+ ),
1790
+ state.voiceListening ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1791
+ VoiceWaveform,
1792
+ {
1793
+ durationMs: state.voiceDurationMs,
1794
+ samples: state.voiceWaveform
1795
+ }
1796
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1797
+ Input,
1798
+ {
1799
+ value: state.inputValue,
1800
+ placeholder: "\u041D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: \u043D\u0430\u0439\u0434\u0438 \u043D\u0443\u0436\u043D\u0443\u044E \u043A\u043D\u043E\u043F\u043A\u0443 \u0438\u043B\u0438 \u0437\u0430\u043F\u043E\u043B\u043D\u0438 \u0444\u043E\u0440\u043C\u0443",
1801
+ onChange: (event) => onInputChange(event.currentTarget.value),
1802
+ disabled: state.isSending
1803
+ }
1804
+ ),
1805
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1806
+ Button,
1807
+ {
1808
+ type: "submit",
1809
+ size: "icon-sm",
1810
+ className: "blinq-input-send",
1811
+ "aria-label": "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
1812
+ title: "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
1813
+ disabled: state.isSending || state.voiceListening,
1814
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SendIcon, {})
1815
+ }
1816
+ )
1817
+ ] }) })
1818
+ }
1819
+ )
1820
+ ] }) : null,
1821
+ state.minimized ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1822
+ Button,
1823
+ {
1824
+ type: "button",
1825
+ size: "icon",
1826
+ className: "blinq-launcher blinq-launcher-icon-only",
1827
+ "aria-label": `\u041E\u0442\u043A\u0440\u044B\u0442\u044C ${state.assistantName}`,
1828
+ title: `\u041E\u0442\u043A\u0440\u044B\u0442\u044C ${state.assistantName}`,
1829
+ onClick: onOpen,
1830
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "blinq-launcher-badge", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LauncherIcon, {}) })
1831
+ }
1832
+ ) : null
1833
+ ]
1834
+ }
1835
+ );
1836
+ }
1837
+
1838
+ // src/voice-input.ts
1839
+ var FILLER_PHRASES = [
1840
+ "you know",
1841
+ "i mean",
1842
+ "sort of",
1843
+ "kind of",
1844
+ "and so on",
1845
+ "\u043A\u0430\u043A \u0431\u044B",
1846
+ "\u0432 \u043E\u0431\u0449\u0435\u043C",
1847
+ "\u0432\u043E\u043E\u0431\u0449\u0435-\u0442\u043E",
1848
+ "\u044D\u0442\u043E \u0441\u0430\u043C\u043E\u0435",
1849
+ "\u0442\u0430\u043A \u0441\u043A\u0430\u0437\u0430\u0442\u044C",
1850
+ "\u043F\u043E \u0441\u0443\u0442\u0438",
1851
+ "\u0441\u043A\u0430\u0436\u0435\u043C \u0442\u0430\u043A",
1852
+ "\u0438 \u0442\u0430\u043A \u0434\u0430\u043B\u0435\u0435",
1853
+ "\u043D\u0430 \u0441\u0430\u043C\u043E\u043C \u0434\u0435\u043B\u0435",
1854
+ "if you know",
1855
+ "basically",
1856
+ "literally",
1857
+ "actually",
1858
+ "really",
1859
+ "\u043A\u043E\u0440\u043E\u0447\u0435",
1860
+ "\u0437\u043D\u0430\u0447\u0438\u0442",
1861
+ "\u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u043E",
1862
+ "\u043F\u0440\u043E\u0441\u0442\u043E",
1863
+ "\u043B\u0430\u0434\u043D\u043E",
1864
+ "\u043D\u0443 \u0432\u043E\u0442",
1865
+ "\u043D\u0443",
1866
+ "\u0432\u043E\u0442",
1867
+ "\u0442\u0438\u043F\u0430",
1868
+ "\u044D\u043C",
1869
+ "\u044D\u044D",
1870
+ "\u044D\u044D\u044D",
1871
+ "\u043C\u043C",
1872
+ "uh",
1873
+ "um",
1874
+ "er",
1875
+ "ah",
1876
+ "like",
1877
+ "well"
1878
+ ];
1879
+ function escapeRegex(value) {
1880
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1881
+ }
1882
+ function normalizeWhitespace(text) {
1883
+ return text.replace(/\s+/g, " ").trim();
1884
+ }
1885
+ function removeFillerPhrases(text) {
1886
+ let cleaned = text;
1887
+ for (const phrase of [...FILLER_PHRASES].sort((left, right) => right.length - left.length)) {
1888
+ const escaped = escapeRegex(phrase);
1889
+ const pattern = new RegExp(`(^|[\\s,.;:!?()\\[\\]{}-])${escaped}(?=$|[\\s,.;:!?()\\[\\]{}-])`, "giu");
1890
+ cleaned = cleaned.replace(pattern, (_match, boundary) => boundary || " ");
1891
+ }
1892
+ return cleaned;
1893
+ }
1894
+ function dedupeAdjacentWords(text) {
1895
+ const parts = text.split(/\s+/);
1896
+ const nextParts = [];
1897
+ let previousKey = "";
1898
+ for (const part of parts) {
1899
+ const wordKey = part.toLowerCase().replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, "");
1900
+ if (wordKey && wordKey === previousKey) {
1901
+ continue;
1902
+ }
1903
+ nextParts.push(part);
1904
+ previousKey = wordKey;
1905
+ }
1906
+ return nextParts.join(" ");
1907
+ }
1908
+ function normalizePunctuation(text) {
1909
+ return text.replace(/\s+([,.;:!?])/g, "$1").replace(/([,.;:!?])([^\s])/g, "$1 $2").replace(/\s+/g, " ").trim();
1910
+ }
1911
+ function sanitizeSpokenText(text) {
1912
+ const cleaned = normalizePunctuation(
1913
+ dedupeAdjacentWords(removeFillerPhrases(normalizeWhitespace(text)))
1914
+ );
1915
+ return cleaned;
1916
+ }
1917
+ function recognitionConstructor() {
1918
+ if (typeof window === "undefined") {
1919
+ return null;
1920
+ }
1921
+ const recognitionWindow = window;
1922
+ return recognitionWindow.SpeechRecognition || recognitionWindow.webkitSpeechRecognition || null;
1923
+ }
1924
+ function errorMessageForCode(code) {
1925
+ switch (code) {
1926
+ case "not-allowed":
1927
+ case "service-not-allowed":
1928
+ return "\u0414\u043E\u0441\u0442\u0443\u043F \u043A \u043C\u0438\u043A\u0440\u043E\u0444\u043E\u043D\u0443 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D.";
1929
+ case "audio-capture":
1930
+ return "\u041C\u0438\u043A\u0440\u043E\u0444\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.";
1931
+ case "network":
1932
+ return "\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0442\u0438 \u043F\u0440\u0438 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u0438 \u0440\u0435\u0447\u0438.";
1933
+ case "no-speech":
1934
+ return "\u0420\u0435\u0447\u044C \u043D\u0435 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u043D\u0430.";
1935
+ case "aborted":
1936
+ return "\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u0431\u044B\u043B \u043E\u0442\u043C\u0435\u043D\u0451\u043D.";
1937
+ default:
1938
+ return "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C \u0433\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434.";
1939
+ }
1940
+ }
1941
+ var VoiceInputController = class {
1942
+ callbacks;
1943
+ recognition;
1944
+ finalTranscript = "";
1945
+ interimTranscript = "";
1946
+ shouldSendOnEnd = false;
1947
+ listening = false;
1948
+ sessionActive = false;
1949
+ manualStopRequested = false;
1950
+ restartTimeoutId = null;
1951
+ statusAfterEnd = "\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D.";
1952
+ mediaStream = null;
1953
+ audioContext = null;
1954
+ analyser = null;
1955
+ sourceNode = null;
1956
+ levelFrameId = null;
1957
+ smoothedLevel = 0;
1958
+ constructor(callbacks) {
1959
+ this.callbacks = callbacks;
1960
+ const Recognition = recognitionConstructor();
1961
+ this.recognition = Recognition ? new Recognition() : null;
1962
+ if (!this.recognition) {
1963
+ return;
1964
+ }
1965
+ this.recognition.continuous = true;
1966
+ this.recognition.interimResults = true;
1967
+ this.recognition.maxAlternatives = 1;
1968
+ this.recognition.onstart = () => {
1969
+ this.listening = true;
1970
+ this.sessionActive = true;
1971
+ this.callbacks.onListeningChange(true);
1972
+ this.callbacks.onStatusChange("\u0421\u043B\u0443\u0448\u0430\u044E. \u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u0441\u0442\u043E\u043F, \u043A\u043E\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442\u0435 \u0433\u043E\u0442\u043E\u0432\u044B \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C.");
1973
+ };
1974
+ this.recognition.onresult = (event) => {
1975
+ let finalChunk = this.finalTranscript;
1976
+ let interimChunk = "";
1977
+ for (let index = event.resultIndex; index < event.results.length; index += 1) {
1978
+ const result = event.results[index];
1979
+ const transcript = result[0]?.transcript ?? "";
1980
+ if (!transcript) {
1981
+ continue;
1982
+ }
1983
+ if (result.isFinal) {
1984
+ finalChunk = `${finalChunk} ${transcript}`.trim();
1985
+ } else {
1986
+ interimChunk = `${interimChunk} ${transcript}`.trim();
1987
+ }
1988
+ }
1989
+ this.finalTranscript = finalChunk;
1990
+ this.interimTranscript = interimChunk;
1991
+ const preview = sanitizeSpokenText(`${finalChunk} ${interimChunk}`.trim());
1992
+ this.callbacks.onInterimTranscript(preview);
1993
+ if (preview) {
1994
+ this.callbacks.onStatusChange(`\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0433\u043E\u043B\u043E\u0441\u0430: ${preview}`);
1995
+ }
1996
+ };
1997
+ this.recognition.onerror = (event) => {
1998
+ if (event.error === "no-speech" && this.sessionActive && !this.manualStopRequested) {
1999
+ this.statusAfterEnd = "\u041F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u044E \u0441\u043B\u0443\u0448\u0430\u0442\u044C...";
2000
+ return;
2001
+ }
2002
+ if (event.error === "aborted" && this.manualStopRequested) {
2003
+ this.statusAfterEnd = "\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D.";
2004
+ return;
2005
+ }
2006
+ this.sessionActive = false;
2007
+ this.shouldSendOnEnd = false;
2008
+ this.statusAfterEnd = errorMessageForCode(event.error);
2009
+ this.stopLevelMonitoring();
2010
+ this.callbacks.onError(this.statusAfterEnd);
2011
+ };
2012
+ this.recognition.onend = () => {
2013
+ this.listening = false;
2014
+ if (this.sessionActive && !this.manualStopRequested) {
2015
+ this.callbacks.onStatusChange("\u041F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u044E \u0441\u043B\u0443\u0448\u0430\u0442\u044C...");
2016
+ this.scheduleRestart();
2017
+ return;
2018
+ }
2019
+ this.sessionActive = false;
2020
+ this.stopLevelMonitoring();
2021
+ this.finalizeTranscript();
2022
+ };
2023
+ }
2024
+ get supported() {
2025
+ return Boolean(this.recognition);
2026
+ }
2027
+ get isListening() {
2028
+ return this.sessionActive || this.listening;
2029
+ }
2030
+ finalizeTranscript() {
2031
+ this.callbacks.onListeningChange(false);
2032
+ this.callbacks.onLevelChange(0);
2033
+ const finalText = sanitizeSpokenText(this.finalTranscript);
2034
+ this.finalTranscript = "";
2035
+ this.interimTranscript = "";
2036
+ const shouldSend = this.shouldSendOnEnd;
2037
+ this.shouldSendOnEnd = false;
2038
+ this.manualStopRequested = false;
2039
+ if (!shouldSend) {
2040
+ this.callbacks.onStatusChange(this.statusAfterEnd);
2041
+ this.statusAfterEnd = "\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D.";
2042
+ return;
2043
+ }
2044
+ if (!finalText) {
2045
+ this.callbacks.onStatusChange("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u043F\u043E\u043B\u0435\u0437\u043D\u044B\u0439 \u0442\u0435\u043A\u0441\u0442 \u0438\u0437 \u0433\u043E\u043B\u043E\u0441\u0430.");
2046
+ this.statusAfterEnd = "\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D.";
2047
+ return;
2048
+ }
2049
+ this.callbacks.onStatusChange("\u0413\u043E\u043B\u043E\u0441 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u043D. \u041F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u0442\u0435\u043A\u0441\u0442 \u0438 \u043E\u0442\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435.");
2050
+ this.statusAfterEnd = "\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D.";
2051
+ this.callbacks.onFinalTranscript(finalText);
2052
+ }
2053
+ stopLevelMonitoring() {
2054
+ if (this.levelFrameId !== null) {
2055
+ window.cancelAnimationFrame(this.levelFrameId);
2056
+ this.levelFrameId = null;
2057
+ }
2058
+ this.analyser?.disconnect();
2059
+ this.sourceNode?.disconnect();
2060
+ this.analyser = null;
2061
+ this.sourceNode = null;
2062
+ if (this.mediaStream) {
2063
+ for (const track of this.mediaStream.getTracks()) {
2064
+ track.stop();
2065
+ }
2066
+ this.mediaStream = null;
2067
+ }
2068
+ if (this.audioContext) {
2069
+ void this.audioContext.close().catch(() => void 0);
2070
+ this.audioContext = null;
2071
+ }
2072
+ this.smoothedLevel = 0;
2073
+ this.callbacks.onLevelChange(0);
2074
+ }
2075
+ sampleLevel() {
2076
+ if (!this.analyser) {
2077
+ this.callbacks.onLevelChange(0);
2078
+ return;
2079
+ }
2080
+ const data = new Uint8Array(this.analyser.fftSize);
2081
+ const tick = () => {
2082
+ if (!this.analyser || !this.sessionActive && !this.listening) {
2083
+ this.callbacks.onLevelChange(0);
2084
+ return;
2085
+ }
2086
+ this.analyser.getByteTimeDomainData(data);
2087
+ let sum = 0;
2088
+ for (const value of data) {
2089
+ const normalized = (value - 128) / 128;
2090
+ sum += normalized * normalized;
2091
+ }
2092
+ const rms = Math.sqrt(sum / data.length);
2093
+ const level = Math.min(1, rms * 4.6);
2094
+ this.smoothedLevel = this.smoothedLevel * 0.68 + level * 0.32;
2095
+ this.callbacks.onLevelChange(this.smoothedLevel);
2096
+ this.levelFrameId = window.requestAnimationFrame(tick);
2097
+ };
2098
+ tick();
2099
+ }
2100
+ async startLevelMonitoring() {
2101
+ if (typeof window === "undefined" || !window.isSecureContext || !navigator.mediaDevices?.getUserMedia) {
2102
+ this.callbacks.onLevelChange(0);
2103
+ return;
2104
+ }
2105
+ this.stopLevelMonitoring();
2106
+ try {
2107
+ const stream = await navigator.mediaDevices.getUserMedia({
2108
+ audio: {
2109
+ echoCancellation: true,
2110
+ noiseSuppression: true
2111
+ }
2112
+ });
2113
+ if (!this.sessionActive && !this.listening) {
2114
+ for (const track of stream.getTracks()) {
2115
+ track.stop();
2116
+ }
2117
+ return;
2118
+ }
2119
+ const AudioContextCtor = window.AudioContext || window.webkitAudioContext;
2120
+ if (!AudioContextCtor) {
2121
+ for (const track of stream.getTracks()) {
2122
+ track.stop();
2123
+ }
2124
+ return;
2125
+ }
2126
+ this.mediaStream = stream;
2127
+ this.audioContext = new AudioContextCtor();
2128
+ this.sourceNode = this.audioContext.createMediaStreamSource(stream);
2129
+ this.analyser = this.audioContext.createAnalyser();
2130
+ this.analyser.fftSize = 128;
2131
+ this.analyser.smoothingTimeConstant = 0.72;
2132
+ this.sourceNode.connect(this.analyser);
2133
+ this.sampleLevel();
2134
+ } catch {
2135
+ this.callbacks.onLevelChange(0);
2136
+ }
2137
+ }
2138
+ clearRestartTimer() {
2139
+ if (this.restartTimeoutId !== null) {
2140
+ window.clearTimeout(this.restartTimeoutId);
2141
+ this.restartTimeoutId = null;
2142
+ }
2143
+ }
2144
+ scheduleRestart() {
2145
+ this.clearRestartTimer();
2146
+ this.restartTimeoutId = window.setTimeout(() => {
2147
+ this.restartTimeoutId = null;
2148
+ if (!this.recognition || !this.sessionActive || this.manualStopRequested) {
2149
+ return;
2150
+ }
2151
+ try {
2152
+ this.recognition.lang = this.callbacks.getLanguage();
2153
+ this.recognition.start();
2154
+ } catch {
2155
+ this.sessionActive = false;
2156
+ this.callbacks.onListeningChange(false);
2157
+ this.callbacks.onError("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0433\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434.");
2158
+ }
2159
+ }, 160);
2160
+ }
2161
+ start() {
2162
+ if (!this.recognition) {
2163
+ this.callbacks.onError("\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0432 \u044D\u0442\u043E\u043C \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435.");
2164
+ return false;
2165
+ }
2166
+ if (this.sessionActive || this.listening) {
2167
+ return true;
2168
+ }
2169
+ this.finalTranscript = "";
2170
+ this.interimTranscript = "";
2171
+ this.shouldSendOnEnd = false;
2172
+ this.sessionActive = true;
2173
+ this.manualStopRequested = false;
2174
+ this.statusAfterEnd = "\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D.";
2175
+ this.recognition.lang = this.callbacks.getLanguage();
2176
+ this.clearRestartTimer();
2177
+ try {
2178
+ this.recognition.start();
2179
+ void this.startLevelMonitoring();
2180
+ return true;
2181
+ } catch {
2182
+ this.sessionActive = false;
2183
+ this.stopLevelMonitoring();
2184
+ this.callbacks.onError("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0433\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434.");
2185
+ return false;
2186
+ }
2187
+ }
2188
+ stop() {
2189
+ if (!this.recognition || !this.listening && !this.sessionActive) {
2190
+ return;
2191
+ }
2192
+ this.manualStopRequested = true;
2193
+ this.sessionActive = false;
2194
+ this.shouldSendOnEnd = true;
2195
+ this.clearRestartTimer();
2196
+ this.stopLevelMonitoring();
2197
+ if (this.listening) {
2198
+ this.recognition.stop();
2199
+ } else {
2200
+ this.finalizeTranscript();
2201
+ }
2202
+ }
2203
+ cancel() {
2204
+ if (!this.recognition) {
2205
+ return;
2206
+ }
2207
+ this.manualStopRequested = true;
2208
+ this.sessionActive = false;
2209
+ this.shouldSendOnEnd = false;
2210
+ this.clearRestartTimer();
2211
+ this.stopLevelMonitoring();
2212
+ if (this.listening) {
2213
+ this.recognition.abort();
2214
+ } else {
2215
+ this.callbacks.onListeningChange(false);
2216
+ this.callbacks.onLevelChange(0);
2217
+ }
2218
+ }
2219
+ destroy() {
2220
+ this.cancel();
2221
+ if (!this.recognition) {
2222
+ return;
2223
+ }
2224
+ this.recognition.onstart = null;
2225
+ this.recognition.onresult = null;
2226
+ this.recognition.onerror = null;
2227
+ this.recognition.onend = null;
2228
+ }
2229
+ };
2230
+
2231
+ // src/widget-core.ts
2232
+ var DEFAULT_API_BASE_URL = "http://localhost:8000";
2233
+ var STORAGE_KEY = "blinq-widget-fingerprint";
2234
+ var HISTORY_STORAGE_PREFIX = "blinq-widget-history";
2235
+ var MINIMIZED_STORAGE_PREFIX = "blinq-widget-minimized";
2236
+ var SESSION_STORAGE_PREFIX = "blinq-widget-session";
2237
+ var MAX_STORED_HISTORY = 30;
2238
+ var MAX_HISTORY_FOR_REQUEST = 12;
2239
+ var DEFAULT_GREETING = "Blinq \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D. \u0421\u043F\u0440\u043E\u0441\u0438\u0442\u0435 \u043F\u0440\u043E \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443, \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0443 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u044B \u0438\u043B\u0438 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0448\u0430\u0433.";
2240
+ function isBrowser3() {
2241
+ return typeof window !== "undefined" && typeof document !== "undefined";
2242
+ }
2243
+ function resolveMount(mount) {
2244
+ if (!isBrowser3()) {
2245
+ throw new Error("Blinq \u043C\u043E\u0436\u043D\u043E \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435");
2246
+ }
2247
+ if (mount instanceof HTMLElement) {
2248
+ return mount;
2249
+ }
2250
+ if (typeof mount === "string") {
2251
+ const target = document.querySelector(mount);
2252
+ if (!target) {
2253
+ throw new Error(`\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D mount-\u044D\u043B\u0435\u043C\u0435\u043D\u0442: ${mount}`);
2254
+ }
2255
+ return target;
2256
+ }
2257
+ return document.body;
2258
+ }
2259
+ function getOrCreateFingerprint() {
2260
+ if (!isBrowser3()) {
2261
+ return "server-widget-user";
2262
+ }
2263
+ const existing = window.localStorage.getItem(STORAGE_KEY);
2264
+ if (existing) {
2265
+ return existing;
2266
+ }
2267
+ const next = `fingerprint_${crypto.randomUUID()}`;
2268
+ window.localStorage.setItem(STORAGE_KEY, next);
2269
+ return next;
2270
+ }
2271
+ function runtimeMessage(mode) {
2272
+ if (mode === "read-only-fallback") {
2273
+ return "\u0413\u043E\u0442\u043E\u0432 \u043E\u0431\u044A\u044F\u0441\u043D\u0438\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443 \u0438 \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u0430\u0442\u044C \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0448\u0430\u0433.";
2274
+ }
2275
+ return "\u0413\u043E\u0442\u043E\u0432 \u043F\u043E\u043C\u043E\u0447\u044C \u0441 \u043E\u043D\u0431\u043E\u0440\u0434\u0438\u043D\u0433\u043E\u043C \u0438 \u0432\u043E\u043F\u0440\u043E\u0441\u0430\u043C\u0438 \u043F\u043E \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435.";
2276
+ }
2277
+ function normalizedRouteFromUrl(value) {
2278
+ try {
2279
+ const url = new URL(value, window.location.origin);
2280
+ const path = url.pathname || "/";
2281
+ return path === "" ? "/" : path;
2282
+ } catch {
2283
+ return "/";
2284
+ }
2285
+ }
2286
+ async function parseSseStream(stream, onEvent) {
2287
+ const reader = stream.getReader();
2288
+ const decoder = new TextDecoder();
2289
+ let buffer = "";
2290
+ let currentEvent = "message";
2291
+ let currentData = "";
2292
+ while (true) {
2293
+ const { done, value } = await reader.read();
2294
+ if (done) {
2295
+ break;
2296
+ }
2297
+ buffer += decoder.decode(value, { stream: true });
2298
+ const chunks = buffer.split("\n");
2299
+ buffer = chunks.pop() ?? "";
2300
+ for (const line of chunks) {
2301
+ if (line.startsWith("event:")) {
2302
+ currentEvent = line.slice(6).trim();
2303
+ } else if (line.startsWith("data:")) {
2304
+ currentData += line.slice(5).trim();
2305
+ } else if (line.trim() === "") {
2306
+ if (currentData) {
2307
+ onEvent({ event: currentEvent, data: currentData });
2308
+ }
2309
+ currentEvent = "message";
2310
+ currentData = "";
2311
+ }
2312
+ }
2313
+ }
2314
+ if (currentData) {
2315
+ onEvent({ event: currentEvent, data: currentData });
2316
+ }
2317
+ }
2318
+ function normalizedConfig(config) {
2319
+ if ("default" in config) {
2320
+ return config.default;
2321
+ }
2322
+ return config;
2323
+ }
2324
+ function createConversationId(prefix) {
2325
+ return `${prefix}_${crypto.randomUUID()}`;
2326
+ }
2327
+ function normalizeAssistantStreamChunk(currentText, nextChunk) {
2328
+ if (!currentText) {
2329
+ return nextChunk.replace(/^\s+/, "");
2330
+ }
2331
+ return nextChunk;
2332
+ }
2333
+ function normalizeAssistantFinalText(text) {
2334
+ return text.replace(/^\s+/, "");
2335
+ }
2336
+ var BlinqWidget = class {
2337
+ options;
2338
+ apiBaseUrl;
2339
+ sessionId = null;
2340
+ sessionToken = null;
2341
+ widgetConfig = null;
2342
+ runtimeStatus = null;
2343
+ registry = null;
2344
+ root = null;
2345
+ reactRoot = null;
2346
+ voiceController = null;
2347
+ history = [];
2348
+ items = [];
2349
+ minimized = false;
2350
+ isSending = false;
2351
+ inputValue = "";
2352
+ statusText = "\u041F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B.";
2353
+ voiceSupported = false;
2354
+ voiceListening = false;
2355
+ voiceWaveform = [];
2356
+ voiceDurationMs = 0;
2357
+ voiceStartedAt = null;
2358
+ lastVoiceSampleAt = 0;
2359
+ confirmResolve = null;
2360
+ confirmItemId = null;
2361
+ activeRun = null;
2362
+ pendingAssistantIndicator = false;
2363
+ executedToolCounts = /* @__PURE__ */ new Map();
2364
+ lastAutoResumeKey = null;
2365
+ shellHandlers = {
2366
+ onOpen: () => this.setMinimized(false),
2367
+ onCollapse: () => this.setMinimized(true),
2368
+ onInputChange: (value) => {
2369
+ this.inputValue = value;
2370
+ this.renderReactApp();
2371
+ },
2372
+ onSubmit: () => {
2373
+ void this.submitComposerMessage();
2374
+ },
2375
+ onToggleVoice: () => this.toggleVoiceInput(),
2376
+ onApproveAction: (id) => {
2377
+ if (this.confirmItemId === id) {
2378
+ this.resolveConfirmation(true);
2379
+ }
2380
+ },
2381
+ onDeclineAction: (id) => {
2382
+ if (this.confirmItemId === id) {
2383
+ this.resolveConfirmation(false);
2384
+ }
2385
+ },
2386
+ onApprovePlan: () => {
2387
+ void this.respondToPlan(true);
2388
+ },
2389
+ onDeclinePlan: () => {
2390
+ void this.respondToPlan(false);
2391
+ }
2392
+ };
2393
+ constructor(options) {
2394
+ this.options = options;
2395
+ this.apiBaseUrl = options.apiBaseUrl ?? DEFAULT_API_BASE_URL;
2396
+ }
2397
+ async init() {
2398
+ await this.startSession();
2399
+ if (isBrowser3()) {
2400
+ this.restoreHistory();
2401
+ this.restoreMinimizedState();
2402
+ this.restoreConversationView();
2403
+ this.render();
2404
+ this.setupVoiceInput();
2405
+ this.registry = new PageToolRegistry(this.confirmToolExecution.bind(this));
2406
+ const nativeRegistered = await this.registry.registerNativeTools();
2407
+ this.syncRuntimeStatus(nativeRegistered);
2408
+ if (this.shouldAutoResumeActiveRun()) {
2409
+ window.setTimeout(() => {
2410
+ void this.continueActiveRun();
2411
+ }, 60);
2412
+ }
2413
+ }
2414
+ return this;
2415
+ }
2416
+ getRuntimeStatus() {
2417
+ return this.runtimeStatus;
2418
+ }
2419
+ destroy() {
2420
+ this.voiceController?.destroy();
2421
+ this.registry?.destroy();
2422
+ this.voiceController = null;
2423
+ this.registry = null;
2424
+ this.reactRoot?.unmount();
2425
+ this.reactRoot = null;
2426
+ this.root?.remove();
2427
+ this.root = null;
2428
+ }
2429
+ currentState() {
2430
+ return {
2431
+ minimized: this.minimized,
2432
+ assistantName: this.widgetConfig?.ai_persona_name ?? "Blinq \u0410\u0441\u0441\u0438\u0441\u0442\u0435\u043D\u0442",
2433
+ runtimeLabel: "webmcp://embedded",
2434
+ statusText: this.statusText,
2435
+ showThinkingIndicator: this.pendingAssistantIndicator,
2436
+ inputValue: this.inputValue,
2437
+ accentColor: "#111111",
2438
+ side: this.widgetConfig?.position === "bottom-left" ? "left" : "right",
2439
+ isSending: this.isSending,
2440
+ voiceSupported: this.voiceSupported,
2441
+ voiceListening: this.voiceListening,
2442
+ voiceWaveform: this.voiceWaveform,
2443
+ voiceDurationMs: this.voiceDurationMs,
2444
+ items: this.items
2445
+ };
2446
+ }
2447
+ renderReactApp() {
2448
+ if (!this.reactRoot) {
2449
+ return;
2450
+ }
2451
+ this.reactRoot.render(
2452
+ React2.createElement(WidgetEmbedShell, {
2453
+ state: this.currentState(),
2454
+ ...this.shellHandlers
2455
+ })
2456
+ );
2457
+ }
2458
+ async startSession() {
2459
+ const response = await fetch(`${this.apiBaseUrl}/pub/init`, {
2460
+ method: "POST",
2461
+ headers: {
2462
+ "Content-Type": "application/json"
2463
+ },
2464
+ body: JSON.stringify({
2465
+ public_token: this.options.publicToken,
2466
+ domain: window.location.hostname,
2467
+ page_url: window.location.href,
2468
+ user_fingerprint: getOrCreateFingerprint(),
2469
+ previous_session_id: this.restoreStoredSessionId(),
2470
+ client_capabilities: {
2471
+ secure_context: window.isSecureContext,
2472
+ native_webmcp_supported: detectNativeWebMcpSupport(),
2473
+ page_actions_supported: true
2474
+ }
2475
+ })
2476
+ });
2477
+ if (!response.ok) {
2478
+ const payload2 = await response.json().catch(() => ({}));
2479
+ throw new Error(payload2.detail ?? "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0438\u0434\u0436\u0435\u0442 Blinq");
2480
+ }
2481
+ const payload = await response.json();
2482
+ this.sessionId = payload.session_id;
2483
+ this.sessionToken = payload.session_token;
2484
+ this.widgetConfig = payload.widget_config;
2485
+ this.activeRun = payload.active_run ?? null;
2486
+ this.runtimeStatus = {
2487
+ ...payload.runtime,
2488
+ message: runtimeMessage(payload.runtime.mode)
2489
+ };
2490
+ this.persistSessionId();
2491
+ this.statusText = this.runtimeStatus.message;
2492
+ }
2493
+ render() {
2494
+ const mount = resolveMount(this.options.mount);
2495
+ if (!this.root) {
2496
+ this.root = document.createElement("div");
2497
+ mount.appendChild(this.root);
2498
+ this.reactRoot = (0, import_client.createRoot)(this.root);
2499
+ }
2500
+ this.renderReactApp();
2501
+ }
2502
+ preferredSpeechLanguage() {
2503
+ const htmlLang = document.documentElement.lang.trim();
2504
+ if (htmlLang) {
2505
+ return htmlLang;
2506
+ }
2507
+ return navigator.language || "ru-RU";
2508
+ }
2509
+ setupVoiceInput() {
2510
+ this.voiceController = new VoiceInputController({
2511
+ getLanguage: () => this.preferredSpeechLanguage(),
2512
+ onInterimTranscript: () => {
2513
+ },
2514
+ onFinalTranscript: (text) => {
2515
+ this.inputValue = text;
2516
+ this.setStatus("\u0413\u043E\u043B\u043E\u0441 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u043D. \u041F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u0442\u0435\u043A\u0441\u0442 \u0438 \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C.");
2517
+ this.renderReactApp();
2518
+ },
2519
+ onListeningChange: (isListening) => {
2520
+ if (isListening) {
2521
+ this.startVoiceVisualization();
2522
+ } else {
2523
+ this.resetVoiceVisualization();
2524
+ }
2525
+ this.syncVoiceState();
2526
+ },
2527
+ onLevelChange: (level) => {
2528
+ this.updateVoiceVisualization(level);
2529
+ },
2530
+ onStatusChange: (status) => {
2531
+ this.setStatus(status);
2532
+ },
2533
+ onError: (message) => {
2534
+ this.setStatus(message);
2535
+ this.syncVoiceState();
2536
+ }
2537
+ });
2538
+ this.syncVoiceState();
2539
+ }
2540
+ syncVoiceState() {
2541
+ this.voiceSupported = this.voiceController?.supported ?? false;
2542
+ this.voiceListening = this.voiceController?.isListening ?? false;
2543
+ if (!this.voiceListening) {
2544
+ this.resetVoiceVisualization();
2545
+ }
2546
+ this.renderReactApp();
2547
+ }
2548
+ startVoiceVisualization() {
2549
+ this.voiceStartedAt = Date.now();
2550
+ this.voiceDurationMs = 0;
2551
+ this.voiceWaveform = [];
2552
+ this.lastVoiceSampleAt = 0;
2553
+ }
2554
+ resetVoiceVisualization() {
2555
+ this.voiceStartedAt = null;
2556
+ this.voiceDurationMs = 0;
2557
+ this.voiceWaveform = [];
2558
+ this.lastVoiceSampleAt = 0;
2559
+ }
2560
+ updateVoiceVisualization(level) {
2561
+ if (!this.voiceListening) {
2562
+ return;
2563
+ }
2564
+ const now = Date.now();
2565
+ if (this.voiceStartedAt === null) {
2566
+ this.startVoiceVisualization();
2567
+ this.voiceStartedAt = now;
2568
+ }
2569
+ this.voiceDurationMs = Math.max(0, now - (this.voiceStartedAt ?? now));
2570
+ const nextLevel = Math.max(0.1, Math.min(1, level));
2571
+ if (this.voiceWaveform.length === 0 || now - this.lastVoiceSampleAt >= 90) {
2572
+ this.voiceWaveform = [...this.voiceWaveform, nextLevel].slice(-40);
2573
+ this.lastVoiceSampleAt = now;
2574
+ } else {
2575
+ this.voiceWaveform = [
2576
+ ...this.voiceWaveform.slice(0, -1),
2577
+ nextLevel
2578
+ ];
2579
+ }
2580
+ this.renderReactApp();
2581
+ }
2582
+ async submitComposerMessage(message = this.inputValue) {
2583
+ const nextMessage = message.trim();
2584
+ if (!nextMessage) {
2585
+ return "";
2586
+ }
2587
+ if (this.isSending) {
2588
+ this.setStatus("\u0414\u043E\u0436\u0434\u0438\u0442\u0435\u0441\u044C \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E \u043E\u0442\u0432\u0435\u0442\u0430.");
2589
+ this.inputValue = nextMessage;
2590
+ this.renderReactApp();
2591
+ return "";
2592
+ }
2593
+ this.inputValue = "";
2594
+ this.renderReactApp();
2595
+ try {
2596
+ return await this.sendMessage(nextMessage);
2597
+ } catch {
2598
+ return "";
2599
+ }
2600
+ }
2601
+ toggleVoiceInput() {
2602
+ if (!this.voiceController) {
2603
+ this.setStatus("\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D.");
2604
+ return;
2605
+ }
2606
+ if (this.voiceController.isListening) {
2607
+ this.voiceController.stop();
2608
+ return;
2609
+ }
2610
+ this.voiceController.start();
2611
+ this.syncVoiceState();
2612
+ }
2613
+ minimizedStorageKey() {
2614
+ return `${MINIMIZED_STORAGE_PREFIX}:${window.location.hostname}:${this.options.publicToken.slice(0, 18)}`;
2615
+ }
2616
+ sessionStorageKey() {
2617
+ return `${SESSION_STORAGE_PREFIX}:${window.location.hostname}:${this.options.publicToken.slice(0, 18)}`;
2618
+ }
2619
+ restoreStoredSessionId() {
2620
+ if (!isBrowser3()) {
2621
+ return null;
2622
+ }
2623
+ return window.localStorage.getItem(this.sessionStorageKey());
2624
+ }
2625
+ persistSessionId() {
2626
+ if (!isBrowser3() || !this.sessionId) {
2627
+ return;
2628
+ }
2629
+ window.localStorage.setItem(this.sessionStorageKey(), this.sessionId);
2630
+ }
2631
+ restoreMinimizedState() {
2632
+ if (!isBrowser3()) {
2633
+ return;
2634
+ }
2635
+ this.minimized = window.localStorage.getItem(this.minimizedStorageKey()) === "1";
2636
+ }
2637
+ persistMinimizedState() {
2638
+ if (!isBrowser3()) {
2639
+ return;
2640
+ }
2641
+ window.localStorage.setItem(this.minimizedStorageKey(), this.minimized ? "1" : "0");
2642
+ }
2643
+ setMinimized(nextValue) {
2644
+ this.minimized = nextValue;
2645
+ this.persistMinimizedState();
2646
+ this.renderReactApp();
2647
+ }
2648
+ historyStorageKey() {
2649
+ return `${HISTORY_STORAGE_PREFIX}:${window.location.hostname}:${this.options.publicToken.slice(0, 18)}`;
2650
+ }
2651
+ restoreHistory() {
2652
+ if (!isBrowser3()) {
2653
+ return;
2654
+ }
2655
+ try {
2656
+ const raw = window.localStorage.getItem(this.historyStorageKey());
2657
+ if (!raw) {
2658
+ this.history = [];
2659
+ return;
2660
+ }
2661
+ const parsed = JSON.parse(raw);
2662
+ if (!Array.isArray(parsed)) {
2663
+ this.history = [];
2664
+ return;
2665
+ }
2666
+ this.history = parsed.filter((entry) => {
2667
+ if (!entry || typeof entry !== "object") {
2668
+ return false;
2669
+ }
2670
+ const role = entry.role;
2671
+ const text = entry.text;
2672
+ return (role === "user" || role === "assistant") && typeof text === "string";
2673
+ }).slice(-MAX_STORED_HISTORY);
2674
+ } catch {
2675
+ this.history = [];
2676
+ }
2677
+ }
2678
+ persistHistory() {
2679
+ if (!isBrowser3()) {
2680
+ return;
2681
+ }
2682
+ try {
2683
+ window.localStorage.setItem(
2684
+ this.historyStorageKey(),
2685
+ JSON.stringify(this.history.slice(-MAX_STORED_HISTORY))
2686
+ );
2687
+ } catch {
2688
+ }
2689
+ }
2690
+ rememberMessage(entry) {
2691
+ const text = entry.text.trim();
2692
+ if (!text) {
2693
+ return;
2694
+ }
2695
+ this.history.push({ role: entry.role, text });
2696
+ this.history = this.history.slice(-MAX_STORED_HISTORY);
2697
+ this.persistHistory();
2698
+ }
2699
+ recentHistoryForRequest() {
2700
+ return this.history.slice(-MAX_HISTORY_FOR_REQUEST).map((entry) => ({
2701
+ role: entry.role,
2702
+ text: entry.text
2703
+ }));
2704
+ }
2705
+ restoreConversationView() {
2706
+ if (this.history.length === 0) {
2707
+ this.items = [
2708
+ {
2709
+ id: createConversationId("message"),
2710
+ type: "message",
2711
+ role: "assistant",
2712
+ text: DEFAULT_GREETING,
2713
+ status: "done"
2714
+ }
2715
+ ];
2716
+ } else {
2717
+ this.items = this.history.map((entry) => ({
2718
+ id: createConversationId("message"),
2719
+ type: "message",
2720
+ role: entry.role,
2721
+ text: entry.text,
2722
+ status: "done"
2723
+ }));
2724
+ }
2725
+ if (this.activeRun) {
2726
+ this.upsertPlanItem(this.activeRun);
2727
+ }
2728
+ }
2729
+ updateActionState(id, state) {
2730
+ this.items = this.items.map(
2731
+ (item) => item.type === "action" && item.id === id ? { ...item, state } : item
2732
+ );
2733
+ this.renderReactApp();
2734
+ }
2735
+ planItemId(runId) {
2736
+ return `plan_${runId}`;
2737
+ }
2738
+ upsertPlanItem(run, progressLabel) {
2739
+ const existingPlanItem = this.items.find(
2740
+ (item) => item.type === "plan" && item.id === this.planItemId(run.id)
2741
+ );
2742
+ if (this.activeRun?.id && this.activeRun.id !== run.id) {
2743
+ this.executedToolCounts.clear();
2744
+ }
2745
+ this.activeRun = {
2746
+ ...run,
2747
+ progress_label: progressLabel ?? run.progress_label ?? null
2748
+ };
2749
+ const nextPlanItem = {
2750
+ id: this.planItemId(run.id),
2751
+ type: "plan",
2752
+ goal: run.goal,
2753
+ status: run.status,
2754
+ progressLabel: progressLabel ?? run.progress_label ?? null,
2755
+ statusReason: run.status_reason ?? null,
2756
+ expectedRoute: run.expected_route ?? null,
2757
+ loopGuarded: run.loop_guarded ?? false,
2758
+ currentStepIndex: run.status === "completed" ? run.total_steps : Math.min(run.total_steps, run.current_step_index + 1),
2759
+ totalSteps: run.total_steps,
2760
+ approvalState: run.requires_approval ? "pending" : run.status === "abandoned" ? "declined" : existingPlanItem && existingPlanItem.type === "plan" && existingPlanItem.approvalState === "declined" ? "declined" : "approved",
2761
+ steps: run.steps.map((step) => ({
2762
+ id: step.id,
2763
+ title: step.title,
2764
+ status: step.status,
2765
+ kind: step.kind
2766
+ }))
2767
+ };
2768
+ const existingIndex = this.items.findIndex(
2769
+ (item) => item.type === "plan" && item.id === nextPlanItem.id
2770
+ );
2771
+ if (existingIndex >= 0) {
2772
+ this.items = this.items.map((item, index) => index === existingIndex ? nextPlanItem : item);
2773
+ } else {
2774
+ this.items = [...this.items, nextPlanItem];
2775
+ }
2776
+ this.renderReactApp();
2777
+ }
2778
+ removeEmptyMessage(id) {
2779
+ if (!id) {
2780
+ return;
2781
+ }
2782
+ this.items = this.items.filter(
2783
+ (item) => !(item.type === "message" && item.id === id && item.text.trim().length === 0)
2784
+ );
2785
+ }
2786
+ resolveConfirmation(value) {
2787
+ if (this.confirmItemId) {
2788
+ this.updateActionState(this.confirmItemId, value ? "approved" : "declined");
2789
+ }
2790
+ if (this.confirmResolve) {
2791
+ const resolve = this.confirmResolve;
2792
+ this.confirmResolve = null;
2793
+ this.confirmItemId = null;
2794
+ resolve(value);
2795
+ }
2796
+ }
2797
+ async confirmToolExecution(request) {
2798
+ if (this.minimized) {
2799
+ this.setMinimized(false);
2800
+ }
2801
+ if (this.confirmResolve) {
2802
+ this.resolveConfirmation(false);
2803
+ }
2804
+ const itemId = createConversationId("action");
2805
+ this.items = [
2806
+ ...this.items,
2807
+ {
2808
+ id: itemId,
2809
+ type: "action",
2810
+ displayName: request.displayName,
2811
+ targetSummary: request.targetSummary,
2812
+ arguments: request.arguments,
2813
+ state: "pending"
2814
+ }
2815
+ ];
2816
+ this.confirmItemId = itemId;
2817
+ this.setStatus(`\u041D\u0443\u0436\u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435: ${request.displayName}`);
2818
+ this.renderReactApp();
2819
+ return await new Promise((resolve) => {
2820
+ this.confirmResolve = resolve;
2821
+ });
2822
+ }
2823
+ syncRuntimeStatus(nativeRegistered) {
2824
+ if (!this.runtimeStatus) {
2825
+ return;
2826
+ }
2827
+ const mode = nativeRegistered ? "native-webmcp-active" : this.registry?.canExecuteWriteTools() ? "blinq-page-tools-active" : "read-only-fallback";
2828
+ this.runtimeStatus = {
2829
+ ...this.runtimeStatus,
2830
+ mode,
2831
+ native_webmcp_supported: nativeRegistered,
2832
+ embedded_page_tools_supported: true,
2833
+ message: runtimeMessage(mode)
2834
+ };
2835
+ this.setStatus(this.runtimeStatus.message);
2836
+ }
2837
+ setStatus(text) {
2838
+ this.statusText = text;
2839
+ this.renderReactApp();
2840
+ }
2841
+ appendMessage(text, role, options) {
2842
+ const id = createConversationId("message");
2843
+ this.items = [
2844
+ ...this.items,
2845
+ {
2846
+ id,
2847
+ type: "message",
2848
+ role,
2849
+ text,
2850
+ status: options?.status ?? "done"
2851
+ }
2852
+ ];
2853
+ this.renderReactApp();
2854
+ if (options?.persist !== false) {
2855
+ this.rememberMessage({ role, text });
2856
+ }
2857
+ return id;
2858
+ }
2859
+ updateMessage(id, text, status) {
2860
+ this.items = this.items.map(
2861
+ (item) => item.type === "message" && item.id === id ? { ...item, text, status: status ?? item.status } : item
2862
+ );
2863
+ this.renderReactApp();
2864
+ }
2865
+ latestAssistantMessage() {
2866
+ const candidate = [...this.items].reverse().find((item) => item.type === "message" && item.role === "assistant");
2867
+ return candidate && candidate.type === "message" ? candidate : null;
2868
+ }
2869
+ finalizeAssistantMessage(text, options) {
2870
+ if (!text.trim()) {
2871
+ return;
2872
+ }
2873
+ const latestAssistant = this.latestAssistantMessage();
2874
+ if (latestAssistant) {
2875
+ this.updateMessage(latestAssistant.id, text, "done");
2876
+ return;
2877
+ }
2878
+ this.appendMessage(text, "assistant", {
2879
+ persist: options?.persist ?? false,
2880
+ status: "done"
2881
+ });
2882
+ }
2883
+ showThinkingIndicator() {
2884
+ if (!this.pendingAssistantIndicator) {
2885
+ this.pendingAssistantIndicator = true;
2886
+ this.renderReactApp();
2887
+ }
2888
+ }
2889
+ hideThinkingIndicator() {
2890
+ if (this.pendingAssistantIndicator) {
2891
+ this.pendingAssistantIndicator = false;
2892
+ this.renderReactApp();
2893
+ }
2894
+ }
2895
+ currentPageContext() {
2896
+ return this.registry?.collectPageContext() ?? collectNormalizedPageContext();
2897
+ }
2898
+ currentRoute() {
2899
+ return normalizedRouteFromUrl(window.location.href);
2900
+ }
2901
+ shouldAutoResumeActiveRun() {
2902
+ if (!this.activeRun || this.activeRun.status !== "awaiting_navigation") {
2903
+ return false;
2904
+ }
2905
+ const currentRoute = this.currentRoute();
2906
+ if (!this.activeRun.expected_route || currentRoute !== this.activeRun.expected_route) {
2907
+ if (this.activeRun.status_reason) {
2908
+ this.setStatus(this.activeRun.status_reason);
2909
+ }
2910
+ return false;
2911
+ }
2912
+ const nextKey = `${this.activeRun.id}:${currentRoute}`;
2913
+ if (this.lastAutoResumeKey === nextKey) {
2914
+ return false;
2915
+ }
2916
+ this.lastAutoResumeKey = nextKey;
2917
+ return true;
2918
+ }
2919
+ async handleToolRequest(request) {
2920
+ if (!this.registry) {
2921
+ return {
2922
+ status: "error",
2923
+ error: "\u0420\u0435\u0435\u0441\u0442\u0440 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D"
2924
+ };
2925
+ }
2926
+ const executionKey = JSON.stringify({
2927
+ stepId: request.step_id,
2928
+ toolName: request.tool_name,
2929
+ arguments: request.arguments,
2930
+ route: this.currentRoute()
2931
+ });
2932
+ const executionCount = (this.executedToolCounts.get(executionKey) ?? 0) + 1;
2933
+ this.executedToolCounts.set(executionKey, executionCount);
2934
+ if (executionCount > 2) {
2935
+ const error = "\u041F\u043E\u0432\u0442\u043E\u0440\u044F\u044E\u0449\u0435\u0435\u0441\u044F \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043D\u0430 \u044D\u0442\u043E\u0439 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435 \u0431\u044B\u043B\u043E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E.";
2936
+ this.setStatus(error);
2937
+ return {
2938
+ status: "error",
2939
+ error
2940
+ };
2941
+ }
2942
+ this.setStatus(`\u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435: ${request.display_name}`);
2943
+ const outcome = await this.registry.executeTool({
2944
+ tool_name: request.tool_name,
2945
+ display_name: request.display_name,
2946
+ target_summary: request.target_summary,
2947
+ requires_confirmation: request.requires_confirmation,
2948
+ arguments: request.arguments
2949
+ });
2950
+ if (outcome.status === "success") {
2951
+ this.setStatus(`\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043E: ${request.display_name}`);
2952
+ } else if (outcome.status === "cancelled") {
2953
+ this.setStatus(`\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E: ${request.display_name}`);
2954
+ } else {
2955
+ this.setStatus(outcome.error ?? "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435.");
2956
+ }
2957
+ return outcome;
2958
+ }
2959
+ async streamChatStep(path, body, responseMessageId, accumulatedText = "") {
2960
+ if (!this.sessionToken) {
2961
+ throw new Error("\u0421\u0435\u0441\u0441\u0438\u044F \u0432\u0438\u0434\u0436\u0435\u0442\u0430 \u0435\u0449\u0451 \u043D\u0435 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u0430");
2962
+ }
2963
+ const response = await fetch(`${this.apiBaseUrl}${path}`, {
2964
+ method: "POST",
2965
+ headers: {
2966
+ "Content-Type": "application/json",
2967
+ Authorization: `Bearer ${this.sessionToken}`
2968
+ },
2969
+ body: JSON.stringify(body)
2970
+ });
2971
+ if (!response.ok || !response.body) {
2972
+ const payload = await response.json().catch(() => ({}));
2973
+ throw new Error(payload.detail ?? "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u043F\u043E\u0442\u043E\u043A\u043E\u0432\u044B\u0439 \u043E\u0442\u0432\u0435\u0442");
2974
+ }
2975
+ let nextToolRequest = null;
2976
+ let finalText = accumulatedText;
2977
+ let deferredNavigationHref = null;
2978
+ await parseSseStream(response.body, ({ event, data }) => {
2979
+ const payload = JSON.parse(data);
2980
+ if (event === "metadata") {
2981
+ return;
2982
+ }
2983
+ if (event === "status") {
2984
+ const statusPayload = JSON.parse(data);
2985
+ this.setStatus(statusPayload.label);
2986
+ }
2987
+ if (event === "plan") {
2988
+ this.hideThinkingIndicator();
2989
+ this.upsertPlanItem(JSON.parse(data));
2990
+ }
2991
+ if (event === "progress") {
2992
+ this.hideThinkingIndicator();
2993
+ const progress = JSON.parse(data);
2994
+ this.upsertPlanItem(progress.run, progress.label);
2995
+ this.setStatus(progress.label);
2996
+ }
2997
+ if (event === "highlight") {
2998
+ const highlight = JSON.parse(data);
2999
+ this.registry?.highlightTargets(highlight.targets ?? []);
3000
+ }
3001
+ if (event === "delta" && payload.text) {
3002
+ const normalizedChunk = normalizeAssistantStreamChunk(finalText, payload.text);
3003
+ if (!normalizedChunk && !finalText.trim()) {
3004
+ return;
3005
+ }
3006
+ this.hideThinkingIndicator();
3007
+ if (!responseMessageId) {
3008
+ responseMessageId = this.appendMessage("", "assistant", {
3009
+ persist: false,
3010
+ status: "thinking"
3011
+ });
3012
+ }
3013
+ finalText += normalizedChunk;
3014
+ if (responseMessageId) {
3015
+ this.updateMessage(responseMessageId, finalText, "streaming");
3016
+ }
3017
+ }
3018
+ if (event === "tool_request") {
3019
+ this.hideThinkingIndicator();
3020
+ nextToolRequest = JSON.parse(data);
3021
+ }
3022
+ if (event === "error") {
3023
+ this.hideThinkingIndicator();
3024
+ this.setStatus(payload.message ?? "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u043E\u0442\u043E\u043A\u0430 \u0432\u0438\u0434\u0436\u0435\u0442\u0430.");
3025
+ }
3026
+ if (event === "done") {
3027
+ this.hideThinkingIndicator();
3028
+ if (payload.text && !finalText.trim()) {
3029
+ finalText = normalizeAssistantFinalText(payload.text);
3030
+ if (responseMessageId) {
3031
+ this.updateMessage(responseMessageId, finalText, "done");
3032
+ }
3033
+ }
3034
+ this.setStatus("\u0413\u043E\u0442\u043E\u0432 \u043A \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C\u0443 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044E.");
3035
+ }
3036
+ });
3037
+ if (!nextToolRequest) {
3038
+ return finalText;
3039
+ }
3040
+ const toolRequest = nextToolRequest;
3041
+ const outcome = await this.handleToolRequest(toolRequest);
3042
+ const possibleNavigationHref = outcome.status === "success" && outcome.result && typeof outcome.result.href === "string" && outcome.result.deferred_navigation ? outcome.result.href : null;
3043
+ if (possibleNavigationHref) {
3044
+ deferredNavigationHref = possibleNavigationHref;
3045
+ }
3046
+ const nextText = await this.streamChatStep(
3047
+ "/pub/chat/tool-result",
3048
+ {
3049
+ session_id: this.sessionId,
3050
+ tool_call_id: toolRequest.tool_call_id,
3051
+ step_id: toolRequest.step_id,
3052
+ status: outcome.status,
3053
+ result: outcome.result ?? null,
3054
+ error: outcome.error ?? null,
3055
+ page_context: this.currentPageContext()
3056
+ },
3057
+ responseMessageId,
3058
+ finalText
3059
+ );
3060
+ if (deferredNavigationHref) {
3061
+ window.location.assign(deferredNavigationHref);
3062
+ }
3063
+ return nextText;
3064
+ }
3065
+ async sendMessage(message) {
3066
+ if (!this.sessionId || !this.sessionToken) {
3067
+ throw new Error("\u0421\u0435\u0441\u0441\u0438\u044F \u0432\u0438\u0434\u0436\u0435\u0442\u0430 \u0435\u0449\u0451 \u043D\u0435 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u0430");
3068
+ }
3069
+ if (this.isSending) {
3070
+ this.setStatus("\u0414\u043E\u0436\u0434\u0438\u0442\u0435\u0441\u044C \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E \u043E\u0442\u0432\u0435\u0442\u0430.");
3071
+ return "";
3072
+ }
3073
+ this.isSending = true;
3074
+ this.showThinkingIndicator();
3075
+ this.syncVoiceState();
3076
+ this.renderReactApp();
3077
+ const requestHistory = this.recentHistoryForRequest();
3078
+ let responseMessageId = null;
3079
+ try {
3080
+ this.appendMessage(message, "user");
3081
+ const finalText = await this.streamChatStep(
3082
+ "/pub/chat",
3083
+ {
3084
+ session_id: this.sessionId,
3085
+ message,
3086
+ history: requestHistory,
3087
+ page_context: this.currentPageContext()
3088
+ },
3089
+ responseMessageId
3090
+ );
3091
+ this.finalizeAssistantMessage(finalText, { persist: false });
3092
+ if (finalText.trim()) {
3093
+ this.rememberMessage({ role: "assistant", text: finalText });
3094
+ }
3095
+ return finalText;
3096
+ } catch (error) {
3097
+ const messageText = error instanceof Error ? error.message : "Blinq \u043D\u0435 \u0441\u043C\u043E\u0433 \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C.";
3098
+ this.finalizeAssistantMessage(messageText, { persist: false });
3099
+ this.rememberMessage({ role: "assistant", text: messageText });
3100
+ throw error;
3101
+ } finally {
3102
+ this.hideThinkingIndicator();
3103
+ this.isSending = false;
3104
+ this.syncVoiceState();
3105
+ this.renderReactApp();
3106
+ }
3107
+ }
3108
+ async continueActiveRun() {
3109
+ if (!this.sessionId || !this.sessionToken || !this.activeRun || this.isSending) {
3110
+ return;
3111
+ }
3112
+ if (this.activeRun.status !== "awaiting_navigation") {
3113
+ return;
3114
+ }
3115
+ if (this.activeRun.expected_route && this.currentRoute() !== this.activeRun.expected_route) {
3116
+ this.setStatus(this.activeRun.status_reason ?? "\u0416\u0434\u0443 \u043F\u0435\u0440\u0435\u0445\u043E\u0434 \u043D\u0430 \u043D\u0443\u0436\u043D\u0443\u044E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443.");
3117
+ return;
3118
+ }
3119
+ this.isSending = true;
3120
+ this.showThinkingIndicator();
3121
+ this.renderReactApp();
3122
+ try {
3123
+ const finalText = await this.streamChatStep(
3124
+ "/pub/chat",
3125
+ {
3126
+ session_id: this.sessionId,
3127
+ message: "",
3128
+ continue_active_run: true,
3129
+ history: this.recentHistoryForRequest(),
3130
+ page_context: this.currentPageContext()
3131
+ },
3132
+ null
3133
+ );
3134
+ this.finalizeAssistantMessage(finalText);
3135
+ } finally {
3136
+ this.hideThinkingIndicator();
3137
+ this.isSending = false;
3138
+ this.renderReactApp();
3139
+ }
3140
+ }
3141
+ async respondToPlan(approved) {
3142
+ if (!this.sessionId || !this.sessionToken || !this.activeRun) {
3143
+ return;
3144
+ }
3145
+ if (this.isSending) {
3146
+ return;
3147
+ }
3148
+ this.isSending = true;
3149
+ this.showThinkingIndicator();
3150
+ this.renderReactApp();
3151
+ try {
3152
+ const finalText = await this.streamChatStep(
3153
+ "/pub/plan/approve",
3154
+ {
3155
+ session_id: this.sessionId,
3156
+ goal_run_id: this.activeRun.id,
3157
+ approved,
3158
+ page_context: this.currentPageContext()
3159
+ },
3160
+ null
3161
+ );
3162
+ if (finalText.trim()) {
3163
+ this.finalizeAssistantMessage(finalText, { persist: false });
3164
+ this.rememberMessage({ role: "assistant", text: finalText });
3165
+ }
3166
+ } finally {
3167
+ this.hideThinkingIndicator();
3168
+ this.isSending = false;
3169
+ this.renderReactApp();
3170
+ }
3171
+ }
3172
+ async endSession() {
3173
+ if (!this.sessionId || !this.sessionToken) {
3174
+ return;
3175
+ }
3176
+ await fetch(`${this.apiBaseUrl}/pub/session/end`, {
3177
+ method: "POST",
3178
+ headers: {
3179
+ "Content-Type": "application/json",
3180
+ Authorization: `Bearer ${this.sessionToken}`
3181
+ },
3182
+ body: JSON.stringify({ session_id: this.sessionId }),
3183
+ keepalive: true
3184
+ }).catch(() => void 0);
3185
+ if (isBrowser3()) {
3186
+ window.localStorage.removeItem(this.sessionStorageKey());
3187
+ }
3188
+ }
3189
+ };
3190
+ async function init(options) {
3191
+ const widget = new BlinqWidget(options);
3192
+ return widget.init();
3193
+ }
3194
+ async function initFromConfig(config) {
3195
+ return init(normalizedConfig(config));
3196
+ }
3197
+ async function createBlinqWidget(options) {
3198
+ return init(options);
3199
+ }
3200
+ // Annotate the CommonJS export names for ESM import in node:
3201
+ 0 && (module.exports = {
3202
+ BlinqWidget,
3203
+ createBlinqWidget,
3204
+ init,
3205
+ initFromConfig
3206
+ });
3207
+ //# sourceMappingURL=index.cjs.map