@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.
@@ -0,0 +1,4390 @@
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/browser.ts
31
+ var browser_exports = {};
32
+ __export(browser_exports, {
33
+ BlinqWidgetElement: () => BlinqWidgetElement,
34
+ init: () => init,
35
+ initFromConfig: () => initFromConfig
36
+ });
37
+ module.exports = __toCommonJS(browser_exports);
38
+
39
+ // src/widget-core.ts
40
+ var React2 = __toESM(require("react"), 1);
41
+ var import_client = require("react-dom/client");
42
+
43
+ // src/page-capabilities.ts
44
+ var MAX_VISIBLE_TEXT_LENGTH = 2500;
45
+ var MAX_ACTIONABLE_HINTS = 30;
46
+ var MAX_SEMANTIC_TOOLS = 40;
47
+ function isBrowser() {
48
+ return typeof window !== "undefined" && typeof document !== "undefined";
49
+ }
50
+ function selectedText() {
51
+ if (!isBrowser()) {
52
+ return "";
53
+ }
54
+ return window.getSelection()?.toString().trim() ?? "";
55
+ }
56
+ function visibleText() {
57
+ if (!isBrowser()) {
58
+ return "";
59
+ }
60
+ return document.body.innerText.replace(/\s+/g, " ").trim().slice(0, MAX_VISIBLE_TEXT_LENGTH);
61
+ }
62
+ function normalizedText(value) {
63
+ return (value ?? "").replace(/\s+/g, " ").trim();
64
+ }
65
+ function normalizedWords(...values) {
66
+ return Array.from(
67
+ new Set(
68
+ values.flatMap((value) => normalizedText(value).toLowerCase().split(/[^a-zа-я0-9]+/i)).filter((part) => part.length >= 2)
69
+ )
70
+ );
71
+ }
72
+ function escapeSelectorValue(value) {
73
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
74
+ return CSS.escape(value);
75
+ }
76
+ return value.replace(/["\\]/g, "\\$&");
77
+ }
78
+ function nthOfType(element) {
79
+ let index = 1;
80
+ let sibling = element.previousElementSibling;
81
+ while (sibling) {
82
+ if (sibling.tagName === element.tagName) {
83
+ index += 1;
84
+ }
85
+ sibling = sibling.previousElementSibling;
86
+ }
87
+ return index;
88
+ }
89
+ function buildPathSelector(element) {
90
+ const segments = [];
91
+ let current = element;
92
+ while (current && segments.length < 4 && current !== document.body) {
93
+ const tag = current.tagName.toLowerCase();
94
+ segments.unshift(`${tag}:nth-of-type(${nthOfType(current)})`);
95
+ current = current.parentElement;
96
+ }
97
+ return segments.length > 0 ? `body > ${segments.join(" > ")}` : null;
98
+ }
99
+ function selectorForElement(element) {
100
+ if (!element || !(element instanceof Element)) {
101
+ return null;
102
+ }
103
+ if (element.id) {
104
+ return `#${escapeSelectorValue(element.id)}`;
105
+ }
106
+ const dataTestId = element.getAttribute("data-testid");
107
+ if (dataTestId) {
108
+ return `[data-testid="${escapeSelectorValue(dataTestId)}"]`;
109
+ }
110
+ const name = element.getAttribute("name");
111
+ if (name) {
112
+ return `${element.tagName.toLowerCase()}[name="${escapeSelectorValue(name)}"]`;
113
+ }
114
+ const ariaLabel = element.getAttribute("aria-label");
115
+ if (ariaLabel) {
116
+ return `${element.tagName.toLowerCase()}[aria-label="${escapeSelectorValue(ariaLabel)}"]`;
117
+ }
118
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLButtonElement) {
119
+ const placeholder = element.getAttribute("placeholder");
120
+ if (placeholder) {
121
+ return `${element.tagName.toLowerCase()}[placeholder="${escapeSelectorValue(placeholder)}"]`;
122
+ }
123
+ }
124
+ return buildPathSelector(element);
125
+ }
126
+ function contextTextForElement(element) {
127
+ const contextualContainer = element.closest("li, tr, [role='listitem'], [data-testid], article, section, form") ?? element.parentElement;
128
+ if (!contextualContainer) {
129
+ return void 0;
130
+ }
131
+ const text = normalizedText(contextualContainer.textContent);
132
+ if (!text) {
133
+ return void 0;
134
+ }
135
+ return text.slice(0, 240);
136
+ }
137
+ function hintFromElement(element) {
138
+ const selector = selectorForElement(element);
139
+ if (!selector) {
140
+ return null;
141
+ }
142
+ const formSelector = selectorForElement(element.closest("form"));
143
+ const textContent = normalizedText(element.textContent);
144
+ const contextText = contextTextForElement(element);
145
+ if (element instanceof HTMLAnchorElement && element.href) {
146
+ return {
147
+ type: "link",
148
+ label: textContent || element.getAttribute("aria-label") || element.href,
149
+ selector,
150
+ href: element.href,
151
+ form_selector: formSelector ?? void 0,
152
+ context_text: contextText
153
+ };
154
+ }
155
+ if (element instanceof HTMLTextAreaElement) {
156
+ return {
157
+ type: "textarea",
158
+ label: textContent || element.getAttribute("aria-label") || void 0,
159
+ selector,
160
+ placeholder: element.placeholder || void 0,
161
+ form_selector: formSelector ?? void 0,
162
+ context_text: contextText
163
+ };
164
+ }
165
+ if (element instanceof HTMLInputElement) {
166
+ const type = element.type === "submit" ? "submit" : "input";
167
+ return {
168
+ type,
169
+ label: textContent || element.value || element.getAttribute("aria-label") || void 0,
170
+ selector,
171
+ placeholder: element.placeholder || void 0,
172
+ form_selector: formSelector ?? void 0,
173
+ context_text: contextText,
174
+ input_type: element.type || void 0
175
+ };
176
+ }
177
+ if (element instanceof HTMLButtonElement || element.getAttribute("role") === "button") {
178
+ return {
179
+ type: element instanceof HTMLButtonElement && element.type === "submit" ? "submit" : "button",
180
+ label: textContent || element.getAttribute("aria-label") || void 0,
181
+ selector,
182
+ form_selector: formSelector ?? void 0,
183
+ context_text: contextText
184
+ };
185
+ }
186
+ return null;
187
+ }
188
+ function collectActionableElements() {
189
+ if (!isBrowser()) {
190
+ return [];
191
+ }
192
+ const elements = Array.from(
193
+ document.querySelectorAll("button, a[href], input:not([type='hidden']), textarea, [role='button']")
194
+ );
195
+ const hints = [];
196
+ for (const element of elements) {
197
+ const hint = hintFromElement(element);
198
+ if (!hint) {
199
+ continue;
200
+ }
201
+ hints.push(hint);
202
+ if (hints.length >= MAX_ACTIONABLE_HINTS) {
203
+ break;
204
+ }
205
+ }
206
+ return hints;
207
+ }
208
+ function slugPart(value) {
209
+ return normalizedText(value).toLowerCase().replace(/[^a-zа-я0-9]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 48);
210
+ }
211
+ function hashValue(value) {
212
+ let hash = 0;
213
+ for (let index = 0; index < value.length; index += 1) {
214
+ hash = hash * 31 + value.charCodeAt(index) >>> 0;
215
+ }
216
+ return hash.toString(36);
217
+ }
218
+ function makeToolName(kind, seed) {
219
+ const slug = slugPart(seed) || "target";
220
+ return `blinq.semantic.${kind}.${slug}.${hashValue(`${kind}:${seed}`)}`;
221
+ }
222
+ function looksLikeAddLabel(value) {
223
+ const text = value.toLowerCase();
224
+ return /(добав|add|create|new|созда|қос|енгіз)/.test(text);
225
+ }
226
+ function looksLikeDeleteLabel(value) {
227
+ const text = value.toLowerCase();
228
+ return /(удал|delete|remove|өшір|жой)/.test(text);
229
+ }
230
+ function looksLikeClearCompletedLabel(value) {
231
+ const text = value.toLowerCase();
232
+ return /(очист|clear|тазала).*(выполн|completed|аяқталған)|(completed).*(clear|remove)/.test(text);
233
+ }
234
+ function looksLikeGenericActionLabel(value) {
235
+ const text = value.toLowerCase();
236
+ return /(open|view|edit|archive|start|run|save|send|submit|launch|continue|next|apply|confirm|перейд|открой|посмотр|измен|архив|запуст|сохран|отправ|продолж|далее|примен|подтверд|аш|өт|сақта|жібер|жалғастыр|таңда)/.test(
237
+ text
238
+ );
239
+ }
240
+ function buttonLikeText(element) {
241
+ if (element instanceof HTMLInputElement) {
242
+ return normalizedText(element.value || element.getAttribute("aria-label"));
243
+ }
244
+ return normalizedText(element.textContent || element.getAttribute("aria-label"));
245
+ }
246
+ function deriveItemTitle(container) {
247
+ const cloned = container.cloneNode(true);
248
+ cloned.querySelectorAll("button, a, [role='button']").forEach((node) => node.remove());
249
+ const text = normalizedText(cloned.textContent);
250
+ if (text) {
251
+ return text.slice(0, 120);
252
+ }
253
+ return normalizedText(container.textContent).slice(0, 120);
254
+ }
255
+ function collectForms() {
256
+ if (!isBrowser()) {
257
+ return [];
258
+ }
259
+ const forms = Array.from(document.querySelectorAll("form")).slice(0, 8);
260
+ const collected = [];
261
+ for (const form of forms) {
262
+ const selector = selectorForElement(form);
263
+ if (!selector) {
264
+ continue;
265
+ }
266
+ const field = form.querySelector(
267
+ "input:not([type='hidden']):not([type='checkbox']):not([type='radio']):not([type='submit']), textarea"
268
+ );
269
+ const submit = form.querySelector(
270
+ "button[type='submit'], input[type='submit'], button:not([type])"
271
+ );
272
+ const fieldSelector = selectorForElement(field);
273
+ const submitSelector = selectorForElement(submit);
274
+ const fieldLabel = normalizedText(field?.getAttribute("aria-label")) || normalizedText(field?.getAttribute("placeholder")) || normalizedText(field?.getAttribute("name"));
275
+ const submitLabel = submit ? buttonLikeText(submit) : "";
276
+ const title = submitLabel || fieldLabel || normalizedText(form.getAttribute("aria-label")) || selector;
277
+ collected.push({
278
+ selector,
279
+ title,
280
+ context_text: contextTextForElement(form),
281
+ field_selector: fieldSelector ?? void 0,
282
+ field_label: fieldLabel || void 0,
283
+ submit_selector: submitSelector ?? void 0,
284
+ submit_label: submitLabel || void 0
285
+ });
286
+ }
287
+ return collected;
288
+ }
289
+ function collectListItems() {
290
+ if (!isBrowser()) {
291
+ return [];
292
+ }
293
+ const candidates = Array.from(
294
+ document.querySelectorAll("li, [role='listitem'], tr, [data-testid='todo-item']")
295
+ ).slice(0, 20);
296
+ const items = [];
297
+ for (const container of candidates) {
298
+ const selector = selectorForElement(container);
299
+ if (!selector) {
300
+ continue;
301
+ }
302
+ const contextText = normalizedText(container.textContent);
303
+ if (!contextText) {
304
+ continue;
305
+ }
306
+ const actions = [];
307
+ const toggleControl = container.querySelector("input[type='checkbox']");
308
+ const toggleSelector = selectorForElement(toggleControl);
309
+ if (toggleSelector) {
310
+ actions.push({
311
+ kind: "toggle-item",
312
+ selector: toggleSelector,
313
+ label: "\u041E\u0442\u043C\u0435\u0442\u0438\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443",
314
+ target_summary: contextText.slice(0, 160)
315
+ });
316
+ }
317
+ const deleteButton = Array.from(
318
+ container.querySelectorAll(
319
+ "button, a, input[type='button'], input[type='submit']"
320
+ )
321
+ ).find((element) => looksLikeDeleteLabel(buttonLikeText(element)));
322
+ const deleteSelector = selectorForElement(deleteButton ?? null);
323
+ if (deleteSelector) {
324
+ actions.push({
325
+ kind: "delete-item",
326
+ selector: deleteSelector,
327
+ label: buttonLikeText(deleteButton ?? container) || "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442",
328
+ target_summary: contextText.slice(0, 160)
329
+ });
330
+ }
331
+ const openLink = Array.from(container.querySelectorAll("a[href]"))[0];
332
+ const openSelector = selectorForElement(openLink);
333
+ if (openSelector && openLink?.href) {
334
+ actions.push({
335
+ kind: "open-link",
336
+ selector: openSelector,
337
+ href: openLink.href,
338
+ label: buttonLikeText(openLink) || openLink.href,
339
+ target_summary: contextText.slice(0, 160)
340
+ });
341
+ }
342
+ if (actions.length === 0) {
343
+ continue;
344
+ }
345
+ items.push({
346
+ selector,
347
+ title: deriveItemTitle(container),
348
+ context_text: contextText.slice(0, 160),
349
+ actions
350
+ });
351
+ }
352
+ return items;
353
+ }
354
+ function collectSemanticTools(actionableElements, pageGraph) {
355
+ const tools = [];
356
+ const usedSelectors = /* @__PURE__ */ new Set();
357
+ const usedHrefs = /* @__PURE__ */ new Set();
358
+ for (const form of pageGraph.forms) {
359
+ if (form.field_selector && (form.submit_selector || form.selector)) {
360
+ const submitSeed = `${form.selector}:${form.submit_selector ?? form.title}`;
361
+ if (looksLikeAddLabel(form.submit_label || form.title)) {
362
+ tools.push({
363
+ name: makeToolName("add-item", submitSeed),
364
+ kind: "add-item",
365
+ title: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442",
366
+ 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}.`,
367
+ target_summary: form.context_text || form.title,
368
+ 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"),
369
+ input_schema: {
370
+ type: "object",
371
+ properties: {
372
+ value: { type: "string" }
373
+ },
374
+ required: ["value"]
375
+ },
376
+ metadata: {
377
+ field_selector: form.field_selector,
378
+ submit_selector: form.submit_selector,
379
+ form_selector: form.selector,
380
+ field_label: form.field_label
381
+ }
382
+ });
383
+ }
384
+ if (form.submit_selector) {
385
+ usedSelectors.add(form.submit_selector);
386
+ tools.push({
387
+ name: makeToolName("submit-form", submitSeed),
388
+ kind: "submit-form",
389
+ 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",
390
+ description: `\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0444\u043E\u0440\u043C\u0443 ${form.title}.`,
391
+ target_summary: form.context_text || form.title,
392
+ 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"),
393
+ metadata: {
394
+ selector: form.selector,
395
+ submit_selector: form.submit_selector,
396
+ form_selector: form.selector
397
+ }
398
+ });
399
+ }
400
+ usedSelectors.add(form.selector);
401
+ if (form.field_selector) {
402
+ usedSelectors.add(form.field_selector);
403
+ }
404
+ }
405
+ }
406
+ for (const item of pageGraph.list_items) {
407
+ for (const action of item.actions) {
408
+ const seed = `${item.selector}:${action.kind}:${action.selector ?? action.href ?? item.title}`;
409
+ if (action.kind === "delete-item" && action.selector) {
410
+ usedSelectors.add(action.selector);
411
+ tools.push({
412
+ name: makeToolName("delete-item", seed),
413
+ kind: "delete-item",
414
+ title: `\u0423\u0434\u0430\u043B\u0438\u0442\u044C: ${item.title}`,
415
+ description: `\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442 ${item.title}.`,
416
+ target_summary: item.context_text,
417
+ 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"),
418
+ metadata: {
419
+ selector: action.selector,
420
+ item_title: item.title,
421
+ item_context: item.context_text
422
+ }
423
+ });
424
+ }
425
+ if (action.kind === "toggle-item" && action.selector) {
426
+ usedSelectors.add(action.selector);
427
+ tools.push({
428
+ name: makeToolName("toggle-item", seed),
429
+ kind: "toggle-item",
430
+ title: `\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0441\u0442\u0430\u0442\u0443\u0441: ${item.title}`,
431
+ 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}.`,
432
+ target_summary: item.context_text,
433
+ 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"),
434
+ metadata: {
435
+ selector: action.selector,
436
+ item_title: item.title,
437
+ item_context: item.context_text
438
+ }
439
+ });
440
+ }
441
+ if (action.kind === "open-link" && action.href) {
442
+ if (action.selector) {
443
+ usedSelectors.add(action.selector);
444
+ }
445
+ usedHrefs.add(action.href);
446
+ tools.push({
447
+ name: makeToolName("open-link", seed),
448
+ kind: "open-link",
449
+ title: `\u041E\u0442\u043A\u0440\u044B\u0442\u044C: ${item.title}`,
450
+ description: `\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 \u0434\u043B\u044F ${item.title}.`,
451
+ target_summary: item.context_text,
452
+ 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"),
453
+ metadata: {
454
+ href: action.href,
455
+ selector: action.selector
456
+ }
457
+ });
458
+ }
459
+ }
460
+ }
461
+ for (const hint of actionableElements) {
462
+ const label = normalizedText(hint.label);
463
+ const contextText = normalizedText(hint.context_text);
464
+ const selector = hint.selector;
465
+ if (usedSelectors.has(selector)) {
466
+ continue;
467
+ }
468
+ if (hint.type === "button" && label && looksLikeClearCompletedLabel(`${label} ${hint.context_text ?? ""}`)) {
469
+ usedSelectors.add(selector);
470
+ tools.push({
471
+ name: makeToolName("clear-completed", `${selector}:${label}`),
472
+ kind: "clear-completed",
473
+ title: label,
474
+ 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}.`,
475
+ target_summary: hint.context_text || label,
476
+ 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"),
477
+ metadata: {
478
+ selector
479
+ }
480
+ });
481
+ }
482
+ if (hint.type === "link" && hint.href) {
483
+ if (usedHrefs.has(hint.href)) {
484
+ continue;
485
+ }
486
+ usedSelectors.add(selector);
487
+ usedHrefs.add(hint.href);
488
+ tools.push({
489
+ name: makeToolName("open-link", `${selector}:${hint.href}`),
490
+ kind: "open-link",
491
+ title: label || hint.href,
492
+ description: `\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 ${label || hint.href}.`,
493
+ target_summary: hint.context_text || label || hint.href,
494
+ 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"),
495
+ metadata: {
496
+ href: hint.href,
497
+ selector
498
+ }
499
+ });
500
+ }
501
+ if (hint.type === "input" && hint.input_type === "checkbox") {
502
+ usedSelectors.add(selector);
503
+ tools.push({
504
+ name: makeToolName("toggle-item", `${selector}:${label}:${contextText}`),
505
+ kind: "toggle-item",
506
+ title: label || "\u041F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0444\u043B\u0430\u0436\u043E\u043A",
507
+ 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}.`,
508
+ target_summary: contextText || label || selector,
509
+ 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"),
510
+ metadata: {
511
+ selector,
512
+ item_title: label || contextText || selector,
513
+ item_context: contextText || label || selector
514
+ }
515
+ });
516
+ }
517
+ if (hint.type === "submit" && (hint.form_selector || selector)) {
518
+ usedSelectors.add(selector);
519
+ if (hint.form_selector) {
520
+ usedSelectors.add(hint.form_selector);
521
+ }
522
+ tools.push({
523
+ name: makeToolName("submit-form", `${hint.form_selector ?? selector}:${label || contextText}`),
524
+ kind: "submit-form",
525
+ title: label || "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0444\u043E\u0440\u043C\u0443",
526
+ description: `\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0444\u043E\u0440\u043C\u0443 \u0447\u0435\u0440\u0435\u0437 ${label || selector}.`,
527
+ target_summary: contextText || label || selector,
528
+ 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"),
529
+ metadata: {
530
+ selector: hint.form_selector || selector,
531
+ submit_selector: selector,
532
+ form_selector: hint.form_selector || selector
533
+ }
534
+ });
535
+ }
536
+ if (hint.type === "button" && label && !looksLikeDeleteLabel(`${label} ${contextText}`) && !looksLikeClearCompletedLabel(`${label} ${contextText}`) && looksLikeGenericActionLabel(`${label} ${contextText}`)) {
537
+ usedSelectors.add(selector);
538
+ tools.push({
539
+ name: makeToolName("activate-element", `${selector}:${label}:${contextText}`),
540
+ kind: "activate-element",
541
+ title: label,
542
+ 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}.`,
543
+ target_summary: contextText || label,
544
+ 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"),
545
+ metadata: {
546
+ selector,
547
+ label
548
+ }
549
+ });
550
+ }
551
+ }
552
+ return tools.slice(0, MAX_SEMANTIC_TOOLS);
553
+ }
554
+ function collectNormalizedPageContext() {
555
+ const actionableElements = collectActionableElements();
556
+ const pageGraph = {
557
+ forms: collectForms(),
558
+ list_items: collectListItems(),
559
+ standalone_actions: actionableElements.filter(
560
+ (hint) => hint.type === "button" || hint.type === "submit" || hint.type === "input" && hint.input_type === "checkbox"
561
+ )
562
+ };
563
+ const semanticTools = collectSemanticTools(actionableElements, pageGraph);
564
+ return {
565
+ url: isBrowser() ? window.location.href : "",
566
+ title: isBrowser() ? document.title : "",
567
+ selection: selectedText(),
568
+ visible_text: visibleText(),
569
+ actionable_elements: actionableElements,
570
+ page_graph: pageGraph,
571
+ semantic_tools: semanticTools
572
+ };
573
+ }
574
+
575
+ // src/page-tools.ts
576
+ function isBrowser2() {
577
+ return typeof window !== "undefined" && typeof document !== "undefined";
578
+ }
579
+ function selectedText2() {
580
+ if (!isBrowser2()) {
581
+ return "";
582
+ }
583
+ return window.getSelection()?.toString().trim() ?? "";
584
+ }
585
+ function normalizedText2(value) {
586
+ return (value ?? "").replace(/\s+/g, " ").trim();
587
+ }
588
+ function setFormFieldValue(target, value) {
589
+ const prototype = target instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
590
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, "value");
591
+ if (descriptor?.set) {
592
+ descriptor.set.call(target, value);
593
+ return;
594
+ }
595
+ target.value = value;
596
+ }
597
+ function associatedForm(target) {
598
+ return target instanceof HTMLFormElement ? target : target.closest("form");
599
+ }
600
+ function submitAssociatedForm(target) {
601
+ const form = associatedForm(target);
602
+ if (!form) {
603
+ 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");
604
+ }
605
+ const isSubmitter = target instanceof HTMLButtonElement || target instanceof HTMLInputElement && target.type === "submit";
606
+ if (typeof form.requestSubmit === "function") {
607
+ if (isSubmitter) {
608
+ form.requestSubmit(target);
609
+ } else {
610
+ form.requestSubmit();
611
+ }
612
+ return;
613
+ }
614
+ const submitEvent = new Event("submit", { bubbles: true, cancelable: true });
615
+ const shouldContinue = form.dispatchEvent(submitEvent);
616
+ if (shouldContinue) {
617
+ form.submit();
618
+ }
619
+ }
620
+ async function waitForDomSettle() {
621
+ await new Promise((resolve) => window.setTimeout(resolve, 80));
622
+ }
623
+ function bodyTextIncludes(query) {
624
+ if (!isBrowser2()) {
625
+ return false;
626
+ }
627
+ const haystack = normalizedText2(document.body.innerText).toLowerCase();
628
+ return haystack.includes(normalizedText2(query).toLowerCase());
629
+ }
630
+ async function verifyFormSubmission(target) {
631
+ const form = associatedForm(target);
632
+ if (!form) {
633
+ 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");
634
+ }
635
+ const successSelector = normalizedText2(form.dataset.blinqSuccessSelector);
636
+ const successText = normalizedText2(form.dataset.blinqSuccessText);
637
+ if (successSelector) {
638
+ const successTarget = document.querySelector(successSelector);
639
+ if (!successTarget) {
640
+ 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}`);
641
+ }
642
+ const content = normalizedText2(successTarget.textContent || successTarget.innerText);
643
+ if (successText && !content.toLowerCase().includes(successText.toLowerCase())) {
644
+ 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");
645
+ }
646
+ return {
647
+ submitted: true,
648
+ verified: true,
649
+ success_selector: successSelector,
650
+ success_text: content
651
+ };
652
+ }
653
+ if (successText) {
654
+ if (!bodyTextIncludes(successText)) {
655
+ 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");
656
+ }
657
+ return {
658
+ submitted: true,
659
+ verified: true,
660
+ success_text: successText
661
+ };
662
+ }
663
+ return { submitted: true };
664
+ }
665
+ function findTarget(selector) {
666
+ const target = document.querySelector(selector);
667
+ if (!target) {
668
+ throw new Error(`\u042D\u043B\u0435\u043C\u0435\u043D\u0442 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${selector}`);
669
+ }
670
+ return target;
671
+ }
672
+ function isDuplicateNativeToolError(error) {
673
+ if (!(error instanceof Error)) {
674
+ return false;
675
+ }
676
+ const message = error.message.toLowerCase();
677
+ return message.includes("duplicate tool name") || message.includes("already registered") || message.includes("already exists");
678
+ }
679
+ function metadataString(descriptor, key) {
680
+ const value = descriptor.metadata?.[key];
681
+ return typeof value === "string" ? value : "";
682
+ }
683
+ function buildSemanticToolDefinition(descriptor) {
684
+ const summary = descriptor.target_summary || descriptor.description;
685
+ if (descriptor.kind === "add-item") {
686
+ return {
687
+ capability: {
688
+ name: descriptor.name,
689
+ description: descriptor.description,
690
+ read_only: false
691
+ },
692
+ inputSchema: descriptor.input_schema,
693
+ describeTarget: () => summary,
694
+ execute: async (input) => {
695
+ const fieldSelector = metadataString(descriptor, "field_selector");
696
+ const submitSelector = metadataString(descriptor, "submit_selector");
697
+ const formSelector = metadataString(descriptor, "form_selector");
698
+ const value = String(input.value ?? "").trim();
699
+ if (!fieldSelector || !value) {
700
+ 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");
701
+ }
702
+ const fieldTarget = findTarget(fieldSelector);
703
+ fieldTarget.focus();
704
+ setFormFieldValue(fieldTarget, value);
705
+ fieldTarget.dispatchEvent(new Event("input", { bubbles: true }));
706
+ fieldTarget.dispatchEvent(new Event("change", { bubbles: true }));
707
+ const submitTarget = submitSelector ? findTarget(submitSelector) : formSelector ? findTarget(formSelector) : fieldTarget;
708
+ submitAssociatedForm(submitTarget);
709
+ await waitForDomSettle();
710
+ if (!bodyTextIncludes(value)) {
711
+ 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`);
712
+ }
713
+ return { success: true, value, verified: true };
714
+ }
715
+ };
716
+ }
717
+ if (descriptor.kind === "delete-item") {
718
+ return {
719
+ capability: {
720
+ name: descriptor.name,
721
+ description: descriptor.description,
722
+ read_only: false
723
+ },
724
+ describeTarget: () => summary,
725
+ execute: async () => {
726
+ const selector = metadataString(descriptor, "selector");
727
+ const itemTitle = metadataString(descriptor, "item_title") || summary;
728
+ findTarget(selector).click();
729
+ await waitForDomSettle();
730
+ if (itemTitle && bodyTextIncludes(itemTitle)) {
731
+ 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`);
732
+ }
733
+ return { success: true, verified: true };
734
+ }
735
+ };
736
+ }
737
+ if (descriptor.kind === "toggle-item") {
738
+ return {
739
+ capability: {
740
+ name: descriptor.name,
741
+ description: descriptor.description,
742
+ read_only: false
743
+ },
744
+ describeTarget: () => summary,
745
+ execute: async () => {
746
+ const selector = metadataString(descriptor, "selector");
747
+ const target = findTarget(selector);
748
+ if (!(target instanceof HTMLInputElement) || target.type !== "checkbox") {
749
+ 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");
750
+ }
751
+ const before = target.checked;
752
+ target.click();
753
+ await waitForDomSettle();
754
+ if (target.checked === before) {
755
+ 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");
756
+ }
757
+ return { success: true, checked: target.checked, verified: true };
758
+ }
759
+ };
760
+ }
761
+ if (descriptor.kind === "clear-completed") {
762
+ return {
763
+ capability: {
764
+ name: descriptor.name,
765
+ description: descriptor.description,
766
+ read_only: false
767
+ },
768
+ describeTarget: () => summary,
769
+ execute: async () => {
770
+ const selector = metadataString(descriptor, "selector");
771
+ const checkedBefore = document.querySelectorAll("input[type='checkbox']:checked").length;
772
+ findTarget(selector).click();
773
+ await waitForDomSettle();
774
+ const checkedAfter = document.querySelectorAll("input[type='checkbox']:checked").length;
775
+ if (checkedAfter >= checkedBefore && checkedBefore > 0) {
776
+ 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");
777
+ }
778
+ return { success: true, checked_before: checkedBefore, checked_after: checkedAfter, verified: true };
779
+ }
780
+ };
781
+ }
782
+ if (descriptor.kind === "open-link") {
783
+ return {
784
+ capability: {
785
+ name: descriptor.name,
786
+ description: descriptor.description,
787
+ read_only: false
788
+ },
789
+ describeTarget: () => summary,
790
+ execute: async () => {
791
+ const href = metadataString(descriptor, "href");
792
+ if (!href) {
793
+ 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");
794
+ }
795
+ return { success: true, href, deferred_navigation: true };
796
+ }
797
+ };
798
+ }
799
+ if (descriptor.kind === "submit-form") {
800
+ return {
801
+ capability: {
802
+ name: descriptor.name,
803
+ description: descriptor.description,
804
+ read_only: false
805
+ },
806
+ describeTarget: () => summary,
807
+ execute: async () => {
808
+ const selector = metadataString(descriptor, "form_selector") || metadataString(descriptor, "selector");
809
+ const target = findTarget(selector);
810
+ submitAssociatedForm(target);
811
+ await waitForDomSettle();
812
+ return { success: true, selector, ...await verifyFormSubmission(target) };
813
+ }
814
+ };
815
+ }
816
+ if (descriptor.kind === "activate-element") {
817
+ return {
818
+ capability: {
819
+ name: descriptor.name,
820
+ description: descriptor.description,
821
+ read_only: false
822
+ },
823
+ describeTarget: () => summary,
824
+ execute: async () => {
825
+ const selector = metadataString(descriptor, "selector");
826
+ findTarget(selector).click();
827
+ return { success: true, selector };
828
+ }
829
+ };
830
+ }
831
+ return null;
832
+ }
833
+ function detectNativeWebMcpSupport() {
834
+ if (!isBrowser2() || !window.isSecureContext) {
835
+ return false;
836
+ }
837
+ const navigatorWithModelContext = navigator;
838
+ return typeof navigatorWithModelContext.modelContext?.registerTool === "function";
839
+ }
840
+ var PageToolRegistry = class {
841
+ confirmationHandler;
842
+ tools = /* @__PURE__ */ new Map();
843
+ nativeRegistrations = [];
844
+ latestPageContext = null;
845
+ nativeToolsActive = false;
846
+ highlightLayer = null;
847
+ highlightTimer = null;
848
+ constructor(confirmationHandler) {
849
+ this.confirmationHandler = confirmationHandler;
850
+ this.refreshToolCatalog();
851
+ }
852
+ buildBaseTools() {
853
+ const tools = /* @__PURE__ */ new Map();
854
+ tools.set("blinq.read_visible_page_context", {
855
+ capability: {
856
+ name: "blinq.read_visible_page_context",
857
+ 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.",
858
+ read_only: true
859
+ },
860
+ execute: async () => ({ page_context: this.collectPageContext() })
861
+ });
862
+ tools.set("blinq.read_selection", {
863
+ capability: {
864
+ name: "blinq.read_selection",
865
+ 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.",
866
+ read_only: true
867
+ },
868
+ execute: async () => ({ selection: selectedText2() })
869
+ });
870
+ tools.set("blinq.highlight_element", {
871
+ capability: {
872
+ name: "blinq.highlight_element",
873
+ 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.",
874
+ read_only: true
875
+ },
876
+ inputSchema: {
877
+ type: "object",
878
+ properties: {
879
+ selector: { type: "string" },
880
+ label: { type: "string" }
881
+ },
882
+ required: ["selector"]
883
+ },
884
+ describeTarget: (input) => `\u041F\u043E\u0434\u0441\u0432\u0435\u0442\u0438\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442: ${String(input.selector ?? "")}`,
885
+ execute: async (input) => {
886
+ const selector = String(input.selector ?? "");
887
+ const label = String(input.label ?? "").trim() || void 0;
888
+ const highlighted = this.highlightTargets([{ selector, label }]);
889
+ if (!highlighted) {
890
+ 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}`);
891
+ }
892
+ return { success: true, selector, label: label ?? null };
893
+ }
894
+ });
895
+ tools.set("blinq.scroll_to_element", {
896
+ capability: {
897
+ name: "blinq.scroll_to_element",
898
+ 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.",
899
+ read_only: false
900
+ },
901
+ inputSchema: { type: "object", properties: { selector: { type: "string" } }, required: ["selector"] },
902
+ describeTarget: (input) => `\u041F\u0440\u043E\u043A\u0440\u0443\u0442\u043A\u0430 \u043A \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u0443: ${String(input.selector ?? "")}`,
903
+ execute: async (input) => {
904
+ const selector = String(input.selector ?? "");
905
+ const target = findTarget(selector);
906
+ target.scrollIntoView({ behavior: "smooth", block: "center" });
907
+ return { success: true, selector };
908
+ }
909
+ });
910
+ tools.set("blinq.click_element", {
911
+ capability: {
912
+ name: "blinq.click_element",
913
+ 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.",
914
+ read_only: false
915
+ },
916
+ inputSchema: { type: "object", properties: { selector: { type: "string" } }, required: ["selector"] },
917
+ describeTarget: (input) => `\u041D\u0430\u0436\u0430\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442: ${String(input.selector ?? "")}`,
918
+ execute: async (input) => {
919
+ const selector = String(input.selector ?? "");
920
+ const target = findTarget(selector);
921
+ const isSubmitButton = target instanceof HTMLButtonElement && target.type === "submit";
922
+ const isSubmitInput = target instanceof HTMLInputElement && target.type === "submit";
923
+ if (isSubmitButton || isSubmitInput) {
924
+ submitAssociatedForm(target);
925
+ await waitForDomSettle();
926
+ return { success: true, selector, ...await verifyFormSubmission(target) };
927
+ }
928
+ target.click();
929
+ await waitForDomSettle();
930
+ return { success: true, selector };
931
+ }
932
+ });
933
+ tools.set("blinq.fill_field", {
934
+ capability: {
935
+ name: "blinq.fill_field",
936
+ 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.",
937
+ read_only: false
938
+ },
939
+ inputSchema: {
940
+ type: "object",
941
+ properties: {
942
+ selector: { type: "string" },
943
+ value: { type: "string" }
944
+ },
945
+ required: ["selector", "value"]
946
+ },
947
+ 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 ?? "")}"`,
948
+ execute: async (input) => {
949
+ const selector = String(input.selector ?? "");
950
+ const value = String(input.value ?? "");
951
+ const target = findTarget(selector);
952
+ target.focus();
953
+ setFormFieldValue(target, value);
954
+ target.dispatchEvent(new Event("input", { bubbles: true }));
955
+ target.dispatchEvent(new Event("change", { bubbles: true }));
956
+ await waitForDomSettle();
957
+ return { success: true, selector, value, verified: target.value === value };
958
+ }
959
+ });
960
+ tools.set("blinq.submit_form", {
961
+ capability: {
962
+ name: "blinq.submit_form",
963
+ 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.",
964
+ read_only: false
965
+ },
966
+ inputSchema: { type: "object", properties: { selector: { type: "string" } }, required: ["selector"] },
967
+ 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 ?? "")}`,
968
+ execute: async (input) => {
969
+ const selector = String(input.selector ?? "");
970
+ const target = findTarget(selector);
971
+ submitAssociatedForm(target);
972
+ await waitForDomSettle();
973
+ return { success: true, selector, ...await verifyFormSubmission(target) };
974
+ }
975
+ });
976
+ tools.set("blinq.open_link", {
977
+ capability: {
978
+ name: "blinq.open_link",
979
+ description: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C URL \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435.",
980
+ read_only: false
981
+ },
982
+ inputSchema: { type: "object", properties: { href: { type: "string" } }, required: ["href"] },
983
+ describeTarget: (input) => `\u041E\u0442\u043A\u0440\u044B\u0442\u044C ${String(input.href ?? "")}`,
984
+ execute: async (input) => {
985
+ const href = String(input.href ?? "");
986
+ if (!href) {
987
+ throw new Error("\u041D\u0443\u0436\u0435\u043D \u0430\u0434\u0440\u0435\u0441 \u0441\u0441\u044B\u043B\u043A\u0438");
988
+ }
989
+ return { success: true, href, deferred_navigation: true };
990
+ }
991
+ });
992
+ return tools;
993
+ }
994
+ refreshToolCatalog() {
995
+ const pageContext = collectNormalizedPageContext();
996
+ const tools = this.buildBaseTools();
997
+ for (const descriptor of pageContext.semantic_tools) {
998
+ const tool = buildSemanticToolDefinition(descriptor);
999
+ if (tool) {
1000
+ tools.set(descriptor.name, tool);
1001
+ }
1002
+ }
1003
+ this.tools.clear();
1004
+ for (const [name, tool] of tools) {
1005
+ this.tools.set(name, tool);
1006
+ }
1007
+ this.latestPageContext = pageContext;
1008
+ return pageContext;
1009
+ }
1010
+ async onMutatingToolSuccess() {
1011
+ this.refreshToolCatalog();
1012
+ if (this.nativeToolsActive) {
1013
+ try {
1014
+ await this.registerNativeTools();
1015
+ } catch {
1016
+ this.nativeToolsActive = false;
1017
+ }
1018
+ }
1019
+ }
1020
+ collectPageContext() {
1021
+ return this.refreshToolCatalog();
1022
+ }
1023
+ getToolCapabilities() {
1024
+ this.refreshToolCatalog();
1025
+ return Array.from(this.tools.values()).map((tool) => tool.capability);
1026
+ }
1027
+ clearHighlights() {
1028
+ if (this.highlightTimer !== null) {
1029
+ window.clearTimeout(this.highlightTimer);
1030
+ this.highlightTimer = null;
1031
+ }
1032
+ this.highlightLayer?.remove();
1033
+ this.highlightLayer = null;
1034
+ }
1035
+ ensureHighlightLayer() {
1036
+ if (this.highlightLayer) {
1037
+ return this.highlightLayer;
1038
+ }
1039
+ const layer = document.createElement("div");
1040
+ layer.setAttribute("data-blinq-highlight-layer", "true");
1041
+ Object.assign(layer.style, {
1042
+ position: "fixed",
1043
+ inset: "0",
1044
+ pointerEvents: "none",
1045
+ zIndex: "2147483646"
1046
+ });
1047
+ document.body.appendChild(layer);
1048
+ this.highlightLayer = layer;
1049
+ return layer;
1050
+ }
1051
+ highlightTargets(targets, options) {
1052
+ if (!isBrowser2() || targets.length === 0) {
1053
+ return false;
1054
+ }
1055
+ this.clearHighlights();
1056
+ const layer = this.ensureHighlightLayer();
1057
+ let highlightedCount = 0;
1058
+ for (const target of targets) {
1059
+ if (!target.selector) {
1060
+ continue;
1061
+ }
1062
+ let element = null;
1063
+ try {
1064
+ element = document.querySelector(target.selector);
1065
+ } catch {
1066
+ element = null;
1067
+ }
1068
+ if (!element) {
1069
+ continue;
1070
+ }
1071
+ const rect = element.getBoundingClientRect();
1072
+ if (rect.width <= 0 || rect.height <= 0) {
1073
+ continue;
1074
+ }
1075
+ const frame = document.createElement("div");
1076
+ Object.assign(frame.style, {
1077
+ position: "fixed",
1078
+ left: `${Math.max(rect.left - 6, 4)}px`,
1079
+ top: `${Math.max(rect.top - 6, 4)}px`,
1080
+ width: `${Math.max(rect.width + 12, 18)}px`,
1081
+ height: `${Math.max(rect.height + 12, 18)}px`,
1082
+ border: "2px solid rgba(17, 17, 17, 0.92)",
1083
+ borderRadius: "16px",
1084
+ background: "rgba(17, 17, 17, 0.05)",
1085
+ boxShadow: "0 0 0 1px rgba(255, 255, 255, 0.85), 0 18px 38px rgba(17, 17, 17, 0.14)",
1086
+ opacity: "0",
1087
+ transform: "scale(0.985)",
1088
+ transition: "opacity 180ms ease, transform 180ms ease"
1089
+ });
1090
+ const glow = document.createElement("div");
1091
+ Object.assign(glow.style, {
1092
+ position: "absolute",
1093
+ inset: "-8px",
1094
+ borderRadius: "20px",
1095
+ border: "1px solid rgba(17, 17, 17, 0.14)",
1096
+ animation: "blinq-highlight-pulse 1.2s ease-in-out infinite"
1097
+ });
1098
+ frame.appendChild(glow);
1099
+ if (target.label) {
1100
+ const badge = document.createElement("div");
1101
+ badge.textContent = target.label;
1102
+ Object.assign(badge.style, {
1103
+ position: "absolute",
1104
+ top: "-14px",
1105
+ left: "10px",
1106
+ maxWidth: "min(220px, 70vw)",
1107
+ padding: "4px 10px",
1108
+ borderRadius: "999px",
1109
+ background: "#111111",
1110
+ color: "#ffffff",
1111
+ fontFamily: '"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
1112
+ fontSize: "11px",
1113
+ fontWeight: "600",
1114
+ lineHeight: "1.3",
1115
+ whiteSpace: "nowrap",
1116
+ overflow: "hidden",
1117
+ textOverflow: "ellipsis",
1118
+ boxShadow: "0 8px 18px rgba(17, 17, 17, 0.16)"
1119
+ });
1120
+ frame.appendChild(badge);
1121
+ }
1122
+ layer.appendChild(frame);
1123
+ window.requestAnimationFrame(() => {
1124
+ frame.style.opacity = "1";
1125
+ frame.style.transform = "scale(1)";
1126
+ });
1127
+ highlightedCount += 1;
1128
+ }
1129
+ if (!document.getElementById("blinq-highlight-style")) {
1130
+ const style = document.createElement("style");
1131
+ style.id = "blinq-highlight-style";
1132
+ style.textContent = `
1133
+ @keyframes blinq-highlight-pulse {
1134
+ 0%, 100% { opacity: 0.55; transform: scale(1); }
1135
+ 50% { opacity: 1; transform: scale(1.02); }
1136
+ }
1137
+ `;
1138
+ document.head.appendChild(style);
1139
+ }
1140
+ if (highlightedCount === 0) {
1141
+ this.clearHighlights();
1142
+ return false;
1143
+ }
1144
+ this.highlightTimer = window.setTimeout(() => {
1145
+ this.clearHighlights();
1146
+ }, options?.durationMs ?? 2200);
1147
+ return true;
1148
+ }
1149
+ canExecuteWriteTools() {
1150
+ return isBrowser2();
1151
+ }
1152
+ async executeTool(request) {
1153
+ let tool = this.tools.get(request.tool_name);
1154
+ if (!tool) {
1155
+ this.refreshToolCatalog();
1156
+ tool = this.tools.get(request.tool_name);
1157
+ }
1158
+ if (!tool) {
1159
+ return {
1160
+ status: "error",
1161
+ 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}`
1162
+ };
1163
+ }
1164
+ const displayName = request.display_name ?? tool.capability.name;
1165
+ const targetSummary = request.target_summary ?? tool.describeTarget?.(request.arguments) ?? tool.capability.description;
1166
+ const requiresConfirmation = request.requires_confirmation ?? !tool.capability.read_only;
1167
+ if (!tool.capability.read_only && requiresConfirmation) {
1168
+ const confirmed = await this.confirmationHandler({
1169
+ displayName,
1170
+ targetSummary,
1171
+ arguments: request.arguments
1172
+ });
1173
+ if (!confirmed) {
1174
+ return {
1175
+ status: "cancelled",
1176
+ 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"
1177
+ };
1178
+ }
1179
+ }
1180
+ try {
1181
+ const result = await tool.execute(request.arguments);
1182
+ if (!tool.capability.read_only) {
1183
+ await this.onMutatingToolSuccess();
1184
+ }
1185
+ return { status: "success", result };
1186
+ } catch (error) {
1187
+ return {
1188
+ status: "error",
1189
+ 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"
1190
+ };
1191
+ }
1192
+ }
1193
+ unregisterNativeTools() {
1194
+ while (this.nativeRegistrations.length > 0) {
1195
+ this.nativeRegistrations.pop()?.abort();
1196
+ }
1197
+ this.nativeToolsActive = false;
1198
+ }
1199
+ async registerNativeTools() {
1200
+ const navigatorWithModelContext = navigator;
1201
+ const modelContext = navigatorWithModelContext.modelContext;
1202
+ if (!window.isSecureContext || !modelContext?.registerTool) {
1203
+ return false;
1204
+ }
1205
+ this.refreshToolCatalog();
1206
+ this.unregisterNativeTools();
1207
+ for (const [toolName, tool] of this.tools) {
1208
+ const controller = new AbortController();
1209
+ try {
1210
+ modelContext.registerTool(
1211
+ {
1212
+ name: toolName,
1213
+ description: tool.capability.description,
1214
+ inputSchema: tool.inputSchema,
1215
+ annotations: {
1216
+ readOnlyHint: tool.capability.read_only
1217
+ },
1218
+ execute: async (input) => {
1219
+ const outcome = await this.executeTool({
1220
+ tool_name: toolName,
1221
+ display_name: tool.capability.name,
1222
+ arguments: input
1223
+ });
1224
+ return outcome;
1225
+ }
1226
+ },
1227
+ { signal: controller.signal }
1228
+ );
1229
+ this.nativeRegistrations.push(controller);
1230
+ } catch (error) {
1231
+ controller.abort();
1232
+ if (isDuplicateNativeToolError(error)) {
1233
+ this.nativeToolsActive = true;
1234
+ continue;
1235
+ }
1236
+ throw error;
1237
+ }
1238
+ }
1239
+ this.nativeToolsActive = true;
1240
+ return true;
1241
+ }
1242
+ destroy() {
1243
+ this.clearHighlights();
1244
+ this.unregisterNativeTools();
1245
+ }
1246
+ };
1247
+
1248
+ // src/embed-shell.tsx
1249
+ var React = __toESM(require("react"), 1);
1250
+ var import_react = require("@assistant-ui/react");
1251
+ var import_jsx_runtime = require("react/jsx-runtime");
1252
+ function cn(...values) {
1253
+ return values.filter(Boolean).join(" ");
1254
+ }
1255
+ function buttonClassName(variant = "default", size = "default") {
1256
+ const variantClass = variant === "outline" ? "blinq-button-outline" : variant === "ghost" ? "blinq-button-ghost" : variant === "destructive" ? "blinq-button-destructive" : "blinq-button-default";
1257
+ const sizeClass = size === "sm" ? "blinq-button-sm" : size === "icon" ? "blinq-button-icon" : size === "icon-sm" ? "blinq-button-icon-sm" : "blinq-button-md";
1258
+ return cn("blinq-button", variantClass, sizeClass);
1259
+ }
1260
+ function badgeClassName(variant = "default") {
1261
+ if (variant === "outline") {
1262
+ return "blinq-badge-pill blinq-badge-pill-outline";
1263
+ }
1264
+ if (variant === "soft") {
1265
+ return "blinq-badge-pill blinq-badge-pill-soft";
1266
+ }
1267
+ if (variant === "accent") {
1268
+ return "blinq-badge-pill blinq-badge-pill-accent";
1269
+ }
1270
+ return "blinq-badge-pill blinq-badge-pill-default";
1271
+ }
1272
+ function Button({
1273
+ className,
1274
+ variant,
1275
+ size,
1276
+ ...props
1277
+ }) {
1278
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: cn(buttonClassName(variant, size), className), ...props });
1279
+ }
1280
+ function Input({ className, ...props }) {
1281
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: cn("blinq-input", className), ...props });
1282
+ }
1283
+ function Card({ className, ...props }) {
1284
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("blinq-card", className), ...props });
1285
+ }
1286
+ function CardHeader({ className, ...props }) {
1287
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("blinq-card-header", className), ...props });
1288
+ }
1289
+ function CardTitle({ className, ...props }) {
1290
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("blinq-card-title", className), ...props });
1291
+ }
1292
+ function CardDescription({ className, ...props }) {
1293
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("blinq-card-description", className), ...props });
1294
+ }
1295
+ function CardContent({ className, ...props }) {
1296
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("blinq-card-content", className), ...props });
1297
+ }
1298
+ function Badge({
1299
+ className,
1300
+ variant,
1301
+ ...props
1302
+ }) {
1303
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn(badgeClassName(variant), className), ...props });
1304
+ }
1305
+ function LauncherIcon() {
1306
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1307
+ "svg",
1308
+ {
1309
+ viewBox: "0 0 24 24",
1310
+ width: "18",
1311
+ height: "18",
1312
+ fill: "none",
1313
+ "aria-hidden": "true",
1314
+ className: "blinq-launcher-agent",
1315
+ children: [
1316
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "blinq-agent-orbit", cx: "12", cy: "12", r: "9" }),
1317
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "blinq-agent-spark", cx: "18.6", cy: "8.2", r: "1.2" }),
1318
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1319
+ "path",
1320
+ {
1321
+ className: "blinq-agent-antenna",
1322
+ d: "M12 4.8V6.8",
1323
+ stroke: "currentColor",
1324
+ strokeWidth: "1.6",
1325
+ strokeLinecap: "round"
1326
+ }
1327
+ ),
1328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "blinq-agent-antenna-dot", cx: "12", cy: "4.1", r: "1.1", fill: "currentColor" }),
1329
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1330
+ "rect",
1331
+ {
1332
+ className: "blinq-agent-head",
1333
+ x: "7",
1334
+ y: "7.4",
1335
+ width: "10",
1336
+ height: "9.2",
1337
+ rx: "3.2",
1338
+ fill: "currentColor"
1339
+ }
1340
+ ),
1341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "blinq-agent-eye", cx: "10.2", cy: "11.3", r: "0.9", fill: "#111111" }),
1342
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "blinq-agent-eye", cx: "13.8", cy: "11.3", r: "0.9", fill: "#111111" }),
1343
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1344
+ "path",
1345
+ {
1346
+ className: "blinq-agent-mouth",
1347
+ d: "M10 14.2c.7.55 1.38.82 2 .82s1.3-.27 2-.82",
1348
+ stroke: "#111111",
1349
+ strokeWidth: "1.3",
1350
+ strokeLinecap: "round"
1351
+ }
1352
+ ),
1353
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1354
+ "path",
1355
+ {
1356
+ className: "blinq-agent-neck",
1357
+ d: "M10.2 17.1h3.6",
1358
+ stroke: "currentColor",
1359
+ strokeWidth: "1.4",
1360
+ strokeLinecap: "round"
1361
+ }
1362
+ )
1363
+ ]
1364
+ }
1365
+ );
1366
+ }
1367
+ function CollapseIcon() {
1368
+ 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" }) });
1369
+ }
1370
+ function MicrophoneIcon({ listening }) {
1371
+ if (listening) {
1372
+ 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" }) });
1373
+ }
1374
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", "aria-hidden": "true", children: [
1375
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1376
+ "path",
1377
+ {
1378
+ 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",
1379
+ fill: "currentColor"
1380
+ }
1381
+ ),
1382
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1383
+ "path",
1384
+ {
1385
+ 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",
1386
+ fill: "currentColor"
1387
+ }
1388
+ )
1389
+ ] });
1390
+ }
1391
+ function SendIcon() {
1392
+ 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)(
1393
+ "path",
1394
+ {
1395
+ d: "M5.5 12h11M12.5 5l6.5 7-6.5 7",
1396
+ stroke: "currentColor",
1397
+ strokeWidth: "1.8",
1398
+ strokeLinecap: "round",
1399
+ strokeLinejoin: "round"
1400
+ }
1401
+ ) });
1402
+ }
1403
+ function ThinkingSparkIcon() {
1404
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "blinq-thinking-dots", "aria-hidden": "true", children: [
1405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
1406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
1407
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {})
1408
+ ] });
1409
+ }
1410
+ function isAssistantMessageRunning(status) {
1411
+ return status?.type === "running";
1412
+ }
1413
+ function itemToThreadMessage(item) {
1414
+ if (item.type === "message") {
1415
+ return {
1416
+ id: item.id,
1417
+ role: item.role,
1418
+ status: item.role === "assistant" ? item.status === "done" ? { type: "complete", reason: "stop" } : { type: "running" } : void 0,
1419
+ content: item.text ? [{ type: "text", text: item.text }] : [],
1420
+ metadata: {
1421
+ custom: {
1422
+ blinqKind: "message"
1423
+ }
1424
+ }
1425
+ };
1426
+ }
1427
+ if (item.type === "plan") {
1428
+ return {
1429
+ id: item.id,
1430
+ role: "assistant",
1431
+ status: item.status === "completed" ? { type: "complete", reason: "stop" } : item.status === "failed" ? { type: "incomplete", reason: "error" } : { type: "running" },
1432
+ content: [{ type: "data", name: "blinq-plan", data: item }],
1433
+ metadata: {
1434
+ custom: {
1435
+ blinqKind: "plan"
1436
+ }
1437
+ }
1438
+ };
1439
+ }
1440
+ return {
1441
+ id: item.id,
1442
+ role: "assistant",
1443
+ status: item.state === "approved" ? { type: "running" } : { type: "complete", reason: "stop" },
1444
+ content: [{ type: "data", name: "blinq-action", data: item }],
1445
+ metadata: {
1446
+ custom: {
1447
+ blinqKind: "action"
1448
+ }
1449
+ }
1450
+ };
1451
+ }
1452
+ function conversationToThreadMessages(items) {
1453
+ return items.map(itemToThreadMessage);
1454
+ }
1455
+ function ThreadMessageBubble({
1456
+ accentColor,
1457
+ onApproveAction,
1458
+ onDeclineAction,
1459
+ onApprovePlan,
1460
+ onDeclinePlan
1461
+ }) {
1462
+ const role = (0, import_react.useMessage)((state) => state.role);
1463
+ const messageId = (0, import_react.useMessage)((state) => state.id);
1464
+ const status = (0, import_react.useMessage)((state) => state.status);
1465
+ const content = (0, import_react.useMessage)((state) => state.content);
1466
+ const isDataOnly = Array.isArray(content) && content.length === 1 && typeof content[0] !== "string" && content[0]?.type === "data";
1467
+ const messageText = React.useMemo(
1468
+ () => content.filter((part) => typeof part !== "string" && part.type === "text").map((part) => part.text).join(""),
1469
+ [content]
1470
+ );
1471
+ const [animateFreshText, setAnimateFreshText] = React.useState(false);
1472
+ const [hasAnimatedStreamingReveal, setHasAnimatedStreamingReveal] = React.useState(false);
1473
+ React.useEffect(() => {
1474
+ setAnimateFreshText(false);
1475
+ setHasAnimatedStreamingReveal(false);
1476
+ }, [messageId]);
1477
+ React.useEffect(() => {
1478
+ if (role !== "assistant" || isDataOnly || !isAssistantMessageRunning(status) || !messageText.trim() || hasAnimatedStreamingReveal) {
1479
+ return;
1480
+ }
1481
+ setAnimateFreshText(true);
1482
+ setHasAnimatedStreamingReveal(true);
1483
+ const timeoutId = window.setTimeout(() => {
1484
+ setAnimateFreshText(false);
1485
+ }, 220);
1486
+ return () => window.clearTimeout(timeoutId);
1487
+ }, [role, isDataOnly, messageText, hasAnimatedStreamingReveal]);
1488
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1489
+ import_react.MessagePrimitive.Root,
1490
+ {
1491
+ className: "blinq-message",
1492
+ "data-role": role,
1493
+ "data-kind": isDataOnly ? "artifact" : "bubble",
1494
+ "data-status": status?.type ?? "complete",
1495
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.MessagePrimitive.Parts, { children: ({ part }) => {
1496
+ if (part.type === "text") {
1497
+ if (!part.text) {
1498
+ return null;
1499
+ }
1500
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1501
+ "div",
1502
+ {
1503
+ className: cn(
1504
+ "blinq-message-copy",
1505
+ role === "assistant" && animateFreshText && "blinq-message-copy-reveal"
1506
+ ),
1507
+ children: part.text
1508
+ }
1509
+ );
1510
+ }
1511
+ if (part.type === "data" && part.name === "blinq-action") {
1512
+ const actionItem = part.data;
1513
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1514
+ ActionCard,
1515
+ {
1516
+ item: actionItem,
1517
+ accentColor,
1518
+ onApprove: () => onApproveAction(actionItem.id),
1519
+ onDecline: () => onDeclineAction(actionItem.id)
1520
+ }
1521
+ );
1522
+ }
1523
+ if (part.type === "data" && part.name === "blinq-plan") {
1524
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1525
+ PlanCard,
1526
+ {
1527
+ item: part.data,
1528
+ onApprove: onApprovePlan,
1529
+ onDecline: onDeclinePlan
1530
+ }
1531
+ );
1532
+ }
1533
+ return null;
1534
+ } })
1535
+ }
1536
+ );
1537
+ }
1538
+ function ThinkingPlaceholder() {
1539
+ 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: [
1540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThinkingSparkIcon, {}),
1541
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "\u0414\u0443\u043C\u0430\u044E \u043D\u0430\u0434 \u043E\u0442\u0432\u0435\u0442\u043E\u043C" })
1542
+ ] }) });
1543
+ }
1544
+ function formatRecordingDuration(durationMs) {
1545
+ const totalSeconds = Math.max(0, Math.floor(durationMs / 1e3));
1546
+ const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, "0");
1547
+ const seconds = String(totalSeconds % 60).padStart(2, "0");
1548
+ return `${minutes}:${seconds}`;
1549
+ }
1550
+ function VoiceWaveform({
1551
+ durationMs,
1552
+ samples
1553
+ }) {
1554
+ const waveform = samples.length > 0 ? samples : [0.18];
1555
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-recording-input", role: "status", "aria-live": "polite", children: [
1556
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-recording-time", children: formatRecordingDuration(durationMs) }),
1557
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-recording-waveform", "aria-hidden": "true", children: waveform.map((sample, index) => {
1558
+ const scale = Math.max(0.16, Math.min(1, sample));
1559
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1560
+ "span",
1561
+ {
1562
+ className: "blinq-recording-waveform-bar",
1563
+ style: { transform: `scaleY(${scale})` }
1564
+ },
1565
+ `voice-wave-${index}`
1566
+ );
1567
+ }) })
1568
+ ] });
1569
+ }
1570
+ function ActionCard({
1571
+ item,
1572
+ accentColor,
1573
+ onApprove,
1574
+ onDecline
1575
+ }) {
1576
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1577
+ Card,
1578
+ {
1579
+ className: "blinq-action-card",
1580
+ style: item.state === "approved" ? { borderColor: "rgba(17, 17, 17, 0.22)" } : item.state === "declined" ? { borderColor: "rgba(17, 17, 17, 0.12)" } : void 0,
1581
+ children: [
1582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, { children: [
1583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, { children: item.displayName }),
1584
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: item.targetSummary })
1585
+ ] }),
1586
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, { children: [
1587
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "blinq-action-details", children: [
1588
+ /* @__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" }),
1589
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "blinq-code", children: JSON.stringify(item.arguments, null, 2) })
1590
+ ] }),
1591
+ 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,
1592
+ item.state === "pending" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-actions", children: [
1593
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, { type: "button", variant: "outline", onClick: onDecline, children: "\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C" }),
1594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1595
+ Button,
1596
+ {
1597
+ type: "button",
1598
+ onClick: onApprove,
1599
+ style: {
1600
+ background: accentColor
1601
+ },
1602
+ children: "\u0412\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C"
1603
+ }
1604
+ )
1605
+ ] }) : null
1606
+ ] })
1607
+ ]
1608
+ }
1609
+ );
1610
+ }
1611
+ function PlanCard({
1612
+ item,
1613
+ onApprove,
1614
+ onDecline
1615
+ }) {
1616
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { className: "blinq-action-card blinq-plan-card", children: [
1617
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
1618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1619
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, { children: "\u041F\u043B\u0430\u043D" }),
1620
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: item.goal })
1621
+ ] }),
1622
+ /* @__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" })
1623
+ ] }) }),
1624
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, { className: "space-y-3", children: [
1625
+ item.progressLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-action-state", children: item.progressLabel }) : null,
1626
+ item.statusReason ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-card-description", children: item.statusReason }) : null,
1627
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "space-y-2", children: item.steps.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1628
+ "div",
1629
+ {
1630
+ className: "blinq-plan-step",
1631
+ "data-current": index === item.currentStepIndex ? "true" : "false",
1632
+ "data-status": step.status,
1633
+ children: [
1634
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "blinq-plan-step-index", children: index + 1 }),
1635
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-plan-step-copy", children: [
1636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: step.title }),
1637
+ /* @__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" })
1638
+ ] })
1639
+ ]
1640
+ },
1641
+ step.id
1642
+ )) }),
1643
+ item.approvalState === "pending" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-actions", children: [
1644
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, { type: "button", variant: "outline", onClick: onDecline, children: "\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C" }),
1645
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, { type: "button", onClick: onApprove, children: "\u0412\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C" })
1646
+ ] }) : null
1647
+ ] })
1648
+ ] });
1649
+ }
1650
+ function AssistantConversationThread({
1651
+ items,
1652
+ showThinkingIndicator,
1653
+ accentColor,
1654
+ onApproveAction,
1655
+ onDeclineAction,
1656
+ onApprovePlan,
1657
+ onDeclinePlan
1658
+ }) {
1659
+ const messages = React.useMemo(() => conversationToThreadMessages(items), [items]);
1660
+ const runtime = (0, import_react.useExternalStoreRuntime)({
1661
+ messages,
1662
+ isRunning: messages.some((message) => message.role === "assistant" && message.status?.type === "running"),
1663
+ convertMessage: (message) => message,
1664
+ onNew: async () => void 0
1665
+ });
1666
+ const scrollRef = React.useRef(null);
1667
+ React.useEffect(() => {
1668
+ if (!scrollRef.current) {
1669
+ return;
1670
+ }
1671
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
1672
+ }, [items]);
1673
+ const assistantMessageComponent = React.useMemo(
1674
+ () => function AssistantMessageComponent() {
1675
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1676
+ ThreadMessageBubble,
1677
+ {
1678
+ accentColor,
1679
+ onApproveAction,
1680
+ onDeclineAction,
1681
+ onApprovePlan,
1682
+ onDeclinePlan
1683
+ }
1684
+ );
1685
+ },
1686
+ [accentColor, onApproveAction, onDeclineAction, onApprovePlan, onDeclinePlan]
1687
+ );
1688
+ const userMessageComponent = React.useMemo(
1689
+ () => function UserMessageComponent() {
1690
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1691
+ ThreadMessageBubble,
1692
+ {
1693
+ accentColor,
1694
+ onApproveAction,
1695
+ onDeclineAction,
1696
+ onApprovePlan,
1697
+ onDeclinePlan
1698
+ }
1699
+ );
1700
+ },
1701
+ [accentColor, onApproveAction, onDeclineAction, onApprovePlan, onDeclinePlan]
1702
+ );
1703
+ 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: [
1704
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1705
+ import_react.ThreadPrimitive.Messages,
1706
+ {
1707
+ components: {
1708
+ UserMessage: userMessageComponent,
1709
+ AssistantMessage: assistantMessageComponent
1710
+ }
1711
+ }
1712
+ ),
1713
+ showThinkingIndicator ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThinkingPlaceholder, {}) : null
1714
+ ] }) }) });
1715
+ }
1716
+ function WidgetEmbedShell({
1717
+ state,
1718
+ onOpen,
1719
+ onCollapse,
1720
+ onInputChange,
1721
+ onSubmit,
1722
+ onToggleVoice,
1723
+ onApproveAction,
1724
+ onDeclineAction,
1725
+ onApprovePlan,
1726
+ onDeclinePlan
1727
+ }) {
1728
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1729
+ "div",
1730
+ {
1731
+ className: "blinq-shell",
1732
+ "data-side": state.side,
1733
+ style: { ["--blinq-accent"]: state.accentColor },
1734
+ children: [
1735
+ !state.minimized ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-panel", children: [
1736
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-header", children: [
1737
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "blinq-header-row", children: [
1738
+ /* @__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 }) }),
1739
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1740
+ Button,
1741
+ {
1742
+ type: "button",
1743
+ size: "icon-sm",
1744
+ variant: "ghost",
1745
+ "aria-label": "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0438\u0434\u0436\u0435\u0442",
1746
+ title: "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0438\u0434\u0436\u0435\u0442",
1747
+ onClick: onCollapse,
1748
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollapseIcon, {})
1749
+ }
1750
+ )
1751
+ ] }),
1752
+ state.statusText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "blinq-status-line", children: state.statusText }) : null
1753
+ ] }),
1754
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1755
+ AssistantConversationThread,
1756
+ {
1757
+ items: state.items,
1758
+ showThinkingIndicator: state.showThinkingIndicator,
1759
+ accentColor: state.accentColor,
1760
+ onApproveAction,
1761
+ onDeclineAction,
1762
+ onApprovePlan,
1763
+ onDeclinePlan
1764
+ }
1765
+ ),
1766
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1767
+ "form",
1768
+ {
1769
+ className: "blinq-composer",
1770
+ onSubmit: (event) => {
1771
+ event.preventDefault();
1772
+ onSubmit();
1773
+ },
1774
+ 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: [
1775
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1776
+ Button,
1777
+ {
1778
+ type: "button",
1779
+ size: "icon-sm",
1780
+ variant: state.voiceListening ? "destructive" : "ghost",
1781
+ className: "blinq-input-mic",
1782
+ "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",
1783
+ 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",
1784
+ disabled: !state.voiceSupported || state.isSending,
1785
+ onClick: onToggleVoice,
1786
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MicrophoneIcon, { listening: state.voiceListening })
1787
+ }
1788
+ ),
1789
+ state.voiceListening ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1790
+ VoiceWaveform,
1791
+ {
1792
+ durationMs: state.voiceDurationMs,
1793
+ samples: state.voiceWaveform
1794
+ }
1795
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1796
+ Input,
1797
+ {
1798
+ value: state.inputValue,
1799
+ 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",
1800
+ onChange: (event) => onInputChange(event.currentTarget.value),
1801
+ disabled: state.isSending
1802
+ }
1803
+ ),
1804
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1805
+ Button,
1806
+ {
1807
+ type: "submit",
1808
+ size: "icon-sm",
1809
+ className: "blinq-input-send",
1810
+ "aria-label": "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
1811
+ title: "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
1812
+ disabled: state.isSending || state.voiceListening,
1813
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SendIcon, {})
1814
+ }
1815
+ )
1816
+ ] }) })
1817
+ }
1818
+ )
1819
+ ] }) : null,
1820
+ state.minimized ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1821
+ Button,
1822
+ {
1823
+ type: "button",
1824
+ size: "icon",
1825
+ className: "blinq-launcher blinq-launcher-icon-only",
1826
+ "aria-label": `\u041E\u0442\u043A\u0440\u044B\u0442\u044C ${state.assistantName}`,
1827
+ title: `\u041E\u0442\u043A\u0440\u044B\u0442\u044C ${state.assistantName}`,
1828
+ onClick: onOpen,
1829
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "blinq-launcher-badge", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LauncherIcon, {}) })
1830
+ }
1831
+ ) : null
1832
+ ]
1833
+ }
1834
+ );
1835
+ }
1836
+
1837
+ // src/voice-input.ts
1838
+ var FILLER_PHRASES = [
1839
+ "you know",
1840
+ "i mean",
1841
+ "sort of",
1842
+ "kind of",
1843
+ "and so on",
1844
+ "\u043A\u0430\u043A \u0431\u044B",
1845
+ "\u0432 \u043E\u0431\u0449\u0435\u043C",
1846
+ "\u0432\u043E\u043E\u0431\u0449\u0435-\u0442\u043E",
1847
+ "\u044D\u0442\u043E \u0441\u0430\u043C\u043E\u0435",
1848
+ "\u0442\u0430\u043A \u0441\u043A\u0430\u0437\u0430\u0442\u044C",
1849
+ "\u043F\u043E \u0441\u0443\u0442\u0438",
1850
+ "\u0441\u043A\u0430\u0436\u0435\u043C \u0442\u0430\u043A",
1851
+ "\u0438 \u0442\u0430\u043A \u0434\u0430\u043B\u0435\u0435",
1852
+ "\u043D\u0430 \u0441\u0430\u043C\u043E\u043C \u0434\u0435\u043B\u0435",
1853
+ "if you know",
1854
+ "basically",
1855
+ "literally",
1856
+ "actually",
1857
+ "really",
1858
+ "\u043A\u043E\u0440\u043E\u0447\u0435",
1859
+ "\u0437\u043D\u0430\u0447\u0438\u0442",
1860
+ "\u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u043E",
1861
+ "\u043F\u0440\u043E\u0441\u0442\u043E",
1862
+ "\u043B\u0430\u0434\u043D\u043E",
1863
+ "\u043D\u0443 \u0432\u043E\u0442",
1864
+ "\u043D\u0443",
1865
+ "\u0432\u043E\u0442",
1866
+ "\u0442\u0438\u043F\u0430",
1867
+ "\u044D\u043C",
1868
+ "\u044D\u044D",
1869
+ "\u044D\u044D\u044D",
1870
+ "\u043C\u043C",
1871
+ "uh",
1872
+ "um",
1873
+ "er",
1874
+ "ah",
1875
+ "like",
1876
+ "well"
1877
+ ];
1878
+ function escapeRegex(value) {
1879
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1880
+ }
1881
+ function normalizeWhitespace(text) {
1882
+ return text.replace(/\s+/g, " ").trim();
1883
+ }
1884
+ function removeFillerPhrases(text) {
1885
+ let cleaned = text;
1886
+ for (const phrase of [...FILLER_PHRASES].sort((left, right) => right.length - left.length)) {
1887
+ const escaped = escapeRegex(phrase);
1888
+ const pattern = new RegExp(`(^|[\\s,.;:!?()\\[\\]{}-])${escaped}(?=$|[\\s,.;:!?()\\[\\]{}-])`, "giu");
1889
+ cleaned = cleaned.replace(pattern, (_match, boundary) => boundary || " ");
1890
+ }
1891
+ return cleaned;
1892
+ }
1893
+ function dedupeAdjacentWords(text) {
1894
+ const parts = text.split(/\s+/);
1895
+ const nextParts = [];
1896
+ let previousKey = "";
1897
+ for (const part of parts) {
1898
+ const wordKey = part.toLowerCase().replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, "");
1899
+ if (wordKey && wordKey === previousKey) {
1900
+ continue;
1901
+ }
1902
+ nextParts.push(part);
1903
+ previousKey = wordKey;
1904
+ }
1905
+ return nextParts.join(" ");
1906
+ }
1907
+ function normalizePunctuation(text) {
1908
+ return text.replace(/\s+([,.;:!?])/g, "$1").replace(/([,.;:!?])([^\s])/g, "$1 $2").replace(/\s+/g, " ").trim();
1909
+ }
1910
+ function sanitizeSpokenText(text) {
1911
+ const cleaned = normalizePunctuation(
1912
+ dedupeAdjacentWords(removeFillerPhrases(normalizeWhitespace(text)))
1913
+ );
1914
+ return cleaned;
1915
+ }
1916
+ function recognitionConstructor() {
1917
+ if (typeof window === "undefined") {
1918
+ return null;
1919
+ }
1920
+ const recognitionWindow = window;
1921
+ return recognitionWindow.SpeechRecognition || recognitionWindow.webkitSpeechRecognition || null;
1922
+ }
1923
+ function errorMessageForCode(code) {
1924
+ switch (code) {
1925
+ case "not-allowed":
1926
+ case "service-not-allowed":
1927
+ 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.";
1928
+ case "audio-capture":
1929
+ return "\u041C\u0438\u043A\u0440\u043E\u0444\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.";
1930
+ case "network":
1931
+ 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.";
1932
+ case "no-speech":
1933
+ return "\u0420\u0435\u0447\u044C \u043D\u0435 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u043D\u0430.";
1934
+ case "aborted":
1935
+ return "\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u0431\u044B\u043B \u043E\u0442\u043C\u0435\u043D\u0451\u043D.";
1936
+ default:
1937
+ 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.";
1938
+ }
1939
+ }
1940
+ var VoiceInputController = class {
1941
+ callbacks;
1942
+ recognition;
1943
+ finalTranscript = "";
1944
+ interimTranscript = "";
1945
+ shouldSendOnEnd = false;
1946
+ listening = false;
1947
+ sessionActive = false;
1948
+ manualStopRequested = false;
1949
+ restartTimeoutId = null;
1950
+ statusAfterEnd = "\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0432\u0432\u043E\u0434 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D.";
1951
+ mediaStream = null;
1952
+ audioContext = null;
1953
+ analyser = null;
1954
+ sourceNode = null;
1955
+ levelFrameId = null;
1956
+ smoothedLevel = 0;
1957
+ constructor(callbacks) {
1958
+ this.callbacks = callbacks;
1959
+ const Recognition = recognitionConstructor();
1960
+ this.recognition = Recognition ? new Recognition() : null;
1961
+ if (!this.recognition) {
1962
+ return;
1963
+ }
1964
+ this.recognition.continuous = true;
1965
+ this.recognition.interimResults = true;
1966
+ this.recognition.maxAlternatives = 1;
1967
+ this.recognition.onstart = () => {
1968
+ this.listening = true;
1969
+ this.sessionActive = true;
1970
+ this.callbacks.onListeningChange(true);
1971
+ 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.");
1972
+ };
1973
+ this.recognition.onresult = (event) => {
1974
+ let finalChunk = this.finalTranscript;
1975
+ let interimChunk = "";
1976
+ for (let index = event.resultIndex; index < event.results.length; index += 1) {
1977
+ const result = event.results[index];
1978
+ const transcript = result[0]?.transcript ?? "";
1979
+ if (!transcript) {
1980
+ continue;
1981
+ }
1982
+ if (result.isFinal) {
1983
+ finalChunk = `${finalChunk} ${transcript}`.trim();
1984
+ } else {
1985
+ interimChunk = `${interimChunk} ${transcript}`.trim();
1986
+ }
1987
+ }
1988
+ this.finalTranscript = finalChunk;
1989
+ this.interimTranscript = interimChunk;
1990
+ const preview = sanitizeSpokenText(`${finalChunk} ${interimChunk}`.trim());
1991
+ this.callbacks.onInterimTranscript(preview);
1992
+ if (preview) {
1993
+ this.callbacks.onStatusChange(`\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u0433\u043E\u043B\u043E\u0441\u0430: ${preview}`);
1994
+ }
1995
+ };
1996
+ this.recognition.onerror = (event) => {
1997
+ if (event.error === "no-speech" && this.sessionActive && !this.manualStopRequested) {
1998
+ this.statusAfterEnd = "\u041F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u044E \u0441\u043B\u0443\u0448\u0430\u0442\u044C...";
1999
+ return;
2000
+ }
2001
+ if (event.error === "aborted" && this.manualStopRequested) {
2002
+ 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.";
2003
+ return;
2004
+ }
2005
+ this.sessionActive = false;
2006
+ this.shouldSendOnEnd = false;
2007
+ this.statusAfterEnd = errorMessageForCode(event.error);
2008
+ this.stopLevelMonitoring();
2009
+ this.callbacks.onError(this.statusAfterEnd);
2010
+ };
2011
+ this.recognition.onend = () => {
2012
+ this.listening = false;
2013
+ if (this.sessionActive && !this.manualStopRequested) {
2014
+ this.callbacks.onStatusChange("\u041F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u044E \u0441\u043B\u0443\u0448\u0430\u0442\u044C...");
2015
+ this.scheduleRestart();
2016
+ return;
2017
+ }
2018
+ this.sessionActive = false;
2019
+ this.stopLevelMonitoring();
2020
+ this.finalizeTranscript();
2021
+ };
2022
+ }
2023
+ get supported() {
2024
+ return Boolean(this.recognition);
2025
+ }
2026
+ get isListening() {
2027
+ return this.sessionActive || this.listening;
2028
+ }
2029
+ finalizeTranscript() {
2030
+ this.callbacks.onListeningChange(false);
2031
+ this.callbacks.onLevelChange(0);
2032
+ const finalText = sanitizeSpokenText(this.finalTranscript);
2033
+ this.finalTranscript = "";
2034
+ this.interimTranscript = "";
2035
+ const shouldSend = this.shouldSendOnEnd;
2036
+ this.shouldSendOnEnd = false;
2037
+ this.manualStopRequested = false;
2038
+ if (!shouldSend) {
2039
+ this.callbacks.onStatusChange(this.statusAfterEnd);
2040
+ 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.";
2041
+ return;
2042
+ }
2043
+ if (!finalText) {
2044
+ 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.");
2045
+ 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.";
2046
+ return;
2047
+ }
2048
+ 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.");
2049
+ 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.";
2050
+ this.callbacks.onFinalTranscript(finalText);
2051
+ }
2052
+ stopLevelMonitoring() {
2053
+ if (this.levelFrameId !== null) {
2054
+ window.cancelAnimationFrame(this.levelFrameId);
2055
+ this.levelFrameId = null;
2056
+ }
2057
+ this.analyser?.disconnect();
2058
+ this.sourceNode?.disconnect();
2059
+ this.analyser = null;
2060
+ this.sourceNode = null;
2061
+ if (this.mediaStream) {
2062
+ for (const track of this.mediaStream.getTracks()) {
2063
+ track.stop();
2064
+ }
2065
+ this.mediaStream = null;
2066
+ }
2067
+ if (this.audioContext) {
2068
+ void this.audioContext.close().catch(() => void 0);
2069
+ this.audioContext = null;
2070
+ }
2071
+ this.smoothedLevel = 0;
2072
+ this.callbacks.onLevelChange(0);
2073
+ }
2074
+ sampleLevel() {
2075
+ if (!this.analyser) {
2076
+ this.callbacks.onLevelChange(0);
2077
+ return;
2078
+ }
2079
+ const data = new Uint8Array(this.analyser.fftSize);
2080
+ const tick = () => {
2081
+ if (!this.analyser || !this.sessionActive && !this.listening) {
2082
+ this.callbacks.onLevelChange(0);
2083
+ return;
2084
+ }
2085
+ this.analyser.getByteTimeDomainData(data);
2086
+ let sum = 0;
2087
+ for (const value of data) {
2088
+ const normalized = (value - 128) / 128;
2089
+ sum += normalized * normalized;
2090
+ }
2091
+ const rms = Math.sqrt(sum / data.length);
2092
+ const level = Math.min(1, rms * 4.6);
2093
+ this.smoothedLevel = this.smoothedLevel * 0.68 + level * 0.32;
2094
+ this.callbacks.onLevelChange(this.smoothedLevel);
2095
+ this.levelFrameId = window.requestAnimationFrame(tick);
2096
+ };
2097
+ tick();
2098
+ }
2099
+ async startLevelMonitoring() {
2100
+ if (typeof window === "undefined" || !window.isSecureContext || !navigator.mediaDevices?.getUserMedia) {
2101
+ this.callbacks.onLevelChange(0);
2102
+ return;
2103
+ }
2104
+ this.stopLevelMonitoring();
2105
+ try {
2106
+ const stream = await navigator.mediaDevices.getUserMedia({
2107
+ audio: {
2108
+ echoCancellation: true,
2109
+ noiseSuppression: true
2110
+ }
2111
+ });
2112
+ if (!this.sessionActive && !this.listening) {
2113
+ for (const track of stream.getTracks()) {
2114
+ track.stop();
2115
+ }
2116
+ return;
2117
+ }
2118
+ const AudioContextCtor = window.AudioContext || window.webkitAudioContext;
2119
+ if (!AudioContextCtor) {
2120
+ for (const track of stream.getTracks()) {
2121
+ track.stop();
2122
+ }
2123
+ return;
2124
+ }
2125
+ this.mediaStream = stream;
2126
+ this.audioContext = new AudioContextCtor();
2127
+ this.sourceNode = this.audioContext.createMediaStreamSource(stream);
2128
+ this.analyser = this.audioContext.createAnalyser();
2129
+ this.analyser.fftSize = 128;
2130
+ this.analyser.smoothingTimeConstant = 0.72;
2131
+ this.sourceNode.connect(this.analyser);
2132
+ this.sampleLevel();
2133
+ } catch {
2134
+ this.callbacks.onLevelChange(0);
2135
+ }
2136
+ }
2137
+ clearRestartTimer() {
2138
+ if (this.restartTimeoutId !== null) {
2139
+ window.clearTimeout(this.restartTimeoutId);
2140
+ this.restartTimeoutId = null;
2141
+ }
2142
+ }
2143
+ scheduleRestart() {
2144
+ this.clearRestartTimer();
2145
+ this.restartTimeoutId = window.setTimeout(() => {
2146
+ this.restartTimeoutId = null;
2147
+ if (!this.recognition || !this.sessionActive || this.manualStopRequested) {
2148
+ return;
2149
+ }
2150
+ try {
2151
+ this.recognition.lang = this.callbacks.getLanguage();
2152
+ this.recognition.start();
2153
+ } catch {
2154
+ this.sessionActive = false;
2155
+ this.callbacks.onListeningChange(false);
2156
+ 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.");
2157
+ }
2158
+ }, 160);
2159
+ }
2160
+ start() {
2161
+ if (!this.recognition) {
2162
+ 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.");
2163
+ return false;
2164
+ }
2165
+ if (this.sessionActive || this.listening) {
2166
+ return true;
2167
+ }
2168
+ this.finalTranscript = "";
2169
+ this.interimTranscript = "";
2170
+ this.shouldSendOnEnd = false;
2171
+ this.sessionActive = true;
2172
+ this.manualStopRequested = false;
2173
+ 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.";
2174
+ this.recognition.lang = this.callbacks.getLanguage();
2175
+ this.clearRestartTimer();
2176
+ try {
2177
+ this.recognition.start();
2178
+ void this.startLevelMonitoring();
2179
+ return true;
2180
+ } catch {
2181
+ this.sessionActive = false;
2182
+ this.stopLevelMonitoring();
2183
+ 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.");
2184
+ return false;
2185
+ }
2186
+ }
2187
+ stop() {
2188
+ if (!this.recognition || !this.listening && !this.sessionActive) {
2189
+ return;
2190
+ }
2191
+ this.manualStopRequested = true;
2192
+ this.sessionActive = false;
2193
+ this.shouldSendOnEnd = true;
2194
+ this.clearRestartTimer();
2195
+ this.stopLevelMonitoring();
2196
+ if (this.listening) {
2197
+ this.recognition.stop();
2198
+ } else {
2199
+ this.finalizeTranscript();
2200
+ }
2201
+ }
2202
+ cancel() {
2203
+ if (!this.recognition) {
2204
+ return;
2205
+ }
2206
+ this.manualStopRequested = true;
2207
+ this.sessionActive = false;
2208
+ this.shouldSendOnEnd = false;
2209
+ this.clearRestartTimer();
2210
+ this.stopLevelMonitoring();
2211
+ if (this.listening) {
2212
+ this.recognition.abort();
2213
+ } else {
2214
+ this.callbacks.onListeningChange(false);
2215
+ this.callbacks.onLevelChange(0);
2216
+ }
2217
+ }
2218
+ destroy() {
2219
+ this.cancel();
2220
+ if (!this.recognition) {
2221
+ return;
2222
+ }
2223
+ this.recognition.onstart = null;
2224
+ this.recognition.onresult = null;
2225
+ this.recognition.onerror = null;
2226
+ this.recognition.onend = null;
2227
+ }
2228
+ };
2229
+
2230
+ // src/widget-core.ts
2231
+ var DEFAULT_API_BASE_URL = "http://localhost:8000";
2232
+ var STORAGE_KEY = "blinq-widget-fingerprint";
2233
+ var HISTORY_STORAGE_PREFIX = "blinq-widget-history";
2234
+ var MINIMIZED_STORAGE_PREFIX = "blinq-widget-minimized";
2235
+ var SESSION_STORAGE_PREFIX = "blinq-widget-session";
2236
+ var MAX_STORED_HISTORY = 30;
2237
+ var MAX_HISTORY_FOR_REQUEST = 12;
2238
+ 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.";
2239
+ function isBrowser3() {
2240
+ return typeof window !== "undefined" && typeof document !== "undefined";
2241
+ }
2242
+ function resolveMount(mount) {
2243
+ if (!isBrowser3()) {
2244
+ 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");
2245
+ }
2246
+ if (mount instanceof HTMLElement) {
2247
+ return mount;
2248
+ }
2249
+ if (typeof mount === "string") {
2250
+ const target = document.querySelector(mount);
2251
+ if (!target) {
2252
+ throw new Error(`\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D mount-\u044D\u043B\u0435\u043C\u0435\u043D\u0442: ${mount}`);
2253
+ }
2254
+ return target;
2255
+ }
2256
+ return document.body;
2257
+ }
2258
+ function getOrCreateFingerprint() {
2259
+ if (!isBrowser3()) {
2260
+ return "server-widget-user";
2261
+ }
2262
+ const existing = window.localStorage.getItem(STORAGE_KEY);
2263
+ if (existing) {
2264
+ return existing;
2265
+ }
2266
+ const next = `fingerprint_${crypto.randomUUID()}`;
2267
+ window.localStorage.setItem(STORAGE_KEY, next);
2268
+ return next;
2269
+ }
2270
+ function runtimeMessage(mode) {
2271
+ if (mode === "read-only-fallback") {
2272
+ 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.";
2273
+ }
2274
+ 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.";
2275
+ }
2276
+ function normalizedRouteFromUrl(value) {
2277
+ try {
2278
+ const url = new URL(value, window.location.origin);
2279
+ const path = url.pathname || "/";
2280
+ return path === "" ? "/" : path;
2281
+ } catch {
2282
+ return "/";
2283
+ }
2284
+ }
2285
+ async function parseSseStream(stream, onEvent) {
2286
+ const reader = stream.getReader();
2287
+ const decoder = new TextDecoder();
2288
+ let buffer = "";
2289
+ let currentEvent = "message";
2290
+ let currentData = "";
2291
+ while (true) {
2292
+ const { done, value } = await reader.read();
2293
+ if (done) {
2294
+ break;
2295
+ }
2296
+ buffer += decoder.decode(value, { stream: true });
2297
+ const chunks = buffer.split("\n");
2298
+ buffer = chunks.pop() ?? "";
2299
+ for (const line of chunks) {
2300
+ if (line.startsWith("event:")) {
2301
+ currentEvent = line.slice(6).trim();
2302
+ } else if (line.startsWith("data:")) {
2303
+ currentData += line.slice(5).trim();
2304
+ } else if (line.trim() === "") {
2305
+ if (currentData) {
2306
+ onEvent({ event: currentEvent, data: currentData });
2307
+ }
2308
+ currentEvent = "message";
2309
+ currentData = "";
2310
+ }
2311
+ }
2312
+ }
2313
+ if (currentData) {
2314
+ onEvent({ event: currentEvent, data: currentData });
2315
+ }
2316
+ }
2317
+ function normalizedConfig(config) {
2318
+ if ("default" in config) {
2319
+ return config.default;
2320
+ }
2321
+ return config;
2322
+ }
2323
+ function createConversationId(prefix) {
2324
+ return `${prefix}_${crypto.randomUUID()}`;
2325
+ }
2326
+ function normalizeAssistantStreamChunk(currentText, nextChunk) {
2327
+ if (!currentText) {
2328
+ return nextChunk.replace(/^\s+/, "");
2329
+ }
2330
+ return nextChunk;
2331
+ }
2332
+ function normalizeAssistantFinalText(text) {
2333
+ return text.replace(/^\s+/, "");
2334
+ }
2335
+ var BlinqWidget = class {
2336
+ options;
2337
+ apiBaseUrl;
2338
+ sessionId = null;
2339
+ sessionToken = null;
2340
+ widgetConfig = null;
2341
+ runtimeStatus = null;
2342
+ registry = null;
2343
+ root = null;
2344
+ reactRoot = null;
2345
+ voiceController = null;
2346
+ history = [];
2347
+ items = [];
2348
+ minimized = false;
2349
+ isSending = false;
2350
+ inputValue = "";
2351
+ 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.";
2352
+ voiceSupported = false;
2353
+ voiceListening = false;
2354
+ voiceWaveform = [];
2355
+ voiceDurationMs = 0;
2356
+ voiceStartedAt = null;
2357
+ lastVoiceSampleAt = 0;
2358
+ confirmResolve = null;
2359
+ confirmItemId = null;
2360
+ activeRun = null;
2361
+ pendingAssistantIndicator = false;
2362
+ executedToolCounts = /* @__PURE__ */ new Map();
2363
+ lastAutoResumeKey = null;
2364
+ shellHandlers = {
2365
+ onOpen: () => this.setMinimized(false),
2366
+ onCollapse: () => this.setMinimized(true),
2367
+ onInputChange: (value) => {
2368
+ this.inputValue = value;
2369
+ this.renderReactApp();
2370
+ },
2371
+ onSubmit: () => {
2372
+ void this.submitComposerMessage();
2373
+ },
2374
+ onToggleVoice: () => this.toggleVoiceInput(),
2375
+ onApproveAction: (id) => {
2376
+ if (this.confirmItemId === id) {
2377
+ this.resolveConfirmation(true);
2378
+ }
2379
+ },
2380
+ onDeclineAction: (id) => {
2381
+ if (this.confirmItemId === id) {
2382
+ this.resolveConfirmation(false);
2383
+ }
2384
+ },
2385
+ onApprovePlan: () => {
2386
+ void this.respondToPlan(true);
2387
+ },
2388
+ onDeclinePlan: () => {
2389
+ void this.respondToPlan(false);
2390
+ }
2391
+ };
2392
+ constructor(options) {
2393
+ this.options = options;
2394
+ this.apiBaseUrl = options.apiBaseUrl ?? DEFAULT_API_BASE_URL;
2395
+ }
2396
+ async init() {
2397
+ await this.startSession();
2398
+ if (isBrowser3()) {
2399
+ this.restoreHistory();
2400
+ this.restoreMinimizedState();
2401
+ this.restoreConversationView();
2402
+ this.render();
2403
+ this.setupVoiceInput();
2404
+ this.registry = new PageToolRegistry(this.confirmToolExecution.bind(this));
2405
+ const nativeRegistered = await this.registry.registerNativeTools();
2406
+ this.syncRuntimeStatus(nativeRegistered);
2407
+ if (this.shouldAutoResumeActiveRun()) {
2408
+ window.setTimeout(() => {
2409
+ void this.continueActiveRun();
2410
+ }, 60);
2411
+ }
2412
+ }
2413
+ return this;
2414
+ }
2415
+ getRuntimeStatus() {
2416
+ return this.runtimeStatus;
2417
+ }
2418
+ destroy() {
2419
+ this.voiceController?.destroy();
2420
+ this.registry?.destroy();
2421
+ this.voiceController = null;
2422
+ this.registry = null;
2423
+ this.reactRoot?.unmount();
2424
+ this.reactRoot = null;
2425
+ this.root?.remove();
2426
+ this.root = null;
2427
+ }
2428
+ currentState() {
2429
+ return {
2430
+ minimized: this.minimized,
2431
+ assistantName: this.widgetConfig?.ai_persona_name ?? "Blinq \u0410\u0441\u0441\u0438\u0441\u0442\u0435\u043D\u0442",
2432
+ runtimeLabel: "webmcp://embedded",
2433
+ statusText: this.statusText,
2434
+ showThinkingIndicator: this.pendingAssistantIndicator,
2435
+ inputValue: this.inputValue,
2436
+ accentColor: "#111111",
2437
+ side: this.widgetConfig?.position === "bottom-left" ? "left" : "right",
2438
+ isSending: this.isSending,
2439
+ voiceSupported: this.voiceSupported,
2440
+ voiceListening: this.voiceListening,
2441
+ voiceWaveform: this.voiceWaveform,
2442
+ voiceDurationMs: this.voiceDurationMs,
2443
+ items: this.items
2444
+ };
2445
+ }
2446
+ renderReactApp() {
2447
+ if (!this.reactRoot) {
2448
+ return;
2449
+ }
2450
+ this.reactRoot.render(
2451
+ React2.createElement(WidgetEmbedShell, {
2452
+ state: this.currentState(),
2453
+ ...this.shellHandlers
2454
+ })
2455
+ );
2456
+ }
2457
+ async startSession() {
2458
+ const response = await fetch(`${this.apiBaseUrl}/pub/init`, {
2459
+ method: "POST",
2460
+ headers: {
2461
+ "Content-Type": "application/json"
2462
+ },
2463
+ body: JSON.stringify({
2464
+ public_token: this.options.publicToken,
2465
+ domain: window.location.hostname,
2466
+ page_url: window.location.href,
2467
+ user_fingerprint: getOrCreateFingerprint(),
2468
+ previous_session_id: this.restoreStoredSessionId(),
2469
+ client_capabilities: {
2470
+ secure_context: window.isSecureContext,
2471
+ native_webmcp_supported: detectNativeWebMcpSupport(),
2472
+ page_actions_supported: true
2473
+ }
2474
+ })
2475
+ });
2476
+ if (!response.ok) {
2477
+ const payload2 = await response.json().catch(() => ({}));
2478
+ 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");
2479
+ }
2480
+ const payload = await response.json();
2481
+ this.sessionId = payload.session_id;
2482
+ this.sessionToken = payload.session_token;
2483
+ this.widgetConfig = payload.widget_config;
2484
+ this.activeRun = payload.active_run ?? null;
2485
+ this.runtimeStatus = {
2486
+ ...payload.runtime,
2487
+ message: runtimeMessage(payload.runtime.mode)
2488
+ };
2489
+ this.persistSessionId();
2490
+ this.statusText = this.runtimeStatus.message;
2491
+ }
2492
+ render() {
2493
+ const mount = resolveMount(this.options.mount);
2494
+ if (!this.root) {
2495
+ this.root = document.createElement("div");
2496
+ mount.appendChild(this.root);
2497
+ this.reactRoot = (0, import_client.createRoot)(this.root);
2498
+ }
2499
+ this.renderReactApp();
2500
+ }
2501
+ preferredSpeechLanguage() {
2502
+ const htmlLang = document.documentElement.lang.trim();
2503
+ if (htmlLang) {
2504
+ return htmlLang;
2505
+ }
2506
+ return navigator.language || "ru-RU";
2507
+ }
2508
+ setupVoiceInput() {
2509
+ this.voiceController = new VoiceInputController({
2510
+ getLanguage: () => this.preferredSpeechLanguage(),
2511
+ onInterimTranscript: () => {
2512
+ },
2513
+ onFinalTranscript: (text) => {
2514
+ this.inputValue = text;
2515
+ 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.");
2516
+ this.renderReactApp();
2517
+ },
2518
+ onListeningChange: (isListening) => {
2519
+ if (isListening) {
2520
+ this.startVoiceVisualization();
2521
+ } else {
2522
+ this.resetVoiceVisualization();
2523
+ }
2524
+ this.syncVoiceState();
2525
+ },
2526
+ onLevelChange: (level) => {
2527
+ this.updateVoiceVisualization(level);
2528
+ },
2529
+ onStatusChange: (status) => {
2530
+ this.setStatus(status);
2531
+ },
2532
+ onError: (message) => {
2533
+ this.setStatus(message);
2534
+ this.syncVoiceState();
2535
+ }
2536
+ });
2537
+ this.syncVoiceState();
2538
+ }
2539
+ syncVoiceState() {
2540
+ this.voiceSupported = this.voiceController?.supported ?? false;
2541
+ this.voiceListening = this.voiceController?.isListening ?? false;
2542
+ if (!this.voiceListening) {
2543
+ this.resetVoiceVisualization();
2544
+ }
2545
+ this.renderReactApp();
2546
+ }
2547
+ startVoiceVisualization() {
2548
+ this.voiceStartedAt = Date.now();
2549
+ this.voiceDurationMs = 0;
2550
+ this.voiceWaveform = [];
2551
+ this.lastVoiceSampleAt = 0;
2552
+ }
2553
+ resetVoiceVisualization() {
2554
+ this.voiceStartedAt = null;
2555
+ this.voiceDurationMs = 0;
2556
+ this.voiceWaveform = [];
2557
+ this.lastVoiceSampleAt = 0;
2558
+ }
2559
+ updateVoiceVisualization(level) {
2560
+ if (!this.voiceListening) {
2561
+ return;
2562
+ }
2563
+ const now = Date.now();
2564
+ if (this.voiceStartedAt === null) {
2565
+ this.startVoiceVisualization();
2566
+ this.voiceStartedAt = now;
2567
+ }
2568
+ this.voiceDurationMs = Math.max(0, now - (this.voiceStartedAt ?? now));
2569
+ const nextLevel = Math.max(0.1, Math.min(1, level));
2570
+ if (this.voiceWaveform.length === 0 || now - this.lastVoiceSampleAt >= 90) {
2571
+ this.voiceWaveform = [...this.voiceWaveform, nextLevel].slice(-40);
2572
+ this.lastVoiceSampleAt = now;
2573
+ } else {
2574
+ this.voiceWaveform = [
2575
+ ...this.voiceWaveform.slice(0, -1),
2576
+ nextLevel
2577
+ ];
2578
+ }
2579
+ this.renderReactApp();
2580
+ }
2581
+ async submitComposerMessage(message = this.inputValue) {
2582
+ const nextMessage = message.trim();
2583
+ if (!nextMessage) {
2584
+ return "";
2585
+ }
2586
+ if (this.isSending) {
2587
+ 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.");
2588
+ this.inputValue = nextMessage;
2589
+ this.renderReactApp();
2590
+ return "";
2591
+ }
2592
+ this.inputValue = "";
2593
+ this.renderReactApp();
2594
+ try {
2595
+ return await this.sendMessage(nextMessage);
2596
+ } catch {
2597
+ return "";
2598
+ }
2599
+ }
2600
+ toggleVoiceInput() {
2601
+ if (!this.voiceController) {
2602
+ 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.");
2603
+ return;
2604
+ }
2605
+ if (this.voiceController.isListening) {
2606
+ this.voiceController.stop();
2607
+ return;
2608
+ }
2609
+ this.voiceController.start();
2610
+ this.syncVoiceState();
2611
+ }
2612
+ minimizedStorageKey() {
2613
+ return `${MINIMIZED_STORAGE_PREFIX}:${window.location.hostname}:${this.options.publicToken.slice(0, 18)}`;
2614
+ }
2615
+ sessionStorageKey() {
2616
+ return `${SESSION_STORAGE_PREFIX}:${window.location.hostname}:${this.options.publicToken.slice(0, 18)}`;
2617
+ }
2618
+ restoreStoredSessionId() {
2619
+ if (!isBrowser3()) {
2620
+ return null;
2621
+ }
2622
+ return window.localStorage.getItem(this.sessionStorageKey());
2623
+ }
2624
+ persistSessionId() {
2625
+ if (!isBrowser3() || !this.sessionId) {
2626
+ return;
2627
+ }
2628
+ window.localStorage.setItem(this.sessionStorageKey(), this.sessionId);
2629
+ }
2630
+ restoreMinimizedState() {
2631
+ if (!isBrowser3()) {
2632
+ return;
2633
+ }
2634
+ this.minimized = window.localStorage.getItem(this.minimizedStorageKey()) === "1";
2635
+ }
2636
+ persistMinimizedState() {
2637
+ if (!isBrowser3()) {
2638
+ return;
2639
+ }
2640
+ window.localStorage.setItem(this.minimizedStorageKey(), this.minimized ? "1" : "0");
2641
+ }
2642
+ setMinimized(nextValue) {
2643
+ this.minimized = nextValue;
2644
+ this.persistMinimizedState();
2645
+ this.renderReactApp();
2646
+ }
2647
+ historyStorageKey() {
2648
+ return `${HISTORY_STORAGE_PREFIX}:${window.location.hostname}:${this.options.publicToken.slice(0, 18)}`;
2649
+ }
2650
+ restoreHistory() {
2651
+ if (!isBrowser3()) {
2652
+ return;
2653
+ }
2654
+ try {
2655
+ const raw = window.localStorage.getItem(this.historyStorageKey());
2656
+ if (!raw) {
2657
+ this.history = [];
2658
+ return;
2659
+ }
2660
+ const parsed = JSON.parse(raw);
2661
+ if (!Array.isArray(parsed)) {
2662
+ this.history = [];
2663
+ return;
2664
+ }
2665
+ this.history = parsed.filter((entry) => {
2666
+ if (!entry || typeof entry !== "object") {
2667
+ return false;
2668
+ }
2669
+ const role = entry.role;
2670
+ const text = entry.text;
2671
+ return (role === "user" || role === "assistant") && typeof text === "string";
2672
+ }).slice(-MAX_STORED_HISTORY);
2673
+ } catch {
2674
+ this.history = [];
2675
+ }
2676
+ }
2677
+ persistHistory() {
2678
+ if (!isBrowser3()) {
2679
+ return;
2680
+ }
2681
+ try {
2682
+ window.localStorage.setItem(
2683
+ this.historyStorageKey(),
2684
+ JSON.stringify(this.history.slice(-MAX_STORED_HISTORY))
2685
+ );
2686
+ } catch {
2687
+ }
2688
+ }
2689
+ rememberMessage(entry) {
2690
+ const text = entry.text.trim();
2691
+ if (!text) {
2692
+ return;
2693
+ }
2694
+ this.history.push({ role: entry.role, text });
2695
+ this.history = this.history.slice(-MAX_STORED_HISTORY);
2696
+ this.persistHistory();
2697
+ }
2698
+ recentHistoryForRequest() {
2699
+ return this.history.slice(-MAX_HISTORY_FOR_REQUEST).map((entry) => ({
2700
+ role: entry.role,
2701
+ text: entry.text
2702
+ }));
2703
+ }
2704
+ restoreConversationView() {
2705
+ if (this.history.length === 0) {
2706
+ this.items = [
2707
+ {
2708
+ id: createConversationId("message"),
2709
+ type: "message",
2710
+ role: "assistant",
2711
+ text: DEFAULT_GREETING,
2712
+ status: "done"
2713
+ }
2714
+ ];
2715
+ } else {
2716
+ this.items = this.history.map((entry) => ({
2717
+ id: createConversationId("message"),
2718
+ type: "message",
2719
+ role: entry.role,
2720
+ text: entry.text,
2721
+ status: "done"
2722
+ }));
2723
+ }
2724
+ if (this.activeRun) {
2725
+ this.upsertPlanItem(this.activeRun);
2726
+ }
2727
+ }
2728
+ updateActionState(id, state) {
2729
+ this.items = this.items.map(
2730
+ (item) => item.type === "action" && item.id === id ? { ...item, state } : item
2731
+ );
2732
+ this.renderReactApp();
2733
+ }
2734
+ planItemId(runId) {
2735
+ return `plan_${runId}`;
2736
+ }
2737
+ upsertPlanItem(run, progressLabel) {
2738
+ const existingPlanItem = this.items.find(
2739
+ (item) => item.type === "plan" && item.id === this.planItemId(run.id)
2740
+ );
2741
+ if (this.activeRun?.id && this.activeRun.id !== run.id) {
2742
+ this.executedToolCounts.clear();
2743
+ }
2744
+ this.activeRun = {
2745
+ ...run,
2746
+ progress_label: progressLabel ?? run.progress_label ?? null
2747
+ };
2748
+ const nextPlanItem = {
2749
+ id: this.planItemId(run.id),
2750
+ type: "plan",
2751
+ goal: run.goal,
2752
+ status: run.status,
2753
+ progressLabel: progressLabel ?? run.progress_label ?? null,
2754
+ statusReason: run.status_reason ?? null,
2755
+ expectedRoute: run.expected_route ?? null,
2756
+ loopGuarded: run.loop_guarded ?? false,
2757
+ currentStepIndex: run.status === "completed" ? run.total_steps : Math.min(run.total_steps, run.current_step_index + 1),
2758
+ totalSteps: run.total_steps,
2759
+ approvalState: run.requires_approval ? "pending" : run.status === "abandoned" ? "declined" : existingPlanItem && existingPlanItem.type === "plan" && existingPlanItem.approvalState === "declined" ? "declined" : "approved",
2760
+ steps: run.steps.map((step) => ({
2761
+ id: step.id,
2762
+ title: step.title,
2763
+ status: step.status,
2764
+ kind: step.kind
2765
+ }))
2766
+ };
2767
+ const existingIndex = this.items.findIndex(
2768
+ (item) => item.type === "plan" && item.id === nextPlanItem.id
2769
+ );
2770
+ if (existingIndex >= 0) {
2771
+ this.items = this.items.map((item, index) => index === existingIndex ? nextPlanItem : item);
2772
+ } else {
2773
+ this.items = [...this.items, nextPlanItem];
2774
+ }
2775
+ this.renderReactApp();
2776
+ }
2777
+ removeEmptyMessage(id) {
2778
+ if (!id) {
2779
+ return;
2780
+ }
2781
+ this.items = this.items.filter(
2782
+ (item) => !(item.type === "message" && item.id === id && item.text.trim().length === 0)
2783
+ );
2784
+ }
2785
+ resolveConfirmation(value) {
2786
+ if (this.confirmItemId) {
2787
+ this.updateActionState(this.confirmItemId, value ? "approved" : "declined");
2788
+ }
2789
+ if (this.confirmResolve) {
2790
+ const resolve = this.confirmResolve;
2791
+ this.confirmResolve = null;
2792
+ this.confirmItemId = null;
2793
+ resolve(value);
2794
+ }
2795
+ }
2796
+ async confirmToolExecution(request) {
2797
+ if (this.minimized) {
2798
+ this.setMinimized(false);
2799
+ }
2800
+ if (this.confirmResolve) {
2801
+ this.resolveConfirmation(false);
2802
+ }
2803
+ const itemId = createConversationId("action");
2804
+ this.items = [
2805
+ ...this.items,
2806
+ {
2807
+ id: itemId,
2808
+ type: "action",
2809
+ displayName: request.displayName,
2810
+ targetSummary: request.targetSummary,
2811
+ arguments: request.arguments,
2812
+ state: "pending"
2813
+ }
2814
+ ];
2815
+ this.confirmItemId = itemId;
2816
+ this.setStatus(`\u041D\u0443\u0436\u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435: ${request.displayName}`);
2817
+ this.renderReactApp();
2818
+ return await new Promise((resolve) => {
2819
+ this.confirmResolve = resolve;
2820
+ });
2821
+ }
2822
+ syncRuntimeStatus(nativeRegistered) {
2823
+ if (!this.runtimeStatus) {
2824
+ return;
2825
+ }
2826
+ const mode = nativeRegistered ? "native-webmcp-active" : this.registry?.canExecuteWriteTools() ? "blinq-page-tools-active" : "read-only-fallback";
2827
+ this.runtimeStatus = {
2828
+ ...this.runtimeStatus,
2829
+ mode,
2830
+ native_webmcp_supported: nativeRegistered,
2831
+ embedded_page_tools_supported: true,
2832
+ message: runtimeMessage(mode)
2833
+ };
2834
+ this.setStatus(this.runtimeStatus.message);
2835
+ }
2836
+ setStatus(text) {
2837
+ this.statusText = text;
2838
+ this.renderReactApp();
2839
+ }
2840
+ appendMessage(text, role, options) {
2841
+ const id = createConversationId("message");
2842
+ this.items = [
2843
+ ...this.items,
2844
+ {
2845
+ id,
2846
+ type: "message",
2847
+ role,
2848
+ text,
2849
+ status: options?.status ?? "done"
2850
+ }
2851
+ ];
2852
+ this.renderReactApp();
2853
+ if (options?.persist !== false) {
2854
+ this.rememberMessage({ role, text });
2855
+ }
2856
+ return id;
2857
+ }
2858
+ updateMessage(id, text, status) {
2859
+ this.items = this.items.map(
2860
+ (item) => item.type === "message" && item.id === id ? { ...item, text, status: status ?? item.status } : item
2861
+ );
2862
+ this.renderReactApp();
2863
+ }
2864
+ latestAssistantMessage() {
2865
+ const candidate = [...this.items].reverse().find((item) => item.type === "message" && item.role === "assistant");
2866
+ return candidate && candidate.type === "message" ? candidate : null;
2867
+ }
2868
+ finalizeAssistantMessage(text, options) {
2869
+ if (!text.trim()) {
2870
+ return;
2871
+ }
2872
+ const latestAssistant = this.latestAssistantMessage();
2873
+ if (latestAssistant) {
2874
+ this.updateMessage(latestAssistant.id, text, "done");
2875
+ return;
2876
+ }
2877
+ this.appendMessage(text, "assistant", {
2878
+ persist: options?.persist ?? false,
2879
+ status: "done"
2880
+ });
2881
+ }
2882
+ showThinkingIndicator() {
2883
+ if (!this.pendingAssistantIndicator) {
2884
+ this.pendingAssistantIndicator = true;
2885
+ this.renderReactApp();
2886
+ }
2887
+ }
2888
+ hideThinkingIndicator() {
2889
+ if (this.pendingAssistantIndicator) {
2890
+ this.pendingAssistantIndicator = false;
2891
+ this.renderReactApp();
2892
+ }
2893
+ }
2894
+ currentPageContext() {
2895
+ return this.registry?.collectPageContext() ?? collectNormalizedPageContext();
2896
+ }
2897
+ currentRoute() {
2898
+ return normalizedRouteFromUrl(window.location.href);
2899
+ }
2900
+ shouldAutoResumeActiveRun() {
2901
+ if (!this.activeRun || this.activeRun.status !== "awaiting_navigation") {
2902
+ return false;
2903
+ }
2904
+ const currentRoute = this.currentRoute();
2905
+ if (!this.activeRun.expected_route || currentRoute !== this.activeRun.expected_route) {
2906
+ if (this.activeRun.status_reason) {
2907
+ this.setStatus(this.activeRun.status_reason);
2908
+ }
2909
+ return false;
2910
+ }
2911
+ const nextKey = `${this.activeRun.id}:${currentRoute}`;
2912
+ if (this.lastAutoResumeKey === nextKey) {
2913
+ return false;
2914
+ }
2915
+ this.lastAutoResumeKey = nextKey;
2916
+ return true;
2917
+ }
2918
+ async handleToolRequest(request) {
2919
+ if (!this.registry) {
2920
+ return {
2921
+ status: "error",
2922
+ 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"
2923
+ };
2924
+ }
2925
+ const executionKey = JSON.stringify({
2926
+ stepId: request.step_id,
2927
+ toolName: request.tool_name,
2928
+ arguments: request.arguments,
2929
+ route: this.currentRoute()
2930
+ });
2931
+ const executionCount = (this.executedToolCounts.get(executionKey) ?? 0) + 1;
2932
+ this.executedToolCounts.set(executionKey, executionCount);
2933
+ if (executionCount > 2) {
2934
+ 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.";
2935
+ this.setStatus(error);
2936
+ return {
2937
+ status: "error",
2938
+ error
2939
+ };
2940
+ }
2941
+ 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}`);
2942
+ const outcome = await this.registry.executeTool({
2943
+ tool_name: request.tool_name,
2944
+ display_name: request.display_name,
2945
+ target_summary: request.target_summary,
2946
+ requires_confirmation: request.requires_confirmation,
2947
+ arguments: request.arguments
2948
+ });
2949
+ if (outcome.status === "success") {
2950
+ this.setStatus(`\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043E: ${request.display_name}`);
2951
+ } else if (outcome.status === "cancelled") {
2952
+ this.setStatus(`\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E: ${request.display_name}`);
2953
+ } else {
2954
+ 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.");
2955
+ }
2956
+ return outcome;
2957
+ }
2958
+ async streamChatStep(path, body, responseMessageId, accumulatedText = "") {
2959
+ if (!this.sessionToken) {
2960
+ 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");
2961
+ }
2962
+ const response = await fetch(`${this.apiBaseUrl}${path}`, {
2963
+ method: "POST",
2964
+ headers: {
2965
+ "Content-Type": "application/json",
2966
+ Authorization: `Bearer ${this.sessionToken}`
2967
+ },
2968
+ body: JSON.stringify(body)
2969
+ });
2970
+ if (!response.ok || !response.body) {
2971
+ const payload = await response.json().catch(() => ({}));
2972
+ 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");
2973
+ }
2974
+ let nextToolRequest = null;
2975
+ let finalText = accumulatedText;
2976
+ let deferredNavigationHref = null;
2977
+ await parseSseStream(response.body, ({ event, data }) => {
2978
+ const payload = JSON.parse(data);
2979
+ if (event === "metadata") {
2980
+ return;
2981
+ }
2982
+ if (event === "status") {
2983
+ const statusPayload = JSON.parse(data);
2984
+ this.setStatus(statusPayload.label);
2985
+ }
2986
+ if (event === "plan") {
2987
+ this.hideThinkingIndicator();
2988
+ this.upsertPlanItem(JSON.parse(data));
2989
+ }
2990
+ if (event === "progress") {
2991
+ this.hideThinkingIndicator();
2992
+ const progress = JSON.parse(data);
2993
+ this.upsertPlanItem(progress.run, progress.label);
2994
+ this.setStatus(progress.label);
2995
+ }
2996
+ if (event === "highlight") {
2997
+ const highlight = JSON.parse(data);
2998
+ this.registry?.highlightTargets(highlight.targets ?? []);
2999
+ }
3000
+ if (event === "delta" && payload.text) {
3001
+ const normalizedChunk = normalizeAssistantStreamChunk(finalText, payload.text);
3002
+ if (!normalizedChunk && !finalText.trim()) {
3003
+ return;
3004
+ }
3005
+ this.hideThinkingIndicator();
3006
+ if (!responseMessageId) {
3007
+ responseMessageId = this.appendMessage("", "assistant", {
3008
+ persist: false,
3009
+ status: "thinking"
3010
+ });
3011
+ }
3012
+ finalText += normalizedChunk;
3013
+ if (responseMessageId) {
3014
+ this.updateMessage(responseMessageId, finalText, "streaming");
3015
+ }
3016
+ }
3017
+ if (event === "tool_request") {
3018
+ this.hideThinkingIndicator();
3019
+ nextToolRequest = JSON.parse(data);
3020
+ }
3021
+ if (event === "error") {
3022
+ this.hideThinkingIndicator();
3023
+ this.setStatus(payload.message ?? "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u043E\u0442\u043E\u043A\u0430 \u0432\u0438\u0434\u0436\u0435\u0442\u0430.");
3024
+ }
3025
+ if (event === "done") {
3026
+ this.hideThinkingIndicator();
3027
+ if (payload.text && !finalText.trim()) {
3028
+ finalText = normalizeAssistantFinalText(payload.text);
3029
+ if (responseMessageId) {
3030
+ this.updateMessage(responseMessageId, finalText, "done");
3031
+ }
3032
+ }
3033
+ 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.");
3034
+ }
3035
+ });
3036
+ if (!nextToolRequest) {
3037
+ return finalText;
3038
+ }
3039
+ const toolRequest = nextToolRequest;
3040
+ const outcome = await this.handleToolRequest(toolRequest);
3041
+ const possibleNavigationHref = outcome.status === "success" && outcome.result && typeof outcome.result.href === "string" && outcome.result.deferred_navigation ? outcome.result.href : null;
3042
+ if (possibleNavigationHref) {
3043
+ deferredNavigationHref = possibleNavigationHref;
3044
+ }
3045
+ const nextText = await this.streamChatStep(
3046
+ "/pub/chat/tool-result",
3047
+ {
3048
+ session_id: this.sessionId,
3049
+ tool_call_id: toolRequest.tool_call_id,
3050
+ step_id: toolRequest.step_id,
3051
+ status: outcome.status,
3052
+ result: outcome.result ?? null,
3053
+ error: outcome.error ?? null,
3054
+ page_context: this.currentPageContext()
3055
+ },
3056
+ responseMessageId,
3057
+ finalText
3058
+ );
3059
+ if (deferredNavigationHref) {
3060
+ window.location.assign(deferredNavigationHref);
3061
+ }
3062
+ return nextText;
3063
+ }
3064
+ async sendMessage(message) {
3065
+ if (!this.sessionId || !this.sessionToken) {
3066
+ 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");
3067
+ }
3068
+ if (this.isSending) {
3069
+ 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.");
3070
+ return "";
3071
+ }
3072
+ this.isSending = true;
3073
+ this.showThinkingIndicator();
3074
+ this.syncVoiceState();
3075
+ this.renderReactApp();
3076
+ const requestHistory = this.recentHistoryForRequest();
3077
+ let responseMessageId = null;
3078
+ try {
3079
+ this.appendMessage(message, "user");
3080
+ const finalText = await this.streamChatStep(
3081
+ "/pub/chat",
3082
+ {
3083
+ session_id: this.sessionId,
3084
+ message,
3085
+ history: requestHistory,
3086
+ page_context: this.currentPageContext()
3087
+ },
3088
+ responseMessageId
3089
+ );
3090
+ this.finalizeAssistantMessage(finalText, { persist: false });
3091
+ if (finalText.trim()) {
3092
+ this.rememberMessage({ role: "assistant", text: finalText });
3093
+ }
3094
+ return finalText;
3095
+ } catch (error) {
3096
+ const messageText = error instanceof Error ? error.message : "Blinq \u043D\u0435 \u0441\u043C\u043E\u0433 \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C.";
3097
+ this.finalizeAssistantMessage(messageText, { persist: false });
3098
+ this.rememberMessage({ role: "assistant", text: messageText });
3099
+ throw error;
3100
+ } finally {
3101
+ this.hideThinkingIndicator();
3102
+ this.isSending = false;
3103
+ this.syncVoiceState();
3104
+ this.renderReactApp();
3105
+ }
3106
+ }
3107
+ async continueActiveRun() {
3108
+ if (!this.sessionId || !this.sessionToken || !this.activeRun || this.isSending) {
3109
+ return;
3110
+ }
3111
+ if (this.activeRun.status !== "awaiting_navigation") {
3112
+ return;
3113
+ }
3114
+ if (this.activeRun.expected_route && this.currentRoute() !== this.activeRun.expected_route) {
3115
+ 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.");
3116
+ return;
3117
+ }
3118
+ this.isSending = true;
3119
+ this.showThinkingIndicator();
3120
+ this.renderReactApp();
3121
+ try {
3122
+ const finalText = await this.streamChatStep(
3123
+ "/pub/chat",
3124
+ {
3125
+ session_id: this.sessionId,
3126
+ message: "",
3127
+ continue_active_run: true,
3128
+ history: this.recentHistoryForRequest(),
3129
+ page_context: this.currentPageContext()
3130
+ },
3131
+ null
3132
+ );
3133
+ this.finalizeAssistantMessage(finalText);
3134
+ } finally {
3135
+ this.hideThinkingIndicator();
3136
+ this.isSending = false;
3137
+ this.renderReactApp();
3138
+ }
3139
+ }
3140
+ async respondToPlan(approved) {
3141
+ if (!this.sessionId || !this.sessionToken || !this.activeRun) {
3142
+ return;
3143
+ }
3144
+ if (this.isSending) {
3145
+ return;
3146
+ }
3147
+ this.isSending = true;
3148
+ this.showThinkingIndicator();
3149
+ this.renderReactApp();
3150
+ try {
3151
+ const finalText = await this.streamChatStep(
3152
+ "/pub/plan/approve",
3153
+ {
3154
+ session_id: this.sessionId,
3155
+ goal_run_id: this.activeRun.id,
3156
+ approved,
3157
+ page_context: this.currentPageContext()
3158
+ },
3159
+ null
3160
+ );
3161
+ if (finalText.trim()) {
3162
+ this.finalizeAssistantMessage(finalText, { persist: false });
3163
+ this.rememberMessage({ role: "assistant", text: finalText });
3164
+ }
3165
+ } finally {
3166
+ this.hideThinkingIndicator();
3167
+ this.isSending = false;
3168
+ this.renderReactApp();
3169
+ }
3170
+ }
3171
+ async endSession() {
3172
+ if (!this.sessionId || !this.sessionToken) {
3173
+ return;
3174
+ }
3175
+ await fetch(`${this.apiBaseUrl}/pub/session/end`, {
3176
+ method: "POST",
3177
+ headers: {
3178
+ "Content-Type": "application/json",
3179
+ Authorization: `Bearer ${this.sessionToken}`
3180
+ },
3181
+ body: JSON.stringify({ session_id: this.sessionId }),
3182
+ keepalive: true
3183
+ }).catch(() => void 0);
3184
+ if (isBrowser3()) {
3185
+ window.localStorage.removeItem(this.sessionStorageKey());
3186
+ }
3187
+ }
3188
+ };
3189
+ async function init(options) {
3190
+ const widget = new BlinqWidget(options);
3191
+ return widget.init();
3192
+ }
3193
+ async function initFromConfig(config) {
3194
+ return init(normalizedConfig(config));
3195
+ }
3196
+
3197
+ // src/embed-styles.ts
3198
+ var EMBED_WIDGET_STYLES = `
3199
+ :host {
3200
+ all: initial;
3201
+ display: block;
3202
+ position: static;
3203
+ color-scheme: light;
3204
+ }
3205
+
3206
+ *, *::before, *::after {
3207
+ box-sizing: border-box;
3208
+ }
3209
+
3210
+ button,
3211
+ input {
3212
+ font: inherit;
3213
+ }
3214
+
3215
+ .blinq-shell {
3216
+ position: fixed;
3217
+ bottom: 24px;
3218
+ z-index: 2147483647;
3219
+ display: flex;
3220
+ flex-direction: column;
3221
+ gap: 12px;
3222
+ width: 420px;
3223
+ max-width: calc(100vw - 24px);
3224
+ font-family:
3225
+ "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
3226
+ "Segoe UI", sans-serif;
3227
+ pointer-events: none;
3228
+ --blinq-accent: #111111;
3229
+ --blinq-background: #ffffff;
3230
+ --blinq-panel: rgba(255, 255, 255, 0.98);
3231
+ --blinq-panel-soft: #fafafa;
3232
+ --blinq-surface: #ffffff;
3233
+ --blinq-surface-soft: #f5f5f5;
3234
+ --blinq-surface-muted: #ededed;
3235
+ --blinq-border: rgba(17, 17, 17, 0.08);
3236
+ --blinq-border-strong: rgba(17, 17, 17, 0.18);
3237
+ --blinq-text: #111111;
3238
+ --blinq-text-soft: #262626;
3239
+ --blinq-text-muted: #737373;
3240
+ --blinq-user-bubble: #111111;
3241
+ --blinq-user-text: #ffffff;
3242
+ --blinq-shadow: 0 24px 60px rgba(17, 17, 17, 0.12);
3243
+ }
3244
+
3245
+ .blinq-shell[data-side="left"] {
3246
+ left: 24px;
3247
+ align-items: flex-start;
3248
+ }
3249
+
3250
+ .blinq-shell[data-side="right"] {
3251
+ right: 24px;
3252
+ align-items: flex-end;
3253
+ }
3254
+
3255
+ .blinq-panel,
3256
+ .blinq-launcher {
3257
+ pointer-events: auto;
3258
+ }
3259
+
3260
+ .blinq-launcher {
3261
+ display: inline-flex;
3262
+ align-items: center;
3263
+ justify-content: center;
3264
+ gap: 12px;
3265
+ min-height: 58px;
3266
+ padding: 0 18px;
3267
+ border: 1px solid var(--blinq-border);
3268
+ border-radius: 20px;
3269
+ background: #ffffff;
3270
+ color: var(--blinq-text);
3271
+ box-shadow: 0 18px 40px rgba(17, 17, 17, 0.12);
3272
+ backdrop-filter: blur(18px);
3273
+ -webkit-backdrop-filter: blur(18px);
3274
+ transition:
3275
+ transform 180ms ease,
3276
+ box-shadow 180ms ease,
3277
+ border-color 180ms ease;
3278
+ }
3279
+
3280
+ .blinq-launcher:hover {
3281
+ transform: translateY(-2px);
3282
+ border-color: rgba(17, 17, 17, 0.16);
3283
+ box-shadow: 0 22px 46px rgba(17, 17, 17, 0.16);
3284
+ }
3285
+
3286
+ .blinq-launcher-badge {
3287
+ display: grid;
3288
+ place-items: center;
3289
+ width: 34px;
3290
+ height: 34px;
3291
+ border-radius: 12px;
3292
+ background: #111111;
3293
+ color: #ffffff;
3294
+ transition:
3295
+ transform 180ms ease,
3296
+ box-shadow 180ms ease;
3297
+ }
3298
+
3299
+ .blinq-launcher:hover .blinq-launcher-badge {
3300
+ transform: scale(1.06);
3301
+ box-shadow: 0 10px 24px rgba(17, 17, 17, 0.22);
3302
+ }
3303
+
3304
+ .blinq-launcher-icon-only {
3305
+ width: 58px;
3306
+ min-width: 58px;
3307
+ padding: 0;
3308
+ border-radius: 18px;
3309
+ }
3310
+
3311
+ .blinq-launcher-agent {
3312
+ overflow: visible;
3313
+ }
3314
+
3315
+ .blinq-agent-orbit {
3316
+ stroke: rgba(255, 255, 255, 0.42);
3317
+ stroke-width: 1.3;
3318
+ stroke-dasharray: 2.6 2.6;
3319
+ transform-origin: center;
3320
+ transition: opacity 180ms ease;
3321
+ }
3322
+
3323
+ .blinq-agent-spark,
3324
+ .blinq-agent-head,
3325
+ .blinq-agent-antenna,
3326
+ .blinq-agent-antenna-dot,
3327
+ .blinq-agent-neck {
3328
+ transform-origin: center;
3329
+ }
3330
+
3331
+ .blinq-launcher:hover .blinq-agent-orbit {
3332
+ animation: blinq-agent-spin 2.2s linear infinite;
3333
+ }
3334
+
3335
+ .blinq-launcher:hover .blinq-agent-spark {
3336
+ animation: blinq-agent-spark 1.1s ease-in-out infinite;
3337
+ }
3338
+
3339
+ .blinq-launcher:hover .blinq-agent-head,
3340
+ .blinq-launcher:hover .blinq-agent-neck,
3341
+ .blinq-launcher:hover .blinq-agent-antenna,
3342
+ .blinq-launcher:hover .blinq-agent-antenna-dot {
3343
+ animation: blinq-agent-bob 1.35s ease-in-out infinite;
3344
+ }
3345
+
3346
+ .blinq-launcher-copy {
3347
+ display: flex;
3348
+ flex-direction: column;
3349
+ align-items: flex-start;
3350
+ line-height: 1.1;
3351
+ }
3352
+
3353
+ .blinq-launcher-label {
3354
+ font-size: 11px;
3355
+ font-weight: 700;
3356
+ letter-spacing: 0.08em;
3357
+ text-transform: uppercase;
3358
+ color: var(--blinq-text-muted);
3359
+ }
3360
+
3361
+ .blinq-launcher-title {
3362
+ margin-top: 3px;
3363
+ font-size: 14px;
3364
+ font-weight: 600;
3365
+ color: var(--blinq-text);
3366
+ }
3367
+
3368
+ .blinq-panel {
3369
+ width: 100%;
3370
+ max-width: 420px;
3371
+ border: 1px solid var(--blinq-border);
3372
+ border-radius: 28px;
3373
+ overflow: hidden;
3374
+ background: #ffffff;
3375
+ box-shadow: var(--blinq-shadow);
3376
+ backdrop-filter: blur(24px);
3377
+ -webkit-backdrop-filter: blur(24px);
3378
+ }
3379
+
3380
+ .blinq-header {
3381
+ padding: 16px;
3382
+ border-bottom: 1px solid var(--blinq-border);
3383
+ background: #ffffff;
3384
+ }
3385
+
3386
+ .blinq-status-line {
3387
+ margin-top: 10px;
3388
+ font-size: 12px;
3389
+ line-height: 1.45;
3390
+ color: var(--blinq-text-muted);
3391
+ }
3392
+
3393
+ .blinq-header-row {
3394
+ display: flex;
3395
+ align-items: center;
3396
+ justify-content: space-between;
3397
+ gap: 12px;
3398
+ }
3399
+
3400
+ .blinq-header-copy {
3401
+ min-width: 0;
3402
+ flex: 1;
3403
+ }
3404
+
3405
+ .blinq-header-badges {
3406
+ display: flex;
3407
+ flex-wrap: wrap;
3408
+ gap: 8px;
3409
+ }
3410
+
3411
+ .blinq-heading-row {
3412
+ margin-top: 14px;
3413
+ display: flex;
3414
+ align-items: flex-start;
3415
+ justify-content: space-between;
3416
+ gap: 12px;
3417
+ }
3418
+
3419
+ .blinq-heading {
3420
+ font-size: 16px;
3421
+ font-weight: 700;
3422
+ letter-spacing: -0.02em;
3423
+ color: var(--blinq-text);
3424
+ }
3425
+
3426
+ .blinq-subheading {
3427
+ margin-top: 6px;
3428
+ max-width: 320px;
3429
+ font-size: 13px;
3430
+ line-height: 1.45;
3431
+ color: var(--blinq-text-muted);
3432
+ }
3433
+
3434
+ .blinq-statusbar {
3435
+ margin-top: 14px;
3436
+ display: flex;
3437
+ align-items: center;
3438
+ justify-content: space-between;
3439
+ gap: 10px;
3440
+ flex-wrap: wrap;
3441
+ }
3442
+
3443
+ .blinq-status {
3444
+ display: inline-flex;
3445
+ align-items: center;
3446
+ min-height: 34px;
3447
+ padding: 0 12px;
3448
+ border-radius: 999px;
3449
+ border: 1px solid var(--blinq-border);
3450
+ background: #f8f8f8;
3451
+ color: var(--blinq-text-soft);
3452
+ font-size: 12px;
3453
+ line-height: 1.45;
3454
+ }
3455
+
3456
+ .blinq-runtime-chip {
3457
+ display: inline-flex;
3458
+ align-items: center;
3459
+ min-height: 34px;
3460
+ padding: 0 12px;
3461
+ border-radius: 999px;
3462
+ background: #111111;
3463
+ border: 1px solid #111111;
3464
+ color: #ffffff;
3465
+ font-size: 12px;
3466
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
3467
+ }
3468
+
3469
+ .blinq-workspace {
3470
+ display: grid;
3471
+ grid-template-columns: 112px minmax(0, 1fr);
3472
+ min-height: 430px;
3473
+ }
3474
+
3475
+ .blinq-sidebar {
3476
+ display: flex;
3477
+ flex-direction: column;
3478
+ gap: 14px;
3479
+ padding: 16px;
3480
+ border-right: 1px solid var(--blinq-border);
3481
+ background: #fafafa;
3482
+ }
3483
+
3484
+ .blinq-sidebar-group {
3485
+ display: flex;
3486
+ flex-direction: column;
3487
+ gap: 8px;
3488
+ }
3489
+
3490
+ .blinq-tab-trigger {
3491
+ display: flex;
3492
+ align-items: center;
3493
+ gap: 10px;
3494
+ width: 100%;
3495
+ min-height: 56px;
3496
+ padding: 10px;
3497
+ border: 1px solid transparent;
3498
+ border-radius: 18px;
3499
+ background: transparent;
3500
+ color: var(--blinq-text-soft);
3501
+ cursor: pointer;
3502
+ transition:
3503
+ background 120ms ease,
3504
+ border-color 120ms ease,
3505
+ color 120ms ease,
3506
+ transform 120ms ease;
3507
+ }
3508
+
3509
+ .blinq-tab-trigger:hover {
3510
+ background: rgba(255,255,255,0.9);
3511
+ border-color: var(--blinq-border);
3512
+ }
3513
+
3514
+ .blinq-tab-trigger-active {
3515
+ background: #ffffff;
3516
+ border-color: rgba(17, 17, 17, 0.16);
3517
+ box-shadow: 0 10px 24px rgba(17, 17, 17, 0.08);
3518
+ color: var(--blinq-text);
3519
+ }
3520
+
3521
+ .blinq-tab-icon {
3522
+ display: grid;
3523
+ place-items: center;
3524
+ width: 32px;
3525
+ height: 32px;
3526
+ border-radius: 12px;
3527
+ background: rgba(15, 23, 42, 0.04);
3528
+ color: currentColor;
3529
+ flex: 0 0 auto;
3530
+ }
3531
+
3532
+ .blinq-tab-copy {
3533
+ display: flex;
3534
+ flex-direction: column;
3535
+ min-width: 0;
3536
+ align-items: flex-start;
3537
+ text-align: left;
3538
+ }
3539
+
3540
+ .blinq-tab-label {
3541
+ font-size: 13px;
3542
+ font-weight: 600;
3543
+ }
3544
+
3545
+ .blinq-tab-caption {
3546
+ margin-top: 2px;
3547
+ font-size: 11px;
3548
+ color: var(--blinq-text-muted);
3549
+ }
3550
+
3551
+ .blinq-tab-count {
3552
+ margin-left: auto;
3553
+ min-width: 22px;
3554
+ height: 22px;
3555
+ padding: 0 6px;
3556
+ display: inline-flex;
3557
+ align-items: center;
3558
+ justify-content: center;
3559
+ border-radius: 999px;
3560
+ background: rgba(15, 23, 42, 0.06);
3561
+ color: var(--blinq-text-soft);
3562
+ font-size: 11px;
3563
+ font-weight: 700;
3564
+ }
3565
+
3566
+ .blinq-separator {
3567
+ height: 1px;
3568
+ background: var(--blinq-border);
3569
+ }
3570
+
3571
+ .blinq-sidebar-card {
3572
+ gap: 12px;
3573
+ border-radius: 18px;
3574
+ background: rgba(255,255,255,0.94);
3575
+ box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
3576
+ }
3577
+
3578
+ .blinq-sidebar-card-header {
3579
+ padding: 14px 14px 0;
3580
+ }
3581
+
3582
+ .blinq-sidebar-card-content {
3583
+ padding: 0 14px 14px;
3584
+ display: flex;
3585
+ flex-direction: column;
3586
+ gap: 10px;
3587
+ }
3588
+
3589
+ .blinq-stat-line {
3590
+ display: flex;
3591
+ align-items: center;
3592
+ justify-content: space-between;
3593
+ gap: 8px;
3594
+ font-size: 12px;
3595
+ color: var(--blinq-text-muted);
3596
+ }
3597
+
3598
+ .blinq-stat-line strong {
3599
+ color: var(--blinq-text);
3600
+ font-size: 12px;
3601
+ }
3602
+
3603
+ .blinq-main {
3604
+ display: flex;
3605
+ flex-direction: column;
3606
+ min-width: 0;
3607
+ background: transparent;
3608
+ }
3609
+
3610
+ .blinq-main-toolbar {
3611
+ padding: 16px 18px 12px;
3612
+ display: flex;
3613
+ flex-direction: column;
3614
+ gap: 10px;
3615
+ }
3616
+
3617
+ .blinq-toolbar-label {
3618
+ font-size: 11px;
3619
+ font-weight: 700;
3620
+ letter-spacing: 0.08em;
3621
+ text-transform: uppercase;
3622
+ color: var(--blinq-text-muted);
3623
+ }
3624
+
3625
+ .blinq-prompt-row {
3626
+ display: flex;
3627
+ gap: 8px;
3628
+ flex-wrap: wrap;
3629
+ }
3630
+
3631
+ .blinq-prompt-chip {
3632
+ padding: 8px 12px;
3633
+ border-radius: 999px;
3634
+ border: 1px solid var(--blinq-border);
3635
+ background: rgba(255,255,255,0.95);
3636
+ color: var(--blinq-text-soft);
3637
+ font-size: 12px;
3638
+ font-weight: 600;
3639
+ cursor: pointer;
3640
+ transition:
3641
+ background 120ms ease,
3642
+ border-color 120ms ease,
3643
+ color 120ms ease,
3644
+ transform 120ms ease;
3645
+ }
3646
+
3647
+ .blinq-prompt-chip:hover {
3648
+ border-color: rgba(17, 17, 17, 0.16);
3649
+ color: #111111;
3650
+ background: #f7f7f7;
3651
+ }
3652
+
3653
+ .blinq-scroll {
3654
+ display: flex;
3655
+ flex-direction: column;
3656
+ gap: 12px;
3657
+ min-height: 0;
3658
+ height: 100%;
3659
+ max-height: 420px;
3660
+ padding: 16px;
3661
+ overflow-y: auto;
3662
+ }
3663
+
3664
+ .blinq-thread {
3665
+ min-height: 0;
3666
+ }
3667
+
3668
+ .blinq-thread-viewport {
3669
+ width: 100%;
3670
+ }
3671
+
3672
+ .blinq-scroll-actions,
3673
+ .blinq-scroll-context {
3674
+ padding-top: 18px;
3675
+ }
3676
+
3677
+ .blinq-scroll::-webkit-scrollbar {
3678
+ width: 8px;
3679
+ }
3680
+
3681
+ .blinq-scroll::-webkit-scrollbar-thumb {
3682
+ background: rgba(148, 163, 184, 0.25);
3683
+ border-radius: 999px;
3684
+ }
3685
+
3686
+ .blinq-message {
3687
+ max-width: 88%;
3688
+ border-radius: 20px;
3689
+ padding: 13px 14px;
3690
+ font-size: 14px;
3691
+ line-height: 1.55;
3692
+ white-space: pre-wrap;
3693
+ border: 1px solid var(--blinq-border);
3694
+ box-shadow: 0 12px 30px rgba(15, 23, 42, 0.05);
3695
+ }
3696
+
3697
+ .blinq-message[data-role="assistant"] {
3698
+ align-self: flex-start;
3699
+ background: rgba(255,255,255,0.98);
3700
+ color: var(--blinq-text);
3701
+ }
3702
+
3703
+ .blinq-message[data-kind="artifact"] {
3704
+ max-width: 100%;
3705
+ width: 100%;
3706
+ padding: 0;
3707
+ border: 0;
3708
+ background: transparent;
3709
+ box-shadow: none;
3710
+ }
3711
+
3712
+ .blinq-message[data-role="user"] {
3713
+ align-self: flex-end;
3714
+ background: var(--blinq-user-bubble);
3715
+ border-color: rgba(17, 17, 17, 0.22);
3716
+ color: var(--blinq-user-text);
3717
+ }
3718
+
3719
+ .blinq-message-copy {
3720
+ display: block;
3721
+ }
3722
+
3723
+ .blinq-message-copy-reveal {
3724
+ animation: blinq-blur-in 220ms cubic-bezier(0.16, 1, 0.3, 1);
3725
+ will-change: opacity, transform, filter;
3726
+ }
3727
+
3728
+ .blinq-thinking-card {
3729
+ align-self: flex-start;
3730
+ background: rgba(255,255,255,0.98);
3731
+ color: var(--blinq-text);
3732
+ }
3733
+
3734
+ .blinq-thinking {
3735
+ display: inline-flex;
3736
+ align-items: center;
3737
+ gap: 10px;
3738
+ min-height: 20px;
3739
+ font-size: 13px;
3740
+ color: var(--blinq-text-muted);
3741
+ opacity: 1;
3742
+ transform: translateY(0);
3743
+ transition:
3744
+ opacity 180ms ease,
3745
+ transform 180ms ease;
3746
+ }
3747
+
3748
+ .blinq-thinking-dots {
3749
+ display: inline-flex;
3750
+ align-items: center;
3751
+ gap: 4px;
3752
+ }
3753
+
3754
+ .blinq-thinking-dots > span {
3755
+ width: 6px;
3756
+ height: 6px;
3757
+ border-radius: 999px;
3758
+ background: #111111;
3759
+ animation: blinq-thinking-pulse 1.1s ease-in-out infinite;
3760
+ }
3761
+
3762
+ .blinq-thinking-dots > span:nth-child(2) {
3763
+ animation-delay: 120ms;
3764
+ }
3765
+
3766
+ .blinq-thinking-dots > span:nth-child(3) {
3767
+ animation-delay: 240ms;
3768
+ }
3769
+
3770
+ .blinq-card {
3771
+ display: flex;
3772
+ flex-direction: column;
3773
+ gap: 14px;
3774
+ border: 1px solid var(--blinq-border);
3775
+ border-radius: 20px;
3776
+ background: var(--blinq-surface);
3777
+ color: var(--blinq-text);
3778
+ box-shadow: 0 16px 36px rgba(15, 23, 42, 0.06);
3779
+ }
3780
+
3781
+ .blinq-card-header {
3782
+ display: flex;
3783
+ flex-direction: column;
3784
+ gap: 7px;
3785
+ padding: 16px 16px 0;
3786
+ }
3787
+
3788
+ .blinq-card-title {
3789
+ font-size: 15px;
3790
+ font-weight: 700;
3791
+ color: var(--blinq-text);
3792
+ }
3793
+
3794
+ .blinq-card-description {
3795
+ font-size: 13px;
3796
+ line-height: 1.5;
3797
+ color: var(--blinq-text-muted);
3798
+ }
3799
+
3800
+ .blinq-card-content {
3801
+ padding: 0 16px 16px;
3802
+ }
3803
+
3804
+ .blinq-action-card {
3805
+ align-self: stretch;
3806
+ gap: 0;
3807
+ }
3808
+
3809
+ .blinq-plan-card {
3810
+ border-color: var(--blinq-border-strong);
3811
+ }
3812
+
3813
+ .blinq-plan-step {
3814
+ display: flex;
3815
+ align-items: flex-start;
3816
+ gap: 10px;
3817
+ padding: 10px 0;
3818
+ border-top: 1px solid rgba(17, 17, 17, 0.06);
3819
+ }
3820
+
3821
+ .blinq-plan-step:first-child {
3822
+ border-top: 0;
3823
+ padding-top: 0;
3824
+ }
3825
+
3826
+ .blinq-plan-step-index {
3827
+ display: inline-flex;
3828
+ align-items: center;
3829
+ justify-content: center;
3830
+ width: 22px;
3831
+ height: 22px;
3832
+ border-radius: 999px;
3833
+ background: #111111;
3834
+ color: #ffffff;
3835
+ font-size: 11px;
3836
+ font-weight: 700;
3837
+ flex: none;
3838
+ }
3839
+
3840
+ .blinq-plan-step-copy {
3841
+ min-width: 0;
3842
+ display: flex;
3843
+ flex-direction: column;
3844
+ gap: 3px;
3845
+ font-size: 13px;
3846
+ color: var(--blinq-text);
3847
+ }
3848
+
3849
+ .blinq-plan-step-meta {
3850
+ font-size: 12px;
3851
+ color: var(--blinq-text-muted);
3852
+ }
3853
+
3854
+ .blinq-plan-step[data-current="true"] .blinq-plan-step-index {
3855
+ box-shadow: 0 0 0 4px rgba(17, 17, 17, 0.08);
3856
+ }
3857
+
3858
+ .blinq-plan-step[data-status="completed"] .blinq-plan-step-index {
3859
+ background: #111111;
3860
+ }
3861
+
3862
+ .blinq-plan-step[data-status="failed"] .blinq-plan-step-index,
3863
+ .blinq-plan-step[data-status="cancelled"] .blinq-plan-step-index {
3864
+ background: #525252;
3865
+ }
3866
+
3867
+ .blinq-action-meta-row {
3868
+ display: flex;
3869
+ align-items: center;
3870
+ justify-content: space-between;
3871
+ gap: 8px;
3872
+ flex-wrap: wrap;
3873
+ }
3874
+
3875
+ .blinq-action-details {
3876
+ margin-top: 8px;
3877
+ }
3878
+
3879
+ .blinq-action-details > summary {
3880
+ cursor: pointer;
3881
+ font-size: 12px;
3882
+ font-weight: 600;
3883
+ color: #111111;
3884
+ list-style: none;
3885
+ }
3886
+
3887
+ .blinq-action-details > summary::-webkit-details-marker {
3888
+ display: none;
3889
+ }
3890
+
3891
+ .blinq-code {
3892
+ margin-top: 10px;
3893
+ padding: 10px 12px;
3894
+ border-radius: 14px;
3895
+ background: var(--blinq-surface-soft);
3896
+ border: 1px solid var(--blinq-border);
3897
+ font-size: 12px;
3898
+ line-height: 1.45;
3899
+ white-space: pre-wrap;
3900
+ color: var(--blinq-text-soft);
3901
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
3902
+ }
3903
+
3904
+ .blinq-code-compact {
3905
+ margin-top: 12px;
3906
+ }
3907
+
3908
+ .blinq-action-state {
3909
+ margin-top: 12px;
3910
+ font-size: 12px;
3911
+ color: var(--blinq-text-muted);
3912
+ }
3913
+
3914
+ .blinq-action-state[data-state="approved"] {
3915
+ color: #111111;
3916
+ }
3917
+
3918
+ .blinq-action-state[data-state="declined"] {
3919
+ color: var(--blinq-text-muted);
3920
+ }
3921
+
3922
+ .blinq-actions {
3923
+ display: flex;
3924
+ gap: 10px;
3925
+ margin-top: 14px;
3926
+ }
3927
+
3928
+ .blinq-actions > * {
3929
+ flex: 1;
3930
+ }
3931
+
3932
+ .blinq-context-grid {
3933
+ display: grid;
3934
+ grid-template-columns: repeat(2, minmax(0, 1fr));
3935
+ gap: 12px;
3936
+ }
3937
+
3938
+ .blinq-context-card {
3939
+ min-height: 150px;
3940
+ }
3941
+
3942
+ .blinq-context-card-wide {
3943
+ grid-column: 1 / -1;
3944
+ }
3945
+
3946
+ .blinq-context-copy {
3947
+ font-size: 13px;
3948
+ line-height: 1.5;
3949
+ color: var(--blinq-text-soft);
3950
+ }
3951
+
3952
+ .blinq-empty-card {
3953
+ min-height: 180px;
3954
+ justify-content: center;
3955
+ }
3956
+
3957
+ .blinq-badge-pill {
3958
+ display: inline-flex;
3959
+ align-items: center;
3960
+ justify-content: center;
3961
+ min-height: 28px;
3962
+ padding: 0 10px;
3963
+ border-radius: 999px;
3964
+ font-size: 11px;
3965
+ font-weight: 700;
3966
+ letter-spacing: 0.06em;
3967
+ text-transform: uppercase;
3968
+ }
3969
+
3970
+ .blinq-badge-pill-default {
3971
+ background: rgba(15, 23, 42, 0.05);
3972
+ color: var(--blinq-text-soft);
3973
+ }
3974
+
3975
+ .blinq-badge-pill-outline {
3976
+ border: 1px solid var(--blinq-border);
3977
+ color: var(--blinq-text-muted);
3978
+ background: rgba(255,255,255,0.84);
3979
+ }
3980
+
3981
+ .blinq-badge-pill-soft {
3982
+ background: #f5f5f5;
3983
+ color: #111111;
3984
+ }
3985
+
3986
+ .blinq-badge-pill-accent {
3987
+ background: #111111;
3988
+ color: #ffffff;
3989
+ }
3990
+
3991
+ .blinq-composer {
3992
+ display: flex;
3993
+ flex-direction: column;
3994
+ gap: 10px;
3995
+ padding: 0 16px 16px;
3996
+ border-top: 1px solid var(--blinq-border);
3997
+ background: #ffffff;
3998
+ }
3999
+
4000
+ .blinq-composer-copy {
4001
+ display: flex;
4002
+ flex-direction: column;
4003
+ gap: 4px;
4004
+ }
4005
+
4006
+ .blinq-composer-title {
4007
+ font-size: 12px;
4008
+ font-weight: 700;
4009
+ letter-spacing: 0.08em;
4010
+ text-transform: uppercase;
4011
+ color: var(--blinq-text-muted);
4012
+ }
4013
+
4014
+ .blinq-composer-hint {
4015
+ font-size: 13px;
4016
+ line-height: 1.45;
4017
+ color: var(--blinq-text-muted);
4018
+ }
4019
+
4020
+ .blinq-composer-controls {
4021
+ display: flex;
4022
+ padding-top: 14px;
4023
+ }
4024
+
4025
+ .blinq-button {
4026
+ display: inline-flex;
4027
+ align-items: center;
4028
+ justify-content: center;
4029
+ gap: 8px;
4030
+ border-radius: 14px;
4031
+ border: 1px solid transparent;
4032
+ font-size: 14px;
4033
+ font-weight: 600;
4034
+ transition:
4035
+ background 120ms ease,
4036
+ border-color 120ms ease,
4037
+ color 120ms ease,
4038
+ opacity 120ms ease,
4039
+ transform 120ms ease;
4040
+ cursor: pointer;
4041
+ outline: none;
4042
+ }
4043
+
4044
+ .blinq-button:disabled {
4045
+ opacity: 0.55;
4046
+ cursor: not-allowed;
4047
+ }
4048
+
4049
+ .blinq-button:focus-visible,
4050
+ .blinq-input:focus-visible,
4051
+ .blinq-tab-trigger:focus-visible,
4052
+ .blinq-prompt-chip:focus-visible {
4053
+ box-shadow: 0 0 0 3px rgba(17, 17, 17, 0.12);
4054
+ outline: none;
4055
+ }
4056
+
4057
+ .blinq-button-default {
4058
+ background: #111111;
4059
+ color: white;
4060
+ box-shadow: 0 12px 24px rgba(17, 17, 17, 0.16);
4061
+ }
4062
+
4063
+ .blinq-button-default:hover:not(:disabled) {
4064
+ filter: brightness(1.04);
4065
+ }
4066
+
4067
+ .blinq-button-outline {
4068
+ border-color: var(--blinq-border-strong);
4069
+ background: #ffffff;
4070
+ color: var(--blinq-text);
4071
+ }
4072
+
4073
+ .blinq-button-outline:hover:not(:disabled),
4074
+ .blinq-button-ghost:hover:not(:disabled) {
4075
+ background: var(--blinq-surface-soft);
4076
+ }
4077
+
4078
+ .blinq-button-ghost {
4079
+ border-color: var(--blinq-border);
4080
+ background: rgba(255,255,255,0.76);
4081
+ color: var(--blinq-text-soft);
4082
+ }
4083
+
4084
+ .blinq-button-destructive {
4085
+ background: #111111;
4086
+ border-color: #111111;
4087
+ color: #ffffff;
4088
+ }
4089
+
4090
+ .blinq-button-md {
4091
+ height: 44px;
4092
+ padding: 0 16px;
4093
+ }
4094
+
4095
+ .blinq-button-sm {
4096
+ height: 36px;
4097
+ padding: 0 12px;
4098
+ font-size: 13px;
4099
+ }
4100
+
4101
+ .blinq-button-icon {
4102
+ width: 44px;
4103
+ height: 44px;
4104
+ padding: 0;
4105
+ }
4106
+
4107
+ .blinq-button-icon-sm {
4108
+ width: 36px;
4109
+ height: 36px;
4110
+ padding: 0;
4111
+ border-radius: 12px;
4112
+ }
4113
+
4114
+ .blinq-input {
4115
+ width: 100%;
4116
+ min-width: 0;
4117
+ height: 52px;
4118
+ border-radius: 18px;
4119
+ border: 1px solid var(--blinq-border-strong);
4120
+ padding: 0 48px 0 44px;
4121
+ background: #ffffff;
4122
+ color: var(--blinq-text);
4123
+ outline: none;
4124
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
4125
+ }
4126
+
4127
+ .blinq-input-shell {
4128
+ position: relative;
4129
+ width: 100%;
4130
+ }
4131
+
4132
+ .blinq-recording-input {
4133
+ display: flex;
4134
+ align-items: center;
4135
+ gap: 12px;
4136
+ width: 100%;
4137
+ min-width: 0;
4138
+ height: 52px;
4139
+ padding: 0 48px 0 44px;
4140
+ border-radius: 18px;
4141
+ border: 1px solid var(--blinq-border-strong);
4142
+ background: #ffffff;
4143
+ color: var(--blinq-text);
4144
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
4145
+ }
4146
+
4147
+ .blinq-recording-time {
4148
+ flex: 0 0 auto;
4149
+ min-width: 42px;
4150
+ font-size: 12px;
4151
+ font-weight: 700;
4152
+ letter-spacing: 0.04em;
4153
+ color: var(--blinq-text-soft);
4154
+ font-variant-numeric: tabular-nums;
4155
+ }
4156
+
4157
+ .blinq-recording-waveform {
4158
+ display: flex;
4159
+ align-items: flex-end;
4160
+ gap: 3px;
4161
+ flex: 1 1 auto;
4162
+ min-width: 0;
4163
+ height: 24px;
4164
+ overflow: hidden;
4165
+ }
4166
+
4167
+ .blinq-recording-waveform-bar {
4168
+ width: 3px;
4169
+ min-width: 3px;
4170
+ height: 100%;
4171
+ border-radius: 999px;
4172
+ background: rgba(17, 17, 17, 0.22);
4173
+ transform-origin: center bottom;
4174
+ transition:
4175
+ transform 110ms ease,
4176
+ background 110ms ease;
4177
+ }
4178
+
4179
+ .blinq-input-mic {
4180
+ position: absolute;
4181
+ left: 8px;
4182
+ top: 50%;
4183
+ transform: translateY(-50%);
4184
+ z-index: 2;
4185
+ }
4186
+
4187
+ .blinq-input-send {
4188
+ position: absolute;
4189
+ right: 8px;
4190
+ top: 50%;
4191
+ transform: translateY(-50%);
4192
+ z-index: 2;
4193
+ width: 36px;
4194
+ height: 36px;
4195
+ min-width: 36px;
4196
+ border-radius: 12px;
4197
+ }
4198
+
4199
+ .blinq-text-animate {
4200
+ display: block;
4201
+ }
4202
+
4203
+ .blinq-text-animate-blur-in .blinq-text-segment {
4204
+ display: inline-block;
4205
+ opacity: 0;
4206
+ filter: blur(8px);
4207
+ animation: blinq-blur-in 420ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
4208
+ }
4209
+
4210
+ @keyframes blinq-thinking-pulse {
4211
+ 0%, 100% {
4212
+ opacity: 0.28;
4213
+ transform: translateY(0);
4214
+ }
4215
+
4216
+ 50% {
4217
+ opacity: 1;
4218
+ transform: translateY(-2px);
4219
+ }
4220
+ }
4221
+
4222
+ @keyframes blinq-agent-spin {
4223
+ from {
4224
+ transform: rotate(0deg);
4225
+ }
4226
+
4227
+ to {
4228
+ transform: rotate(360deg);
4229
+ }
4230
+ }
4231
+
4232
+ @keyframes blinq-agent-bob {
4233
+ 0%, 100% {
4234
+ transform: translateY(0);
4235
+ }
4236
+
4237
+ 50% {
4238
+ transform: translateY(-0.8px);
4239
+ }
4240
+ }
4241
+
4242
+ @keyframes blinq-agent-spark {
4243
+ 0%, 100% {
4244
+ transform: scale(1);
4245
+ opacity: 0.72;
4246
+ }
4247
+
4248
+ 50% {
4249
+ transform: scale(1.25);
4250
+ opacity: 1;
4251
+ }
4252
+ }
4253
+
4254
+ @keyframes blinq-blur-in {
4255
+ 0% {
4256
+ opacity: 0;
4257
+ filter: blur(8px);
4258
+ transform: translateY(4px);
4259
+ }
4260
+
4261
+ 100% {
4262
+ opacity: 1;
4263
+ filter: blur(0);
4264
+ transform: translateY(0);
4265
+ }
4266
+ }
4267
+
4268
+ .blinq-input::placeholder {
4269
+ color: var(--blinq-text-muted);
4270
+ }
4271
+
4272
+ @media (max-width: 720px) {
4273
+ .blinq-shell {
4274
+ bottom: 12px;
4275
+ left: 12px;
4276
+ right: 12px;
4277
+ width: auto;
4278
+ max-width: none;
4279
+ align-items: stretch;
4280
+ }
4281
+
4282
+ .blinq-shell[data-side="left"],
4283
+ .blinq-shell[data-side="right"] {
4284
+ left: 12px;
4285
+ right: 12px;
4286
+ align-items: stretch;
4287
+ }
4288
+
4289
+ .blinq-panel,
4290
+ .blinq-launcher {
4291
+ max-width: none;
4292
+ width: 100%;
4293
+ }
4294
+
4295
+ .blinq-workspace {
4296
+ grid-template-columns: 1fr;
4297
+ }
4298
+
4299
+ .blinq-sidebar {
4300
+ border-right: none;
4301
+ border-bottom: 1px solid var(--blinq-border);
4302
+ }
4303
+
4304
+ .blinq-sidebar-group {
4305
+ flex-direction: row;
4306
+ overflow-x: auto;
4307
+ }
4308
+
4309
+ .blinq-tab-trigger {
4310
+ min-width: 116px;
4311
+ }
4312
+
4313
+ .blinq-context-grid {
4314
+ grid-template-columns: 1fr;
4315
+ }
4316
+ }
4317
+ `;
4318
+
4319
+ // src/browser.ts
4320
+ var EMBED_TAG_NAME = "blinq-widget";
4321
+ function shadowRootStyles() {
4322
+ return EMBED_WIDGET_STYLES;
4323
+ }
4324
+ var BlinqWidgetElement = class extends HTMLElement {
4325
+ widget = null;
4326
+ widgetPromise = null;
4327
+ mountEl = null;
4328
+ connectedCallback() {
4329
+ if (this.widget || this.widgetPromise) {
4330
+ return;
4331
+ }
4332
+ const publicToken = this.getAttribute("public-token")?.trim();
4333
+ if (!publicToken) {
4334
+ console.error("Blinq widget: missing required public-token attribute.");
4335
+ return;
4336
+ }
4337
+ const root = this.shadowRoot ?? this.attachShadow({ mode: "open" });
4338
+ root.replaceChildren();
4339
+ const style = document.createElement("style");
4340
+ style.textContent = shadowRootStyles();
4341
+ const mount = document.createElement("div");
4342
+ mount.dataset.blinqMount = "true";
4343
+ root.append(style, mount);
4344
+ this.mountEl = mount;
4345
+ const options = {
4346
+ publicToken,
4347
+ apiBaseUrl: this.getAttribute("api-base-url")?.trim() || void 0,
4348
+ mcpEndpoint: this.getAttribute("mcp-endpoint")?.trim() || void 0,
4349
+ mount
4350
+ };
4351
+ this.widgetPromise = init(options).then((widget) => {
4352
+ this.widget = widget;
4353
+ return widget;
4354
+ }).catch((error) => {
4355
+ console.error("Blinq widget failed to initialize.", error);
4356
+ throw error;
4357
+ }).finally(() => {
4358
+ this.widgetPromise = null;
4359
+ });
4360
+ }
4361
+ disconnectedCallback() {
4362
+ this.widget?.destroy();
4363
+ void this.widget?.endSession();
4364
+ this.widget = null;
4365
+ this.widgetPromise = null;
4366
+ this.mountEl = null;
4367
+ }
4368
+ };
4369
+ function defineBlinqWidgetElement() {
4370
+ if (typeof window === "undefined" || typeof customElements === "undefined") {
4371
+ return;
4372
+ }
4373
+ if (!customElements.get(EMBED_TAG_NAME)) {
4374
+ customElements.define(EMBED_TAG_NAME, BlinqWidgetElement);
4375
+ }
4376
+ }
4377
+ if (typeof window !== "undefined") {
4378
+ defineBlinqWidgetElement();
4379
+ window.BlinqWidget = {
4380
+ init,
4381
+ initFromConfig
4382
+ };
4383
+ }
4384
+ // Annotate the CommonJS export names for ESM import in node:
4385
+ 0 && (module.exports = {
4386
+ BlinqWidgetElement,
4387
+ init,
4388
+ initFromConfig
4389
+ });
4390
+ //# sourceMappingURL=browser.cjs.map