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