wealth-alpha-chat-widget 1.0.1 → 1.0.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.cjs +294 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1478 -152
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.mjs +295 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/docs/BACKEND_CHAT_WIDGET.md +0 -357
package/dist/index.cjs
CHANGED
|
@@ -284,6 +284,50 @@ async function sendMessage(apiBase, message, sessionId, context, signal) {
|
|
|
284
284
|
signal
|
|
285
285
|
});
|
|
286
286
|
}
|
|
287
|
+
async function uploadPortfolioCsv(apiBase, file, sessionId, signal) {
|
|
288
|
+
const url = joinUrl(apiBase, "/upload-portfolio-csv");
|
|
289
|
+
const requestId = generateRequestId();
|
|
290
|
+
const session = readSession();
|
|
291
|
+
const form = new FormData();
|
|
292
|
+
form.append("sessionId", sessionId);
|
|
293
|
+
form.append("file", file, file.name);
|
|
294
|
+
const controller = new AbortController();
|
|
295
|
+
const timeoutId = setTimeout(() => controller.abort(), 6e4);
|
|
296
|
+
if (signal) {
|
|
297
|
+
if (signal.aborted) {
|
|
298
|
+
clearTimeout(timeoutId);
|
|
299
|
+
throw new DOMException("Aborted", "AbortError");
|
|
300
|
+
}
|
|
301
|
+
signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const headers = { "X-Request-Id": requestId };
|
|
305
|
+
if (session?.token) headers["Authorization"] = `Bearer ${session.token}`;
|
|
306
|
+
const res = await fetch(url, {
|
|
307
|
+
method: "POST",
|
|
308
|
+
headers,
|
|
309
|
+
body: form,
|
|
310
|
+
signal: controller.signal,
|
|
311
|
+
credentials: "same-origin"
|
|
312
|
+
});
|
|
313
|
+
if (res.status === 401 || res.status === 403) {
|
|
314
|
+
clearSession();
|
|
315
|
+
throw new AuthExpiredError(requestId);
|
|
316
|
+
}
|
|
317
|
+
if (!res.ok) {
|
|
318
|
+
let detail = res.statusText;
|
|
319
|
+
try {
|
|
320
|
+
const errBody = await res.json();
|
|
321
|
+
detail = errBody.message ?? errBody.detail ?? detail;
|
|
322
|
+
} catch {
|
|
323
|
+
}
|
|
324
|
+
throw new ApiError(detail || `HTTP ${res.status}`, res.status, requestId);
|
|
325
|
+
}
|
|
326
|
+
return await res.json();
|
|
327
|
+
} finally {
|
|
328
|
+
clearTimeout(timeoutId);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
287
331
|
async function logout(apiBase, signal) {
|
|
288
332
|
try {
|
|
289
333
|
await request(apiBase, "/auth/logout", {
|
|
@@ -430,6 +474,37 @@ function useChat(opts) {
|
|
|
430
474
|
const deactivatePriorChips = react.useCallback((keepId) => {
|
|
431
475
|
dispatch({ type: "DEACTIVATE_CHIPS", payload: { exceptId: keepId } });
|
|
432
476
|
}, []);
|
|
477
|
+
const uploadCsv = react.useCallback(
|
|
478
|
+
async (file) => {
|
|
479
|
+
if (!sessionId) return;
|
|
480
|
+
abortRef.current?.abort();
|
|
481
|
+
const controller = new AbortController();
|
|
482
|
+
abortRef.current = controller;
|
|
483
|
+
deactivatePriorChips();
|
|
484
|
+
appendUserMessage(`Uploaded ${file.name}`);
|
|
485
|
+
dispatch({ type: "SET_TYPING", payload: true });
|
|
486
|
+
dispatch({ type: "SET_STATUS", payload: "sending" });
|
|
487
|
+
dispatch({ type: "SET_ERROR", payload: null });
|
|
488
|
+
try {
|
|
489
|
+
const resp = await uploadPortfolioCsv(apiBase, file, sessionId, controller.signal);
|
|
490
|
+
appendBotResponse(resp);
|
|
491
|
+
dispatch({ type: "SET_STATUS", payload: "idle" });
|
|
492
|
+
} catch (err) {
|
|
493
|
+
const error = err;
|
|
494
|
+
if (error.name === "AbortError") return;
|
|
495
|
+
if (error instanceof AuthExpiredError) {
|
|
496
|
+
onAuthExpiredRef.current?.();
|
|
497
|
+
} else {
|
|
498
|
+
onErrorRef.current?.(error);
|
|
499
|
+
dispatch({ type: "SET_ERROR", payload: error.message });
|
|
500
|
+
}
|
|
501
|
+
dispatch({ type: "SET_STATUS", payload: "error" });
|
|
502
|
+
} finally {
|
|
503
|
+
dispatch({ type: "SET_TYPING", payload: false });
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
[apiBase, sessionId, appendUserMessage, appendBotResponse, deactivatePriorChips]
|
|
507
|
+
);
|
|
433
508
|
const sendText = react.useCallback(
|
|
434
509
|
async (text) => {
|
|
435
510
|
const trimmed = text.trim();
|
|
@@ -476,6 +551,7 @@ function useChat(opts) {
|
|
|
476
551
|
return {
|
|
477
552
|
state,
|
|
478
553
|
sendText,
|
|
554
|
+
uploadCsv,
|
|
479
555
|
appendBotResponse,
|
|
480
556
|
appendUserMessage,
|
|
481
557
|
deactivatePriorChips,
|
|
@@ -611,6 +687,8 @@ var chat_default = {
|
|
|
611
687
|
positionRight: "chat_positionRight",
|
|
612
688
|
positionLeft: "chat_positionLeft",
|
|
613
689
|
widget: "chat_widget",
|
|
690
|
+
popupBubbleLeft: "chat_popupBubbleLeft",
|
|
691
|
+
popupBubbleRight: "chat_popupBubbleRight",
|
|
614
692
|
header: "chat_header",
|
|
615
693
|
headerTitle: "chat_headerTitle",
|
|
616
694
|
headerMeta: "chat_headerMeta",
|
|
@@ -621,15 +699,19 @@ var chat_default = {
|
|
|
621
699
|
markdown: "chat_markdown",
|
|
622
700
|
bubbleBot: "chat_bubbleBot",
|
|
623
701
|
bubbleUser: "chat_bubbleUser",
|
|
702
|
+
bubbleMeta: "chat_bubbleMeta",
|
|
624
703
|
chipRow: "chat_chipRow",
|
|
625
704
|
chip: "chat_chip",
|
|
626
705
|
chipActive: "chat_chipActive",
|
|
627
706
|
chipDisabled: "chat_chipDisabled",
|
|
628
707
|
typing: "chat_typing",
|
|
629
708
|
typingDot: "chat_typingDot",
|
|
709
|
+
wacBlink: "chat_wacBlink",
|
|
630
710
|
input: "chat_input",
|
|
631
711
|
inputBox: "chat_inputBox",
|
|
632
712
|
sendButton: "chat_sendButton",
|
|
713
|
+
hiddenFileInput: "chat_hiddenFileInput",
|
|
714
|
+
csvButton: "chat_csvButton",
|
|
633
715
|
authGate: "chat_authGate",
|
|
634
716
|
authGateIcon: "chat_authGateIcon",
|
|
635
717
|
authGateTitle: "chat_authGateTitle",
|
|
@@ -637,7 +719,23 @@ var chat_default = {
|
|
|
637
719
|
authGateButton: "chat_authGateButton",
|
|
638
720
|
authGateDisclaimer: "chat_authGateDisclaimer",
|
|
639
721
|
authGateDisclaimerTitle: "chat_authGateDisclaimerTitle",
|
|
640
|
-
errorBanner: "chat_errorBanner"
|
|
722
|
+
errorBanner: "chat_errorBanner",
|
|
723
|
+
popupBubble: "chat_popupBubble",
|
|
724
|
+
wacPopIn: "chat_wacPopIn",
|
|
725
|
+
popupDismiss: "chat_popupDismiss",
|
|
726
|
+
menuGrid: "chat_menuGrid",
|
|
727
|
+
menuItem: "chat_menuItem",
|
|
728
|
+
menuItemIcon: "chat_menuItemIcon",
|
|
729
|
+
menuItemTitle: "chat_menuItemTitle",
|
|
730
|
+
menuItemSub: "chat_menuItemSub",
|
|
731
|
+
menuIconGreen: "chat_menuIconGreen",
|
|
732
|
+
menuIconBlue: "chat_menuIconBlue",
|
|
733
|
+
menuIconLeaf: "chat_menuIconLeaf",
|
|
734
|
+
menuIconRed: "chat_menuIconRed",
|
|
735
|
+
menuIconOrange: "chat_menuIconOrange",
|
|
736
|
+
menuIconGold: "chat_menuIconGold",
|
|
737
|
+
menuIconTeal: "chat_menuIconTeal",
|
|
738
|
+
menuIconPurple: "chat_menuIconPurple"
|
|
641
739
|
};
|
|
642
740
|
function AuthGate({ brandName, loginUrl, onLoginClick }) {
|
|
643
741
|
const handleClick = () => {
|
|
@@ -709,9 +807,10 @@ var ALLOWED_TAGS = [
|
|
|
709
807
|
"ol",
|
|
710
808
|
"li",
|
|
711
809
|
"blockquote",
|
|
712
|
-
"span"
|
|
810
|
+
"span",
|
|
811
|
+
"div"
|
|
713
812
|
];
|
|
714
|
-
var ALLOWED_ATTR = ["href", "target", "rel", "class"];
|
|
813
|
+
var ALLOWED_ATTR = ["href", "target", "rel", "class", "style", "data-wa-gauge-pct"];
|
|
715
814
|
var SANITIZE_CFG = {
|
|
716
815
|
ALLOWED_TAGS,
|
|
717
816
|
ALLOWED_ATTR,
|
|
@@ -725,25 +824,87 @@ function inlineMarkdownToHtml(text) {
|
|
|
725
824
|
function renderMarkdown(text) {
|
|
726
825
|
if (!text) return "";
|
|
727
826
|
const inlined = inlineMarkdownToHtml(text);
|
|
827
|
+
if (/<div[\s>]/i.test(inlined)) {
|
|
828
|
+
return DOMPurify__default.default.sanitize(inlined, SANITIZE_CFG);
|
|
829
|
+
}
|
|
728
830
|
const paragraphs = inlined.split(/\n{2,}/).map((para) => para.replace(/\n/g, "<br>")).filter((p) => p.length > 0);
|
|
729
831
|
const html = paragraphs.length > 1 ? paragraphs.map((p) => `<p>${p}</p>`).join("") : paragraphs[0] ?? "";
|
|
730
832
|
return DOMPurify__default.default.sanitize(html, SANITIZE_CFG);
|
|
731
833
|
}
|
|
834
|
+
var ROOT_MENU_CHIP_IDS = /* @__PURE__ */ new Set([
|
|
835
|
+
"stock_analysis",
|
|
836
|
+
"stock_discovery",
|
|
837
|
+
"new_listings",
|
|
838
|
+
"portfolio_risk",
|
|
839
|
+
"market_forecast",
|
|
840
|
+
"crypto",
|
|
841
|
+
"tradable_picks",
|
|
842
|
+
"peer_compare"
|
|
843
|
+
]);
|
|
844
|
+
var MENU_SUBTITLES = {
|
|
845
|
+
stock_analysis: "Fundamental & technical",
|
|
846
|
+
stock_discovery: "Find promising stocks",
|
|
847
|
+
new_listings: "Track IPOs & new stocks",
|
|
848
|
+
portfolio_risk: "Analyze your portfolio",
|
|
849
|
+
market_forecast: "Outlook & key events",
|
|
850
|
+
crypto: "BTC, ETH & trends",
|
|
851
|
+
tradable_picks: "Short-term setups",
|
|
852
|
+
peer_compare: "Compare peer stocks"
|
|
853
|
+
};
|
|
854
|
+
var MENU_ICON_KEY = {
|
|
855
|
+
stock_analysis: "menuIconGreen",
|
|
856
|
+
stock_discovery: "menuIconBlue",
|
|
857
|
+
new_listings: "menuIconLeaf",
|
|
858
|
+
portfolio_risk: "menuIconRed",
|
|
859
|
+
market_forecast: "menuIconOrange",
|
|
860
|
+
crypto: "menuIconGold",
|
|
861
|
+
tradable_picks: "menuIconTeal",
|
|
862
|
+
peer_compare: "menuIconPurple"
|
|
863
|
+
};
|
|
864
|
+
function isRootMenuChips(chips) {
|
|
865
|
+
return chips.some((c) => ROOT_MENU_CHIP_IDS.has(c.id));
|
|
866
|
+
}
|
|
732
867
|
function MessageBubble({ message, onChipClick }) {
|
|
733
868
|
const isBot = message.role === "bot";
|
|
734
869
|
const bubbleClass = isBot ? `${chat_default.bubble} ${chat_default.bubbleBot}` : `${chat_default.bubble} ${chat_default.bubbleUser}`;
|
|
870
|
+
const handleMarkdownClick = (e) => {
|
|
871
|
+
const anchor = e.target.closest("a");
|
|
872
|
+
if (!anchor?.href) return;
|
|
873
|
+
e.preventDefault();
|
|
874
|
+
window.location.assign(anchor.href);
|
|
875
|
+
};
|
|
876
|
+
const chips = message.chips ?? [];
|
|
877
|
+
const showMenuGrid = isBot && message.chipsActive && isRootMenuChips(chips);
|
|
735
878
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column" }, children: [
|
|
736
879
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: bubbleClass, children: isBot ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
737
880
|
"div",
|
|
738
881
|
{
|
|
739
882
|
className: chat_default.markdown,
|
|
740
|
-
dangerouslySetInnerHTML: { __html: renderMarkdown(message.content) }
|
|
883
|
+
dangerouslySetInnerHTML: { __html: renderMarkdown(message.content) },
|
|
884
|
+
onClick: handleMarkdownClick
|
|
741
885
|
}
|
|
742
886
|
) : message.content }),
|
|
743
|
-
|
|
887
|
+
showMenuGrid ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: chat_default.menuGrid, children: chips.map((chip) => {
|
|
888
|
+
const iconKey = MENU_ICON_KEY[chip.id];
|
|
889
|
+
const iconClass = iconKey && chat_default[iconKey] || "";
|
|
890
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
891
|
+
"button",
|
|
892
|
+
{
|
|
893
|
+
type: "button",
|
|
894
|
+
className: chat_default.menuItem,
|
|
895
|
+
onClick: () => onChipClick(chip),
|
|
896
|
+
children: [
|
|
897
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `${chat_default.menuItemIcon} ${iconClass}`, children: chip.icon }),
|
|
898
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: chat_default.menuItemTitle, children: chip.label }),
|
|
899
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: chat_default.menuItemSub, children: MENU_SUBTITLES[chip.id] ?? "" })
|
|
900
|
+
]
|
|
901
|
+
},
|
|
902
|
+
chip.id
|
|
903
|
+
);
|
|
904
|
+
}) }) : isBot && chips.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
744
905
|
ChipRow,
|
|
745
906
|
{
|
|
746
|
-
chips
|
|
907
|
+
chips,
|
|
747
908
|
disabled: !message.chipsActive,
|
|
748
909
|
onClick: onChipClick
|
|
749
910
|
}
|
|
@@ -847,16 +1008,24 @@ function ChatHeader({
|
|
|
847
1008
|
}
|
|
848
1009
|
function ChatInput({ disabled, placeholder, onSend }) {
|
|
849
1010
|
const [value, setValue] = react.useState("");
|
|
1011
|
+
const inputRef = react.useRef(null);
|
|
1012
|
+
react.useEffect(() => {
|
|
1013
|
+
if (!disabled) {
|
|
1014
|
+
inputRef.current?.focus();
|
|
1015
|
+
}
|
|
1016
|
+
}, [disabled]);
|
|
850
1017
|
const submit = () => {
|
|
851
1018
|
const trimmed = value.trim();
|
|
852
1019
|
if (!trimmed || disabled) return;
|
|
853
1020
|
onSend(trimmed);
|
|
854
1021
|
setValue("");
|
|
1022
|
+
inputRef.current?.focus();
|
|
855
1023
|
};
|
|
856
1024
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chat_default.input, children: [
|
|
857
1025
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
858
1026
|
"input",
|
|
859
1027
|
{
|
|
1028
|
+
ref: inputRef,
|
|
860
1029
|
type: "text",
|
|
861
1030
|
className: chat_default.inputBox,
|
|
862
1031
|
value,
|
|
@@ -884,31 +1053,62 @@ function ChatInput({ disabled, placeholder, onSend }) {
|
|
|
884
1053
|
)
|
|
885
1054
|
] });
|
|
886
1055
|
}
|
|
887
|
-
function FloatingButton({ position, onClick, brandColor }) {
|
|
1056
|
+
function FloatingButton({ position, onClick, brandColor, showPopup, popupMessage, onPopupDismiss }) {
|
|
888
1057
|
const posClass = position === "bottom-left" ? chat_default.positionLeft : chat_default.positionRight;
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1058
|
+
const popupPosClass = position === "bottom-left" ? chat_default.popupBubbleLeft : chat_default.popupBubbleRight;
|
|
1059
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1060
|
+
showPopup && popupMessage ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1061
|
+
"div",
|
|
1062
|
+
{
|
|
1063
|
+
className: `${chat_default.popupBubble} ${popupPosClass}`,
|
|
1064
|
+
role: "status",
|
|
1065
|
+
"aria-live": "polite",
|
|
1066
|
+
children: [
|
|
1067
|
+
popupMessage,
|
|
1068
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1069
|
+
"button",
|
|
1070
|
+
{
|
|
1071
|
+
type: "button",
|
|
1072
|
+
className: chat_default.popupDismiss,
|
|
1073
|
+
onClick: onPopupDismiss,
|
|
1074
|
+
"aria-label": "Dismiss greeting",
|
|
1075
|
+
children: "\u2715"
|
|
1076
|
+
}
|
|
1077
|
+
)
|
|
1078
|
+
]
|
|
1079
|
+
}
|
|
1080
|
+
) : null,
|
|
1081
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1082
|
+
"button",
|
|
1083
|
+
{
|
|
1084
|
+
type: "button",
|
|
1085
|
+
className: `${chat_default.floatingButton} ${posClass}`,
|
|
1086
|
+
onClick,
|
|
1087
|
+
"aria-label": "Open chat",
|
|
1088
|
+
style: brandColor ? { background: brandColor } : void 0,
|
|
1089
|
+
children: "\u{1F4AC}"
|
|
1090
|
+
}
|
|
1091
|
+
)
|
|
1092
|
+
] });
|
|
900
1093
|
}
|
|
901
1094
|
var DEFAULT_BRAND_NAME = "Wealth Alpha AI";
|
|
902
1095
|
var DEFAULT_BRAND_COLOR = "#1a2d5a";
|
|
903
1096
|
var DEFAULT_AUTH_CHECK = "/me";
|
|
904
|
-
var
|
|
905
|
-
var CTA_LINE = "Select a quick command below
|
|
1097
|
+
var DEFAULT_GREETING = "Hi! I'm your Wealth Alpha AI assistant. How can I help you?";
|
|
1098
|
+
var CTA_LINE = "Select a quick command below";
|
|
1099
|
+
var PORTFOLIO_CSV_CHIP_ID = "__portfolio_csv_upload";
|
|
1100
|
+
var UPGRADE_PREMIUM_CHIP_ID = "__upgrade_premium__";
|
|
1101
|
+
function resolvePricingUrl(upgradeUrl) {
|
|
1102
|
+
if (upgradeUrl) return upgradeUrl;
|
|
1103
|
+
if (typeof window !== "undefined") {
|
|
1104
|
+
return `${window.location.origin}/pricing`;
|
|
1105
|
+
}
|
|
1106
|
+
return "";
|
|
1107
|
+
}
|
|
906
1108
|
function buildWelcome(name, plan) {
|
|
907
1109
|
const greeting = name ? `Welcome back, ${name}! You have ${plan ? plan : "Premium"} access.` : `Welcome! You have ${plan ? plan : "Premium"} access.`;
|
|
908
1110
|
return `${greeting}
|
|
909
1111
|
|
|
910
|
-
${DISCLAIMER_BLOCK}
|
|
911
|
-
|
|
912
1112
|
${CTA_LINE}`;
|
|
913
1113
|
}
|
|
914
1114
|
function WealthChat(props) {
|
|
@@ -923,6 +1123,7 @@ function WealthChat(props) {
|
|
|
923
1123
|
position = "bottom-right",
|
|
924
1124
|
defaultOpen = false,
|
|
925
1125
|
showCountdown = true,
|
|
1126
|
+
greetingMessage = DEFAULT_GREETING,
|
|
926
1127
|
onLogin,
|
|
927
1128
|
onLogout,
|
|
928
1129
|
onSessionExpire,
|
|
@@ -930,9 +1131,19 @@ function WealthChat(props) {
|
|
|
930
1131
|
} = props;
|
|
931
1132
|
const [mounted, setMounted] = react.useState(false);
|
|
932
1133
|
const [open, setOpen] = react.useState(defaultOpen);
|
|
1134
|
+
const [showPopup, setShowPopup] = react.useState(false);
|
|
1135
|
+
const popupShownRef = react.useRef(defaultOpen);
|
|
933
1136
|
react.useEffect(() => {
|
|
934
1137
|
setMounted(true);
|
|
935
1138
|
}, []);
|
|
1139
|
+
react.useEffect(() => {
|
|
1140
|
+
if (!mounted || open || popupShownRef.current) return;
|
|
1141
|
+
const timer = setTimeout(() => {
|
|
1142
|
+
setShowPopup(true);
|
|
1143
|
+
popupShownRef.current = true;
|
|
1144
|
+
}, 1e3);
|
|
1145
|
+
return () => clearTimeout(timer);
|
|
1146
|
+
}, [mounted, open]);
|
|
936
1147
|
const {
|
|
937
1148
|
session,
|
|
938
1149
|
remainingMs,
|
|
@@ -960,9 +1171,11 @@ function WealthChat(props) {
|
|
|
960
1171
|
},
|
|
961
1172
|
[session, setHistory]
|
|
962
1173
|
);
|
|
1174
|
+
const csvFileRef = react.useRef(null);
|
|
963
1175
|
const {
|
|
964
1176
|
state: chatState,
|
|
965
1177
|
sendText,
|
|
1178
|
+
uploadCsv,
|
|
966
1179
|
appendBotResponse,
|
|
967
1180
|
appendUserMessage,
|
|
968
1181
|
deactivatePriorChips,
|
|
@@ -1002,16 +1215,46 @@ function WealthChat(props) {
|
|
|
1002
1215
|
async (chip) => {
|
|
1003
1216
|
touch();
|
|
1004
1217
|
deactivatePriorChips();
|
|
1218
|
+
if (chip.id === PORTFOLIO_CSV_CHIP_ID) {
|
|
1219
|
+
appendUserMessage(chip.label);
|
|
1220
|
+
csvFileRef.current?.click();
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
if (chip.id === UPGRADE_PREMIUM_CHIP_ID) {
|
|
1224
|
+
appendUserMessage(chip.label);
|
|
1225
|
+
const pricingUrl = resolvePricingUrl(user?.upgradeUrl);
|
|
1226
|
+
if (pricingUrl && typeof window !== "undefined") {
|
|
1227
|
+
window.location.assign(pricingUrl);
|
|
1228
|
+
}
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1005
1231
|
appendUserMessage(chip.label);
|
|
1006
1232
|
setTyping(true);
|
|
1007
1233
|
try {
|
|
1008
1234
|
const resp = await callChip(chip);
|
|
1009
|
-
if (resp)
|
|
1235
|
+
if (!resp) return;
|
|
1236
|
+
const redirectUrl = resp.metadata?.redirectUrl;
|
|
1237
|
+
if (typeof redirectUrl === "string" && redirectUrl && typeof window !== "undefined") {
|
|
1238
|
+
window.location.assign(redirectUrl);
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
appendBotResponse(resp);
|
|
1010
1242
|
} finally {
|
|
1011
1243
|
setTyping(false);
|
|
1012
1244
|
}
|
|
1013
1245
|
},
|
|
1014
|
-
[touch, deactivatePriorChips, appendUserMessage, callChip, setTyping, appendBotResponse]
|
|
1246
|
+
[touch, deactivatePriorChips, appendUserMessage, callChip, setTyping, appendBotResponse, user?.upgradeUrl]
|
|
1247
|
+
);
|
|
1248
|
+
const handleCsvFileSelected = react.useCallback(
|
|
1249
|
+
(e) => {
|
|
1250
|
+
const file = e.target.files?.[0];
|
|
1251
|
+
e.target.value = "";
|
|
1252
|
+
if (file) {
|
|
1253
|
+
touch();
|
|
1254
|
+
void uploadCsv(file);
|
|
1255
|
+
}
|
|
1256
|
+
},
|
|
1257
|
+
[touch, uploadCsv]
|
|
1015
1258
|
);
|
|
1016
1259
|
const handleSend = react.useCallback(
|
|
1017
1260
|
(text) => {
|
|
@@ -1020,6 +1263,10 @@ function WealthChat(props) {
|
|
|
1020
1263
|
},
|
|
1021
1264
|
[touch, sendText]
|
|
1022
1265
|
);
|
|
1266
|
+
const handleFloatingButtonClick = react.useCallback(() => {
|
|
1267
|
+
setOpen(true);
|
|
1268
|
+
setShowPopup(false);
|
|
1269
|
+
}, []);
|
|
1023
1270
|
const handleClose = react.useCallback(() => setOpen(false), []);
|
|
1024
1271
|
const handleClear = react.useCallback(() => {
|
|
1025
1272
|
clearChat();
|
|
@@ -1036,7 +1283,17 @@ function WealthChat(props) {
|
|
|
1036
1283
|
["--wac-brand"]: brandColor
|
|
1037
1284
|
};
|
|
1038
1285
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chat_default.root, style: rootStyle, children: [
|
|
1039
|
-
!open ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1286
|
+
!open ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1287
|
+
FloatingButton,
|
|
1288
|
+
{
|
|
1289
|
+
position,
|
|
1290
|
+
onClick: handleFloatingButtonClick,
|
|
1291
|
+
brandColor,
|
|
1292
|
+
showPopup,
|
|
1293
|
+
popupMessage: greetingMessage,
|
|
1294
|
+
onPopupDismiss: () => setShowPopup(false)
|
|
1295
|
+
}
|
|
1296
|
+
) : null,
|
|
1040
1297
|
open ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1041
1298
|
"div",
|
|
1042
1299
|
{
|
|
@@ -1066,6 +1323,18 @@ function WealthChat(props) {
|
|
|
1066
1323
|
onLoginClick: handleLoginClick
|
|
1067
1324
|
}
|
|
1068
1325
|
),
|
|
1326
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1327
|
+
"input",
|
|
1328
|
+
{
|
|
1329
|
+
ref: csvFileRef,
|
|
1330
|
+
type: "file",
|
|
1331
|
+
accept: ".csv,text/csv",
|
|
1332
|
+
className: chat_default.hiddenFileInput,
|
|
1333
|
+
onChange: handleCsvFileSelected,
|
|
1334
|
+
"aria-hidden": true,
|
|
1335
|
+
tabIndex: -1
|
|
1336
|
+
}
|
|
1337
|
+
),
|
|
1069
1338
|
isLoggedIn ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1070
1339
|
ChatInput,
|
|
1071
1340
|
{
|