@wallavi/widget 1.13.2 → 1.13.3

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 CHANGED
@@ -358,7 +358,10 @@ interface CommandPanelProps {
358
358
  ragTopics?: RagTopicInfo[];
359
359
  ragDocuments?: RagDocumentInfo[];
360
360
  onSend: (text: string) => void;
361
- onExecuteAction?: (steps: any[]) => void;
361
+ onExecuteAction?: (action: {
362
+ name: string;
363
+ steps: any[];
364
+ }) => void;
362
365
  accentColor?: string;
363
366
  isDark?: boolean;
364
367
  watermark?: boolean;
package/dist/index.d.ts CHANGED
@@ -358,7 +358,10 @@ interface CommandPanelProps {
358
358
  ragTopics?: RagTopicInfo[];
359
359
  ragDocuments?: RagDocumentInfo[];
360
360
  onSend: (text: string) => void;
361
- onExecuteAction?: (steps: any[]) => void;
361
+ onExecuteAction?: (action: {
362
+ name: string;
363
+ steps: any[];
364
+ }) => void;
362
365
  accentColor?: string;
363
366
  isDark?: boolean;
364
367
  watermark?: boolean;
package/dist/index.js CHANGED
@@ -351,6 +351,55 @@ function applyUiEventToMessages(prev, proto, msgId) {
351
351
  return copy;
352
352
  }
353
353
 
354
+ // src/core/quick-action.ts
355
+ var BRACES = /\{\{(?:toolInput\.)?([a-zA-Z0-9_]+)\}\}|\{([a-zA-Z0-9_]+)\}/g;
356
+ var ROUTE = /\{\{(?:toolInput\.)?([a-zA-Z0-9_]+)\}\}|\{([a-zA-Z0-9_]+)\}|\[([a-zA-Z0-9_]+)\]|(?<=^|\/):([a-zA-Z0-9_]+)(?=[/?#]|$)/g;
357
+ function fill(text, pattern, sources, missing) {
358
+ return text.replace(pattern, (match, ...groups) => {
359
+ const key = groups.find((g) => typeof g === "string");
360
+ if (!key) return match;
361
+ for (const source of sources) {
362
+ const value = source?.[key];
363
+ if (typeof value === "string" && value !== "") return value;
364
+ if (typeof value === "number") return String(value);
365
+ }
366
+ missing?.add(key);
367
+ return match;
368
+ });
369
+ }
370
+ function fillPayload(value, sources) {
371
+ if (typeof value === "string") return fill(value, BRACES, sources);
372
+ if (Array.isArray(value)) return value.map((v) => fillPayload(v, sources));
373
+ if (value && typeof value === "object") {
374
+ return Object.fromEntries(
375
+ Object.entries(value).map(([k, v]) => [k, fillPayload(v, sources)])
376
+ );
377
+ }
378
+ return value;
379
+ }
380
+ function planQuickAction(steps, pageParams, metadata) {
381
+ const sources = [pageParams, metadata];
382
+ const missing = /* @__PURE__ */ new Set();
383
+ const filled = steps.map((original) => {
384
+ const step = original;
385
+ const out = { ...step };
386
+ if (typeof step.value === "string") {
387
+ out.value = step.action === "navigate" ? fill(step.value, ROUTE, sources, missing) : fill(step.value, BRACES, sources);
388
+ }
389
+ if (typeof step.selector === "string") {
390
+ out.selector = fill(step.selector, BRACES, sources);
391
+ }
392
+ if (step.payload !== void 0) {
393
+ out.payload = fillPayload(step.payload, sources);
394
+ }
395
+ return out;
396
+ });
397
+ return missing.size > 0 ? { kind: "ask_agent", missing: [...missing] } : { kind: "run", steps: filled };
398
+ }
399
+ function hasRoutePlaceholder(url) {
400
+ return new RegExp(ROUTE.source).test(url);
401
+ }
402
+
354
403
  // src/core/declarative-actions.ts
355
404
  function sanitizeSelector(selector) {
356
405
  if (!selector || typeof selector !== "string") return null;
@@ -625,6 +674,12 @@ async function executeDeclarativeSteps(steps, onNavigate, copilotColor) {
625
674
  }
626
675
  case "navigate": {
627
676
  const url = sanitizeUrl(step.value);
677
+ if (url && hasRoutePlaceholder(url)) {
678
+ console.warn(
679
+ "[Wallavi Widget] Blocked navigate with unfilled route params"
680
+ );
681
+ break;
682
+ }
628
683
  if (url) {
629
684
  if (onNavigate) {
630
685
  onNavigate(url);
@@ -3631,7 +3686,7 @@ function CommandPanel({
3631
3686
  const hasContent = hasActions || hasDocs || hasTopics;
3632
3687
  if (!hasContent) return null;
3633
3688
  const isSidebar = mode === "sidebar";
3634
- const handleExecute = (steps) => onExecuteAction?.(steps);
3689
+ const handleExecute = (action) => onExecuteAction?.(action);
3635
3690
  if (isSidebar) {
3636
3691
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ww-w-[300px] ww-shrink-0 ww-border-l ww-border-border ww-bg-muted/50 ww-flex ww-flex-col ww-h-full ww-overflow-hidden ww-animate-in ww-fade-in ww-slide-in-from-right-2 ww-duration-300", children: [
3637
3692
  /* @__PURE__ */ jsxRuntime.jsx(PageBadge, { pageTitle, pageUrl, locale }),
@@ -3644,7 +3699,7 @@ function CommandPanel({
3644
3699
  action,
3645
3700
  index: i,
3646
3701
  accentColor,
3647
- onExecute: () => handleExecute(action.steps)
3702
+ onExecute: () => handleExecute(action)
3648
3703
  },
3649
3704
  actionKeys[i]
3650
3705
  )) })
@@ -3716,7 +3771,7 @@ function CommandPanel({
3716
3771
  action,
3717
3772
  index: i,
3718
3773
  accentColor,
3719
- onExecute: () => handleExecute(action.steps)
3774
+ onExecute: () => handleExecute(action)
3720
3775
  },
3721
3776
  actionKeys[i]
3722
3777
  )) })
@@ -3936,12 +3991,18 @@ function ChatWidget({
3936
3991
  const messagesList = isDefaultGreeting ? [getWidgetTranslation("defaultGreeting", locale)] : initialMessages;
3937
3992
  const showSidebar = showCommandPanel && widgetLayout === "center" && isDesktop;
3938
3993
  const showInlineCommandPanel = showCommandPanel && !showSidebar && chat.messages.length === 0 && (showQuickActions && clientActions.length > 0 || showRagTopics && ragTopics.length > 0 || showRagDocuments && ragDocuments.length > 0);
3939
- const handleExecuteAction = react.useCallback(
3940
- (steps) => {
3941
- executeDeclarativeSteps(steps, onNavigate, userMessageColor);
3942
- },
3943
- [onNavigate, userMessageColor]
3944
- );
3994
+ const handleExecuteAction = (action) => {
3995
+ const plan = planQuickAction(
3996
+ action.steps,
3997
+ resolvePageContext()?.params,
3998
+ userContext?.metadata
3999
+ );
4000
+ if (plan.kind === "run") {
4001
+ executeDeclarativeSteps(plan.steps, onNavigate, userMessageColor);
4002
+ } else {
4003
+ chat.send(action.name);
4004
+ }
4005
+ };
3945
4006
  const [activeActionIndex, setActiveActionIndex] = react.useState(-1);
3946
4007
  const query = chat.input.toLowerCase().trim();
3947
4008
  const matchingActions = react.useMemo(() => {
@@ -3987,7 +4048,7 @@ function ChatWidget({
3987
4048
  e.preventDefault();
3988
4049
  const selectedAction = matchingActions[activeActionIndex];
3989
4050
  if (selectedAction) {
3990
- handleExecuteAction(selectedAction.steps);
4051
+ handleExecuteAction(selectedAction);
3991
4052
  chat.setInput("");
3992
4053
  }
3993
4054
  } else if (e.key === "Escape") {
@@ -4222,7 +4283,7 @@ function ChatWidget({
4222
4283
  "button",
4223
4284
  {
4224
4285
  onClick: () => {
4225
- handleExecuteAction(action.steps);
4286
+ handleExecuteAction(action);
4226
4287
  chat.setInput("");
4227
4288
  },
4228
4289
  className: cn(
package/dist/index.mjs CHANGED
@@ -325,6 +325,55 @@ function applyUiEventToMessages(prev, proto, msgId) {
325
325
  return copy;
326
326
  }
327
327
 
328
+ // src/core/quick-action.ts
329
+ var BRACES = /\{\{(?:toolInput\.)?([a-zA-Z0-9_]+)\}\}|\{([a-zA-Z0-9_]+)\}/g;
330
+ var ROUTE = /\{\{(?:toolInput\.)?([a-zA-Z0-9_]+)\}\}|\{([a-zA-Z0-9_]+)\}|\[([a-zA-Z0-9_]+)\]|(?<=^|\/):([a-zA-Z0-9_]+)(?=[/?#]|$)/g;
331
+ function fill(text, pattern, sources, missing) {
332
+ return text.replace(pattern, (match, ...groups) => {
333
+ const key = groups.find((g) => typeof g === "string");
334
+ if (!key) return match;
335
+ for (const source of sources) {
336
+ const value = source?.[key];
337
+ if (typeof value === "string" && value !== "") return value;
338
+ if (typeof value === "number") return String(value);
339
+ }
340
+ missing?.add(key);
341
+ return match;
342
+ });
343
+ }
344
+ function fillPayload(value, sources) {
345
+ if (typeof value === "string") return fill(value, BRACES, sources);
346
+ if (Array.isArray(value)) return value.map((v) => fillPayload(v, sources));
347
+ if (value && typeof value === "object") {
348
+ return Object.fromEntries(
349
+ Object.entries(value).map(([k, v]) => [k, fillPayload(v, sources)])
350
+ );
351
+ }
352
+ return value;
353
+ }
354
+ function planQuickAction(steps, pageParams, metadata) {
355
+ const sources = [pageParams, metadata];
356
+ const missing = /* @__PURE__ */ new Set();
357
+ const filled = steps.map((original) => {
358
+ const step = original;
359
+ const out = { ...step };
360
+ if (typeof step.value === "string") {
361
+ out.value = step.action === "navigate" ? fill(step.value, ROUTE, sources, missing) : fill(step.value, BRACES, sources);
362
+ }
363
+ if (typeof step.selector === "string") {
364
+ out.selector = fill(step.selector, BRACES, sources);
365
+ }
366
+ if (step.payload !== void 0) {
367
+ out.payload = fillPayload(step.payload, sources);
368
+ }
369
+ return out;
370
+ });
371
+ return missing.size > 0 ? { kind: "ask_agent", missing: [...missing] } : { kind: "run", steps: filled };
372
+ }
373
+ function hasRoutePlaceholder(url) {
374
+ return new RegExp(ROUTE.source).test(url);
375
+ }
376
+
328
377
  // src/core/declarative-actions.ts
329
378
  function sanitizeSelector(selector) {
330
379
  if (!selector || typeof selector !== "string") return null;
@@ -599,6 +648,12 @@ async function executeDeclarativeSteps(steps, onNavigate, copilotColor) {
599
648
  }
600
649
  case "navigate": {
601
650
  const url = sanitizeUrl(step.value);
651
+ if (url && hasRoutePlaceholder(url)) {
652
+ console.warn(
653
+ "[Wallavi Widget] Blocked navigate with unfilled route params"
654
+ );
655
+ break;
656
+ }
602
657
  if (url) {
603
658
  if (onNavigate) {
604
659
  onNavigate(url);
@@ -3605,7 +3660,7 @@ function CommandPanel({
3605
3660
  const hasContent = hasActions || hasDocs || hasTopics;
3606
3661
  if (!hasContent) return null;
3607
3662
  const isSidebar = mode === "sidebar";
3608
- const handleExecute = (steps) => onExecuteAction?.(steps);
3663
+ const handleExecute = (action) => onExecuteAction?.(action);
3609
3664
  if (isSidebar) {
3610
3665
  return /* @__PURE__ */ jsxs("div", { className: "ww-w-[300px] ww-shrink-0 ww-border-l ww-border-border ww-bg-muted/50 ww-flex ww-flex-col ww-h-full ww-overflow-hidden ww-animate-in ww-fade-in ww-slide-in-from-right-2 ww-duration-300", children: [
3611
3666
  /* @__PURE__ */ jsx(PageBadge, { pageTitle, pageUrl, locale }),
@@ -3618,7 +3673,7 @@ function CommandPanel({
3618
3673
  action,
3619
3674
  index: i,
3620
3675
  accentColor,
3621
- onExecute: () => handleExecute(action.steps)
3676
+ onExecute: () => handleExecute(action)
3622
3677
  },
3623
3678
  actionKeys[i]
3624
3679
  )) })
@@ -3690,7 +3745,7 @@ function CommandPanel({
3690
3745
  action,
3691
3746
  index: i,
3692
3747
  accentColor,
3693
- onExecute: () => handleExecute(action.steps)
3748
+ onExecute: () => handleExecute(action)
3694
3749
  },
3695
3750
  actionKeys[i]
3696
3751
  )) })
@@ -3910,12 +3965,18 @@ function ChatWidget({
3910
3965
  const messagesList = isDefaultGreeting ? [getWidgetTranslation("defaultGreeting", locale)] : initialMessages;
3911
3966
  const showSidebar = showCommandPanel && widgetLayout === "center" && isDesktop;
3912
3967
  const showInlineCommandPanel = showCommandPanel && !showSidebar && chat.messages.length === 0 && (showQuickActions && clientActions.length > 0 || showRagTopics && ragTopics.length > 0 || showRagDocuments && ragDocuments.length > 0);
3913
- const handleExecuteAction = useCallback(
3914
- (steps) => {
3915
- executeDeclarativeSteps(steps, onNavigate, userMessageColor);
3916
- },
3917
- [onNavigate, userMessageColor]
3918
- );
3968
+ const handleExecuteAction = (action) => {
3969
+ const plan = planQuickAction(
3970
+ action.steps,
3971
+ resolvePageContext()?.params,
3972
+ userContext?.metadata
3973
+ );
3974
+ if (plan.kind === "run") {
3975
+ executeDeclarativeSteps(plan.steps, onNavigate, userMessageColor);
3976
+ } else {
3977
+ chat.send(action.name);
3978
+ }
3979
+ };
3919
3980
  const [activeActionIndex, setActiveActionIndex] = useState(-1);
3920
3981
  const query = chat.input.toLowerCase().trim();
3921
3982
  const matchingActions = useMemo(() => {
@@ -3961,7 +4022,7 @@ function ChatWidget({
3961
4022
  e.preventDefault();
3962
4023
  const selectedAction = matchingActions[activeActionIndex];
3963
4024
  if (selectedAction) {
3964
- handleExecuteAction(selectedAction.steps);
4025
+ handleExecuteAction(selectedAction);
3965
4026
  chat.setInput("");
3966
4027
  }
3967
4028
  } else if (e.key === "Escape") {
@@ -4196,7 +4257,7 @@ function ChatWidget({
4196
4257
  "button",
4197
4258
  {
4198
4259
  onClick: () => {
4199
- handleExecuteAction(action.steps);
4260
+ handleExecuteAction(action);
4200
4261
  chat.setInput("");
4201
4262
  },
4202
4263
  className: cn(
package/package.json CHANGED
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "private": false,
46
46
  "types": "./dist/index.d.ts",
47
- "version": "1.13.2",
47
+ "version": "1.13.3",
48
48
  "scripts": {
49
49
  "build": "tsup && pnpm build:css",
50
50
  "build:css": "tailwindcss -i ./src/styles.css -o ./dist/styles.css --config tailwind.config.cjs --minify",