@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.js
CHANGED
|
@@ -92,6 +92,83 @@ async function getFreshClerkToken() {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// src/lib/page-context.ts
|
|
96
|
+
function candidatesFrom(actions) {
|
|
97
|
+
const out = [];
|
|
98
|
+
for (const action of actions) {
|
|
99
|
+
const title = action.displayName ?? action.name;
|
|
100
|
+
const target = action.steps?.find((s) => s.action === "navigate")?.value;
|
|
101
|
+
if (!title || typeof target !== "string" || !target.startsWith("/")) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const [path = "", search = ""] = target.split("?");
|
|
105
|
+
out.push({
|
|
106
|
+
title,
|
|
107
|
+
segments: path.split("/").filter(Boolean),
|
|
108
|
+
query: new URLSearchParams(search)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
var PARAM = /^\[(\w+)\]$/;
|
|
114
|
+
var LOCALE = /^[a-z]{2}(?:-[a-z]{2,4})?$/i;
|
|
115
|
+
function offsetsFor(pathSegments, routeLength) {
|
|
116
|
+
const offsets = [];
|
|
117
|
+
if (pathSegments.length === routeLength) offsets.push(0);
|
|
118
|
+
if (pathSegments.length === routeLength + 1 && LOCALE.test(pathSegments[0] ?? "")) {
|
|
119
|
+
offsets.push(1);
|
|
120
|
+
}
|
|
121
|
+
return offsets;
|
|
122
|
+
}
|
|
123
|
+
function safeDecode(segment) {
|
|
124
|
+
try {
|
|
125
|
+
return decodeURIComponent(segment);
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function score(candidate, pathSegments, search) {
|
|
131
|
+
const { segments, query } = candidate;
|
|
132
|
+
if (segments.length === 0) return null;
|
|
133
|
+
for (const [key, value] of query) {
|
|
134
|
+
if (search.get(key) !== value) return null;
|
|
135
|
+
}
|
|
136
|
+
for (const offset of offsetsFor(pathSegments, segments.length)) {
|
|
137
|
+
const params = {};
|
|
138
|
+
let points = Array.from(query.keys()).length;
|
|
139
|
+
let matched = true;
|
|
140
|
+
for (let i = 0; i < segments.length && matched; i++) {
|
|
141
|
+
const pattern = segments[i];
|
|
142
|
+
const actual = safeDecode(pathSegments[offset + i]);
|
|
143
|
+
const param = PARAM.exec(pattern);
|
|
144
|
+
if (actual === null) matched = false;
|
|
145
|
+
else if (param) params[param[1]] = actual;
|
|
146
|
+
else if (pattern === actual) points += 2;
|
|
147
|
+
else matched = false;
|
|
148
|
+
}
|
|
149
|
+
if (matched) return { score: points, params };
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
function derivePageContext(location, actions) {
|
|
154
|
+
if (!actions?.length) return void 0;
|
|
155
|
+
const pathSegments = location.pathname.split("/").filter(Boolean);
|
|
156
|
+
const search = new URLSearchParams(location.search);
|
|
157
|
+
let best = null;
|
|
158
|
+
for (const candidate of candidatesFrom(actions)) {
|
|
159
|
+
const result = score(candidate, pathSegments, search);
|
|
160
|
+
if (result && (!best || result.score > best.score)) {
|
|
161
|
+
best = { title: candidate.title, ...result };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (!best) return void 0;
|
|
165
|
+
return {
|
|
166
|
+
url: `${location.pathname}${location.search}`,
|
|
167
|
+
title: best.title,
|
|
168
|
+
...Object.keys(best.params).length > 0 ? { params: best.params } : {}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
95
172
|
// src/lib/types.ts
|
|
96
173
|
function getContrastColor(hex) {
|
|
97
174
|
const clean = hex.replace("#", "");
|
|
@@ -105,7 +182,7 @@ function formatToolName(name) {
|
|
|
105
182
|
return name.replace(/([A-Z])/g, " $1").replace(/_/g, " ").trim();
|
|
106
183
|
}
|
|
107
184
|
|
|
108
|
-
// src/
|
|
185
|
+
// ../protocol/src/index.ts
|
|
109
186
|
var STREAM_DELIMITER = "\u03B6\u236E";
|
|
110
187
|
async function consumeStream(body, handler) {
|
|
111
188
|
const reader = body.getReader();
|
|
@@ -121,9 +198,9 @@ async function consumeStream(body, handler) {
|
|
|
121
198
|
if (!raw) continue;
|
|
122
199
|
try {
|
|
123
200
|
const parsed = JSON.parse(raw);
|
|
124
|
-
|
|
125
|
-
if (proto) {
|
|
201
|
+
if (parsed.data?.uiMessageProtocol) {
|
|
126
202
|
eventCount++;
|
|
203
|
+
const proto = parsed.data.uiMessageProtocol;
|
|
127
204
|
if (proto.type === "text-delta") {
|
|
128
205
|
textAccumulator += proto.delta ?? "";
|
|
129
206
|
}
|
|
@@ -273,12 +350,321 @@ function applyUiEventToMessages(prev, proto, msgId) {
|
|
|
273
350
|
copy[idx] = msg;
|
|
274
351
|
return copy;
|
|
275
352
|
}
|
|
276
|
-
|
|
353
|
+
|
|
354
|
+
// src/core/declarative-actions.ts
|
|
355
|
+
function sanitizeSelector(selector) {
|
|
356
|
+
if (!selector || typeof selector !== "string") return null;
|
|
357
|
+
const lower = selector.toLowerCase();
|
|
358
|
+
if (lower.includes("<") || lower.includes(">") || lower.includes("script") || lower.includes("iframe") || lower.includes("onload") || lower.includes("onerror") || lower.includes("javascript:")) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const cleanRegex = /^[a-zA-Z0-9\s\.\-#_\[\]="':]+$/;
|
|
362
|
+
if (!cleanRegex.test(selector)) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
return selector;
|
|
366
|
+
}
|
|
367
|
+
function sanitizeEventName(name) {
|
|
368
|
+
if (!name || typeof name !== "string") return null;
|
|
369
|
+
const nameRegex = /^[a-zA-Z0-9\-_:]+$/;
|
|
370
|
+
if (!nameRegex.test(name)) return null;
|
|
371
|
+
const nativeEvents = /* @__PURE__ */ new Set([
|
|
372
|
+
"click",
|
|
373
|
+
"dblclick",
|
|
374
|
+
"mouseup",
|
|
375
|
+
"mousedown",
|
|
376
|
+
"mouseover",
|
|
377
|
+
"mouseout",
|
|
378
|
+
"submit",
|
|
379
|
+
"reset",
|
|
380
|
+
"change",
|
|
381
|
+
"select",
|
|
382
|
+
"keydown",
|
|
383
|
+
"keypress",
|
|
384
|
+
"keyup",
|
|
385
|
+
"load",
|
|
386
|
+
"unload",
|
|
387
|
+
"abort",
|
|
388
|
+
"error",
|
|
389
|
+
"resize",
|
|
390
|
+
"scroll",
|
|
391
|
+
"contextmenu"
|
|
392
|
+
]);
|
|
393
|
+
if (nativeEvents.has(name.toLowerCase())) return null;
|
|
394
|
+
return name;
|
|
395
|
+
}
|
|
396
|
+
function sanitizeUrl(url) {
|
|
397
|
+
if (!url || typeof url !== "string") return null;
|
|
398
|
+
const trimmed = url.trim();
|
|
399
|
+
const lower = trimmed.toLowerCase();
|
|
400
|
+
if (trimmed.startsWith("//")) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
if (lower.startsWith("javascript:") || lower.startsWith("data:")) {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
if (trimmed.startsWith("/")) {
|
|
407
|
+
return trimmed;
|
|
408
|
+
}
|
|
409
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
|
410
|
+
return trimmed;
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
async function waitForElement(selector, retries = 5, delayMs = 100) {
|
|
415
|
+
const cleanId = selector.startsWith("#") ? selector.substring(1) : selector;
|
|
416
|
+
for (let i = 0; i < retries; i++) {
|
|
417
|
+
try {
|
|
418
|
+
const el = document.querySelector(selector);
|
|
419
|
+
if (el) return el;
|
|
420
|
+
} catch (e) {
|
|
421
|
+
console.error(e);
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
const byId = document.getElementById(cleanId);
|
|
425
|
+
if (byId) return byId;
|
|
426
|
+
} catch (e) {
|
|
427
|
+
console.error(e);
|
|
428
|
+
}
|
|
429
|
+
if (!selector.startsWith("#") && !selector.startsWith(".") && !selector.startsWith("[")) {
|
|
430
|
+
try {
|
|
431
|
+
const byPrependedId = document.querySelector(`#${selector}`);
|
|
432
|
+
if (byPrependedId) return byPrependedId;
|
|
433
|
+
} catch (e) {
|
|
434
|
+
console.error(e);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
var HEX_COLOR_REGEX = /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/;
|
|
442
|
+
function sanitizeColor(color, fallback = "#6366f1") {
|
|
443
|
+
if (!color || typeof color !== "string") return fallback;
|
|
444
|
+
const trimmed = color.trim();
|
|
445
|
+
return HEX_COLOR_REGEX.test(trimmed) ? trimmed : fallback;
|
|
446
|
+
}
|
|
447
|
+
function injectCopilotStyles(color = "#6366f1") {
|
|
448
|
+
if (typeof document === "undefined") return;
|
|
449
|
+
const safeColor = sanitizeColor(color, "#6366f1");
|
|
450
|
+
let style = document.getElementById(
|
|
451
|
+
"wallavi-copilot-styles"
|
|
452
|
+
);
|
|
453
|
+
if (!style) {
|
|
454
|
+
style = document.createElement("style");
|
|
455
|
+
style.id = "wallavi-copilot-styles";
|
|
456
|
+
document.head.appendChild(style);
|
|
457
|
+
}
|
|
458
|
+
if (style.dataset.color === safeColor) return;
|
|
459
|
+
style.dataset.color = safeColor;
|
|
460
|
+
style.textContent = `
|
|
461
|
+
@keyframes wallavi-pulse-beacon {
|
|
462
|
+
0% {
|
|
463
|
+
box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.7);
|
|
464
|
+
outline: 2px solid ${safeColor};
|
|
465
|
+
}
|
|
466
|
+
70% {
|
|
467
|
+
box-shadow: 0 0 0 10px rgba(99, 102, 241, 0);
|
|
468
|
+
outline: 2px solid rgba(99, 102, 241, 0.3);
|
|
469
|
+
}
|
|
470
|
+
100% {
|
|
471
|
+
box-shadow: 0 0 0 0 rgba(99, 102, 241, 0);
|
|
472
|
+
outline: 2px solid transparent;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
.wallavi-element-beacon {
|
|
476
|
+
animation: wallavi-pulse-beacon 1.8s ease-out infinite !important;
|
|
477
|
+
outline-offset: 3px !important;
|
|
478
|
+
border-radius: inherit;
|
|
479
|
+
transition: outline 0.2s ease, box-shadow 0.2s ease !important;
|
|
480
|
+
}
|
|
481
|
+
@keyframes wallavi-flash-touch-anim {
|
|
482
|
+
0% {
|
|
483
|
+
outline: 3px solid ${safeColor};
|
|
484
|
+
outline-offset: 2px;
|
|
485
|
+
filter: brightness(1.08);
|
|
486
|
+
}
|
|
487
|
+
100% {
|
|
488
|
+
outline: 2px solid transparent;
|
|
489
|
+
outline-offset: 6px;
|
|
490
|
+
filter: none;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
.wallavi-flash-touch {
|
|
494
|
+
animation: wallavi-flash-touch-anim 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards !important;
|
|
495
|
+
}
|
|
496
|
+
`;
|
|
497
|
+
document.head.appendChild(style);
|
|
498
|
+
}
|
|
499
|
+
function flashElement(el, color) {
|
|
500
|
+
injectCopilotStyles(color);
|
|
501
|
+
el.classList.remove("wallavi-flash-touch");
|
|
502
|
+
void el.offsetWidth;
|
|
503
|
+
el.classList.add("wallavi-flash-touch");
|
|
504
|
+
setTimeout(() => {
|
|
505
|
+
el.classList.remove("wallavi-flash-touch");
|
|
506
|
+
}, 600);
|
|
507
|
+
}
|
|
508
|
+
function highlightElement(el, color, durationMs = 3e3) {
|
|
509
|
+
injectCopilotStyles(color);
|
|
510
|
+
el.classList.add("wallavi-element-beacon");
|
|
511
|
+
setTimeout(() => {
|
|
512
|
+
el.classList.remove("wallavi-element-beacon");
|
|
513
|
+
}, durationMs);
|
|
514
|
+
}
|
|
515
|
+
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
516
|
+
async function executeDeclarativeSteps(steps, onNavigate, copilotColor) {
|
|
517
|
+
if (typeof window === "undefined" || !Array.isArray(steps)) return;
|
|
518
|
+
const safeCopilotColor = sanitizeColor(copilotColor, "#6366f1");
|
|
519
|
+
for (const step of steps) {
|
|
520
|
+
try {
|
|
521
|
+
const { action } = step;
|
|
522
|
+
switch (action) {
|
|
523
|
+
case "scroll_to":
|
|
524
|
+
case "scroll_into_view": {
|
|
525
|
+
const selector = sanitizeSelector(step.selector);
|
|
526
|
+
if (!selector) break;
|
|
527
|
+
const el = await waitForElement(selector);
|
|
528
|
+
if (el) {
|
|
529
|
+
flashElement(el, safeCopilotColor);
|
|
530
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
531
|
+
}
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
case "click": {
|
|
535
|
+
const selector = sanitizeSelector(step.selector);
|
|
536
|
+
if (!selector) break;
|
|
537
|
+
const el = await waitForElement(selector);
|
|
538
|
+
if (el) {
|
|
539
|
+
flashElement(el, safeCopilotColor);
|
|
540
|
+
await delay(80);
|
|
541
|
+
el.click();
|
|
542
|
+
}
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
case "focus": {
|
|
546
|
+
const selector = sanitizeSelector(step.selector);
|
|
547
|
+
if (!selector) break;
|
|
548
|
+
const el = await waitForElement(selector);
|
|
549
|
+
if (el) {
|
|
550
|
+
flashElement(el, safeCopilotColor);
|
|
551
|
+
el.focus();
|
|
552
|
+
}
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
case "highlight": {
|
|
556
|
+
const selector = sanitizeSelector(step.selector);
|
|
557
|
+
if (!selector) break;
|
|
558
|
+
const el = await waitForElement(selector);
|
|
559
|
+
if (el) {
|
|
560
|
+
highlightElement(
|
|
561
|
+
el,
|
|
562
|
+
sanitizeColor(step.color, safeCopilotColor),
|
|
563
|
+
step.durationMs ?? 3e3
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case "wait": {
|
|
569
|
+
const parsedVal = typeof step.value === "number" ? step.value : parseInt(String(step.value ?? "500"), 10);
|
|
570
|
+
const ms = step.delayMs ?? (Number.isNaN(parsedVal) ? 500 : parsedVal);
|
|
571
|
+
await delay(ms);
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
case "add_class": {
|
|
575
|
+
const selector = sanitizeSelector(step.selector);
|
|
576
|
+
const value = step.value;
|
|
577
|
+
if (!selector || !value) break;
|
|
578
|
+
const el = await waitForElement(selector);
|
|
579
|
+
if (el) el.classList.add(...value.split(/\s+/).filter(Boolean));
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
case "remove_class": {
|
|
583
|
+
const selector = sanitizeSelector(step.selector);
|
|
584
|
+
const value = step.value;
|
|
585
|
+
if (!selector || !value) break;
|
|
586
|
+
const el = await waitForElement(selector);
|
|
587
|
+
if (el) el.classList.remove(...value.split(/\s+/).filter(Boolean));
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
case "fill_value": {
|
|
591
|
+
const selector = sanitizeSelector(step.selector);
|
|
592
|
+
const value = step.value;
|
|
593
|
+
if (!selector) break;
|
|
594
|
+
const el = await waitForElement(selector);
|
|
595
|
+
if (el && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {
|
|
596
|
+
if (el instanceof HTMLInputElement) {
|
|
597
|
+
const type = el.type.toLowerCase();
|
|
598
|
+
if (type === "password" || type === "hidden") {
|
|
599
|
+
console.warn(
|
|
600
|
+
`[Wallavi Widget] Blocked fill_value action on sensitive field: ${type}`
|
|
601
|
+
);
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const isReadOnly = (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) && el.readOnly;
|
|
606
|
+
if (el.disabled || isReadOnly) {
|
|
607
|
+
console.warn(
|
|
608
|
+
"[Wallavi Widget] Blocked fill_value action on disabled/readOnly element"
|
|
609
|
+
);
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
flashElement(el, safeCopilotColor);
|
|
613
|
+
el.value = value ?? "";
|
|
614
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
615
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
616
|
+
}
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
case "dispatch_event": {
|
|
620
|
+
const eventName = sanitizeEventName(step.value);
|
|
621
|
+
if (!eventName) break;
|
|
622
|
+
const detail = step.payload ?? {};
|
|
623
|
+
window.dispatchEvent(new CustomEvent(eventName, { detail }));
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
case "navigate": {
|
|
627
|
+
const url = sanitizeUrl(step.value);
|
|
628
|
+
if (url) {
|
|
629
|
+
if (onNavigate) {
|
|
630
|
+
onNavigate(url);
|
|
631
|
+
} else {
|
|
632
|
+
window.location.href = url;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
default: {
|
|
638
|
+
const unknownAction = step.action;
|
|
639
|
+
console.warn(
|
|
640
|
+
`[Wallavi Widget] Unknown declarative action: ${unknownAction}`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
await delay(120);
|
|
645
|
+
} catch (err) {
|
|
646
|
+
console.error(
|
|
647
|
+
"[Wallavi Widget] Failed to execute declarative step:",
|
|
648
|
+
step,
|
|
649
|
+
err
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/lib/api-url.ts
|
|
656
|
+
var WALLAVI_PRODUCTION_API_URL = "https://wallavi-production.up.railway.app";
|
|
657
|
+
function resolveApiUrl(explicit) {
|
|
658
|
+
return explicit ?? process.env.NEXT_PUBLIC_API_URL ?? WALLAVI_PRODUCTION_API_URL;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/hooks/use-voice-call.ts
|
|
277
662
|
function useVoiceCall({
|
|
278
663
|
agentId,
|
|
279
664
|
threadId,
|
|
280
665
|
workspaceId,
|
|
281
|
-
customBackend
|
|
666
|
+
customBackend,
|
|
667
|
+
apiUrl
|
|
282
668
|
}) {
|
|
283
669
|
const [active, setActive] = react.useState(false);
|
|
284
670
|
const [token, setToken] = react.useState(null);
|
|
@@ -291,7 +677,7 @@ function useVoiceCall({
|
|
|
291
677
|
setError(null);
|
|
292
678
|
try {
|
|
293
679
|
const isPrivate = Boolean(workspaceId);
|
|
294
|
-
const url = isPrivate ? `${
|
|
680
|
+
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)}`;
|
|
295
681
|
const token2 = isPrivate ? await getFreshClerkToken() : null;
|
|
296
682
|
const res = await fetch(url, {
|
|
297
683
|
headers: {
|
|
@@ -307,7 +693,7 @@ function useVoiceCall({
|
|
|
307
693
|
setError(err.message);
|
|
308
694
|
}
|
|
309
695
|
setLoading(false);
|
|
310
|
-
}, [agentId, threadId, workspaceId, customBackend]);
|
|
696
|
+
}, [agentId, threadId, workspaceId, customBackend, apiUrl]);
|
|
311
697
|
const stop = react.useCallback(() => {
|
|
312
698
|
setActive(false);
|
|
313
699
|
setToken(null);
|
|
@@ -324,13 +710,33 @@ function useVoiceCall({
|
|
|
324
710
|
};
|
|
325
711
|
}
|
|
326
712
|
|
|
713
|
+
// src/lib/user-headers.ts
|
|
714
|
+
var warnedHeadersFailure = false;
|
|
715
|
+
async function resolveUserHeaders(userContext) {
|
|
716
|
+
const source = userContext?.headers;
|
|
717
|
+
if (!source) return {};
|
|
718
|
+
if (typeof source !== "function") return source;
|
|
719
|
+
try {
|
|
720
|
+
return await source() ?? {};
|
|
721
|
+
} catch (err) {
|
|
722
|
+
if (!warnedHeadersFailure) {
|
|
723
|
+
warnedHeadersFailure = true;
|
|
724
|
+
console.warn(
|
|
725
|
+
"[wallavi-widget] userContext.headers threw; sending without host headers",
|
|
726
|
+
err
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
return {};
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
327
733
|
// src/hooks/use-chat.ts
|
|
328
|
-
var API_URL2 = process.env.NEXT_PUBLIC_API_URL ?? "https://wallavi-production.up.railway.app";
|
|
329
734
|
function newId() {
|
|
330
735
|
return Math.random().toString(36).slice(2, 10);
|
|
331
736
|
}
|
|
332
737
|
function useChat({
|
|
333
738
|
agentId,
|
|
739
|
+
apiUrl,
|
|
334
740
|
workspaceId = "",
|
|
335
741
|
envId,
|
|
336
742
|
source = "playground",
|
|
@@ -338,13 +744,20 @@ function useChat({
|
|
|
338
744
|
persist = false,
|
|
339
745
|
onNavigate,
|
|
340
746
|
playgroundOverrides,
|
|
341
|
-
customBackend
|
|
747
|
+
customBackend,
|
|
748
|
+
copilotColor,
|
|
749
|
+
resolvePageContext
|
|
342
750
|
}) {
|
|
751
|
+
const baseUrl = resolveApiUrl(apiUrl);
|
|
343
752
|
const userId = userContext?.userId;
|
|
344
753
|
const persistKey = persist ? userId ? `wallavi_${agentId}_${userId}` : `wallavi_${agentId}` : null;
|
|
345
754
|
const onNavigateRef = react.useRef(onNavigate);
|
|
755
|
+
const resolvePageContextRef = react.useRef(resolvePageContext);
|
|
756
|
+
const copilotColorRef = react.useRef(copilotColor);
|
|
346
757
|
react.useEffect(() => {
|
|
347
758
|
onNavigateRef.current = onNavigate;
|
|
759
|
+
resolvePageContextRef.current = resolvePageContext;
|
|
760
|
+
copilotColorRef.current = copilotColor;
|
|
348
761
|
});
|
|
349
762
|
const [messages, setMessages] = react.useState(() => {
|
|
350
763
|
if (!persistKey || typeof window === "undefined") return [];
|
|
@@ -438,7 +851,11 @@ function useChat({
|
|
|
438
851
|
return;
|
|
439
852
|
}
|
|
440
853
|
if (proto.type === "client-action") {
|
|
441
|
-
executeDeclarativeSteps(
|
|
854
|
+
executeDeclarativeSteps(
|
|
855
|
+
proto.steps,
|
|
856
|
+
onNavigateRef.current,
|
|
857
|
+
copilotColorRef.current
|
|
858
|
+
);
|
|
442
859
|
return;
|
|
443
860
|
}
|
|
444
861
|
if (proto.type === "debug-trace") {
|
|
@@ -457,7 +874,9 @@ function useChat({
|
|
|
457
874
|
const { input: userInput, msgId, extraMetadata, attachments } = opts;
|
|
458
875
|
const isPrivate = Boolean(workspaceId);
|
|
459
876
|
const token = isPrivate ? await getFreshClerkToken() : null;
|
|
460
|
-
const
|
|
877
|
+
const hostHeaders = await resolveUserHeaders(userContext);
|
|
878
|
+
const pageContext = userContext?.pageContext ?? resolvePageContextRef.current?.();
|
|
879
|
+
const url = isPrivate ? `${baseUrl}/api/threads/${threadId}/stream` : `${baseUrl}/api/chat/stream`;
|
|
461
880
|
const res = await fetch(url, {
|
|
462
881
|
method: "POST",
|
|
463
882
|
headers: {
|
|
@@ -478,10 +897,10 @@ function useChat({
|
|
|
478
897
|
...userContext?.userEmail ? { userEmail: userContext.userEmail } : {},
|
|
479
898
|
userMetadata: {
|
|
480
899
|
...userContext?.metadata ?? {},
|
|
481
|
-
...
|
|
900
|
+
...pageContext ? { pageContext } : {},
|
|
482
901
|
headers: {
|
|
483
902
|
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
484
|
-
...
|
|
903
|
+
...hostHeaders,
|
|
485
904
|
...userContext?.metadata?.headers ?? {}
|
|
486
905
|
},
|
|
487
906
|
...extraMetadata ?? {}
|
|
@@ -526,6 +945,7 @@ function useChat({
|
|
|
526
945
|
}
|
|
527
946
|
},
|
|
528
947
|
[
|
|
948
|
+
baseUrl,
|
|
529
949
|
agentId,
|
|
530
950
|
workspaceId,
|
|
531
951
|
envId,
|
|
@@ -540,7 +960,8 @@ function useChat({
|
|
|
540
960
|
agentId,
|
|
541
961
|
threadId,
|
|
542
962
|
workspaceId,
|
|
543
|
-
customBackend
|
|
963
|
+
customBackend,
|
|
964
|
+
apiUrl: baseUrl
|
|
544
965
|
});
|
|
545
966
|
react.useEffect(() => {
|
|
546
967
|
if (customBackend || !persistKey) return;
|
|
@@ -589,7 +1010,7 @@ function useChat({
|
|
|
589
1010
|
try {
|
|
590
1011
|
const isPrivate = Boolean(workspaceId);
|
|
591
1012
|
const token = isPrivate ? await getFreshClerkToken() : null;
|
|
592
|
-
const url = isPrivate ? `${
|
|
1013
|
+
const url = isPrivate ? `${baseUrl}/api/threads/${threadId}/messages` : `${baseUrl}/api/chat/messages?agentId=${encodeURIComponent(agentId)}&threadId=${encodeURIComponent(threadId)}`;
|
|
593
1014
|
const res = await fetch(url, {
|
|
594
1015
|
headers: {
|
|
595
1016
|
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
@@ -653,7 +1074,15 @@ function useChat({
|
|
|
653
1074
|
}
|
|
654
1075
|
}
|
|
655
1076
|
}
|
|
656
|
-
}, [
|
|
1077
|
+
}, [
|
|
1078
|
+
baseUrl,
|
|
1079
|
+
threadId,
|
|
1080
|
+
agentId,
|
|
1081
|
+
workspaceId,
|
|
1082
|
+
envId,
|
|
1083
|
+
persistKey,
|
|
1084
|
+
fetchAndStream
|
|
1085
|
+
]);
|
|
657
1086
|
const reset = react.useCallback(() => {
|
|
658
1087
|
setMessages([]);
|
|
659
1088
|
setInput("");
|
|
@@ -837,106 +1266,6 @@ function useChat({
|
|
|
837
1266
|
setSelectedContext
|
|
838
1267
|
};
|
|
839
1268
|
}
|
|
840
|
-
async function waitForElement(selector, retries = 5, delayMs = 100) {
|
|
841
|
-
const cleanId = selector.startsWith("#") ? selector.substring(1) : selector;
|
|
842
|
-
for (let i = 0; i < retries; i++) {
|
|
843
|
-
try {
|
|
844
|
-
const el = document.querySelector(selector);
|
|
845
|
-
if (el) return el;
|
|
846
|
-
} catch (e) {
|
|
847
|
-
}
|
|
848
|
-
try {
|
|
849
|
-
const byId = document.getElementById(cleanId);
|
|
850
|
-
if (byId) return byId;
|
|
851
|
-
} catch (e) {
|
|
852
|
-
}
|
|
853
|
-
if (!selector.startsWith("#") && !selector.startsWith(".") && !selector.startsWith("[")) {
|
|
854
|
-
try {
|
|
855
|
-
const byPrependedId = document.querySelector(`#${selector}`);
|
|
856
|
-
if (byPrependedId) return byPrependedId;
|
|
857
|
-
} catch (e) {
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
861
|
-
}
|
|
862
|
-
return null;
|
|
863
|
-
}
|
|
864
|
-
async function executeDeclarativeSteps(steps, onNavigate) {
|
|
865
|
-
if (typeof window === "undefined" || !Array.isArray(steps)) return;
|
|
866
|
-
for (const step of steps) {
|
|
867
|
-
try {
|
|
868
|
-
const { action, selector, value, payload } = step;
|
|
869
|
-
switch (action) {
|
|
870
|
-
case "scroll_into_view": {
|
|
871
|
-
if (!selector) break;
|
|
872
|
-
const el = await waitForElement(selector);
|
|
873
|
-
el?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
874
|
-
break;
|
|
875
|
-
}
|
|
876
|
-
case "click": {
|
|
877
|
-
if (!selector) break;
|
|
878
|
-
const el = await waitForElement(selector);
|
|
879
|
-
el?.click();
|
|
880
|
-
break;
|
|
881
|
-
}
|
|
882
|
-
case "focus": {
|
|
883
|
-
if (!selector) break;
|
|
884
|
-
const el = await waitForElement(selector);
|
|
885
|
-
el?.focus();
|
|
886
|
-
break;
|
|
887
|
-
}
|
|
888
|
-
case "add_class": {
|
|
889
|
-
if (!selector || !value) break;
|
|
890
|
-
const el = await waitForElement(selector);
|
|
891
|
-
if (el) el.classList.add(...value.split(/\s+/).filter(Boolean));
|
|
892
|
-
break;
|
|
893
|
-
}
|
|
894
|
-
case "remove_class": {
|
|
895
|
-
if (!selector || !value) break;
|
|
896
|
-
const el = await waitForElement(selector);
|
|
897
|
-
if (el) el.classList.remove(...value.split(/\s+/).filter(Boolean));
|
|
898
|
-
break;
|
|
899
|
-
}
|
|
900
|
-
case "fill_value": {
|
|
901
|
-
if (!selector) break;
|
|
902
|
-
const el = await waitForElement(selector);
|
|
903
|
-
if (el) {
|
|
904
|
-
el.value = value ?? "";
|
|
905
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
906
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
907
|
-
}
|
|
908
|
-
break;
|
|
909
|
-
}
|
|
910
|
-
case "dispatch_event": {
|
|
911
|
-
if (!value) break;
|
|
912
|
-
const detail = payload ?? {};
|
|
913
|
-
window.dispatchEvent(new CustomEvent(value, { detail }));
|
|
914
|
-
break;
|
|
915
|
-
}
|
|
916
|
-
case "navigate": {
|
|
917
|
-
if (value) {
|
|
918
|
-
if (onNavigate) {
|
|
919
|
-
onNavigate(value);
|
|
920
|
-
} else {
|
|
921
|
-
window.location.href = value;
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
break;
|
|
925
|
-
}
|
|
926
|
-
default:
|
|
927
|
-
console.warn(
|
|
928
|
-
`[Wallavi Widget] Unknown declarative action: ${action}`
|
|
929
|
-
);
|
|
930
|
-
}
|
|
931
|
-
} catch (err) {
|
|
932
|
-
console.error(
|
|
933
|
-
"[Wallavi Widget] Failed to execute declarative step:",
|
|
934
|
-
step,
|
|
935
|
-
err
|
|
936
|
-
);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
1269
|
function getPreferredMimeType() {
|
|
941
1270
|
if (typeof MediaRecorder === "undefined") return "";
|
|
942
1271
|
const candidates = [
|
|
@@ -953,7 +1282,6 @@ function mimeTypeToExtension(mimeType) {
|
|
|
953
1282
|
if (mimeType.includes("mp4")) return "mp4";
|
|
954
1283
|
return "webm";
|
|
955
1284
|
}
|
|
956
|
-
var DEFAULT_API_URL = process.env.NEXT_PUBLIC_API_URL ?? "https://wallavi-production.up.railway.app";
|
|
957
1285
|
function useVoice({
|
|
958
1286
|
agentId,
|
|
959
1287
|
apiUrl,
|
|
@@ -966,7 +1294,7 @@ function useVoice({
|
|
|
966
1294
|
const streamRef = react.useRef(null);
|
|
967
1295
|
const errorTimerRef = react.useRef(null);
|
|
968
1296
|
const isSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined" && !!navigator?.mediaDevices?.getUserMedia;
|
|
969
|
-
const base = apiUrl
|
|
1297
|
+
const base = resolveApiUrl(apiUrl);
|
|
970
1298
|
const transcribeBlob = react.useCallback(
|
|
971
1299
|
async (blob, mimeType) => {
|
|
972
1300
|
setVoiceState("transcribing");
|
|
@@ -1045,7 +1373,6 @@ function useVoice({
|
|
|
1045
1373
|
}, []);
|
|
1046
1374
|
return { voiceState, isSupported, start, stop };
|
|
1047
1375
|
}
|
|
1048
|
-
var DEFAULT_API_URL2 = process.env.NEXT_PUBLIC_API_URL ?? "https://wallavi-production.up.railway.app";
|
|
1049
1376
|
function makeId() {
|
|
1050
1377
|
return `att_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
|
1051
1378
|
}
|
|
@@ -1055,7 +1382,7 @@ function useAttachments({
|
|
|
1055
1382
|
maxFiles = 5
|
|
1056
1383
|
}) {
|
|
1057
1384
|
const [attachments, setAttachments] = react.useState([]);
|
|
1058
|
-
const base = apiUrl
|
|
1385
|
+
const base = resolveApiUrl(apiUrl);
|
|
1059
1386
|
const uploadOne = react.useCallback(
|
|
1060
1387
|
async (file, id) => {
|
|
1061
1388
|
const form = new FormData();
|
|
@@ -2850,6 +3177,20 @@ function VoiceOverlay({
|
|
|
2850
3177
|
}
|
|
2851
3178
|
) });
|
|
2852
3179
|
}
|
|
3180
|
+
|
|
3181
|
+
// src/lib/unique-keys.ts
|
|
3182
|
+
function uniqueKeys(items, baseKey) {
|
|
3183
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3184
|
+
return items.map((item) => {
|
|
3185
|
+
const base = baseKey(item);
|
|
3186
|
+
const count = seen.get(base) ?? 0;
|
|
3187
|
+
seen.set(base, count + 1);
|
|
3188
|
+
return count === 0 ? base : `${base}#${count}`;
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
var actionKey = (action) => `${action.name}:${JSON.stringify(action.steps ?? [])}`;
|
|
3192
|
+
var docKey = (doc) => `${doc.name}:${doc.url ?? ""}`;
|
|
3193
|
+
var topicKey = (topic) => topic.name;
|
|
2853
3194
|
function SvgIcon({
|
|
2854
3195
|
className,
|
|
2855
3196
|
strokeWidth = "1.8",
|
|
@@ -2923,11 +3264,11 @@ function urlRelevance(docUrl, pageUrl) {
|
|
|
2923
3264
|
return 50;
|
|
2924
3265
|
const docParts = docClean.split(/[/?#]/).filter(Boolean);
|
|
2925
3266
|
const pageParts = pageClean.split(/[/?#]/).filter(Boolean);
|
|
2926
|
-
let
|
|
3267
|
+
let score2 = 0;
|
|
2927
3268
|
for (const part of pageParts) {
|
|
2928
|
-
if (docParts.includes(part))
|
|
3269
|
+
if (docParts.includes(part)) score2 += 10;
|
|
2929
3270
|
}
|
|
2930
|
-
return
|
|
3271
|
+
return score2;
|
|
2931
3272
|
}
|
|
2932
3273
|
function topicToQuestion(topic, locale) {
|
|
2933
3274
|
const name = topic.name.trim();
|
|
@@ -3018,9 +3359,9 @@ function getContextKeywords(pageUrl, pageTitle, pageParams, pageVars, metadata)
|
|
|
3018
3359
|
return Array.from(keywords);
|
|
3019
3360
|
}
|
|
3020
3361
|
function computeRelevanceScore(item, keywords, pageUrl) {
|
|
3021
|
-
let
|
|
3362
|
+
let score2 = 0;
|
|
3022
3363
|
if (item.url && pageUrl) {
|
|
3023
|
-
|
|
3364
|
+
score2 += urlRelevance(item.url, pageUrl);
|
|
3024
3365
|
}
|
|
3025
3366
|
const itemName = (item.name || "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
3026
3367
|
const itemDesc = (item.description || item.summary || "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
@@ -3028,33 +3369,41 @@ function computeRelevanceScore(item, keywords, pageUrl) {
|
|
|
3028
3369
|
const nameWords = itemName.split(/[^a-z0-9]/).filter(Boolean);
|
|
3029
3370
|
for (const keyword of keywords) {
|
|
3030
3371
|
if (itemName.includes(keyword)) {
|
|
3031
|
-
|
|
3372
|
+
score2 += 30;
|
|
3032
3373
|
if (nameWords.includes(keyword)) {
|
|
3033
|
-
|
|
3374
|
+
score2 += 20;
|
|
3034
3375
|
}
|
|
3035
3376
|
}
|
|
3036
3377
|
if (itemDesc.includes(keyword)) {
|
|
3037
|
-
|
|
3378
|
+
score2 += 15;
|
|
3038
3379
|
}
|
|
3039
3380
|
if (itemUrl.includes(keyword)) {
|
|
3040
|
-
|
|
3381
|
+
score2 += 20;
|
|
3041
3382
|
}
|
|
3042
3383
|
}
|
|
3043
|
-
if (item.keywords) {
|
|
3044
|
-
const
|
|
3045
|
-
|
|
3046
|
-
...item.keywords
|
|
3047
|
-
|
|
3048
|
-
|
|
3384
|
+
if (item.keywords && typeof item.keywords === "object") {
|
|
3385
|
+
const rawList = [];
|
|
3386
|
+
if (Array.isArray(item.keywords)) {
|
|
3387
|
+
rawList.push(...item.keywords);
|
|
3388
|
+
} else {
|
|
3389
|
+
const kwRecord = item.keywords;
|
|
3390
|
+
for (const lang of ["en", "es", "fr"]) {
|
|
3391
|
+
const langVal = kwRecord[lang];
|
|
3392
|
+
if (Array.isArray(langVal)) {
|
|
3393
|
+
rawList.push(...langVal);
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
const allItemKeywords = rawList.filter((k) => typeof k === "string" && k.trim().length > 0).map(
|
|
3049
3398
|
(k) => k.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "")
|
|
3050
3399
|
);
|
|
3051
3400
|
for (const keyword of keywords) {
|
|
3052
3401
|
if (allItemKeywords.includes(keyword)) {
|
|
3053
|
-
|
|
3402
|
+
score2 += 45;
|
|
3054
3403
|
}
|
|
3055
3404
|
}
|
|
3056
3405
|
}
|
|
3057
|
-
return
|
|
3406
|
+
return score2;
|
|
3058
3407
|
}
|
|
3059
3408
|
var STAGGER_MS = 50;
|
|
3060
3409
|
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";
|
|
@@ -3267,6 +3616,9 @@ function CommandPanel({
|
|
|
3267
3616
|
}));
|
|
3268
3617
|
return scored.sort((a, b) => b.score - a.score);
|
|
3269
3618
|
}, [clientActions, pageUrl, pageTitle, pageParams, pageVars, metadata]);
|
|
3619
|
+
const actionKeys = uniqueKeys(sortedActions, actionKey);
|
|
3620
|
+
const docKeys = uniqueKeys(relevantDocs, docKey);
|
|
3621
|
+
const topicKeys = uniqueKeys(topicQuestions, topicKey);
|
|
3270
3622
|
const hasActions = sortedActions.length > 0;
|
|
3271
3623
|
const hasDocs = relevantDocs.length > 0;
|
|
3272
3624
|
const hasTopics = topicQuestions.length > 0;
|
|
@@ -3288,7 +3640,7 @@ function CommandPanel({
|
|
|
3288
3640
|
accentColor,
|
|
3289
3641
|
onExecute: () => handleExecute(action.steps)
|
|
3290
3642
|
},
|
|
3291
|
-
|
|
3643
|
+
actionKeys[i]
|
|
3292
3644
|
)) })
|
|
3293
3645
|
] }),
|
|
3294
3646
|
hasDocs && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
|
|
@@ -3301,7 +3653,7 @@ function CommandPanel({
|
|
|
3301
3653
|
onSend,
|
|
3302
3654
|
locale
|
|
3303
3655
|
},
|
|
3304
|
-
|
|
3656
|
+
docKeys[i]
|
|
3305
3657
|
)) })
|
|
3306
3658
|
] }),
|
|
3307
3659
|
hasTopics && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
|
|
@@ -3313,7 +3665,7 @@ function CommandPanel({
|
|
|
3313
3665
|
index: i,
|
|
3314
3666
|
onSend
|
|
3315
3667
|
},
|
|
3316
|
-
|
|
3668
|
+
topicKeys[i]
|
|
3317
3669
|
)) })
|
|
3318
3670
|
] })
|
|
3319
3671
|
] }),
|
|
@@ -3360,7 +3712,7 @@ function CommandPanel({
|
|
|
3360
3712
|
accentColor,
|
|
3361
3713
|
onExecute: () => handleExecute(action.steps)
|
|
3362
3714
|
},
|
|
3363
|
-
|
|
3715
|
+
actionKeys[i]
|
|
3364
3716
|
)) })
|
|
3365
3717
|
] }),
|
|
3366
3718
|
hasDocs && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
|
|
@@ -3373,7 +3725,7 @@ function CommandPanel({
|
|
|
3373
3725
|
onSend,
|
|
3374
3726
|
locale
|
|
3375
3727
|
},
|
|
3376
|
-
|
|
3728
|
+
docKeys[i]
|
|
3377
3729
|
)) })
|
|
3378
3730
|
] }),
|
|
3379
3731
|
hasTopics && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
|
|
@@ -3386,13 +3738,14 @@ function CommandPanel({
|
|
|
3386
3738
|
compact: true,
|
|
3387
3739
|
onSend
|
|
3388
3740
|
},
|
|
3389
|
-
|
|
3741
|
+
topicKeys[i]
|
|
3390
3742
|
)) })
|
|
3391
3743
|
] })
|
|
3392
3744
|
] });
|
|
3393
3745
|
}
|
|
3394
3746
|
function ChatWidget({
|
|
3395
3747
|
agentId,
|
|
3748
|
+
apiUrl,
|
|
3396
3749
|
workspaceId,
|
|
3397
3750
|
agentName,
|
|
3398
3751
|
displayName,
|
|
@@ -3453,8 +3806,10 @@ function ChatWidget({
|
|
|
3453
3806
|
window.addEventListener("message", handleMessage);
|
|
3454
3807
|
return () => window.removeEventListener("message", handleMessage);
|
|
3455
3808
|
}, []);
|
|
3809
|
+
const resolvePageContext = () => userContext?.pageContext ?? (typeof window === "undefined" ? void 0 : derivePageContext(window.location, clientActions));
|
|
3456
3810
|
const chat = useChat({
|
|
3457
3811
|
agentId,
|
|
3812
|
+
apiUrl,
|
|
3458
3813
|
workspaceId,
|
|
3459
3814
|
envId,
|
|
3460
3815
|
source,
|
|
@@ -3462,10 +3817,13 @@ function ChatWidget({
|
|
|
3462
3817
|
persist,
|
|
3463
3818
|
onNavigate,
|
|
3464
3819
|
playgroundOverrides,
|
|
3465
|
-
customBackend
|
|
3820
|
+
customBackend,
|
|
3821
|
+
copilotColor: userMessageColor,
|
|
3822
|
+
resolvePageContext: () => resolvePageContext()
|
|
3466
3823
|
});
|
|
3467
3824
|
const voice = useVoice({
|
|
3468
3825
|
agentId,
|
|
3826
|
+
apiUrl,
|
|
3469
3827
|
onTranscript: (text) => {
|
|
3470
3828
|
if (voiceAutoSend) {
|
|
3471
3829
|
void chat.send(text);
|
|
@@ -3474,7 +3832,7 @@ function ChatWidget({
|
|
|
3474
3832
|
}
|
|
3475
3833
|
}
|
|
3476
3834
|
});
|
|
3477
|
-
const attachmentHook = useAttachments({ agentId });
|
|
3835
|
+
const attachmentHook = useAttachments({ agentId, apiUrl });
|
|
3478
3836
|
const debugTraceLenRef = react.useRef(0);
|
|
3479
3837
|
react.useEffect(() => {
|
|
3480
3838
|
if (!onDebugTrace || chat.debugTraces.length <= debugTraceLenRef.current)
|
|
@@ -3574,9 +3932,9 @@ function ChatWidget({
|
|
|
3574
3932
|
const showInlineCommandPanel = showCommandPanel && !showSidebar && chat.messages.length === 0 && (showQuickActions && clientActions.length > 0 || showRagTopics && ragTopics.length > 0 || showRagDocuments && ragDocuments.length > 0);
|
|
3575
3933
|
const handleExecuteAction = react.useCallback(
|
|
3576
3934
|
(steps) => {
|
|
3577
|
-
executeDeclarativeSteps(steps, onNavigate);
|
|
3935
|
+
executeDeclarativeSteps(steps, onNavigate, userMessageColor);
|
|
3578
3936
|
},
|
|
3579
|
-
[onNavigate]
|
|
3937
|
+
[onNavigate, userMessageColor]
|
|
3580
3938
|
);
|
|
3581
3939
|
const [activeActionIndex, setActiveActionIndex] = react.useState(-1);
|
|
3582
3940
|
const query = chat.input.toLowerCase().trim();
|
|
@@ -3633,11 +3991,12 @@ function ChatWidget({
|
|
|
3633
3991
|
},
|
|
3634
3992
|
[matchingActions, activeActionIndex, handleExecuteAction, chat]
|
|
3635
3993
|
);
|
|
3994
|
+
const pageContext = resolvePageContext();
|
|
3636
3995
|
const commandPanelProps = {
|
|
3637
|
-
pageUrl:
|
|
3638
|
-
pageTitle:
|
|
3639
|
-
pageParams:
|
|
3640
|
-
pageVars:
|
|
3996
|
+
pageUrl: pageContext?.url,
|
|
3997
|
+
pageTitle: pageContext?.title,
|
|
3998
|
+
pageParams: pageContext?.params,
|
|
3999
|
+
pageVars: pageContext?.vars,
|
|
3641
4000
|
metadata: userContext?.metadata,
|
|
3642
4001
|
clientActions: showQuickActions ? clientActions : [],
|
|
3643
4002
|
ragTopics: showRagTopics ? ragTopics : [],
|
|
@@ -3957,7 +4316,6 @@ function ChatWidget({
|
|
|
3957
4316
|
}
|
|
3958
4317
|
);
|
|
3959
4318
|
}
|
|
3960
|
-
var WALLAVI_PUBLIC_API = "https://wallavi-production.up.railway.app";
|
|
3961
4319
|
var EMPTY = {
|
|
3962
4320
|
remoteConfig: {},
|
|
3963
4321
|
bubbleIconUrl: void 0,
|
|
@@ -3971,7 +4329,37 @@ var EMPTY = {
|
|
|
3971
4329
|
panelHeight: 580,
|
|
3972
4330
|
loading: false
|
|
3973
4331
|
};
|
|
3974
|
-
function
|
|
4332
|
+
function toChatProps(cfg) {
|
|
4333
|
+
const remote = {};
|
|
4334
|
+
const set = (key, value) => {
|
|
4335
|
+
if (value !== null && value !== void 0) remote[key] = value;
|
|
4336
|
+
};
|
|
4337
|
+
set("agentName", cfg.agentName);
|
|
4338
|
+
set("profilePicture", cfg.profilePicture);
|
|
4339
|
+
set("displayName", cfg.displayName);
|
|
4340
|
+
set("theme", cfg.theme);
|
|
4341
|
+
set("userMessageColor", cfg.userMessageColor);
|
|
4342
|
+
if (cfg.initialMessages?.length) remote.initialMessages = cfg.initialMessages;
|
|
4343
|
+
if (cfg.suggestedMessages?.length)
|
|
4344
|
+
remote.suggestedMessages = cfg.suggestedMessages;
|
|
4345
|
+
set("messagePlaceholder", cfg.messagePlaceholder);
|
|
4346
|
+
set("watermark", cfg.watermark);
|
|
4347
|
+
set("footer", cfg.footer);
|
|
4348
|
+
set("showThinking", cfg.showThinking);
|
|
4349
|
+
set("regenerateMessage", cfg.regenerateMessage);
|
|
4350
|
+
set("widgetLayout", cfg.widgetLayout);
|
|
4351
|
+
set("enableVoice", cfg.enableVoice);
|
|
4352
|
+
set("showCommandPanel", cfg.showCommandPanel);
|
|
4353
|
+
set("showRagTopics", cfg.showRagTopics);
|
|
4354
|
+
set("showRagDocuments", cfg.showRagDocuments);
|
|
4355
|
+
set("showQuickActions", cfg.showQuickActions);
|
|
4356
|
+
set("clientActions", cfg.clientActions);
|
|
4357
|
+
set("ragTopics", cfg.ragTopics);
|
|
4358
|
+
set("ragDocuments", cfg.ragDocuments);
|
|
4359
|
+
set("locale", cfg.locale);
|
|
4360
|
+
return remote;
|
|
4361
|
+
}
|
|
4362
|
+
function useAutoConfig(agentId, enabled, apiUrl) {
|
|
3975
4363
|
const [result, setResult] = react.useState(() => ({
|
|
3976
4364
|
...EMPTY,
|
|
3977
4365
|
loading: enabled && Boolean(agentId)
|
|
@@ -3982,61 +4370,34 @@ function useAutoConfig(agentId, enabled) {
|
|
|
3982
4370
|
return;
|
|
3983
4371
|
}
|
|
3984
4372
|
let cancelled = false;
|
|
3985
|
-
|
|
4373
|
+
setResult({ ...EMPTY, loading: true });
|
|
4374
|
+
fetch(`${resolveApiUrl(apiUrl)}/api/public/widget/${agentId}`).then((r) => r.json()).then((body) => {
|
|
3986
4375
|
if (cancelled) return;
|
|
3987
|
-
const cfg = body?.data
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
remote.displayName = cfg.displayName;
|
|
3993
|
-
if (cfg.theme) remote.theme = cfg.theme;
|
|
3994
|
-
if (cfg.userMessageColor)
|
|
3995
|
-
remote.userMessageColor = cfg.userMessageColor;
|
|
3996
|
-
if (Array.isArray(cfg.initialMessages) && cfg.initialMessages.length > 0)
|
|
3997
|
-
remote.initialMessages = cfg.initialMessages;
|
|
3998
|
-
if (Array.isArray(cfg.suggestedMessages))
|
|
3999
|
-
remote.suggestedMessages = cfg.suggestedMessages;
|
|
4000
|
-
if (cfg.messagePlaceholder != null)
|
|
4001
|
-
remote.messagePlaceholder = cfg.messagePlaceholder;
|
|
4002
|
-
if (cfg.watermark != null) remote.watermark = cfg.watermark;
|
|
4003
|
-
if (cfg.footer != null) remote.footer = cfg.footer;
|
|
4004
|
-
if (cfg.showThinking != null)
|
|
4005
|
-
remote.showThinking = cfg.showThinking;
|
|
4006
|
-
if (cfg.regenerateMessage != null)
|
|
4007
|
-
remote.regenerateMessage = cfg.regenerateMessage;
|
|
4008
|
-
if (cfg.widgetLayout)
|
|
4009
|
-
remote.widgetLayout = cfg.widgetLayout;
|
|
4010
|
-
if (cfg.enableVoice != null)
|
|
4011
|
-
remote.enableVoice = cfg.enableVoice;
|
|
4012
|
-
if (cfg.clientActions)
|
|
4013
|
-
remote.clientActions = cfg.clientActions;
|
|
4014
|
-
if (Array.isArray(cfg.ragTopics))
|
|
4015
|
-
remote.ragTopics = cfg.ragTopics;
|
|
4016
|
-
if (Array.isArray(cfg.ragDocuments))
|
|
4017
|
-
remote.ragDocuments = cfg.ragDocuments;
|
|
4018
|
-
if (cfg.locale != null)
|
|
4019
|
-
remote.locale = cfg.locale;
|
|
4376
|
+
const cfg = body?.data;
|
|
4377
|
+
if (!cfg) {
|
|
4378
|
+
setResult(EMPTY);
|
|
4379
|
+
return;
|
|
4380
|
+
}
|
|
4020
4381
|
setResult({
|
|
4021
|
-
remoteConfig:
|
|
4382
|
+
remoteConfig: toChatProps(cfg),
|
|
4022
4383
|
bubbleIconUrl: cfg.chatIcon || cfg.profilePicture || void 0,
|
|
4023
4384
|
autoOpen: Boolean(cfg.autoOpen),
|
|
4024
4385
|
keyboardShortcut: Boolean(cfg.keyboardShortcut),
|
|
4025
4386
|
position: cfg.alignChatBubbleButton === "left" ? "bottom-left" : "bottom-right",
|
|
4026
4387
|
widgetLayout: cfg.widgetLayout === "center" ? "center" : "bubble",
|
|
4027
|
-
clientActions:
|
|
4028
|
-
bubbleSize:
|
|
4029
|
-
panelWidth:
|
|
4030
|
-
panelHeight:
|
|
4388
|
+
clientActions: cfg.clientActions ?? [],
|
|
4389
|
+
bubbleSize: cfg.bubbleSize ?? 52,
|
|
4390
|
+
panelWidth: cfg.panelWidth ?? 360,
|
|
4391
|
+
panelHeight: cfg.panelHeight ?? 580,
|
|
4031
4392
|
loading: false
|
|
4032
4393
|
});
|
|
4033
4394
|
}).catch(() => {
|
|
4034
|
-
if (!cancelled) setResult(
|
|
4395
|
+
if (!cancelled) setResult(EMPTY);
|
|
4035
4396
|
});
|
|
4036
4397
|
return () => {
|
|
4037
4398
|
cancelled = true;
|
|
4038
4399
|
};
|
|
4039
|
-
}, [agentId, enabled]);
|
|
4400
|
+
}, [agentId, enabled, apiUrl]);
|
|
4040
4401
|
return result;
|
|
4041
4402
|
}
|
|
4042
4403
|
function toWidgetMsg(m) {
|
|
@@ -4117,6 +4478,38 @@ function useSupportChat({
|
|
|
4117
4478
|
},
|
|
4118
4479
|
[base]
|
|
4119
4480
|
);
|
|
4481
|
+
function loadAblyScript() {
|
|
4482
|
+
return new Promise((resolve, reject) => {
|
|
4483
|
+
if (typeof window === "undefined") {
|
|
4484
|
+
resolve(null);
|
|
4485
|
+
return;
|
|
4486
|
+
}
|
|
4487
|
+
if (window.Ably) {
|
|
4488
|
+
resolve(window.Ably);
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
const existing = document.querySelector("script[data-ably-sdk]");
|
|
4492
|
+
if (existing) {
|
|
4493
|
+
existing.addEventListener("load", () => resolve(window.Ably));
|
|
4494
|
+
existing.addEventListener(
|
|
4495
|
+
"error",
|
|
4496
|
+
() => reject(new Error("Ably CDN load failed"))
|
|
4497
|
+
);
|
|
4498
|
+
return;
|
|
4499
|
+
}
|
|
4500
|
+
const script = document.createElement("script");
|
|
4501
|
+
script.src = "https://cdn.ably.com/lib/ably.min-2.js";
|
|
4502
|
+
script.crossOrigin = "anonymous";
|
|
4503
|
+
script.async = true;
|
|
4504
|
+
script.setAttribute("data-ably-sdk", "true");
|
|
4505
|
+
script.onload = () => resolve(window.Ably);
|
|
4506
|
+
script.onerror = () => {
|
|
4507
|
+
script.remove();
|
|
4508
|
+
reject(new Error("Ably CDN load failed"));
|
|
4509
|
+
};
|
|
4510
|
+
document.head.appendChild(script);
|
|
4511
|
+
});
|
|
4512
|
+
}
|
|
4120
4513
|
react.useEffect(() => {
|
|
4121
4514
|
if (!enabled || !session) return;
|
|
4122
4515
|
void loadMessages(session);
|
|
@@ -4131,7 +4524,8 @@ function useSupportChat({
|
|
|
4131
4524
|
);
|
|
4132
4525
|
if (!res.ok) return;
|
|
4133
4526
|
const { tokenRequest, channel: channelName } = await res.json();
|
|
4134
|
-
const AblyLib =
|
|
4527
|
+
const AblyLib = await loadAblyScript();
|
|
4528
|
+
if (!AblyLib) return;
|
|
4135
4529
|
ablyClient = new AblyLib.Realtime({
|
|
4136
4530
|
authCallback: (_, cb) => cb(null, tokenRequest)
|
|
4137
4531
|
});
|
|
@@ -4439,9 +4833,9 @@ function BubbleWidget({
|
|
|
4439
4833
|
height: heightProp,
|
|
4440
4834
|
expandedWidth = 640,
|
|
4441
4835
|
expandedHeight = "calc(100vh - 100px)",
|
|
4442
|
-
keyboardShortcut: keyboardShortcutProp
|
|
4836
|
+
keyboardShortcut: keyboardShortcutProp,
|
|
4443
4837
|
shortcutKey = "k",
|
|
4444
|
-
autoOpen: autoOpenProp
|
|
4838
|
+
autoOpen: autoOpenProp,
|
|
4445
4839
|
bubbleIconUrl: bubbleIconUrlProp,
|
|
4446
4840
|
bubbleSize: bubbleSizeProp,
|
|
4447
4841
|
panelClassName,
|
|
@@ -4482,24 +4876,25 @@ function BubbleWidget({
|
|
|
4482
4876
|
}, []);
|
|
4483
4877
|
const remote = useAutoConfig(
|
|
4484
4878
|
inboxToken ? "" : chatProps.agentId ?? "",
|
|
4485
|
-
!inboxToken && autoConfig
|
|
4879
|
+
!inboxToken && autoConfig,
|
|
4880
|
+
chatProps.apiUrl
|
|
4486
4881
|
);
|
|
4487
|
-
const resolvedPosition = remote.position
|
|
4488
|
-
const resolvedLayout =
|
|
4489
|
-
const resolvedBubbleIcon = remote.bubbleIconUrl
|
|
4490
|
-
const resolvedAutoOpen = remote.autoOpen
|
|
4491
|
-
const resolvedKeyboardShortcut = remote.keyboardShortcut
|
|
4492
|
-
const resolvedBubbleSize = remote.bubbleSize
|
|
4493
|
-
const resolvedWidth = remote.panelWidth
|
|
4494
|
-
const resolvedHeight = remote.panelHeight
|
|
4882
|
+
const resolvedPosition = positionProp ?? remote.position;
|
|
4883
|
+
const resolvedLayout = chatProps.widgetLayout ?? remote.widgetLayout;
|
|
4884
|
+
const resolvedBubbleIcon = bubbleIconUrlProp ?? remote.bubbleIconUrl;
|
|
4885
|
+
const resolvedAutoOpen = autoOpenProp ?? remote.autoOpen;
|
|
4886
|
+
const resolvedKeyboardShortcut = keyboardShortcutProp ?? remote.keyboardShortcut;
|
|
4887
|
+
const resolvedBubbleSize = bubbleSizeProp ?? remote.bubbleSize;
|
|
4888
|
+
const resolvedWidth = widthProp ?? remote.panelWidth;
|
|
4889
|
+
const resolvedHeight = heightProp ?? remote.panelHeight;
|
|
4495
4890
|
const definedChatProps = Object.fromEntries(
|
|
4496
4891
|
Object.entries(chatProps).filter(([, v]) => v !== void 0)
|
|
4497
4892
|
);
|
|
4498
4893
|
const mergedConfig = {
|
|
4499
|
-
...definedChatProps,
|
|
4500
4894
|
...remote.remoteConfig,
|
|
4895
|
+
...definedChatProps,
|
|
4501
4896
|
agentId: chatProps.agentId ?? "",
|
|
4502
|
-
agentName:
|
|
4897
|
+
agentName: chatProps.agentName ?? remote.remoteConfig.agentName ?? "Asistente",
|
|
4503
4898
|
source: chatProps.source ?? remote.remoteConfig.source ?? "web"
|
|
4504
4899
|
};
|
|
4505
4900
|
const supportBackend = useSupportChat(
|
|
@@ -4515,6 +4910,10 @@ function BubbleWidget({
|
|
|
4515
4910
|
react.useEffect(() => {
|
|
4516
4911
|
setOpenRef.current = setOpen;
|
|
4517
4912
|
});
|
|
4913
|
+
const openRef = react.useRef(open);
|
|
4914
|
+
react.useEffect(() => {
|
|
4915
|
+
openRef.current = open;
|
|
4916
|
+
});
|
|
4518
4917
|
react.useEffect(() => {
|
|
4519
4918
|
if (!resolvedAutoOpen || autoOpenedRef.current) return;
|
|
4520
4919
|
const dismissedUntil = Number(localStorage.getItem(KEY_DISMISSED) ?? 0);
|
|
@@ -4524,15 +4923,29 @@ function BubbleWidget({
|
|
|
4524
4923
|
}
|
|
4525
4924
|
}, [resolvedAutoOpen]);
|
|
4526
4925
|
react.useEffect(() => {
|
|
4527
|
-
|
|
4926
|
+
const onToggle = () => setOpenRef.current((v) => !v);
|
|
4927
|
+
const onOpen = () => setOpenRef.current(true);
|
|
4928
|
+
const onClose = () => setOpenRef.current(false);
|
|
4929
|
+
window.addEventListener("wallavi:toggle-assistant", onToggle);
|
|
4930
|
+
window.addEventListener("wallavi:open-assistant", onOpen);
|
|
4931
|
+
window.addEventListener("wallavi:close-assistant", onClose);
|
|
4528
4932
|
const onKey = (e) => {
|
|
4529
|
-
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === shortcutKey.toLowerCase()) {
|
|
4933
|
+
if (resolvedKeyboardShortcut && (e.metaKey || e.ctrlKey) && e.key.toLowerCase() === shortcutKey.toLowerCase()) {
|
|
4530
4934
|
e.preventDefault();
|
|
4531
4935
|
setOpenRef.current((v) => !v);
|
|
4936
|
+
} else if (e.key === "Escape" && openRef.current) {
|
|
4937
|
+
e.preventDefault();
|
|
4938
|
+
e.stopPropagation();
|
|
4939
|
+
setOpenRef.current(false);
|
|
4532
4940
|
}
|
|
4533
4941
|
};
|
|
4534
4942
|
window.addEventListener("keydown", onKey);
|
|
4535
|
-
return () =>
|
|
4943
|
+
return () => {
|
|
4944
|
+
window.removeEventListener("wallavi:toggle-assistant", onToggle);
|
|
4945
|
+
window.removeEventListener("wallavi:open-assistant", onOpen);
|
|
4946
|
+
window.removeEventListener("wallavi:close-assistant", onClose);
|
|
4947
|
+
window.removeEventListener("keydown", onKey);
|
|
4948
|
+
};
|
|
4536
4949
|
}, [resolvedKeyboardShortcut, shortcutKey]);
|
|
4537
4950
|
react.useEffect(() => {
|
|
4538
4951
|
if (!open) return;
|
|
@@ -4587,7 +5000,8 @@ function BubbleWidget({
|
|
|
4587
5000
|
display: "flex",
|
|
4588
5001
|
flexDirection: "column",
|
|
4589
5002
|
alignItems: isLeft ? "flex-start" : "flex-end",
|
|
4590
|
-
gap: 12
|
|
5003
|
+
gap: 12,
|
|
5004
|
+
pointerEvents: !open && hideBubble ? "none" : void 0
|
|
4591
5005
|
},
|
|
4592
5006
|
children: [
|
|
4593
5007
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4604,12 +5018,14 @@ function BubbleWidget({
|
|
|
4604
5018
|
zIndex: 9999,
|
|
4605
5019
|
width: panelWidth,
|
|
4606
5020
|
height: panelHeight,
|
|
4607
|
-
transition: "width 0.3s ease, height 0.3s ease"
|
|
5021
|
+
transition: "width 0.3s ease, height 0.3s ease",
|
|
5022
|
+
pointerEvents: "auto"
|
|
4608
5023
|
} : {
|
|
4609
5024
|
display: open ? "block" : "none",
|
|
4610
5025
|
width: panelWidth,
|
|
4611
5026
|
height: panelHeight,
|
|
4612
|
-
transition: "width 0.3s ease, height 0.3s ease"
|
|
5027
|
+
transition: "width 0.3s ease, height 0.3s ease",
|
|
5028
|
+
pointerEvents: "auto"
|
|
4613
5029
|
},
|
|
4614
5030
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4615
5031
|
ChatWidget,
|