wealth-alpha-chat-widget 1.0.2 → 1.0.4

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.cjs CHANGED
@@ -683,6 +683,9 @@ function useSession(opts = {}) {
683
683
  // src/styles/chat.module.css
684
684
  var chat_default = {
685
685
  root: "chat_root",
686
+ headerTitle: "chat_headerTitle",
687
+ menuItemTitle: "chat_menuItemTitle",
688
+ markdown: "chat_markdown",
686
689
  floatingButton: "chat_floatingButton",
687
690
  positionRight: "chat_positionRight",
688
691
  positionLeft: "chat_positionLeft",
@@ -690,25 +693,27 @@ var chat_default = {
690
693
  popupBubbleLeft: "chat_popupBubbleLeft",
691
694
  popupBubbleRight: "chat_popupBubbleRight",
692
695
  header: "chat_header",
693
- headerTitle: "chat_headerTitle",
694
696
  headerMeta: "chat_headerMeta",
695
697
  headerActions: "chat_headerActions",
696
698
  iconButton: "chat_iconButton",
697
699
  body: "chat_body",
698
700
  bubble: "chat_bubble",
699
- markdown: "chat_markdown",
700
701
  bubbleBot: "chat_bubbleBot",
701
702
  bubbleUser: "chat_bubbleUser",
703
+ bubbleCard: "chat_bubbleCard",
704
+ bubbleMeta: "chat_bubbleMeta",
702
705
  chipRow: "chat_chipRow",
703
706
  chip: "chat_chip",
704
707
  chipActive: "chat_chipActive",
705
708
  chipDisabled: "chat_chipDisabled",
706
709
  typing: "chat_typing",
707
710
  typingDot: "chat_typingDot",
711
+ wacBlink: "chat_wacBlink",
708
712
  input: "chat_input",
709
713
  inputBox: "chat_inputBox",
710
714
  sendButton: "chat_sendButton",
711
715
  hiddenFileInput: "chat_hiddenFileInput",
716
+ csvButton: "chat_csvButton",
712
717
  authGate: "chat_authGate",
713
718
  authGateIcon: "chat_authGateIcon",
714
719
  authGateTitle: "chat_authGateTitle",
@@ -718,7 +723,22 @@ var chat_default = {
718
723
  authGateDisclaimerTitle: "chat_authGateDisclaimerTitle",
719
724
  errorBanner: "chat_errorBanner",
720
725
  popupBubble: "chat_popupBubble",
721
- popupDismiss: "chat_popupDismiss"
726
+ wacPopIn: "chat_wacPopIn",
727
+ popupDismiss: "chat_popupDismiss",
728
+ menuGrid: "chat_menuGrid",
729
+ menuItem: "chat_menuItem",
730
+ menuItemIcon: "chat_menuItemIcon",
731
+ menuItemSvg: "chat_menuItemSvg",
732
+ menuItemSub: "chat_menuItemSub",
733
+ menuIconGreen: "chat_menuIconGreen",
734
+ menuIconBlue: "chat_menuIconBlue",
735
+ menuIconLeaf: "chat_menuIconLeaf",
736
+ menuIconRed: "chat_menuIconRed",
737
+ menuIconOrange: "chat_menuIconOrange",
738
+ menuIconGold: "chat_menuIconGold",
739
+ menuIconTeal: "chat_menuIconTeal",
740
+ menuIconPurple: "chat_menuIconPurple",
741
+ chat_bubbleBot: "chat_chat_bubbleBot"
722
742
  };
723
743
  function AuthGate({ brandName, loginUrl, onLoginClick }) {
724
744
  const handleClick = () => {
@@ -790,9 +810,10 @@ var ALLOWED_TAGS = [
790
810
  "ol",
791
811
  "li",
792
812
  "blockquote",
793
- "span"
813
+ "span",
814
+ "div"
794
815
  ];
795
- var ALLOWED_ATTR = ["href", "target", "rel", "class"];
816
+ var ALLOWED_ATTR = ["href", "target", "rel", "class", "style", "data-wa-gauge-pct"];
796
817
  var SANITIZE_CFG = {
797
818
  ALLOWED_TAGS,
798
819
  ALLOWED_ATTR,
@@ -803,28 +824,116 @@ var SANITIZE_CFG = {
803
824
  function inlineMarkdownToHtml(text) {
804
825
  return text.replace(/^[ \t]*#{1,6}[ \t]+(.+?)[ \t]*$/gm, "<b>$1</b>").replace(/\*\*([^\n<>]+?)\*\*/g, "<b>$1</b>").replace(/__([^\n<>]+?)__/g, "<b>$1</b>").replace(/(?<!\*)\*([^\n*<>]+?)\*(?!\*)/g, "<b>$1</b>").replace(/(?<![_a-zA-Z0-9])_([^\n_<>]+?)_(?![_a-zA-Z0-9])/g, "<i>$1</i>");
805
826
  }
827
+ function stripCardDateTime(html) {
828
+ return html.replace(
829
+ /(<span class="wa-card-date">)([^<]*)(<\/span>)/g,
830
+ (_m, open, content, close) => open + content.replace(/,\s*\d{1,2}:\d{2}.*$/, "").trim() + close
831
+ );
832
+ }
806
833
  function renderMarkdown(text) {
807
834
  if (!text) return "";
808
835
  const inlined = inlineMarkdownToHtml(text);
836
+ if (/<div[\s>]/i.test(inlined)) {
837
+ return DOMPurify__default.default.sanitize(stripCardDateTime(inlined), SANITIZE_CFG);
838
+ }
809
839
  const paragraphs = inlined.split(/\n{2,}/).map((para) => para.replace(/\n/g, "<br>")).filter((p) => p.length > 0);
810
840
  const html = paragraphs.length > 1 ? paragraphs.map((p) => `<p>${p}</p>`).join("") : paragraphs[0] ?? "";
811
- return DOMPurify__default.default.sanitize(html, SANITIZE_CFG);
841
+ return DOMPurify__default.default.sanitize(stripCardDateTime(html), SANITIZE_CFG);
842
+ }
843
+ var ROOT_MENU_CHIP_IDS = /* @__PURE__ */ new Set([
844
+ "stock_analysis",
845
+ "stock_discovery",
846
+ "new_listings",
847
+ "portfolio_risk",
848
+ "market_forecast",
849
+ "crypto",
850
+ "tradable_picks",
851
+ "peer_compare"
852
+ ]);
853
+ var MENU_SUBTITLES = {
854
+ stock_analysis: "Fundamental & technical",
855
+ stock_discovery: "Find promising stocks",
856
+ new_listings: "Track IPOs & new stocks",
857
+ portfolio_risk: "Analyze your portfolio",
858
+ market_forecast: "Outlook & key events",
859
+ crypto: "BTC, ETH & trends",
860
+ tradable_picks: "Short-term setups",
861
+ peer_compare: "Compare peer stocks"
862
+ };
863
+ var MENU_ICON_KEY = {
864
+ stock_analysis: "menuIconGreen",
865
+ stock_discovery: "menuIconBlue",
866
+ new_listings: "menuIconLeaf",
867
+ portfolio_risk: "menuIconRed",
868
+ market_forecast: "menuIconOrange",
869
+ crypto: "menuIconGold",
870
+ tradable_picks: "menuIconTeal",
871
+ peer_compare: "menuIconPurple"
872
+ };
873
+ var MENU_ICON_SVG = {
874
+ stock_analysis: ' <path d="M4 16L9 11L13 15L20 8" /> <path d="M20 8V13" /> <path d="M20 8H15" />',
875
+ stock_discovery: '<circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/>',
876
+ new_listings: '<rect x="5" y="5" width="14" height="14" rx="3"/><path d="M12 8v8"/> <path d="M8 12h8"/>',
877
+ portfolio_risk: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z"/>',
878
+ market_forecast: '<path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z"/>',
879
+ crypto: '<path d="M4 20V10M10 20V4M16 20v-7M22 20v-4"/>',
880
+ tradable_picks: '<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1.5"/>',
881
+ peer_compare: '<path d="M12 3v18"/><path d="M5 8l-3 6h6z"/><path d="M19 8l-3 6h6z"/>'
882
+ };
883
+ function isRootMenuChips(chips) {
884
+ return chips.length > 0 && chips.every((c) => ROOT_MENU_CHIP_IDS.has(c.id));
812
885
  }
813
886
  function MessageBubble({ message, onChipClick }) {
814
887
  const isBot = message.role === "bot";
815
- const bubbleClass = isBot ? `${chat_default.bubble} ${chat_default.bubbleBot}` : `${chat_default.bubble} ${chat_default.bubbleUser}`;
888
+ const isCard = isBot && message.content.includes("<div");
889
+ const bubbleClass = isBot ? `${chat_default.bubble} ${isCard ? chat_default.bubbleCard : chat_default.bubbleBot}` : `${chat_default.bubble} ${chat_default.bubbleUser}`;
890
+ const handleMarkdownClick = (e) => {
891
+ const anchor = e.target.closest("a");
892
+ if (!anchor?.href) return;
893
+ e.preventDefault();
894
+ window.location.assign(anchor.href);
895
+ };
896
+ const chips = message.chips ?? [];
897
+ const showMenuGrid = isBot && message.chipsActive && isRootMenuChips(chips);
816
898
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column" }, children: [
817
899
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: bubbleClass, children: isBot ? /* @__PURE__ */ jsxRuntime.jsx(
818
900
  "div",
819
901
  {
820
902
  className: chat_default.markdown,
821
- dangerouslySetInnerHTML: { __html: renderMarkdown(message.content) }
903
+ dangerouslySetInnerHTML: { __html: renderMarkdown(message.content) },
904
+ onClick: handleMarkdownClick
822
905
  }
823
906
  ) : message.content }),
824
- isBot && message.chips && message.chips.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
907
+ showMenuGrid ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: chat_default.menuGrid, children: chips.map((chip) => {
908
+ const iconKey = MENU_ICON_KEY[chip.id];
909
+ const iconClass = iconKey && chat_default[iconKey] || "";
910
+ const svgIcon = MENU_ICON_SVG[chip.id];
911
+ return /* @__PURE__ */ jsxRuntime.jsxs(
912
+ "button",
913
+ {
914
+ type: "button",
915
+ className: chat_default.menuItem,
916
+ onClick: () => onChipClick(chip),
917
+ children: [
918
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${chat_default.menuItemIcon} ${iconClass}`, children: svgIcon ? /* @__PURE__ */ jsxRuntime.jsx(
919
+ "svg",
920
+ {
921
+ className: chat_default.menuItemSvg,
922
+ viewBox: "0 0 24 24",
923
+ "aria-hidden": "true",
924
+ dangerouslySetInnerHTML: { __html: svgIcon }
925
+ }
926
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: chip.icon }) }),
927
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: chat_default.menuItemTitle, children: chip.label }),
928
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: chat_default.menuItemSub, children: MENU_SUBTITLES[chip.id] ?? "" })
929
+ ]
930
+ },
931
+ chip.id
932
+ );
933
+ }) }) : isBot && chips.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
825
934
  ChipRow,
826
935
  {
827
- chips: message.chips,
936
+ chips,
828
937
  disabled: !message.chipsActive,
829
938
  onClick: onChipClick
830
939
  }
@@ -873,18 +982,6 @@ function ChatBody({
873
982
  /* @__PURE__ */ jsxRuntime.jsx("div", { ref: endRef })
874
983
  ] });
875
984
  }
876
-
877
- // src/utils/time.ts
878
- function formatCountdown(remainingMs) {
879
- if (remainingMs <= 0) return "expired";
880
- const totalSeconds = Math.floor(remainingMs / 1e3);
881
- const hours = Math.floor(totalSeconds / 3600);
882
- const minutes = Math.floor(totalSeconds % 3600 / 60);
883
- const seconds = totalSeconds % 60;
884
- if (hours > 0) return `${hours}h ${minutes}m left`;
885
- if (minutes > 0) return `${minutes}m ${seconds}s left`;
886
- return `${seconds}s left`;
887
- }
888
985
  function ChatHeader({
889
986
  brandName,
890
987
  remainingMs,
@@ -893,13 +990,7 @@ function ChatHeader({
893
990
  onClear
894
991
  }) {
895
992
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chat_default.header, children: [
896
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
897
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: chat_default.headerTitle, children: brandName }),
898
- showCountdown && remainingMs > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chat_default.headerMeta, children: [
899
- "Session: ",
900
- formatCountdown(remainingMs)
901
- ] }) : null
902
- ] }),
993
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: chat_default.headerTitle, children: brandName }) }),
903
994
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chat_default.headerActions, children: [
904
995
  onClear ? /* @__PURE__ */ jsxRuntime.jsx(
905
996
  "button",
@@ -1015,15 +1106,20 @@ var DEFAULT_BRAND_NAME = "Wealth Alpha AI";
1015
1106
  var DEFAULT_BRAND_COLOR = "#1a2d5a";
1016
1107
  var DEFAULT_AUTH_CHECK = "/me";
1017
1108
  var DEFAULT_GREETING = "Hi! I'm your Wealth Alpha AI assistant. How can I help you?";
1018
- var DISCLAIMER_BLOCK = "DISCLAIMER:\n\u2022 AI-assisted analysis for educational purposes only.\n\u2022 Not financial advice. Markets involve risk.\n\u2022 Consult a SEBI-registered advisor before investing.";
1019
- var CTA_LINE = "Select a quick command below \u2014 or type your query to ask our AI Research Analyst directly.";
1109
+ var CTA_LINE = "Select a quick command below";
1020
1110
  var PORTFOLIO_CSV_CHIP_ID = "__portfolio_csv_upload";
1111
+ var UPGRADE_PREMIUM_CHIP_ID = "__upgrade_premium__";
1112
+ function resolvePricingUrl(upgradeUrl) {
1113
+ if (upgradeUrl) return upgradeUrl;
1114
+ if (typeof window !== "undefined") {
1115
+ return `${window.location.origin}/pricing`;
1116
+ }
1117
+ return "";
1118
+ }
1021
1119
  function buildWelcome(name, plan) {
1022
1120
  const greeting = name ? `Welcome back, ${name}! You have ${plan ? plan : "Premium"} access.` : `Welcome! You have ${plan ? plan : "Premium"} access.`;
1023
1121
  return `${greeting}
1024
1122
 
1025
- ${DISCLAIMER_BLOCK}
1026
-
1027
1123
  ${CTA_LINE}`;
1028
1124
  }
1029
1125
  function WealthChat(props) {
@@ -1051,6 +1147,16 @@ function WealthChat(props) {
1051
1147
  react.useEffect(() => {
1052
1148
  setMounted(true);
1053
1149
  }, []);
1150
+ react.useEffect(() => {
1151
+ if (typeof document === "undefined") return;
1152
+ const id = "wac-google-fonts";
1153
+ if (document.getElementById(id)) return;
1154
+ const link = document.createElement("link");
1155
+ link.id = id;
1156
+ link.rel = "stylesheet";
1157
+ link.href = "https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Sora:wght@600;700;800&display=swap";
1158
+ document.head.appendChild(link);
1159
+ }, []);
1054
1160
  react.useEffect(() => {
1055
1161
  if (!mounted || open || popupShownRef.current) return;
1056
1162
  const timer = setTimeout(() => {
@@ -1135,16 +1241,30 @@ function WealthChat(props) {
1135
1241
  csvFileRef.current?.click();
1136
1242
  return;
1137
1243
  }
1244
+ if (chip.id === UPGRADE_PREMIUM_CHIP_ID) {
1245
+ appendUserMessage(chip.label);
1246
+ const pricingUrl = resolvePricingUrl(user?.upgradeUrl);
1247
+ if (pricingUrl && typeof window !== "undefined") {
1248
+ window.location.assign(pricingUrl);
1249
+ }
1250
+ return;
1251
+ }
1138
1252
  appendUserMessage(chip.label);
1139
1253
  setTyping(true);
1140
1254
  try {
1141
1255
  const resp = await callChip(chip);
1142
- if (resp) appendBotResponse(resp);
1256
+ if (!resp) return;
1257
+ const redirectUrl = resp.metadata?.redirectUrl;
1258
+ if (typeof redirectUrl === "string" && redirectUrl && typeof window !== "undefined") {
1259
+ window.location.assign(redirectUrl);
1260
+ return;
1261
+ }
1262
+ appendBotResponse(resp);
1143
1263
  } finally {
1144
1264
  setTyping(false);
1145
1265
  }
1146
1266
  },
1147
- [touch, deactivatePriorChips, appendUserMessage, callChip, setTyping, appendBotResponse]
1267
+ [touch, deactivatePriorChips, appendUserMessage, callChip, setTyping, appendBotResponse, user?.upgradeUrl]
1148
1268
  );
1149
1269
  const handleCsvFileSelected = react.useCallback(
1150
1270
  (e) => {