@wallavi/widget 1.12.7 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +48 -6
- package/dist/index.d.ts +48 -6
- package/dist/index.js +630 -214
- package/dist/index.mjs +630 -214
- package/package.json +7 -4
package/dist/index.mjs
CHANGED
|
@@ -66,6 +66,83 @@ async function getFreshClerkToken() {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// src/lib/page-context.ts
|
|
70
|
+
function candidatesFrom(actions) {
|
|
71
|
+
const out = [];
|
|
72
|
+
for (const action of actions) {
|
|
73
|
+
const title = action.displayName ?? action.name;
|
|
74
|
+
const target = action.steps?.find((s) => s.action === "navigate")?.value;
|
|
75
|
+
if (!title || typeof target !== "string" || !target.startsWith("/")) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const [path = "", search = ""] = target.split("?");
|
|
79
|
+
out.push({
|
|
80
|
+
title,
|
|
81
|
+
segments: path.split("/").filter(Boolean),
|
|
82
|
+
query: new URLSearchParams(search)
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
var PARAM = /^\[(\w+)\]$/;
|
|
88
|
+
var LOCALE = /^[a-z]{2}(?:-[a-z]{2,4})?$/i;
|
|
89
|
+
function offsetsFor(pathSegments, routeLength) {
|
|
90
|
+
const offsets = [];
|
|
91
|
+
if (pathSegments.length === routeLength) offsets.push(0);
|
|
92
|
+
if (pathSegments.length === routeLength + 1 && LOCALE.test(pathSegments[0] ?? "")) {
|
|
93
|
+
offsets.push(1);
|
|
94
|
+
}
|
|
95
|
+
return offsets;
|
|
96
|
+
}
|
|
97
|
+
function safeDecode(segment) {
|
|
98
|
+
try {
|
|
99
|
+
return decodeURIComponent(segment);
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function score(candidate, pathSegments, search) {
|
|
105
|
+
const { segments, query } = candidate;
|
|
106
|
+
if (segments.length === 0) return null;
|
|
107
|
+
for (const [key, value] of query) {
|
|
108
|
+
if (search.get(key) !== value) return null;
|
|
109
|
+
}
|
|
110
|
+
for (const offset of offsetsFor(pathSegments, segments.length)) {
|
|
111
|
+
const params = {};
|
|
112
|
+
let points = Array.from(query.keys()).length;
|
|
113
|
+
let matched = true;
|
|
114
|
+
for (let i = 0; i < segments.length && matched; i++) {
|
|
115
|
+
const pattern = segments[i];
|
|
116
|
+
const actual = safeDecode(pathSegments[offset + i]);
|
|
117
|
+
const param = PARAM.exec(pattern);
|
|
118
|
+
if (actual === null) matched = false;
|
|
119
|
+
else if (param) params[param[1]] = actual;
|
|
120
|
+
else if (pattern === actual) points += 2;
|
|
121
|
+
else matched = false;
|
|
122
|
+
}
|
|
123
|
+
if (matched) return { score: points, params };
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
function derivePageContext(location, actions) {
|
|
128
|
+
if (!actions?.length) return void 0;
|
|
129
|
+
const pathSegments = location.pathname.split("/").filter(Boolean);
|
|
130
|
+
const search = new URLSearchParams(location.search);
|
|
131
|
+
let best = null;
|
|
132
|
+
for (const candidate of candidatesFrom(actions)) {
|
|
133
|
+
const result = score(candidate, pathSegments, search);
|
|
134
|
+
if (result && (!best || result.score > best.score)) {
|
|
135
|
+
best = { title: candidate.title, ...result };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (!best) return void 0;
|
|
139
|
+
return {
|
|
140
|
+
url: `${location.pathname}${location.search}`,
|
|
141
|
+
title: best.title,
|
|
142
|
+
...Object.keys(best.params).length > 0 ? { params: best.params } : {}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
69
146
|
// src/lib/types.ts
|
|
70
147
|
function getContrastColor(hex) {
|
|
71
148
|
const clean = hex.replace("#", "");
|
|
@@ -79,7 +156,7 @@ function formatToolName(name) {
|
|
|
79
156
|
return name.replace(/([A-Z])/g, " $1").replace(/_/g, " ").trim();
|
|
80
157
|
}
|
|
81
158
|
|
|
82
|
-
// src/
|
|
159
|
+
// ../protocol/src/index.ts
|
|
83
160
|
var STREAM_DELIMITER = "\u03B6\u236E";
|
|
84
161
|
async function consumeStream(body, handler) {
|
|
85
162
|
const reader = body.getReader();
|
|
@@ -95,9 +172,9 @@ async function consumeStream(body, handler) {
|
|
|
95
172
|
if (!raw) continue;
|
|
96
173
|
try {
|
|
97
174
|
const parsed = JSON.parse(raw);
|
|
98
|
-
|
|
99
|
-
if (proto) {
|
|
175
|
+
if (parsed.data?.uiMessageProtocol) {
|
|
100
176
|
eventCount++;
|
|
177
|
+
const proto = parsed.data.uiMessageProtocol;
|
|
101
178
|
if (proto.type === "text-delta") {
|
|
102
179
|
textAccumulator += proto.delta ?? "";
|
|
103
180
|
}
|
|
@@ -247,12 +324,321 @@ function applyUiEventToMessages(prev, proto, msgId) {
|
|
|
247
324
|
copy[idx] = msg;
|
|
248
325
|
return copy;
|
|
249
326
|
}
|
|
250
|
-
|
|
327
|
+
|
|
328
|
+
// src/core/declarative-actions.ts
|
|
329
|
+
function sanitizeSelector(selector) {
|
|
330
|
+
if (!selector || typeof selector !== "string") return null;
|
|
331
|
+
const lower = selector.toLowerCase();
|
|
332
|
+
if (lower.includes("<") || lower.includes(">") || lower.includes("script") || lower.includes("iframe") || lower.includes("onload") || lower.includes("onerror") || lower.includes("javascript:")) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
const cleanRegex = /^[a-zA-Z0-9\s\.\-#_\[\]="':]+$/;
|
|
336
|
+
if (!cleanRegex.test(selector)) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
return selector;
|
|
340
|
+
}
|
|
341
|
+
function sanitizeEventName(name) {
|
|
342
|
+
if (!name || typeof name !== "string") return null;
|
|
343
|
+
const nameRegex = /^[a-zA-Z0-9\-_:]+$/;
|
|
344
|
+
if (!nameRegex.test(name)) return null;
|
|
345
|
+
const nativeEvents = /* @__PURE__ */ new Set([
|
|
346
|
+
"click",
|
|
347
|
+
"dblclick",
|
|
348
|
+
"mouseup",
|
|
349
|
+
"mousedown",
|
|
350
|
+
"mouseover",
|
|
351
|
+
"mouseout",
|
|
352
|
+
"submit",
|
|
353
|
+
"reset",
|
|
354
|
+
"change",
|
|
355
|
+
"select",
|
|
356
|
+
"keydown",
|
|
357
|
+
"keypress",
|
|
358
|
+
"keyup",
|
|
359
|
+
"load",
|
|
360
|
+
"unload",
|
|
361
|
+
"abort",
|
|
362
|
+
"error",
|
|
363
|
+
"resize",
|
|
364
|
+
"scroll",
|
|
365
|
+
"contextmenu"
|
|
366
|
+
]);
|
|
367
|
+
if (nativeEvents.has(name.toLowerCase())) return null;
|
|
368
|
+
return name;
|
|
369
|
+
}
|
|
370
|
+
function sanitizeUrl(url) {
|
|
371
|
+
if (!url || typeof url !== "string") return null;
|
|
372
|
+
const trimmed = url.trim();
|
|
373
|
+
const lower = trimmed.toLowerCase();
|
|
374
|
+
if (trimmed.startsWith("//")) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
if (lower.startsWith("javascript:") || lower.startsWith("data:")) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
if (trimmed.startsWith("/")) {
|
|
381
|
+
return trimmed;
|
|
382
|
+
}
|
|
383
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
|
384
|
+
return trimmed;
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
async function waitForElement(selector, retries = 5, delayMs = 100) {
|
|
389
|
+
const cleanId = selector.startsWith("#") ? selector.substring(1) : selector;
|
|
390
|
+
for (let i = 0; i < retries; i++) {
|
|
391
|
+
try {
|
|
392
|
+
const el = document.querySelector(selector);
|
|
393
|
+
if (el) return el;
|
|
394
|
+
} catch (e) {
|
|
395
|
+
console.error(e);
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
const byId = document.getElementById(cleanId);
|
|
399
|
+
if (byId) return byId;
|
|
400
|
+
} catch (e) {
|
|
401
|
+
console.error(e);
|
|
402
|
+
}
|
|
403
|
+
if (!selector.startsWith("#") && !selector.startsWith(".") && !selector.startsWith("[")) {
|
|
404
|
+
try {
|
|
405
|
+
const byPrependedId = document.querySelector(`#${selector}`);
|
|
406
|
+
if (byPrependedId) return byPrependedId;
|
|
407
|
+
} catch (e) {
|
|
408
|
+
console.error(e);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
var HEX_COLOR_REGEX = /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/;
|
|
416
|
+
function sanitizeColor(color, fallback = "#6366f1") {
|
|
417
|
+
if (!color || typeof color !== "string") return fallback;
|
|
418
|
+
const trimmed = color.trim();
|
|
419
|
+
return HEX_COLOR_REGEX.test(trimmed) ? trimmed : fallback;
|
|
420
|
+
}
|
|
421
|
+
function injectCopilotStyles(color = "#6366f1") {
|
|
422
|
+
if (typeof document === "undefined") return;
|
|
423
|
+
const safeColor = sanitizeColor(color, "#6366f1");
|
|
424
|
+
let style = document.getElementById(
|
|
425
|
+
"wallavi-copilot-styles"
|
|
426
|
+
);
|
|
427
|
+
if (!style) {
|
|
428
|
+
style = document.createElement("style");
|
|
429
|
+
style.id = "wallavi-copilot-styles";
|
|
430
|
+
document.head.appendChild(style);
|
|
431
|
+
}
|
|
432
|
+
if (style.dataset.color === safeColor) return;
|
|
433
|
+
style.dataset.color = safeColor;
|
|
434
|
+
style.textContent = `
|
|
435
|
+
@keyframes wallavi-pulse-beacon {
|
|
436
|
+
0% {
|
|
437
|
+
box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.7);
|
|
438
|
+
outline: 2px solid ${safeColor};
|
|
439
|
+
}
|
|
440
|
+
70% {
|
|
441
|
+
box-shadow: 0 0 0 10px rgba(99, 102, 241, 0);
|
|
442
|
+
outline: 2px solid rgba(99, 102, 241, 0.3);
|
|
443
|
+
}
|
|
444
|
+
100% {
|
|
445
|
+
box-shadow: 0 0 0 0 rgba(99, 102, 241, 0);
|
|
446
|
+
outline: 2px solid transparent;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
.wallavi-element-beacon {
|
|
450
|
+
animation: wallavi-pulse-beacon 1.8s ease-out infinite !important;
|
|
451
|
+
outline-offset: 3px !important;
|
|
452
|
+
border-radius: inherit;
|
|
453
|
+
transition: outline 0.2s ease, box-shadow 0.2s ease !important;
|
|
454
|
+
}
|
|
455
|
+
@keyframes wallavi-flash-touch-anim {
|
|
456
|
+
0% {
|
|
457
|
+
outline: 3px solid ${safeColor};
|
|
458
|
+
outline-offset: 2px;
|
|
459
|
+
filter: brightness(1.08);
|
|
460
|
+
}
|
|
461
|
+
100% {
|
|
462
|
+
outline: 2px solid transparent;
|
|
463
|
+
outline-offset: 6px;
|
|
464
|
+
filter: none;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
.wallavi-flash-touch {
|
|
468
|
+
animation: wallavi-flash-touch-anim 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards !important;
|
|
469
|
+
}
|
|
470
|
+
`;
|
|
471
|
+
document.head.appendChild(style);
|
|
472
|
+
}
|
|
473
|
+
function flashElement(el, color) {
|
|
474
|
+
injectCopilotStyles(color);
|
|
475
|
+
el.classList.remove("wallavi-flash-touch");
|
|
476
|
+
void el.offsetWidth;
|
|
477
|
+
el.classList.add("wallavi-flash-touch");
|
|
478
|
+
setTimeout(() => {
|
|
479
|
+
el.classList.remove("wallavi-flash-touch");
|
|
480
|
+
}, 600);
|
|
481
|
+
}
|
|
482
|
+
function highlightElement(el, color, durationMs = 3e3) {
|
|
483
|
+
injectCopilotStyles(color);
|
|
484
|
+
el.classList.add("wallavi-element-beacon");
|
|
485
|
+
setTimeout(() => {
|
|
486
|
+
el.classList.remove("wallavi-element-beacon");
|
|
487
|
+
}, durationMs);
|
|
488
|
+
}
|
|
489
|
+
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
490
|
+
async function executeDeclarativeSteps(steps, onNavigate, copilotColor) {
|
|
491
|
+
if (typeof window === "undefined" || !Array.isArray(steps)) return;
|
|
492
|
+
const safeCopilotColor = sanitizeColor(copilotColor, "#6366f1");
|
|
493
|
+
for (const step of steps) {
|
|
494
|
+
try {
|
|
495
|
+
const { action } = step;
|
|
496
|
+
switch (action) {
|
|
497
|
+
case "scroll_to":
|
|
498
|
+
case "scroll_into_view": {
|
|
499
|
+
const selector = sanitizeSelector(step.selector);
|
|
500
|
+
if (!selector) break;
|
|
501
|
+
const el = await waitForElement(selector);
|
|
502
|
+
if (el) {
|
|
503
|
+
flashElement(el, safeCopilotColor);
|
|
504
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
505
|
+
}
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
case "click": {
|
|
509
|
+
const selector = sanitizeSelector(step.selector);
|
|
510
|
+
if (!selector) break;
|
|
511
|
+
const el = await waitForElement(selector);
|
|
512
|
+
if (el) {
|
|
513
|
+
flashElement(el, safeCopilotColor);
|
|
514
|
+
await delay(80);
|
|
515
|
+
el.click();
|
|
516
|
+
}
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
case "focus": {
|
|
520
|
+
const selector = sanitizeSelector(step.selector);
|
|
521
|
+
if (!selector) break;
|
|
522
|
+
const el = await waitForElement(selector);
|
|
523
|
+
if (el) {
|
|
524
|
+
flashElement(el, safeCopilotColor);
|
|
525
|
+
el.focus();
|
|
526
|
+
}
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
case "highlight": {
|
|
530
|
+
const selector = sanitizeSelector(step.selector);
|
|
531
|
+
if (!selector) break;
|
|
532
|
+
const el = await waitForElement(selector);
|
|
533
|
+
if (el) {
|
|
534
|
+
highlightElement(
|
|
535
|
+
el,
|
|
536
|
+
sanitizeColor(step.color, safeCopilotColor),
|
|
537
|
+
step.durationMs ?? 3e3
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
case "wait": {
|
|
543
|
+
const parsedVal = typeof step.value === "number" ? step.value : parseInt(String(step.value ?? "500"), 10);
|
|
544
|
+
const ms = step.delayMs ?? (Number.isNaN(parsedVal) ? 500 : parsedVal);
|
|
545
|
+
await delay(ms);
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
case "add_class": {
|
|
549
|
+
const selector = sanitizeSelector(step.selector);
|
|
550
|
+
const value = step.value;
|
|
551
|
+
if (!selector || !value) break;
|
|
552
|
+
const el = await waitForElement(selector);
|
|
553
|
+
if (el) el.classList.add(...value.split(/\s+/).filter(Boolean));
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
case "remove_class": {
|
|
557
|
+
const selector = sanitizeSelector(step.selector);
|
|
558
|
+
const value = step.value;
|
|
559
|
+
if (!selector || !value) break;
|
|
560
|
+
const el = await waitForElement(selector);
|
|
561
|
+
if (el) el.classList.remove(...value.split(/\s+/).filter(Boolean));
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
case "fill_value": {
|
|
565
|
+
const selector = sanitizeSelector(step.selector);
|
|
566
|
+
const value = step.value;
|
|
567
|
+
if (!selector) break;
|
|
568
|
+
const el = await waitForElement(selector);
|
|
569
|
+
if (el && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {
|
|
570
|
+
if (el instanceof HTMLInputElement) {
|
|
571
|
+
const type = el.type.toLowerCase();
|
|
572
|
+
if (type === "password" || type === "hidden") {
|
|
573
|
+
console.warn(
|
|
574
|
+
`[Wallavi Widget] Blocked fill_value action on sensitive field: ${type}`
|
|
575
|
+
);
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const isReadOnly = (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) && el.readOnly;
|
|
580
|
+
if (el.disabled || isReadOnly) {
|
|
581
|
+
console.warn(
|
|
582
|
+
"[Wallavi Widget] Blocked fill_value action on disabled/readOnly element"
|
|
583
|
+
);
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
flashElement(el, safeCopilotColor);
|
|
587
|
+
el.value = value ?? "";
|
|
588
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
589
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
590
|
+
}
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
case "dispatch_event": {
|
|
594
|
+
const eventName = sanitizeEventName(step.value);
|
|
595
|
+
if (!eventName) break;
|
|
596
|
+
const detail = step.payload ?? {};
|
|
597
|
+
window.dispatchEvent(new CustomEvent(eventName, { detail }));
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
case "navigate": {
|
|
601
|
+
const url = sanitizeUrl(step.value);
|
|
602
|
+
if (url) {
|
|
603
|
+
if (onNavigate) {
|
|
604
|
+
onNavigate(url);
|
|
605
|
+
} else {
|
|
606
|
+
window.location.href = url;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
default: {
|
|
612
|
+
const unknownAction = step.action;
|
|
613
|
+
console.warn(
|
|
614
|
+
`[Wallavi Widget] Unknown declarative action: ${unknownAction}`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
await delay(120);
|
|
619
|
+
} catch (err) {
|
|
620
|
+
console.error(
|
|
621
|
+
"[Wallavi Widget] Failed to execute declarative step:",
|
|
622
|
+
step,
|
|
623
|
+
err
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/lib/api-url.ts
|
|
630
|
+
var WALLAVI_PRODUCTION_API_URL = "https://wallavi-production.up.railway.app";
|
|
631
|
+
function resolveApiUrl(explicit) {
|
|
632
|
+
return explicit ?? process.env.NEXT_PUBLIC_API_URL ?? WALLAVI_PRODUCTION_API_URL;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// src/hooks/use-voice-call.ts
|
|
251
636
|
function useVoiceCall({
|
|
252
637
|
agentId,
|
|
253
638
|
threadId,
|
|
254
639
|
workspaceId,
|
|
255
|
-
customBackend
|
|
640
|
+
customBackend,
|
|
641
|
+
apiUrl
|
|
256
642
|
}) {
|
|
257
643
|
const [active, setActive] = useState(false);
|
|
258
644
|
const [token, setToken] = useState(null);
|
|
@@ -265,7 +651,7 @@ function useVoiceCall({
|
|
|
265
651
|
setError(null);
|
|
266
652
|
try {
|
|
267
653
|
const isPrivate = Boolean(workspaceId);
|
|
268
|
-
const url = isPrivate ? `${
|
|
654
|
+
const url = isPrivate ? `${resolveApiUrl(apiUrl)}/api/threads/${threadId}/livekit-token?agentId=${encodeURIComponent(agentId)}` : `${resolveApiUrl(apiUrl)}/api/chat/livekit-token?agentId=${encodeURIComponent(agentId)}&threadId=${encodeURIComponent(threadId)}`;
|
|
269
655
|
const token2 = isPrivate ? await getFreshClerkToken() : null;
|
|
270
656
|
const res = await fetch(url, {
|
|
271
657
|
headers: {
|
|
@@ -281,7 +667,7 @@ function useVoiceCall({
|
|
|
281
667
|
setError(err.message);
|
|
282
668
|
}
|
|
283
669
|
setLoading(false);
|
|
284
|
-
}, [agentId, threadId, workspaceId, customBackend]);
|
|
670
|
+
}, [agentId, threadId, workspaceId, customBackend, apiUrl]);
|
|
285
671
|
const stop = useCallback(() => {
|
|
286
672
|
setActive(false);
|
|
287
673
|
setToken(null);
|
|
@@ -298,13 +684,33 @@ function useVoiceCall({
|
|
|
298
684
|
};
|
|
299
685
|
}
|
|
300
686
|
|
|
687
|
+
// src/lib/user-headers.ts
|
|
688
|
+
var warnedHeadersFailure = false;
|
|
689
|
+
async function resolveUserHeaders(userContext) {
|
|
690
|
+
const source = userContext?.headers;
|
|
691
|
+
if (!source) return {};
|
|
692
|
+
if (typeof source !== "function") return source;
|
|
693
|
+
try {
|
|
694
|
+
return await source() ?? {};
|
|
695
|
+
} catch (err) {
|
|
696
|
+
if (!warnedHeadersFailure) {
|
|
697
|
+
warnedHeadersFailure = true;
|
|
698
|
+
console.warn(
|
|
699
|
+
"[wallavi-widget] userContext.headers threw; sending without host headers",
|
|
700
|
+
err
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
return {};
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
301
707
|
// src/hooks/use-chat.ts
|
|
302
|
-
var API_URL2 = process.env.NEXT_PUBLIC_API_URL ?? "https://wallavi-production.up.railway.app";
|
|
303
708
|
function newId() {
|
|
304
709
|
return Math.random().toString(36).slice(2, 10);
|
|
305
710
|
}
|
|
306
711
|
function useChat({
|
|
307
712
|
agentId,
|
|
713
|
+
apiUrl,
|
|
308
714
|
workspaceId = "",
|
|
309
715
|
envId,
|
|
310
716
|
source = "playground",
|
|
@@ -312,13 +718,20 @@ function useChat({
|
|
|
312
718
|
persist = false,
|
|
313
719
|
onNavigate,
|
|
314
720
|
playgroundOverrides,
|
|
315
|
-
customBackend
|
|
721
|
+
customBackend,
|
|
722
|
+
copilotColor,
|
|
723
|
+
resolvePageContext
|
|
316
724
|
}) {
|
|
725
|
+
const baseUrl = resolveApiUrl(apiUrl);
|
|
317
726
|
const userId = userContext?.userId;
|
|
318
727
|
const persistKey = persist ? userId ? `wallavi_${agentId}_${userId}` : `wallavi_${agentId}` : null;
|
|
319
728
|
const onNavigateRef = useRef(onNavigate);
|
|
729
|
+
const resolvePageContextRef = useRef(resolvePageContext);
|
|
730
|
+
const copilotColorRef = useRef(copilotColor);
|
|
320
731
|
useEffect(() => {
|
|
321
732
|
onNavigateRef.current = onNavigate;
|
|
733
|
+
resolvePageContextRef.current = resolvePageContext;
|
|
734
|
+
copilotColorRef.current = copilotColor;
|
|
322
735
|
});
|
|
323
736
|
const [messages, setMessages] = useState(() => {
|
|
324
737
|
if (!persistKey || typeof window === "undefined") return [];
|
|
@@ -412,7 +825,11 @@ function useChat({
|
|
|
412
825
|
return;
|
|
413
826
|
}
|
|
414
827
|
if (proto.type === "client-action") {
|
|
415
|
-
executeDeclarativeSteps(
|
|
828
|
+
executeDeclarativeSteps(
|
|
829
|
+
proto.steps,
|
|
830
|
+
onNavigateRef.current,
|
|
831
|
+
copilotColorRef.current
|
|
832
|
+
);
|
|
416
833
|
return;
|
|
417
834
|
}
|
|
418
835
|
if (proto.type === "debug-trace") {
|
|
@@ -431,7 +848,9 @@ function useChat({
|
|
|
431
848
|
const { input: userInput, msgId, extraMetadata, attachments } = opts;
|
|
432
849
|
const isPrivate = Boolean(workspaceId);
|
|
433
850
|
const token = isPrivate ? await getFreshClerkToken() : null;
|
|
434
|
-
const
|
|
851
|
+
const hostHeaders = await resolveUserHeaders(userContext);
|
|
852
|
+
const pageContext = userContext?.pageContext ?? resolvePageContextRef.current?.();
|
|
853
|
+
const url = isPrivate ? `${baseUrl}/api/threads/${threadId}/stream` : `${baseUrl}/api/chat/stream`;
|
|
435
854
|
const res = await fetch(url, {
|
|
436
855
|
method: "POST",
|
|
437
856
|
headers: {
|
|
@@ -452,10 +871,10 @@ function useChat({
|
|
|
452
871
|
...userContext?.userEmail ? { userEmail: userContext.userEmail } : {},
|
|
453
872
|
userMetadata: {
|
|
454
873
|
...userContext?.metadata ?? {},
|
|
455
|
-
...
|
|
874
|
+
...pageContext ? { pageContext } : {},
|
|
456
875
|
headers: {
|
|
457
876
|
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
458
|
-
...
|
|
877
|
+
...hostHeaders,
|
|
459
878
|
...userContext?.metadata?.headers ?? {}
|
|
460
879
|
},
|
|
461
880
|
...extraMetadata ?? {}
|
|
@@ -500,6 +919,7 @@ function useChat({
|
|
|
500
919
|
}
|
|
501
920
|
},
|
|
502
921
|
[
|
|
922
|
+
baseUrl,
|
|
503
923
|
agentId,
|
|
504
924
|
workspaceId,
|
|
505
925
|
envId,
|
|
@@ -514,7 +934,8 @@ function useChat({
|
|
|
514
934
|
agentId,
|
|
515
935
|
threadId,
|
|
516
936
|
workspaceId,
|
|
517
|
-
customBackend
|
|
937
|
+
customBackend,
|
|
938
|
+
apiUrl: baseUrl
|
|
518
939
|
});
|
|
519
940
|
useEffect(() => {
|
|
520
941
|
if (customBackend || !persistKey) return;
|
|
@@ -563,7 +984,7 @@ function useChat({
|
|
|
563
984
|
try {
|
|
564
985
|
const isPrivate = Boolean(workspaceId);
|
|
565
986
|
const token = isPrivate ? await getFreshClerkToken() : null;
|
|
566
|
-
const url = isPrivate ? `${
|
|
987
|
+
const url = isPrivate ? `${baseUrl}/api/threads/${threadId}/messages` : `${baseUrl}/api/chat/messages?agentId=${encodeURIComponent(agentId)}&threadId=${encodeURIComponent(threadId)}`;
|
|
567
988
|
const res = await fetch(url, {
|
|
568
989
|
headers: {
|
|
569
990
|
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
@@ -627,7 +1048,15 @@ function useChat({
|
|
|
627
1048
|
}
|
|
628
1049
|
}
|
|
629
1050
|
}
|
|
630
|
-
}, [
|
|
1051
|
+
}, [
|
|
1052
|
+
baseUrl,
|
|
1053
|
+
threadId,
|
|
1054
|
+
agentId,
|
|
1055
|
+
workspaceId,
|
|
1056
|
+
envId,
|
|
1057
|
+
persistKey,
|
|
1058
|
+
fetchAndStream
|
|
1059
|
+
]);
|
|
631
1060
|
const reset = useCallback(() => {
|
|
632
1061
|
setMessages([]);
|
|
633
1062
|
setInput("");
|
|
@@ -811,106 +1240,6 @@ function useChat({
|
|
|
811
1240
|
setSelectedContext
|
|
812
1241
|
};
|
|
813
1242
|
}
|
|
814
|
-
async function waitForElement(selector, retries = 5, delayMs = 100) {
|
|
815
|
-
const cleanId = selector.startsWith("#") ? selector.substring(1) : selector;
|
|
816
|
-
for (let i = 0; i < retries; i++) {
|
|
817
|
-
try {
|
|
818
|
-
const el = document.querySelector(selector);
|
|
819
|
-
if (el) return el;
|
|
820
|
-
} catch (e) {
|
|
821
|
-
}
|
|
822
|
-
try {
|
|
823
|
-
const byId = document.getElementById(cleanId);
|
|
824
|
-
if (byId) return byId;
|
|
825
|
-
} catch (e) {
|
|
826
|
-
}
|
|
827
|
-
if (!selector.startsWith("#") && !selector.startsWith(".") && !selector.startsWith("[")) {
|
|
828
|
-
try {
|
|
829
|
-
const byPrependedId = document.querySelector(`#${selector}`);
|
|
830
|
-
if (byPrependedId) return byPrependedId;
|
|
831
|
-
} catch (e) {
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
835
|
-
}
|
|
836
|
-
return null;
|
|
837
|
-
}
|
|
838
|
-
async function executeDeclarativeSteps(steps, onNavigate) {
|
|
839
|
-
if (typeof window === "undefined" || !Array.isArray(steps)) return;
|
|
840
|
-
for (const step of steps) {
|
|
841
|
-
try {
|
|
842
|
-
const { action, selector, value, payload } = step;
|
|
843
|
-
switch (action) {
|
|
844
|
-
case "scroll_into_view": {
|
|
845
|
-
if (!selector) break;
|
|
846
|
-
const el = await waitForElement(selector);
|
|
847
|
-
el?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
848
|
-
break;
|
|
849
|
-
}
|
|
850
|
-
case "click": {
|
|
851
|
-
if (!selector) break;
|
|
852
|
-
const el = await waitForElement(selector);
|
|
853
|
-
el?.click();
|
|
854
|
-
break;
|
|
855
|
-
}
|
|
856
|
-
case "focus": {
|
|
857
|
-
if (!selector) break;
|
|
858
|
-
const el = await waitForElement(selector);
|
|
859
|
-
el?.focus();
|
|
860
|
-
break;
|
|
861
|
-
}
|
|
862
|
-
case "add_class": {
|
|
863
|
-
if (!selector || !value) break;
|
|
864
|
-
const el = await waitForElement(selector);
|
|
865
|
-
if (el) el.classList.add(...value.split(/\s+/).filter(Boolean));
|
|
866
|
-
break;
|
|
867
|
-
}
|
|
868
|
-
case "remove_class": {
|
|
869
|
-
if (!selector || !value) break;
|
|
870
|
-
const el = await waitForElement(selector);
|
|
871
|
-
if (el) el.classList.remove(...value.split(/\s+/).filter(Boolean));
|
|
872
|
-
break;
|
|
873
|
-
}
|
|
874
|
-
case "fill_value": {
|
|
875
|
-
if (!selector) break;
|
|
876
|
-
const el = await waitForElement(selector);
|
|
877
|
-
if (el) {
|
|
878
|
-
el.value = value ?? "";
|
|
879
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
880
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
881
|
-
}
|
|
882
|
-
break;
|
|
883
|
-
}
|
|
884
|
-
case "dispatch_event": {
|
|
885
|
-
if (!value) break;
|
|
886
|
-
const detail = payload ?? {};
|
|
887
|
-
window.dispatchEvent(new CustomEvent(value, { detail }));
|
|
888
|
-
break;
|
|
889
|
-
}
|
|
890
|
-
case "navigate": {
|
|
891
|
-
if (value) {
|
|
892
|
-
if (onNavigate) {
|
|
893
|
-
onNavigate(value);
|
|
894
|
-
} else {
|
|
895
|
-
window.location.href = value;
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
break;
|
|
899
|
-
}
|
|
900
|
-
default:
|
|
901
|
-
console.warn(
|
|
902
|
-
`[Wallavi Widget] Unknown declarative action: ${action}`
|
|
903
|
-
);
|
|
904
|
-
}
|
|
905
|
-
} catch (err) {
|
|
906
|
-
console.error(
|
|
907
|
-
"[Wallavi Widget] Failed to execute declarative step:",
|
|
908
|
-
step,
|
|
909
|
-
err
|
|
910
|
-
);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
1243
|
function getPreferredMimeType() {
|
|
915
1244
|
if (typeof MediaRecorder === "undefined") return "";
|
|
916
1245
|
const candidates = [
|
|
@@ -927,7 +1256,6 @@ function mimeTypeToExtension(mimeType) {
|
|
|
927
1256
|
if (mimeType.includes("mp4")) return "mp4";
|
|
928
1257
|
return "webm";
|
|
929
1258
|
}
|
|
930
|
-
var DEFAULT_API_URL = process.env.NEXT_PUBLIC_API_URL ?? "https://wallavi-production.up.railway.app";
|
|
931
1259
|
function useVoice({
|
|
932
1260
|
agentId,
|
|
933
1261
|
apiUrl,
|
|
@@ -940,7 +1268,7 @@ function useVoice({
|
|
|
940
1268
|
const streamRef = useRef(null);
|
|
941
1269
|
const errorTimerRef = useRef(null);
|
|
942
1270
|
const isSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined" && !!navigator?.mediaDevices?.getUserMedia;
|
|
943
|
-
const base = apiUrl
|
|
1271
|
+
const base = resolveApiUrl(apiUrl);
|
|
944
1272
|
const transcribeBlob = useCallback(
|
|
945
1273
|
async (blob, mimeType) => {
|
|
946
1274
|
setVoiceState("transcribing");
|
|
@@ -1019,7 +1347,6 @@ function useVoice({
|
|
|
1019
1347
|
}, []);
|
|
1020
1348
|
return { voiceState, isSupported, start, stop };
|
|
1021
1349
|
}
|
|
1022
|
-
var DEFAULT_API_URL2 = process.env.NEXT_PUBLIC_API_URL ?? "https://wallavi-production.up.railway.app";
|
|
1023
1350
|
function makeId() {
|
|
1024
1351
|
return `att_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
|
1025
1352
|
}
|
|
@@ -1029,7 +1356,7 @@ function useAttachments({
|
|
|
1029
1356
|
maxFiles = 5
|
|
1030
1357
|
}) {
|
|
1031
1358
|
const [attachments, setAttachments] = useState([]);
|
|
1032
|
-
const base = apiUrl
|
|
1359
|
+
const base = resolveApiUrl(apiUrl);
|
|
1033
1360
|
const uploadOne = useCallback(
|
|
1034
1361
|
async (file, id) => {
|
|
1035
1362
|
const form = new FormData();
|
|
@@ -2824,6 +3151,20 @@ function VoiceOverlay({
|
|
|
2824
3151
|
}
|
|
2825
3152
|
) });
|
|
2826
3153
|
}
|
|
3154
|
+
|
|
3155
|
+
// src/lib/unique-keys.ts
|
|
3156
|
+
function uniqueKeys(items, baseKey) {
|
|
3157
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3158
|
+
return items.map((item) => {
|
|
3159
|
+
const base = baseKey(item);
|
|
3160
|
+
const count = seen.get(base) ?? 0;
|
|
3161
|
+
seen.set(base, count + 1);
|
|
3162
|
+
return count === 0 ? base : `${base}#${count}`;
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
var actionKey = (action) => `${action.name}:${JSON.stringify(action.steps ?? [])}`;
|
|
3166
|
+
var docKey = (doc) => `${doc.name}:${doc.url ?? ""}`;
|
|
3167
|
+
var topicKey = (topic) => topic.name;
|
|
2827
3168
|
function SvgIcon({
|
|
2828
3169
|
className,
|
|
2829
3170
|
strokeWidth = "1.8",
|
|
@@ -2897,11 +3238,11 @@ function urlRelevance(docUrl, pageUrl) {
|
|
|
2897
3238
|
return 50;
|
|
2898
3239
|
const docParts = docClean.split(/[/?#]/).filter(Boolean);
|
|
2899
3240
|
const pageParts = pageClean.split(/[/?#]/).filter(Boolean);
|
|
2900
|
-
let
|
|
3241
|
+
let score2 = 0;
|
|
2901
3242
|
for (const part of pageParts) {
|
|
2902
|
-
if (docParts.includes(part))
|
|
3243
|
+
if (docParts.includes(part)) score2 += 10;
|
|
2903
3244
|
}
|
|
2904
|
-
return
|
|
3245
|
+
return score2;
|
|
2905
3246
|
}
|
|
2906
3247
|
function topicToQuestion(topic, locale) {
|
|
2907
3248
|
const name = topic.name.trim();
|
|
@@ -2992,9 +3333,9 @@ function getContextKeywords(pageUrl, pageTitle, pageParams, pageVars, metadata)
|
|
|
2992
3333
|
return Array.from(keywords);
|
|
2993
3334
|
}
|
|
2994
3335
|
function computeRelevanceScore(item, keywords, pageUrl) {
|
|
2995
|
-
let
|
|
3336
|
+
let score2 = 0;
|
|
2996
3337
|
if (item.url && pageUrl) {
|
|
2997
|
-
|
|
3338
|
+
score2 += urlRelevance(item.url, pageUrl);
|
|
2998
3339
|
}
|
|
2999
3340
|
const itemName = (item.name || "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
3000
3341
|
const itemDesc = (item.description || item.summary || "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
@@ -3002,33 +3343,41 @@ function computeRelevanceScore(item, keywords, pageUrl) {
|
|
|
3002
3343
|
const nameWords = itemName.split(/[^a-z0-9]/).filter(Boolean);
|
|
3003
3344
|
for (const keyword of keywords) {
|
|
3004
3345
|
if (itemName.includes(keyword)) {
|
|
3005
|
-
|
|
3346
|
+
score2 += 30;
|
|
3006
3347
|
if (nameWords.includes(keyword)) {
|
|
3007
|
-
|
|
3348
|
+
score2 += 20;
|
|
3008
3349
|
}
|
|
3009
3350
|
}
|
|
3010
3351
|
if (itemDesc.includes(keyword)) {
|
|
3011
|
-
|
|
3352
|
+
score2 += 15;
|
|
3012
3353
|
}
|
|
3013
3354
|
if (itemUrl.includes(keyword)) {
|
|
3014
|
-
|
|
3355
|
+
score2 += 20;
|
|
3015
3356
|
}
|
|
3016
3357
|
}
|
|
3017
|
-
if (item.keywords) {
|
|
3018
|
-
const
|
|
3019
|
-
|
|
3020
|
-
...item.keywords
|
|
3021
|
-
|
|
3022
|
-
|
|
3358
|
+
if (item.keywords && typeof item.keywords === "object") {
|
|
3359
|
+
const rawList = [];
|
|
3360
|
+
if (Array.isArray(item.keywords)) {
|
|
3361
|
+
rawList.push(...item.keywords);
|
|
3362
|
+
} else {
|
|
3363
|
+
const kwRecord = item.keywords;
|
|
3364
|
+
for (const lang of ["en", "es", "fr"]) {
|
|
3365
|
+
const langVal = kwRecord[lang];
|
|
3366
|
+
if (Array.isArray(langVal)) {
|
|
3367
|
+
rawList.push(...langVal);
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
const allItemKeywords = rawList.filter((k) => typeof k === "string" && k.trim().length > 0).map(
|
|
3023
3372
|
(k) => k.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "")
|
|
3024
3373
|
);
|
|
3025
3374
|
for (const keyword of keywords) {
|
|
3026
3375
|
if (allItemKeywords.includes(keyword)) {
|
|
3027
|
-
|
|
3376
|
+
score2 += 45;
|
|
3028
3377
|
}
|
|
3029
3378
|
}
|
|
3030
3379
|
}
|
|
3031
|
-
return
|
|
3380
|
+
return score2;
|
|
3032
3381
|
}
|
|
3033
3382
|
var STAGGER_MS = 50;
|
|
3034
3383
|
var cardBase = "ww-group ww-w-full ww-rounded-xl ww-border ww-border-border ww-bg-background ww-text-left ww-shadow-[0_1px_2px_rgba(0,0,0,0.03)] hover:ww-bg-muted/20 hover:ww-border-border/80 hover:ww-shadow-md active:ww-scale-[0.98] ww-transition-all ww-duration-200 ww-animate-in ww-fade-in ww-slide-in-from-bottom-1 focus-visible:ww-ring-2 focus-visible:ww-ring-ring/40 focus-visible:ww-outline-none";
|
|
@@ -3241,6 +3590,9 @@ function CommandPanel({
|
|
|
3241
3590
|
}));
|
|
3242
3591
|
return scored.sort((a, b) => b.score - a.score);
|
|
3243
3592
|
}, [clientActions, pageUrl, pageTitle, pageParams, pageVars, metadata]);
|
|
3593
|
+
const actionKeys = uniqueKeys(sortedActions, actionKey);
|
|
3594
|
+
const docKeys = uniqueKeys(relevantDocs, docKey);
|
|
3595
|
+
const topicKeys = uniqueKeys(topicQuestions, topicKey);
|
|
3244
3596
|
const hasActions = sortedActions.length > 0;
|
|
3245
3597
|
const hasDocs = relevantDocs.length > 0;
|
|
3246
3598
|
const hasTopics = topicQuestions.length > 0;
|
|
@@ -3262,7 +3614,7 @@ function CommandPanel({
|
|
|
3262
3614
|
accentColor,
|
|
3263
3615
|
onExecute: () => handleExecute(action.steps)
|
|
3264
3616
|
},
|
|
3265
|
-
|
|
3617
|
+
actionKeys[i]
|
|
3266
3618
|
)) })
|
|
3267
3619
|
] }),
|
|
3268
3620
|
hasDocs && /* @__PURE__ */ jsxs("section", { children: [
|
|
@@ -3275,7 +3627,7 @@ function CommandPanel({
|
|
|
3275
3627
|
onSend,
|
|
3276
3628
|
locale
|
|
3277
3629
|
},
|
|
3278
|
-
|
|
3630
|
+
docKeys[i]
|
|
3279
3631
|
)) })
|
|
3280
3632
|
] }),
|
|
3281
3633
|
hasTopics && /* @__PURE__ */ jsxs("section", { children: [
|
|
@@ -3287,7 +3639,7 @@ function CommandPanel({
|
|
|
3287
3639
|
index: i,
|
|
3288
3640
|
onSend
|
|
3289
3641
|
},
|
|
3290
|
-
|
|
3642
|
+
topicKeys[i]
|
|
3291
3643
|
)) })
|
|
3292
3644
|
] })
|
|
3293
3645
|
] }),
|
|
@@ -3334,7 +3686,7 @@ function CommandPanel({
|
|
|
3334
3686
|
accentColor,
|
|
3335
3687
|
onExecute: () => handleExecute(action.steps)
|
|
3336
3688
|
},
|
|
3337
|
-
|
|
3689
|
+
actionKeys[i]
|
|
3338
3690
|
)) })
|
|
3339
3691
|
] }),
|
|
3340
3692
|
hasDocs && /* @__PURE__ */ jsxs("section", { children: [
|
|
@@ -3347,7 +3699,7 @@ function CommandPanel({
|
|
|
3347
3699
|
onSend,
|
|
3348
3700
|
locale
|
|
3349
3701
|
},
|
|
3350
|
-
|
|
3702
|
+
docKeys[i]
|
|
3351
3703
|
)) })
|
|
3352
3704
|
] }),
|
|
3353
3705
|
hasTopics && /* @__PURE__ */ jsxs("section", { children: [
|
|
@@ -3360,13 +3712,14 @@ function CommandPanel({
|
|
|
3360
3712
|
compact: true,
|
|
3361
3713
|
onSend
|
|
3362
3714
|
},
|
|
3363
|
-
|
|
3715
|
+
topicKeys[i]
|
|
3364
3716
|
)) })
|
|
3365
3717
|
] })
|
|
3366
3718
|
] });
|
|
3367
3719
|
}
|
|
3368
3720
|
function ChatWidget({
|
|
3369
3721
|
agentId,
|
|
3722
|
+
apiUrl,
|
|
3370
3723
|
workspaceId,
|
|
3371
3724
|
agentName,
|
|
3372
3725
|
displayName,
|
|
@@ -3427,8 +3780,10 @@ function ChatWidget({
|
|
|
3427
3780
|
window.addEventListener("message", handleMessage);
|
|
3428
3781
|
return () => window.removeEventListener("message", handleMessage);
|
|
3429
3782
|
}, []);
|
|
3783
|
+
const resolvePageContext = () => userContext?.pageContext ?? (typeof window === "undefined" ? void 0 : derivePageContext(window.location, clientActions));
|
|
3430
3784
|
const chat = useChat({
|
|
3431
3785
|
agentId,
|
|
3786
|
+
apiUrl,
|
|
3432
3787
|
workspaceId,
|
|
3433
3788
|
envId,
|
|
3434
3789
|
source,
|
|
@@ -3436,10 +3791,13 @@ function ChatWidget({
|
|
|
3436
3791
|
persist,
|
|
3437
3792
|
onNavigate,
|
|
3438
3793
|
playgroundOverrides,
|
|
3439
|
-
customBackend
|
|
3794
|
+
customBackend,
|
|
3795
|
+
copilotColor: userMessageColor,
|
|
3796
|
+
resolvePageContext: () => resolvePageContext()
|
|
3440
3797
|
});
|
|
3441
3798
|
const voice = useVoice({
|
|
3442
3799
|
agentId,
|
|
3800
|
+
apiUrl,
|
|
3443
3801
|
onTranscript: (text) => {
|
|
3444
3802
|
if (voiceAutoSend) {
|
|
3445
3803
|
void chat.send(text);
|
|
@@ -3448,7 +3806,7 @@ function ChatWidget({
|
|
|
3448
3806
|
}
|
|
3449
3807
|
}
|
|
3450
3808
|
});
|
|
3451
|
-
const attachmentHook = useAttachments({ agentId });
|
|
3809
|
+
const attachmentHook = useAttachments({ agentId, apiUrl });
|
|
3452
3810
|
const debugTraceLenRef = useRef(0);
|
|
3453
3811
|
useEffect(() => {
|
|
3454
3812
|
if (!onDebugTrace || chat.debugTraces.length <= debugTraceLenRef.current)
|
|
@@ -3548,9 +3906,9 @@ function ChatWidget({
|
|
|
3548
3906
|
const showInlineCommandPanel = showCommandPanel && !showSidebar && chat.messages.length === 0 && (showQuickActions && clientActions.length > 0 || showRagTopics && ragTopics.length > 0 || showRagDocuments && ragDocuments.length > 0);
|
|
3549
3907
|
const handleExecuteAction = useCallback(
|
|
3550
3908
|
(steps) => {
|
|
3551
|
-
executeDeclarativeSteps(steps, onNavigate);
|
|
3909
|
+
executeDeclarativeSteps(steps, onNavigate, userMessageColor);
|
|
3552
3910
|
},
|
|
3553
|
-
[onNavigate]
|
|
3911
|
+
[onNavigate, userMessageColor]
|
|
3554
3912
|
);
|
|
3555
3913
|
const [activeActionIndex, setActiveActionIndex] = useState(-1);
|
|
3556
3914
|
const query = chat.input.toLowerCase().trim();
|
|
@@ -3607,11 +3965,12 @@ function ChatWidget({
|
|
|
3607
3965
|
},
|
|
3608
3966
|
[matchingActions, activeActionIndex, handleExecuteAction, chat]
|
|
3609
3967
|
);
|
|
3968
|
+
const pageContext = resolvePageContext();
|
|
3610
3969
|
const commandPanelProps = {
|
|
3611
|
-
pageUrl:
|
|
3612
|
-
pageTitle:
|
|
3613
|
-
pageParams:
|
|
3614
|
-
pageVars:
|
|
3970
|
+
pageUrl: pageContext?.url,
|
|
3971
|
+
pageTitle: pageContext?.title,
|
|
3972
|
+
pageParams: pageContext?.params,
|
|
3973
|
+
pageVars: pageContext?.vars,
|
|
3615
3974
|
metadata: userContext?.metadata,
|
|
3616
3975
|
clientActions: showQuickActions ? clientActions : [],
|
|
3617
3976
|
ragTopics: showRagTopics ? ragTopics : [],
|
|
@@ -3931,7 +4290,6 @@ function ChatWidget({
|
|
|
3931
4290
|
}
|
|
3932
4291
|
);
|
|
3933
4292
|
}
|
|
3934
|
-
var WALLAVI_PUBLIC_API = "https://wallavi-production.up.railway.app";
|
|
3935
4293
|
var EMPTY = {
|
|
3936
4294
|
remoteConfig: {},
|
|
3937
4295
|
bubbleIconUrl: void 0,
|
|
@@ -3945,7 +4303,37 @@ var EMPTY = {
|
|
|
3945
4303
|
panelHeight: 580,
|
|
3946
4304
|
loading: false
|
|
3947
4305
|
};
|
|
3948
|
-
function
|
|
4306
|
+
function toChatProps(cfg) {
|
|
4307
|
+
const remote = {};
|
|
4308
|
+
const set = (key, value) => {
|
|
4309
|
+
if (value !== null && value !== void 0) remote[key] = value;
|
|
4310
|
+
};
|
|
4311
|
+
set("agentName", cfg.agentName);
|
|
4312
|
+
set("profilePicture", cfg.profilePicture);
|
|
4313
|
+
set("displayName", cfg.displayName);
|
|
4314
|
+
set("theme", cfg.theme);
|
|
4315
|
+
set("userMessageColor", cfg.userMessageColor);
|
|
4316
|
+
if (cfg.initialMessages?.length) remote.initialMessages = cfg.initialMessages;
|
|
4317
|
+
if (cfg.suggestedMessages?.length)
|
|
4318
|
+
remote.suggestedMessages = cfg.suggestedMessages;
|
|
4319
|
+
set("messagePlaceholder", cfg.messagePlaceholder);
|
|
4320
|
+
set("watermark", cfg.watermark);
|
|
4321
|
+
set("footer", cfg.footer);
|
|
4322
|
+
set("showThinking", cfg.showThinking);
|
|
4323
|
+
set("regenerateMessage", cfg.regenerateMessage);
|
|
4324
|
+
set("widgetLayout", cfg.widgetLayout);
|
|
4325
|
+
set("enableVoice", cfg.enableVoice);
|
|
4326
|
+
set("showCommandPanel", cfg.showCommandPanel);
|
|
4327
|
+
set("showRagTopics", cfg.showRagTopics);
|
|
4328
|
+
set("showRagDocuments", cfg.showRagDocuments);
|
|
4329
|
+
set("showQuickActions", cfg.showQuickActions);
|
|
4330
|
+
set("clientActions", cfg.clientActions);
|
|
4331
|
+
set("ragTopics", cfg.ragTopics);
|
|
4332
|
+
set("ragDocuments", cfg.ragDocuments);
|
|
4333
|
+
set("locale", cfg.locale);
|
|
4334
|
+
return remote;
|
|
4335
|
+
}
|
|
4336
|
+
function useAutoConfig(agentId, enabled, apiUrl) {
|
|
3949
4337
|
const [result, setResult] = useState(() => ({
|
|
3950
4338
|
...EMPTY,
|
|
3951
4339
|
loading: enabled && Boolean(agentId)
|
|
@@ -3956,61 +4344,34 @@ function useAutoConfig(agentId, enabled) {
|
|
|
3956
4344
|
return;
|
|
3957
4345
|
}
|
|
3958
4346
|
let cancelled = false;
|
|
3959
|
-
|
|
4347
|
+
setResult({ ...EMPTY, loading: true });
|
|
4348
|
+
fetch(`${resolveApiUrl(apiUrl)}/api/public/widget/${agentId}`).then((r) => r.json()).then((body) => {
|
|
3960
4349
|
if (cancelled) return;
|
|
3961
|
-
const cfg = body?.data
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
remote.displayName = cfg.displayName;
|
|
3967
|
-
if (cfg.theme) remote.theme = cfg.theme;
|
|
3968
|
-
if (cfg.userMessageColor)
|
|
3969
|
-
remote.userMessageColor = cfg.userMessageColor;
|
|
3970
|
-
if (Array.isArray(cfg.initialMessages) && cfg.initialMessages.length > 0)
|
|
3971
|
-
remote.initialMessages = cfg.initialMessages;
|
|
3972
|
-
if (Array.isArray(cfg.suggestedMessages))
|
|
3973
|
-
remote.suggestedMessages = cfg.suggestedMessages;
|
|
3974
|
-
if (cfg.messagePlaceholder != null)
|
|
3975
|
-
remote.messagePlaceholder = cfg.messagePlaceholder;
|
|
3976
|
-
if (cfg.watermark != null) remote.watermark = cfg.watermark;
|
|
3977
|
-
if (cfg.footer != null) remote.footer = cfg.footer;
|
|
3978
|
-
if (cfg.showThinking != null)
|
|
3979
|
-
remote.showThinking = cfg.showThinking;
|
|
3980
|
-
if (cfg.regenerateMessage != null)
|
|
3981
|
-
remote.regenerateMessage = cfg.regenerateMessage;
|
|
3982
|
-
if (cfg.widgetLayout)
|
|
3983
|
-
remote.widgetLayout = cfg.widgetLayout;
|
|
3984
|
-
if (cfg.enableVoice != null)
|
|
3985
|
-
remote.enableVoice = cfg.enableVoice;
|
|
3986
|
-
if (cfg.clientActions)
|
|
3987
|
-
remote.clientActions = cfg.clientActions;
|
|
3988
|
-
if (Array.isArray(cfg.ragTopics))
|
|
3989
|
-
remote.ragTopics = cfg.ragTopics;
|
|
3990
|
-
if (Array.isArray(cfg.ragDocuments))
|
|
3991
|
-
remote.ragDocuments = cfg.ragDocuments;
|
|
3992
|
-
if (cfg.locale != null)
|
|
3993
|
-
remote.locale = cfg.locale;
|
|
4350
|
+
const cfg = body?.data;
|
|
4351
|
+
if (!cfg) {
|
|
4352
|
+
setResult(EMPTY);
|
|
4353
|
+
return;
|
|
4354
|
+
}
|
|
3994
4355
|
setResult({
|
|
3995
|
-
remoteConfig:
|
|
4356
|
+
remoteConfig: toChatProps(cfg),
|
|
3996
4357
|
bubbleIconUrl: cfg.chatIcon || cfg.profilePicture || void 0,
|
|
3997
4358
|
autoOpen: Boolean(cfg.autoOpen),
|
|
3998
4359
|
keyboardShortcut: Boolean(cfg.keyboardShortcut),
|
|
3999
4360
|
position: cfg.alignChatBubbleButton === "left" ? "bottom-left" : "bottom-right",
|
|
4000
4361
|
widgetLayout: cfg.widgetLayout === "center" ? "center" : "bubble",
|
|
4001
|
-
clientActions:
|
|
4002
|
-
bubbleSize:
|
|
4003
|
-
panelWidth:
|
|
4004
|
-
panelHeight:
|
|
4362
|
+
clientActions: cfg.clientActions ?? [],
|
|
4363
|
+
bubbleSize: cfg.bubbleSize ?? 52,
|
|
4364
|
+
panelWidth: cfg.panelWidth ?? 360,
|
|
4365
|
+
panelHeight: cfg.panelHeight ?? 580,
|
|
4005
4366
|
loading: false
|
|
4006
4367
|
});
|
|
4007
4368
|
}).catch(() => {
|
|
4008
|
-
if (!cancelled) setResult(
|
|
4369
|
+
if (!cancelled) setResult(EMPTY);
|
|
4009
4370
|
});
|
|
4010
4371
|
return () => {
|
|
4011
4372
|
cancelled = true;
|
|
4012
4373
|
};
|
|
4013
|
-
}, [agentId, enabled]);
|
|
4374
|
+
}, [agentId, enabled, apiUrl]);
|
|
4014
4375
|
return result;
|
|
4015
4376
|
}
|
|
4016
4377
|
function toWidgetMsg(m) {
|
|
@@ -4091,6 +4452,38 @@ function useSupportChat({
|
|
|
4091
4452
|
},
|
|
4092
4453
|
[base]
|
|
4093
4454
|
);
|
|
4455
|
+
function loadAblyScript() {
|
|
4456
|
+
return new Promise((resolve, reject) => {
|
|
4457
|
+
if (typeof window === "undefined") {
|
|
4458
|
+
resolve(null);
|
|
4459
|
+
return;
|
|
4460
|
+
}
|
|
4461
|
+
if (window.Ably) {
|
|
4462
|
+
resolve(window.Ably);
|
|
4463
|
+
return;
|
|
4464
|
+
}
|
|
4465
|
+
const existing = document.querySelector("script[data-ably-sdk]");
|
|
4466
|
+
if (existing) {
|
|
4467
|
+
existing.addEventListener("load", () => resolve(window.Ably));
|
|
4468
|
+
existing.addEventListener(
|
|
4469
|
+
"error",
|
|
4470
|
+
() => reject(new Error("Ably CDN load failed"))
|
|
4471
|
+
);
|
|
4472
|
+
return;
|
|
4473
|
+
}
|
|
4474
|
+
const script = document.createElement("script");
|
|
4475
|
+
script.src = "https://cdn.ably.com/lib/ably.min-2.js";
|
|
4476
|
+
script.crossOrigin = "anonymous";
|
|
4477
|
+
script.async = true;
|
|
4478
|
+
script.setAttribute("data-ably-sdk", "true");
|
|
4479
|
+
script.onload = () => resolve(window.Ably);
|
|
4480
|
+
script.onerror = () => {
|
|
4481
|
+
script.remove();
|
|
4482
|
+
reject(new Error("Ably CDN load failed"));
|
|
4483
|
+
};
|
|
4484
|
+
document.head.appendChild(script);
|
|
4485
|
+
});
|
|
4486
|
+
}
|
|
4094
4487
|
useEffect(() => {
|
|
4095
4488
|
if (!enabled || !session) return;
|
|
4096
4489
|
void loadMessages(session);
|
|
@@ -4105,7 +4498,8 @@ function useSupportChat({
|
|
|
4105
4498
|
);
|
|
4106
4499
|
if (!res.ok) return;
|
|
4107
4500
|
const { tokenRequest, channel: channelName } = await res.json();
|
|
4108
|
-
const AblyLib =
|
|
4501
|
+
const AblyLib = await loadAblyScript();
|
|
4502
|
+
if (!AblyLib) return;
|
|
4109
4503
|
ablyClient = new AblyLib.Realtime({
|
|
4110
4504
|
authCallback: (_, cb) => cb(null, tokenRequest)
|
|
4111
4505
|
});
|
|
@@ -4413,9 +4807,9 @@ function BubbleWidget({
|
|
|
4413
4807
|
height: heightProp,
|
|
4414
4808
|
expandedWidth = 640,
|
|
4415
4809
|
expandedHeight = "calc(100vh - 100px)",
|
|
4416
|
-
keyboardShortcut: keyboardShortcutProp
|
|
4810
|
+
keyboardShortcut: keyboardShortcutProp,
|
|
4417
4811
|
shortcutKey = "k",
|
|
4418
|
-
autoOpen: autoOpenProp
|
|
4812
|
+
autoOpen: autoOpenProp,
|
|
4419
4813
|
bubbleIconUrl: bubbleIconUrlProp,
|
|
4420
4814
|
bubbleSize: bubbleSizeProp,
|
|
4421
4815
|
panelClassName,
|
|
@@ -4456,24 +4850,25 @@ function BubbleWidget({
|
|
|
4456
4850
|
}, []);
|
|
4457
4851
|
const remote = useAutoConfig(
|
|
4458
4852
|
inboxToken ? "" : chatProps.agentId ?? "",
|
|
4459
|
-
!inboxToken && autoConfig
|
|
4853
|
+
!inboxToken && autoConfig,
|
|
4854
|
+
chatProps.apiUrl
|
|
4460
4855
|
);
|
|
4461
|
-
const resolvedPosition = remote.position
|
|
4462
|
-
const resolvedLayout =
|
|
4463
|
-
const resolvedBubbleIcon = remote.bubbleIconUrl
|
|
4464
|
-
const resolvedAutoOpen = remote.autoOpen
|
|
4465
|
-
const resolvedKeyboardShortcut = remote.keyboardShortcut
|
|
4466
|
-
const resolvedBubbleSize = remote.bubbleSize
|
|
4467
|
-
const resolvedWidth = remote.panelWidth
|
|
4468
|
-
const resolvedHeight = remote.panelHeight
|
|
4856
|
+
const resolvedPosition = positionProp ?? remote.position;
|
|
4857
|
+
const resolvedLayout = chatProps.widgetLayout ?? remote.widgetLayout;
|
|
4858
|
+
const resolvedBubbleIcon = bubbleIconUrlProp ?? remote.bubbleIconUrl;
|
|
4859
|
+
const resolvedAutoOpen = autoOpenProp ?? remote.autoOpen;
|
|
4860
|
+
const resolvedKeyboardShortcut = keyboardShortcutProp ?? remote.keyboardShortcut;
|
|
4861
|
+
const resolvedBubbleSize = bubbleSizeProp ?? remote.bubbleSize;
|
|
4862
|
+
const resolvedWidth = widthProp ?? remote.panelWidth;
|
|
4863
|
+
const resolvedHeight = heightProp ?? remote.panelHeight;
|
|
4469
4864
|
const definedChatProps = Object.fromEntries(
|
|
4470
4865
|
Object.entries(chatProps).filter(([, v]) => v !== void 0)
|
|
4471
4866
|
);
|
|
4472
4867
|
const mergedConfig = {
|
|
4473
|
-
...definedChatProps,
|
|
4474
4868
|
...remote.remoteConfig,
|
|
4869
|
+
...definedChatProps,
|
|
4475
4870
|
agentId: chatProps.agentId ?? "",
|
|
4476
|
-
agentName:
|
|
4871
|
+
agentName: chatProps.agentName ?? remote.remoteConfig.agentName ?? "Asistente",
|
|
4477
4872
|
source: chatProps.source ?? remote.remoteConfig.source ?? "web"
|
|
4478
4873
|
};
|
|
4479
4874
|
const supportBackend = useSupportChat(
|
|
@@ -4489,6 +4884,10 @@ function BubbleWidget({
|
|
|
4489
4884
|
useEffect(() => {
|
|
4490
4885
|
setOpenRef.current = setOpen;
|
|
4491
4886
|
});
|
|
4887
|
+
const openRef = useRef(open);
|
|
4888
|
+
useEffect(() => {
|
|
4889
|
+
openRef.current = open;
|
|
4890
|
+
});
|
|
4492
4891
|
useEffect(() => {
|
|
4493
4892
|
if (!resolvedAutoOpen || autoOpenedRef.current) return;
|
|
4494
4893
|
const dismissedUntil = Number(localStorage.getItem(KEY_DISMISSED) ?? 0);
|
|
@@ -4498,15 +4897,29 @@ function BubbleWidget({
|
|
|
4498
4897
|
}
|
|
4499
4898
|
}, [resolvedAutoOpen]);
|
|
4500
4899
|
useEffect(() => {
|
|
4501
|
-
|
|
4900
|
+
const onToggle = () => setOpenRef.current((v) => !v);
|
|
4901
|
+
const onOpen = () => setOpenRef.current(true);
|
|
4902
|
+
const onClose = () => setOpenRef.current(false);
|
|
4903
|
+
window.addEventListener("wallavi:toggle-assistant", onToggle);
|
|
4904
|
+
window.addEventListener("wallavi:open-assistant", onOpen);
|
|
4905
|
+
window.addEventListener("wallavi:close-assistant", onClose);
|
|
4502
4906
|
const onKey = (e) => {
|
|
4503
|
-
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === shortcutKey.toLowerCase()) {
|
|
4907
|
+
if (resolvedKeyboardShortcut && (e.metaKey || e.ctrlKey) && e.key.toLowerCase() === shortcutKey.toLowerCase()) {
|
|
4504
4908
|
e.preventDefault();
|
|
4505
4909
|
setOpenRef.current((v) => !v);
|
|
4910
|
+
} else if (e.key === "Escape" && openRef.current) {
|
|
4911
|
+
e.preventDefault();
|
|
4912
|
+
e.stopPropagation();
|
|
4913
|
+
setOpenRef.current(false);
|
|
4506
4914
|
}
|
|
4507
4915
|
};
|
|
4508
4916
|
window.addEventListener("keydown", onKey);
|
|
4509
|
-
return () =>
|
|
4917
|
+
return () => {
|
|
4918
|
+
window.removeEventListener("wallavi:toggle-assistant", onToggle);
|
|
4919
|
+
window.removeEventListener("wallavi:open-assistant", onOpen);
|
|
4920
|
+
window.removeEventListener("wallavi:close-assistant", onClose);
|
|
4921
|
+
window.removeEventListener("keydown", onKey);
|
|
4922
|
+
};
|
|
4510
4923
|
}, [resolvedKeyboardShortcut, shortcutKey]);
|
|
4511
4924
|
useEffect(() => {
|
|
4512
4925
|
if (!open) return;
|
|
@@ -4561,7 +4974,8 @@ function BubbleWidget({
|
|
|
4561
4974
|
display: "flex",
|
|
4562
4975
|
flexDirection: "column",
|
|
4563
4976
|
alignItems: isLeft ? "flex-start" : "flex-end",
|
|
4564
|
-
gap: 12
|
|
4977
|
+
gap: 12,
|
|
4978
|
+
pointerEvents: !open && hideBubble ? "none" : void 0
|
|
4565
4979
|
},
|
|
4566
4980
|
children: [
|
|
4567
4981
|
/* @__PURE__ */ jsx(
|
|
@@ -4578,12 +4992,14 @@ function BubbleWidget({
|
|
|
4578
4992
|
zIndex: 9999,
|
|
4579
4993
|
width: panelWidth,
|
|
4580
4994
|
height: panelHeight,
|
|
4581
|
-
transition: "width 0.3s ease, height 0.3s ease"
|
|
4995
|
+
transition: "width 0.3s ease, height 0.3s ease",
|
|
4996
|
+
pointerEvents: "auto"
|
|
4582
4997
|
} : {
|
|
4583
4998
|
display: open ? "block" : "none",
|
|
4584
4999
|
width: panelWidth,
|
|
4585
5000
|
height: panelHeight,
|
|
4586
|
-
transition: "width 0.3s ease, height 0.3s ease"
|
|
5001
|
+
transition: "width 0.3s ease, height 0.3s ease",
|
|
5002
|
+
pointerEvents: "auto"
|
|
4587
5003
|
},
|
|
4588
5004
|
children: /* @__PURE__ */ jsx(
|
|
4589
5005
|
ChatWidget,
|