@wallavi/widget 1.12.7 → 1.13.1
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 +641 -218
- package/dist/index.mjs +641 -218
- package/package.json +8 -5
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") {
|
|
@@ -454,10 +871,18 @@ function useChat({
|
|
|
454
871
|
);
|
|
455
872
|
const fetchAndStream = react.useCallback(
|
|
456
873
|
async (opts) => {
|
|
457
|
-
const {
|
|
874
|
+
const {
|
|
875
|
+
input: userInput,
|
|
876
|
+
msgId,
|
|
877
|
+
extraMetadata,
|
|
878
|
+
attachments,
|
|
879
|
+
pickerSelection
|
|
880
|
+
} = opts;
|
|
458
881
|
const isPrivate = Boolean(workspaceId);
|
|
459
882
|
const token = isPrivate ? await getFreshClerkToken() : null;
|
|
460
|
-
const
|
|
883
|
+
const hostHeaders = await resolveUserHeaders(userContext);
|
|
884
|
+
const pageContext = userContext?.pageContext ?? resolvePageContextRef.current?.();
|
|
885
|
+
const url = isPrivate ? `${baseUrl}/api/threads/${threadId}/stream` : `${baseUrl}/api/chat/stream`;
|
|
461
886
|
const res = await fetch(url, {
|
|
462
887
|
method: "POST",
|
|
463
888
|
headers: {
|
|
@@ -474,14 +899,17 @@ function useChat({
|
|
|
474
899
|
} : { threadId },
|
|
475
900
|
source,
|
|
476
901
|
...attachments?.length ? { attachments } : {},
|
|
902
|
+
// Its own field: userMetadata keys starting with __ belong to the
|
|
903
|
+
// engine and the API rejects them.
|
|
904
|
+
...pickerSelection ? { pickerSelection } : {},
|
|
477
905
|
...userContext?.userName ? { userName: userContext.userName } : {},
|
|
478
906
|
...userContext?.userEmail ? { userEmail: userContext.userEmail } : {},
|
|
479
907
|
userMetadata: {
|
|
480
908
|
...userContext?.metadata ?? {},
|
|
481
|
-
...
|
|
909
|
+
...pageContext ? { pageContext } : {},
|
|
482
910
|
headers: {
|
|
483
911
|
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
484
|
-
...
|
|
912
|
+
...hostHeaders,
|
|
485
913
|
...userContext?.metadata?.headers ?? {}
|
|
486
914
|
},
|
|
487
915
|
...extraMetadata ?? {}
|
|
@@ -526,6 +954,7 @@ function useChat({
|
|
|
526
954
|
}
|
|
527
955
|
},
|
|
528
956
|
[
|
|
957
|
+
baseUrl,
|
|
529
958
|
agentId,
|
|
530
959
|
workspaceId,
|
|
531
960
|
envId,
|
|
@@ -540,7 +969,8 @@ function useChat({
|
|
|
540
969
|
agentId,
|
|
541
970
|
threadId,
|
|
542
971
|
workspaceId,
|
|
543
|
-
customBackend
|
|
972
|
+
customBackend,
|
|
973
|
+
apiUrl: baseUrl
|
|
544
974
|
});
|
|
545
975
|
react.useEffect(() => {
|
|
546
976
|
if (customBackend || !persistKey) return;
|
|
@@ -589,7 +1019,7 @@ function useChat({
|
|
|
589
1019
|
try {
|
|
590
1020
|
const isPrivate = Boolean(workspaceId);
|
|
591
1021
|
const token = isPrivate ? await getFreshClerkToken() : null;
|
|
592
|
-
const url = isPrivate ? `${
|
|
1022
|
+
const url = isPrivate ? `${baseUrl}/api/threads/${threadId}/messages` : `${baseUrl}/api/chat/messages?agentId=${encodeURIComponent(agentId)}&threadId=${encodeURIComponent(threadId)}`;
|
|
593
1023
|
const res = await fetch(url, {
|
|
594
1024
|
headers: {
|
|
595
1025
|
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
@@ -653,7 +1083,15 @@ function useChat({
|
|
|
653
1083
|
}
|
|
654
1084
|
}
|
|
655
1085
|
}
|
|
656
|
-
}, [
|
|
1086
|
+
}, [
|
|
1087
|
+
baseUrl,
|
|
1088
|
+
threadId,
|
|
1089
|
+
agentId,
|
|
1090
|
+
workspaceId,
|
|
1091
|
+
envId,
|
|
1092
|
+
persistKey,
|
|
1093
|
+
fetchAndStream
|
|
1094
|
+
]);
|
|
657
1095
|
const reset = react.useCallback(() => {
|
|
658
1096
|
setMessages([]);
|
|
659
1097
|
setInput("");
|
|
@@ -776,9 +1214,7 @@ function useChat({
|
|
|
776
1214
|
await fetchAndStream({
|
|
777
1215
|
input: label,
|
|
778
1216
|
msgId: assistantMsgId,
|
|
779
|
-
|
|
780
|
-
__pickerSelection: { pickerId, paramName, value, label }
|
|
781
|
-
}
|
|
1217
|
+
pickerSelection: { pickerId, paramName, value, label }
|
|
782
1218
|
});
|
|
783
1219
|
} catch {
|
|
784
1220
|
setMessages((prev) => {
|
|
@@ -837,106 +1273,6 @@ function useChat({
|
|
|
837
1273
|
setSelectedContext
|
|
838
1274
|
};
|
|
839
1275
|
}
|
|
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
1276
|
function getPreferredMimeType() {
|
|
941
1277
|
if (typeof MediaRecorder === "undefined") return "";
|
|
942
1278
|
const candidates = [
|
|
@@ -953,7 +1289,6 @@ function mimeTypeToExtension(mimeType) {
|
|
|
953
1289
|
if (mimeType.includes("mp4")) return "mp4";
|
|
954
1290
|
return "webm";
|
|
955
1291
|
}
|
|
956
|
-
var DEFAULT_API_URL = process.env.NEXT_PUBLIC_API_URL ?? "https://wallavi-production.up.railway.app";
|
|
957
1292
|
function useVoice({
|
|
958
1293
|
agentId,
|
|
959
1294
|
apiUrl,
|
|
@@ -966,7 +1301,7 @@ function useVoice({
|
|
|
966
1301
|
const streamRef = react.useRef(null);
|
|
967
1302
|
const errorTimerRef = react.useRef(null);
|
|
968
1303
|
const isSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined" && !!navigator?.mediaDevices?.getUserMedia;
|
|
969
|
-
const base = apiUrl
|
|
1304
|
+
const base = resolveApiUrl(apiUrl);
|
|
970
1305
|
const transcribeBlob = react.useCallback(
|
|
971
1306
|
async (blob, mimeType) => {
|
|
972
1307
|
setVoiceState("transcribing");
|
|
@@ -1045,7 +1380,6 @@ function useVoice({
|
|
|
1045
1380
|
}, []);
|
|
1046
1381
|
return { voiceState, isSupported, start, stop };
|
|
1047
1382
|
}
|
|
1048
|
-
var DEFAULT_API_URL2 = process.env.NEXT_PUBLIC_API_URL ?? "https://wallavi-production.up.railway.app";
|
|
1049
1383
|
function makeId() {
|
|
1050
1384
|
return `att_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
|
1051
1385
|
}
|
|
@@ -1055,7 +1389,7 @@ function useAttachments({
|
|
|
1055
1389
|
maxFiles = 5
|
|
1056
1390
|
}) {
|
|
1057
1391
|
const [attachments, setAttachments] = react.useState([]);
|
|
1058
|
-
const base = apiUrl
|
|
1392
|
+
const base = resolveApiUrl(apiUrl);
|
|
1059
1393
|
const uploadOne = react.useCallback(
|
|
1060
1394
|
async (file, id) => {
|
|
1061
1395
|
const form = new FormData();
|
|
@@ -2850,6 +3184,20 @@ function VoiceOverlay({
|
|
|
2850
3184
|
}
|
|
2851
3185
|
) });
|
|
2852
3186
|
}
|
|
3187
|
+
|
|
3188
|
+
// src/lib/unique-keys.ts
|
|
3189
|
+
function uniqueKeys(items, baseKey) {
|
|
3190
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3191
|
+
return items.map((item) => {
|
|
3192
|
+
const base = baseKey(item);
|
|
3193
|
+
const count = seen.get(base) ?? 0;
|
|
3194
|
+
seen.set(base, count + 1);
|
|
3195
|
+
return count === 0 ? base : `${base}#${count}`;
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3198
|
+
var actionKey = (action) => `${action.name}:${JSON.stringify(action.steps ?? [])}`;
|
|
3199
|
+
var docKey = (doc) => `${doc.name}:${doc.url ?? ""}`;
|
|
3200
|
+
var topicKey = (topic) => topic.name;
|
|
2853
3201
|
function SvgIcon({
|
|
2854
3202
|
className,
|
|
2855
3203
|
strokeWidth = "1.8",
|
|
@@ -2923,11 +3271,11 @@ function urlRelevance(docUrl, pageUrl) {
|
|
|
2923
3271
|
return 50;
|
|
2924
3272
|
const docParts = docClean.split(/[/?#]/).filter(Boolean);
|
|
2925
3273
|
const pageParts = pageClean.split(/[/?#]/).filter(Boolean);
|
|
2926
|
-
let
|
|
3274
|
+
let score2 = 0;
|
|
2927
3275
|
for (const part of pageParts) {
|
|
2928
|
-
if (docParts.includes(part))
|
|
3276
|
+
if (docParts.includes(part)) score2 += 10;
|
|
2929
3277
|
}
|
|
2930
|
-
return
|
|
3278
|
+
return score2;
|
|
2931
3279
|
}
|
|
2932
3280
|
function topicToQuestion(topic, locale) {
|
|
2933
3281
|
const name = topic.name.trim();
|
|
@@ -3018,9 +3366,9 @@ function getContextKeywords(pageUrl, pageTitle, pageParams, pageVars, metadata)
|
|
|
3018
3366
|
return Array.from(keywords);
|
|
3019
3367
|
}
|
|
3020
3368
|
function computeRelevanceScore(item, keywords, pageUrl) {
|
|
3021
|
-
let
|
|
3369
|
+
let score2 = 0;
|
|
3022
3370
|
if (item.url && pageUrl) {
|
|
3023
|
-
|
|
3371
|
+
score2 += urlRelevance(item.url, pageUrl);
|
|
3024
3372
|
}
|
|
3025
3373
|
const itemName = (item.name || "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
3026
3374
|
const itemDesc = (item.description || item.summary || "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
@@ -3028,33 +3376,41 @@ function computeRelevanceScore(item, keywords, pageUrl) {
|
|
|
3028
3376
|
const nameWords = itemName.split(/[^a-z0-9]/).filter(Boolean);
|
|
3029
3377
|
for (const keyword of keywords) {
|
|
3030
3378
|
if (itemName.includes(keyword)) {
|
|
3031
|
-
|
|
3379
|
+
score2 += 30;
|
|
3032
3380
|
if (nameWords.includes(keyword)) {
|
|
3033
|
-
|
|
3381
|
+
score2 += 20;
|
|
3034
3382
|
}
|
|
3035
3383
|
}
|
|
3036
3384
|
if (itemDesc.includes(keyword)) {
|
|
3037
|
-
|
|
3385
|
+
score2 += 15;
|
|
3038
3386
|
}
|
|
3039
3387
|
if (itemUrl.includes(keyword)) {
|
|
3040
|
-
|
|
3388
|
+
score2 += 20;
|
|
3041
3389
|
}
|
|
3042
3390
|
}
|
|
3043
|
-
if (item.keywords) {
|
|
3044
|
-
const
|
|
3045
|
-
|
|
3046
|
-
...item.keywords
|
|
3047
|
-
|
|
3048
|
-
|
|
3391
|
+
if (item.keywords && typeof item.keywords === "object") {
|
|
3392
|
+
const rawList = [];
|
|
3393
|
+
if (Array.isArray(item.keywords)) {
|
|
3394
|
+
rawList.push(...item.keywords);
|
|
3395
|
+
} else {
|
|
3396
|
+
const kwRecord = item.keywords;
|
|
3397
|
+
for (const lang of ["en", "es", "fr"]) {
|
|
3398
|
+
const langVal = kwRecord[lang];
|
|
3399
|
+
if (Array.isArray(langVal)) {
|
|
3400
|
+
rawList.push(...langVal);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
const allItemKeywords = rawList.filter((k) => typeof k === "string" && k.trim().length > 0).map(
|
|
3049
3405
|
(k) => k.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "")
|
|
3050
3406
|
);
|
|
3051
3407
|
for (const keyword of keywords) {
|
|
3052
3408
|
if (allItemKeywords.includes(keyword)) {
|
|
3053
|
-
|
|
3409
|
+
score2 += 45;
|
|
3054
3410
|
}
|
|
3055
3411
|
}
|
|
3056
3412
|
}
|
|
3057
|
-
return
|
|
3413
|
+
return score2;
|
|
3058
3414
|
}
|
|
3059
3415
|
var STAGGER_MS = 50;
|
|
3060
3416
|
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 +3623,9 @@ function CommandPanel({
|
|
|
3267
3623
|
}));
|
|
3268
3624
|
return scored.sort((a, b) => b.score - a.score);
|
|
3269
3625
|
}, [clientActions, pageUrl, pageTitle, pageParams, pageVars, metadata]);
|
|
3626
|
+
const actionKeys = uniqueKeys(sortedActions, actionKey);
|
|
3627
|
+
const docKeys = uniqueKeys(relevantDocs, docKey);
|
|
3628
|
+
const topicKeys = uniqueKeys(topicQuestions, topicKey);
|
|
3270
3629
|
const hasActions = sortedActions.length > 0;
|
|
3271
3630
|
const hasDocs = relevantDocs.length > 0;
|
|
3272
3631
|
const hasTopics = topicQuestions.length > 0;
|
|
@@ -3288,7 +3647,7 @@ function CommandPanel({
|
|
|
3288
3647
|
accentColor,
|
|
3289
3648
|
onExecute: () => handleExecute(action.steps)
|
|
3290
3649
|
},
|
|
3291
|
-
|
|
3650
|
+
actionKeys[i]
|
|
3292
3651
|
)) })
|
|
3293
3652
|
] }),
|
|
3294
3653
|
hasDocs && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
|
|
@@ -3301,7 +3660,7 @@ function CommandPanel({
|
|
|
3301
3660
|
onSend,
|
|
3302
3661
|
locale
|
|
3303
3662
|
},
|
|
3304
|
-
|
|
3663
|
+
docKeys[i]
|
|
3305
3664
|
)) })
|
|
3306
3665
|
] }),
|
|
3307
3666
|
hasTopics && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
|
|
@@ -3313,7 +3672,7 @@ function CommandPanel({
|
|
|
3313
3672
|
index: i,
|
|
3314
3673
|
onSend
|
|
3315
3674
|
},
|
|
3316
|
-
|
|
3675
|
+
topicKeys[i]
|
|
3317
3676
|
)) })
|
|
3318
3677
|
] })
|
|
3319
3678
|
] }),
|
|
@@ -3360,7 +3719,7 @@ function CommandPanel({
|
|
|
3360
3719
|
accentColor,
|
|
3361
3720
|
onExecute: () => handleExecute(action.steps)
|
|
3362
3721
|
},
|
|
3363
|
-
|
|
3722
|
+
actionKeys[i]
|
|
3364
3723
|
)) })
|
|
3365
3724
|
] }),
|
|
3366
3725
|
hasDocs && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
|
|
@@ -3373,7 +3732,7 @@ function CommandPanel({
|
|
|
3373
3732
|
onSend,
|
|
3374
3733
|
locale
|
|
3375
3734
|
},
|
|
3376
|
-
|
|
3735
|
+
docKeys[i]
|
|
3377
3736
|
)) })
|
|
3378
3737
|
] }),
|
|
3379
3738
|
hasTopics && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
|
|
@@ -3386,13 +3745,14 @@ function CommandPanel({
|
|
|
3386
3745
|
compact: true,
|
|
3387
3746
|
onSend
|
|
3388
3747
|
},
|
|
3389
|
-
|
|
3748
|
+
topicKeys[i]
|
|
3390
3749
|
)) })
|
|
3391
3750
|
] })
|
|
3392
3751
|
] });
|
|
3393
3752
|
}
|
|
3394
3753
|
function ChatWidget({
|
|
3395
3754
|
agentId,
|
|
3755
|
+
apiUrl,
|
|
3396
3756
|
workspaceId,
|
|
3397
3757
|
agentName,
|
|
3398
3758
|
displayName,
|
|
@@ -3453,8 +3813,10 @@ function ChatWidget({
|
|
|
3453
3813
|
window.addEventListener("message", handleMessage);
|
|
3454
3814
|
return () => window.removeEventListener("message", handleMessage);
|
|
3455
3815
|
}, []);
|
|
3816
|
+
const resolvePageContext = () => userContext?.pageContext ?? (typeof window === "undefined" ? void 0 : derivePageContext(window.location, clientActions));
|
|
3456
3817
|
const chat = useChat({
|
|
3457
3818
|
agentId,
|
|
3819
|
+
apiUrl,
|
|
3458
3820
|
workspaceId,
|
|
3459
3821
|
envId,
|
|
3460
3822
|
source,
|
|
@@ -3462,10 +3824,13 @@ function ChatWidget({
|
|
|
3462
3824
|
persist,
|
|
3463
3825
|
onNavigate,
|
|
3464
3826
|
playgroundOverrides,
|
|
3465
|
-
customBackend
|
|
3827
|
+
customBackend,
|
|
3828
|
+
copilotColor: userMessageColor,
|
|
3829
|
+
resolvePageContext: () => resolvePageContext()
|
|
3466
3830
|
});
|
|
3467
3831
|
const voice = useVoice({
|
|
3468
3832
|
agentId,
|
|
3833
|
+
apiUrl,
|
|
3469
3834
|
onTranscript: (text) => {
|
|
3470
3835
|
if (voiceAutoSend) {
|
|
3471
3836
|
void chat.send(text);
|
|
@@ -3474,7 +3839,7 @@ function ChatWidget({
|
|
|
3474
3839
|
}
|
|
3475
3840
|
}
|
|
3476
3841
|
});
|
|
3477
|
-
const attachmentHook = useAttachments({ agentId });
|
|
3842
|
+
const attachmentHook = useAttachments({ agentId, apiUrl });
|
|
3478
3843
|
const debugTraceLenRef = react.useRef(0);
|
|
3479
3844
|
react.useEffect(() => {
|
|
3480
3845
|
if (!onDebugTrace || chat.debugTraces.length <= debugTraceLenRef.current)
|
|
@@ -3574,9 +3939,9 @@ function ChatWidget({
|
|
|
3574
3939
|
const showInlineCommandPanel = showCommandPanel && !showSidebar && chat.messages.length === 0 && (showQuickActions && clientActions.length > 0 || showRagTopics && ragTopics.length > 0 || showRagDocuments && ragDocuments.length > 0);
|
|
3575
3940
|
const handleExecuteAction = react.useCallback(
|
|
3576
3941
|
(steps) => {
|
|
3577
|
-
executeDeclarativeSteps(steps, onNavigate);
|
|
3942
|
+
executeDeclarativeSteps(steps, onNavigate, userMessageColor);
|
|
3578
3943
|
},
|
|
3579
|
-
[onNavigate]
|
|
3944
|
+
[onNavigate, userMessageColor]
|
|
3580
3945
|
);
|
|
3581
3946
|
const [activeActionIndex, setActiveActionIndex] = react.useState(-1);
|
|
3582
3947
|
const query = chat.input.toLowerCase().trim();
|
|
@@ -3633,11 +3998,12 @@ function ChatWidget({
|
|
|
3633
3998
|
},
|
|
3634
3999
|
[matchingActions, activeActionIndex, handleExecuteAction, chat]
|
|
3635
4000
|
);
|
|
4001
|
+
const pageContext = resolvePageContext();
|
|
3636
4002
|
const commandPanelProps = {
|
|
3637
|
-
pageUrl:
|
|
3638
|
-
pageTitle:
|
|
3639
|
-
pageParams:
|
|
3640
|
-
pageVars:
|
|
4003
|
+
pageUrl: pageContext?.url,
|
|
4004
|
+
pageTitle: pageContext?.title,
|
|
4005
|
+
pageParams: pageContext?.params,
|
|
4006
|
+
pageVars: pageContext?.vars,
|
|
3641
4007
|
metadata: userContext?.metadata,
|
|
3642
4008
|
clientActions: showQuickActions ? clientActions : [],
|
|
3643
4009
|
ragTopics: showRagTopics ? ragTopics : [],
|
|
@@ -3957,7 +4323,6 @@ function ChatWidget({
|
|
|
3957
4323
|
}
|
|
3958
4324
|
);
|
|
3959
4325
|
}
|
|
3960
|
-
var WALLAVI_PUBLIC_API = "https://wallavi-production.up.railway.app";
|
|
3961
4326
|
var EMPTY = {
|
|
3962
4327
|
remoteConfig: {},
|
|
3963
4328
|
bubbleIconUrl: void 0,
|
|
@@ -3971,7 +4336,37 @@ var EMPTY = {
|
|
|
3971
4336
|
panelHeight: 580,
|
|
3972
4337
|
loading: false
|
|
3973
4338
|
};
|
|
3974
|
-
function
|
|
4339
|
+
function toChatProps(cfg) {
|
|
4340
|
+
const remote = {};
|
|
4341
|
+
const set = (key, value) => {
|
|
4342
|
+
if (value !== null && value !== void 0) remote[key] = value;
|
|
4343
|
+
};
|
|
4344
|
+
set("agentName", cfg.agentName);
|
|
4345
|
+
set("profilePicture", cfg.profilePicture);
|
|
4346
|
+
set("displayName", cfg.displayName);
|
|
4347
|
+
set("theme", cfg.theme);
|
|
4348
|
+
set("userMessageColor", cfg.userMessageColor);
|
|
4349
|
+
if (cfg.initialMessages?.length) remote.initialMessages = cfg.initialMessages;
|
|
4350
|
+
if (cfg.suggestedMessages?.length)
|
|
4351
|
+
remote.suggestedMessages = cfg.suggestedMessages;
|
|
4352
|
+
set("messagePlaceholder", cfg.messagePlaceholder);
|
|
4353
|
+
set("watermark", cfg.watermark);
|
|
4354
|
+
set("footer", cfg.footer);
|
|
4355
|
+
set("showThinking", cfg.showThinking);
|
|
4356
|
+
set("regenerateMessage", cfg.regenerateMessage);
|
|
4357
|
+
set("widgetLayout", cfg.widgetLayout);
|
|
4358
|
+
set("enableVoice", cfg.enableVoice);
|
|
4359
|
+
set("showCommandPanel", cfg.showCommandPanel);
|
|
4360
|
+
set("showRagTopics", cfg.showRagTopics);
|
|
4361
|
+
set("showRagDocuments", cfg.showRagDocuments);
|
|
4362
|
+
set("showQuickActions", cfg.showQuickActions);
|
|
4363
|
+
set("clientActions", cfg.clientActions);
|
|
4364
|
+
set("ragTopics", cfg.ragTopics);
|
|
4365
|
+
set("ragDocuments", cfg.ragDocuments);
|
|
4366
|
+
set("locale", cfg.locale);
|
|
4367
|
+
return remote;
|
|
4368
|
+
}
|
|
4369
|
+
function useAutoConfig(agentId, enabled, apiUrl) {
|
|
3975
4370
|
const [result, setResult] = react.useState(() => ({
|
|
3976
4371
|
...EMPTY,
|
|
3977
4372
|
loading: enabled && Boolean(agentId)
|
|
@@ -3982,61 +4377,34 @@ function useAutoConfig(agentId, enabled) {
|
|
|
3982
4377
|
return;
|
|
3983
4378
|
}
|
|
3984
4379
|
let cancelled = false;
|
|
3985
|
-
|
|
4380
|
+
setResult({ ...EMPTY, loading: true });
|
|
4381
|
+
fetch(`${resolveApiUrl(apiUrl)}/api/public/widget/${agentId}`).then((r) => r.json()).then((body) => {
|
|
3986
4382
|
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;
|
|
4383
|
+
const cfg = body?.data;
|
|
4384
|
+
if (!cfg) {
|
|
4385
|
+
setResult(EMPTY);
|
|
4386
|
+
return;
|
|
4387
|
+
}
|
|
4020
4388
|
setResult({
|
|
4021
|
-
remoteConfig:
|
|
4389
|
+
remoteConfig: toChatProps(cfg),
|
|
4022
4390
|
bubbleIconUrl: cfg.chatIcon || cfg.profilePicture || void 0,
|
|
4023
4391
|
autoOpen: Boolean(cfg.autoOpen),
|
|
4024
4392
|
keyboardShortcut: Boolean(cfg.keyboardShortcut),
|
|
4025
4393
|
position: cfg.alignChatBubbleButton === "left" ? "bottom-left" : "bottom-right",
|
|
4026
4394
|
widgetLayout: cfg.widgetLayout === "center" ? "center" : "bubble",
|
|
4027
|
-
clientActions:
|
|
4028
|
-
bubbleSize:
|
|
4029
|
-
panelWidth:
|
|
4030
|
-
panelHeight:
|
|
4395
|
+
clientActions: cfg.clientActions ?? [],
|
|
4396
|
+
bubbleSize: cfg.bubbleSize ?? 52,
|
|
4397
|
+
panelWidth: cfg.panelWidth ?? 360,
|
|
4398
|
+
panelHeight: cfg.panelHeight ?? 580,
|
|
4031
4399
|
loading: false
|
|
4032
4400
|
});
|
|
4033
4401
|
}).catch(() => {
|
|
4034
|
-
if (!cancelled) setResult(
|
|
4402
|
+
if (!cancelled) setResult(EMPTY);
|
|
4035
4403
|
});
|
|
4036
4404
|
return () => {
|
|
4037
4405
|
cancelled = true;
|
|
4038
4406
|
};
|
|
4039
|
-
}, [agentId, enabled]);
|
|
4407
|
+
}, [agentId, enabled, apiUrl]);
|
|
4040
4408
|
return result;
|
|
4041
4409
|
}
|
|
4042
4410
|
function toWidgetMsg(m) {
|
|
@@ -4117,6 +4485,38 @@ function useSupportChat({
|
|
|
4117
4485
|
},
|
|
4118
4486
|
[base]
|
|
4119
4487
|
);
|
|
4488
|
+
function loadAblyScript() {
|
|
4489
|
+
return new Promise((resolve, reject) => {
|
|
4490
|
+
if (typeof window === "undefined") {
|
|
4491
|
+
resolve(null);
|
|
4492
|
+
return;
|
|
4493
|
+
}
|
|
4494
|
+
if (window.Ably) {
|
|
4495
|
+
resolve(window.Ably);
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
4498
|
+
const existing = document.querySelector("script[data-ably-sdk]");
|
|
4499
|
+
if (existing) {
|
|
4500
|
+
existing.addEventListener("load", () => resolve(window.Ably));
|
|
4501
|
+
existing.addEventListener(
|
|
4502
|
+
"error",
|
|
4503
|
+
() => reject(new Error("Ably CDN load failed"))
|
|
4504
|
+
);
|
|
4505
|
+
return;
|
|
4506
|
+
}
|
|
4507
|
+
const script = document.createElement("script");
|
|
4508
|
+
script.src = "https://cdn.ably.com/lib/ably.min-2.js";
|
|
4509
|
+
script.crossOrigin = "anonymous";
|
|
4510
|
+
script.async = true;
|
|
4511
|
+
script.setAttribute("data-ably-sdk", "true");
|
|
4512
|
+
script.onload = () => resolve(window.Ably);
|
|
4513
|
+
script.onerror = () => {
|
|
4514
|
+
script.remove();
|
|
4515
|
+
reject(new Error("Ably CDN load failed"));
|
|
4516
|
+
};
|
|
4517
|
+
document.head.appendChild(script);
|
|
4518
|
+
});
|
|
4519
|
+
}
|
|
4120
4520
|
react.useEffect(() => {
|
|
4121
4521
|
if (!enabled || !session) return;
|
|
4122
4522
|
void loadMessages(session);
|
|
@@ -4131,7 +4531,8 @@ function useSupportChat({
|
|
|
4131
4531
|
);
|
|
4132
4532
|
if (!res.ok) return;
|
|
4133
4533
|
const { tokenRequest, channel: channelName } = await res.json();
|
|
4134
|
-
const AblyLib =
|
|
4534
|
+
const AblyLib = await loadAblyScript();
|
|
4535
|
+
if (!AblyLib) return;
|
|
4135
4536
|
ablyClient = new AblyLib.Realtime({
|
|
4136
4537
|
authCallback: (_, cb) => cb(null, tokenRequest)
|
|
4137
4538
|
});
|
|
@@ -4439,9 +4840,9 @@ function BubbleWidget({
|
|
|
4439
4840
|
height: heightProp,
|
|
4440
4841
|
expandedWidth = 640,
|
|
4441
4842
|
expandedHeight = "calc(100vh - 100px)",
|
|
4442
|
-
keyboardShortcut: keyboardShortcutProp
|
|
4843
|
+
keyboardShortcut: keyboardShortcutProp,
|
|
4443
4844
|
shortcutKey = "k",
|
|
4444
|
-
autoOpen: autoOpenProp
|
|
4845
|
+
autoOpen: autoOpenProp,
|
|
4445
4846
|
bubbleIconUrl: bubbleIconUrlProp,
|
|
4446
4847
|
bubbleSize: bubbleSizeProp,
|
|
4447
4848
|
panelClassName,
|
|
@@ -4482,24 +4883,25 @@ function BubbleWidget({
|
|
|
4482
4883
|
}, []);
|
|
4483
4884
|
const remote = useAutoConfig(
|
|
4484
4885
|
inboxToken ? "" : chatProps.agentId ?? "",
|
|
4485
|
-
!inboxToken && autoConfig
|
|
4886
|
+
!inboxToken && autoConfig,
|
|
4887
|
+
chatProps.apiUrl
|
|
4486
4888
|
);
|
|
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
|
|
4889
|
+
const resolvedPosition = positionProp ?? remote.position;
|
|
4890
|
+
const resolvedLayout = chatProps.widgetLayout ?? remote.widgetLayout;
|
|
4891
|
+
const resolvedBubbleIcon = bubbleIconUrlProp ?? remote.bubbleIconUrl;
|
|
4892
|
+
const resolvedAutoOpen = autoOpenProp ?? remote.autoOpen;
|
|
4893
|
+
const resolvedKeyboardShortcut = keyboardShortcutProp ?? remote.keyboardShortcut;
|
|
4894
|
+
const resolvedBubbleSize = bubbleSizeProp ?? remote.bubbleSize;
|
|
4895
|
+
const resolvedWidth = widthProp ?? remote.panelWidth;
|
|
4896
|
+
const resolvedHeight = heightProp ?? remote.panelHeight;
|
|
4495
4897
|
const definedChatProps = Object.fromEntries(
|
|
4496
4898
|
Object.entries(chatProps).filter(([, v]) => v !== void 0)
|
|
4497
4899
|
);
|
|
4498
4900
|
const mergedConfig = {
|
|
4499
|
-
...definedChatProps,
|
|
4500
4901
|
...remote.remoteConfig,
|
|
4902
|
+
...definedChatProps,
|
|
4501
4903
|
agentId: chatProps.agentId ?? "",
|
|
4502
|
-
agentName:
|
|
4904
|
+
agentName: chatProps.agentName ?? remote.remoteConfig.agentName ?? "Asistente",
|
|
4503
4905
|
source: chatProps.source ?? remote.remoteConfig.source ?? "web"
|
|
4504
4906
|
};
|
|
4505
4907
|
const supportBackend = useSupportChat(
|
|
@@ -4515,6 +4917,10 @@ function BubbleWidget({
|
|
|
4515
4917
|
react.useEffect(() => {
|
|
4516
4918
|
setOpenRef.current = setOpen;
|
|
4517
4919
|
});
|
|
4920
|
+
const openRef = react.useRef(open);
|
|
4921
|
+
react.useEffect(() => {
|
|
4922
|
+
openRef.current = open;
|
|
4923
|
+
});
|
|
4518
4924
|
react.useEffect(() => {
|
|
4519
4925
|
if (!resolvedAutoOpen || autoOpenedRef.current) return;
|
|
4520
4926
|
const dismissedUntil = Number(localStorage.getItem(KEY_DISMISSED) ?? 0);
|
|
@@ -4524,15 +4930,29 @@ function BubbleWidget({
|
|
|
4524
4930
|
}
|
|
4525
4931
|
}, [resolvedAutoOpen]);
|
|
4526
4932
|
react.useEffect(() => {
|
|
4527
|
-
|
|
4933
|
+
const onToggle = () => setOpenRef.current((v) => !v);
|
|
4934
|
+
const onOpen = () => setOpenRef.current(true);
|
|
4935
|
+
const onClose = () => setOpenRef.current(false);
|
|
4936
|
+
window.addEventListener("wallavi:toggle-assistant", onToggle);
|
|
4937
|
+
window.addEventListener("wallavi:open-assistant", onOpen);
|
|
4938
|
+
window.addEventListener("wallavi:close-assistant", onClose);
|
|
4528
4939
|
const onKey = (e) => {
|
|
4529
|
-
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === shortcutKey.toLowerCase()) {
|
|
4940
|
+
if (resolvedKeyboardShortcut && (e.metaKey || e.ctrlKey) && e.key.toLowerCase() === shortcutKey.toLowerCase()) {
|
|
4530
4941
|
e.preventDefault();
|
|
4531
4942
|
setOpenRef.current((v) => !v);
|
|
4943
|
+
} else if (e.key === "Escape" && openRef.current) {
|
|
4944
|
+
e.preventDefault();
|
|
4945
|
+
e.stopPropagation();
|
|
4946
|
+
setOpenRef.current(false);
|
|
4532
4947
|
}
|
|
4533
4948
|
};
|
|
4534
4949
|
window.addEventListener("keydown", onKey);
|
|
4535
|
-
return () =>
|
|
4950
|
+
return () => {
|
|
4951
|
+
window.removeEventListener("wallavi:toggle-assistant", onToggle);
|
|
4952
|
+
window.removeEventListener("wallavi:open-assistant", onOpen);
|
|
4953
|
+
window.removeEventListener("wallavi:close-assistant", onClose);
|
|
4954
|
+
window.removeEventListener("keydown", onKey);
|
|
4955
|
+
};
|
|
4536
4956
|
}, [resolvedKeyboardShortcut, shortcutKey]);
|
|
4537
4957
|
react.useEffect(() => {
|
|
4538
4958
|
if (!open) return;
|
|
@@ -4587,7 +5007,8 @@ function BubbleWidget({
|
|
|
4587
5007
|
display: "flex",
|
|
4588
5008
|
flexDirection: "column",
|
|
4589
5009
|
alignItems: isLeft ? "flex-start" : "flex-end",
|
|
4590
|
-
gap: 12
|
|
5010
|
+
gap: 12,
|
|
5011
|
+
pointerEvents: !open && hideBubble ? "none" : void 0
|
|
4591
5012
|
},
|
|
4592
5013
|
children: [
|
|
4593
5014
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4604,12 +5025,14 @@ function BubbleWidget({
|
|
|
4604
5025
|
zIndex: 9999,
|
|
4605
5026
|
width: panelWidth,
|
|
4606
5027
|
height: panelHeight,
|
|
4607
|
-
transition: "width 0.3s ease, height 0.3s ease"
|
|
5028
|
+
transition: "width 0.3s ease, height 0.3s ease",
|
|
5029
|
+
pointerEvents: "auto"
|
|
4608
5030
|
} : {
|
|
4609
5031
|
display: open ? "block" : "none",
|
|
4610
5032
|
width: panelWidth,
|
|
4611
5033
|
height: panelHeight,
|
|
4612
|
-
transition: "width 0.3s ease, height 0.3s ease"
|
|
5034
|
+
transition: "width 0.3s ease, height 0.3s ease",
|
|
5035
|
+
pointerEvents: "auto"
|
|
4613
5036
|
},
|
|
4614
5037
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4615
5038
|
ChatWidget,
|