@constructor-io/constructorio-ui-asa 1.0.0
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/LICENSE +21 -0
- package/README.md +253 -0
- package/dist/constructorio-ui-asa-bundled.js +33 -0
- package/lib/cjs/components/Button/Button.css +61 -0
- package/lib/cjs/components/Button/Button.js +12 -0
- package/lib/cjs/components/Chat/AiMessage.css +35 -0
- package/lib/cjs/components/Chat/AiMessage.js +34 -0
- package/lib/cjs/components/Chat/Chat.css +103 -0
- package/lib/cjs/components/Chat/Chat.js +34 -0
- package/lib/cjs/components/Chat/ChatHeader.css +41 -0
- package/lib/cjs/components/Chat/ChatHeader.js +18 -0
- package/lib/cjs/components/Chat/ChatInput.css +83 -0
- package/lib/cjs/components/Chat/ChatInput.js +30 -0
- package/lib/cjs/components/Chat/ChatMessageList.css +23 -0
- package/lib/cjs/components/Chat/ChatMessageList.js +46 -0
- package/lib/cjs/components/Chat/TypingIndicator.css +51 -0
- package/lib/cjs/components/Chat/TypingIndicator.js +14 -0
- package/lib/cjs/components/Chat/UserMessage.css +25 -0
- package/lib/cjs/components/Chat/UserMessage.js +14 -0
- package/lib/cjs/components/Chat/WelcomeScreen.css +205 -0
- package/lib/cjs/components/Chat/WelcomeScreen.js +43 -0
- package/lib/cjs/components/CioAsaProvider/CioAsaProvider.js +23 -0
- package/lib/cjs/components/ResultsBlock/ResultsBlock.css +217 -0
- package/lib/cjs/components/ResultsBlock/ResultsBlock.js +73 -0
- package/lib/cjs/components/ResultsBlock/constants.js +11 -0
- package/lib/cjs/components/ResultsBlock/renderProductCard.js +21 -0
- package/lib/cjs/components/icons/ArrowRightIcon.js +9 -0
- package/lib/cjs/components/icons/ChatBubbleDarkIcon.js +9 -0
- package/lib/cjs/components/icons/ChatBubbleLightIcon.js +9 -0
- package/lib/cjs/components/icons/SendArrowIcon.js +9 -0
- package/lib/cjs/components/icons/SendPlaneIcon.js +9 -0
- package/lib/cjs/components/icons/index.js +16 -0
- package/lib/cjs/constants.js +6 -0
- package/lib/cjs/hooks/asaStreamHandlers.js +34 -0
- package/lib/cjs/hooks/useAsaResults.js +120 -0
- package/lib/cjs/hooks/useCioAsaContext.js +15 -0
- package/lib/cjs/hooks/useCioClient.js +21 -0
- package/lib/cjs/hooks/useFocusTrap.js +38 -0
- package/lib/cjs/hooks/useMessageInput.js +22 -0
- package/lib/cjs/hooks/useRequestConfigs.js +27 -0
- package/lib/cjs/index.js +24 -0
- package/lib/cjs/styles.css +34 -0
- package/lib/cjs/types.js +6 -0
- package/lib/cjs/utils/formatters.js +11 -0
- package/lib/cjs/utils/productNormalizer.js +20 -0
- package/lib/cjs/utils/translate.js +30 -0
- package/lib/cjs/utils/typeHelpers.js +2 -0
- package/lib/cjs/utils/urlHelpers.js +102 -0
- package/lib/cjs/version.js +3 -0
- package/lib/mjs/components/Button/Button.css +61 -0
- package/lib/mjs/components/Button/Button.js +8 -0
- package/lib/mjs/components/Chat/AiMessage.css +35 -0
- package/lib/mjs/components/Chat/AiMessage.js +29 -0
- package/lib/mjs/components/Chat/Chat.css +103 -0
- package/lib/mjs/components/Chat/Chat.js +31 -0
- package/lib/mjs/components/Chat/ChatHeader.css +41 -0
- package/lib/mjs/components/Chat/ChatHeader.js +14 -0
- package/lib/mjs/components/Chat/ChatInput.css +83 -0
- package/lib/mjs/components/Chat/ChatInput.js +26 -0
- package/lib/mjs/components/Chat/ChatMessageList.css +23 -0
- package/lib/mjs/components/Chat/ChatMessageList.js +41 -0
- package/lib/mjs/components/Chat/TypingIndicator.css +51 -0
- package/lib/mjs/components/Chat/TypingIndicator.js +10 -0
- package/lib/mjs/components/Chat/UserMessage.css +25 -0
- package/lib/mjs/components/Chat/UserMessage.js +10 -0
- package/lib/mjs/components/Chat/WelcomeScreen.css +205 -0
- package/lib/mjs/components/Chat/WelcomeScreen.js +38 -0
- package/lib/mjs/components/CioAsaProvider/CioAsaProvider.js +19 -0
- package/lib/mjs/components/ResultsBlock/ResultsBlock.css +217 -0
- package/lib/mjs/components/ResultsBlock/ResultsBlock.js +76 -0
- package/lib/mjs/components/ResultsBlock/constants.js +8 -0
- package/lib/mjs/components/ResultsBlock/renderProductCard.js +17 -0
- package/lib/mjs/components/icons/ArrowRightIcon.js +5 -0
- package/lib/mjs/components/icons/ChatBubbleDarkIcon.js +5 -0
- package/lib/mjs/components/icons/ChatBubbleLightIcon.js +5 -0
- package/lib/mjs/components/icons/SendArrowIcon.js +5 -0
- package/lib/mjs/components/icons/SendPlaneIcon.js +5 -0
- package/lib/mjs/components/icons/index.js +5 -0
- package/lib/mjs/constants.js +3 -0
- package/lib/mjs/hooks/asaStreamHandlers.js +39 -0
- package/lib/mjs/hooks/useAsaResults.js +118 -0
- package/lib/mjs/hooks/useCioAsaContext.js +11 -0
- package/lib/mjs/hooks/useCioClient.js +23 -0
- package/lib/mjs/hooks/useFocusTrap.js +35 -0
- package/lib/mjs/hooks/useMessageInput.js +19 -0
- package/lib/mjs/hooks/useRequestConfigs.js +24 -0
- package/lib/mjs/index.js +11 -0
- package/lib/mjs/styles.css +34 -0
- package/lib/mjs/types.js +2 -0
- package/lib/mjs/utils/formatters.js +7 -0
- package/lib/mjs/utils/productNormalizer.js +15 -0
- package/lib/mjs/utils/translate.js +26 -0
- package/lib/mjs/utils/typeHelpers.js +1 -0
- package/lib/mjs/utils/urlHelpers.js +90 -0
- package/lib/mjs/version.js +1 -0
- package/lib/types/components/Button/Button.d.ts +12 -0
- package/lib/types/components/Chat/AiMessage.d.ts +12 -0
- package/lib/types/components/Chat/Chat.d.ts +38 -0
- package/lib/types/components/Chat/ChatHeader.d.ts +12 -0
- package/lib/types/components/Chat/ChatInput.d.ts +13 -0
- package/lib/types/components/Chat/ChatMessageList.d.ts +24 -0
- package/lib/types/components/Chat/TypingIndicator.d.ts +10 -0
- package/lib/types/components/Chat/UserMessage.d.ts +12 -0
- package/lib/types/components/Chat/WelcomeScreen.d.ts +16 -0
- package/lib/types/components/CioAsaProvider/CioAsaProvider.d.ts +3 -0
- package/lib/types/components/ResultsBlock/ResultsBlock.d.ts +32 -0
- package/lib/types/components/ResultsBlock/constants.d.ts +3 -0
- package/lib/types/components/ResultsBlock/renderProductCard.d.ts +12 -0
- package/lib/types/components/icons/ArrowRightIcon.d.ts +2 -0
- package/lib/types/components/icons/ChatBubbleDarkIcon.d.ts +2 -0
- package/lib/types/components/icons/ChatBubbleLightIcon.d.ts +2 -0
- package/lib/types/components/icons/SendArrowIcon.d.ts +2 -0
- package/lib/types/components/icons/SendPlaneIcon.d.ts +2 -0
- package/lib/types/components/icons/index.d.ts +5 -0
- package/lib/types/constants.d.ts +2 -0
- package/lib/types/hooks/asaStreamHandlers.d.ts +8 -0
- package/lib/types/hooks/useAsaResults.d.ts +2 -0
- package/lib/types/hooks/useCioAsaContext.d.ts +9 -0
- package/lib/types/hooks/useCioClient.d.ts +6 -0
- package/lib/types/hooks/useFocusTrap.d.ts +11 -0
- package/lib/types/hooks/useMessageInput.d.ts +15 -0
- package/lib/types/hooks/useRequestConfigs.d.ts +7 -0
- package/lib/types/index.d.ts +12 -0
- package/lib/types/types.d.ts +178 -0
- package/lib/types/utils/formatters.d.ts +1 -0
- package/lib/types/utils/productNormalizer.d.ts +14 -0
- package/lib/types/utils/translate.d.ts +2 -0
- package/lib/types/utils/typeHelpers.d.ts +4 -0
- package/lib/types/utils/urlHelpers.d.ts +8 -0
- package/lib/types/version.d.ts +2 -0
- package/package.json +150 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PLACEHOLDER_IMAGE = exports.DEMO_API_KEY = void 0;
|
|
4
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
5
|
+
exports.DEMO_API_KEY = 'key_fygjntHGW7usvxC8';
|
|
6
|
+
exports.PLACEHOLDER_IMAGE = "data:image/svg+xml,%3Csvg width='200' height='200' viewBox='0 0 200 200' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='200' height='200' rx='12' fill='%23F3F4F6'/%3E%3Crect x='0.5' y='0.5' width='199' height='199' rx='11.5' stroke='%23E5E7EB'/%3E%3C/svg%3E";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleStreamError = exports.handleStreamEnd = exports.handleServerError = exports.handleMessage = exports.handleSearchResult = void 0;
|
|
4
|
+
function updateMessageById(setMessages, id, patch) {
|
|
5
|
+
setMessages((prev) => prev.map((msg) => (msg.id === id ? patch(msg) : msg)));
|
|
6
|
+
}
|
|
7
|
+
function handleSearchResult(data, pendingGroup, assistantId, setMessages) {
|
|
8
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
9
|
+
const resolvedGroup = pendingGroup !== null && pendingGroup !== void 0 ? pendingGroup : {
|
|
10
|
+
display_name: (_d = (_c = (_b = (_a = data === null || data === void 0 ? void 0 : data.response) === null || _a === void 0 ? void 0 : _a.search_request) === null || _b === void 0 ? void 0 : _b.display_name) !== null && _c !== void 0 ? _c : data === null || data === void 0 ? void 0 : data.title) !== null && _d !== void 0 ? _d : '',
|
|
11
|
+
value: (_h = (_g = (_f = (_e = data === null || data === void 0 ? void 0 : data.response) === null || _e === void 0 ? void 0 : _e.search_request) === null || _f === void 0 ? void 0 : _f.search_term) !== null && _g !== void 0 ? _g : data === null || data === void 0 ? void 0 : data.title) !== null && _h !== void 0 ? _h : '',
|
|
12
|
+
};
|
|
13
|
+
const results = (_k = (_j = data === null || data === void 0 ? void 0 : data.response) === null || _j === void 0 ? void 0 : _j.results) !== null && _k !== void 0 ? _k : ((data === null || data === void 0 ? void 0 : data.results) ? data.results : [data]);
|
|
14
|
+
const newGroup = { group: resolvedGroup, searchResults: results };
|
|
15
|
+
updateMessageById(setMessages, assistantId, (msg) => (Object.assign(Object.assign({}, msg), { status: 'streaming', groups: [...(msg.groups || []), newGroup] })));
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
exports.handleSearchResult = handleSearchResult;
|
|
19
|
+
function handleMessage(data, assistantId, setMessages) {
|
|
20
|
+
updateMessageById(setMessages, assistantId, (msg) => (Object.assign(Object.assign({}, msg), { status: 'streaming', text: (msg.text || '') + ((data === null || data === void 0 ? void 0 : data.text) || '') })));
|
|
21
|
+
}
|
|
22
|
+
exports.handleMessage = handleMessage;
|
|
23
|
+
function handleServerError(assistantId, setMessages) {
|
|
24
|
+
updateMessageById(setMessages, assistantId, (msg) => (Object.assign(Object.assign({}, msg), { status: 'error' })));
|
|
25
|
+
}
|
|
26
|
+
exports.handleServerError = handleServerError;
|
|
27
|
+
function handleStreamEnd(assistantId, setMessages) {
|
|
28
|
+
updateMessageById(setMessages, assistantId, (msg) => (Object.assign(Object.assign({}, msg), { status: 'done' })));
|
|
29
|
+
}
|
|
30
|
+
exports.handleStreamEnd = handleStreamEnd;
|
|
31
|
+
function handleStreamError(assistantId, setMessages) {
|
|
32
|
+
updateMessageById(setMessages, assistantId, (msg) => (Object.assign(Object.assign({}, msg), { status: 'error' })));
|
|
33
|
+
}
|
|
34
|
+
exports.handleStreamError = handleStreamError;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const useCioAsaContext_1 = require("./useCioAsaContext");
|
|
6
|
+
const asaStreamHandlers_1 = require("./asaStreamHandlers");
|
|
7
|
+
function useAsaResults() {
|
|
8
|
+
const [messages, setMessages] = (0, react_1.useState)([]);
|
|
9
|
+
const [isStreaming, setIsStreaming] = (0, react_1.useState)(false);
|
|
10
|
+
const isStreamingRef = (0, react_1.useRef)(false);
|
|
11
|
+
const killSwitchRef = (0, react_1.useRef)(false);
|
|
12
|
+
const readerRef = (0, react_1.useRef)(null);
|
|
13
|
+
const threadIdRef = (0, react_1.useRef)(null);
|
|
14
|
+
const idCounterRef = (0, react_1.useRef)(0);
|
|
15
|
+
const contextValue = (0, useCioAsaContext_1.useCioAsaContext)();
|
|
16
|
+
if (!contextValue) {
|
|
17
|
+
throw new Error('useAsaResults must be used within a CioAsaProvider.');
|
|
18
|
+
}
|
|
19
|
+
const { cioClient, staticRequestConfigs } = contextValue;
|
|
20
|
+
const { domain } = staticRequestConfigs || {};
|
|
21
|
+
if (!cioClient || !domain) {
|
|
22
|
+
throw new Error('useAsaResults requires a configured cioClient and domain. Check your CioAsaProvider props.');
|
|
23
|
+
}
|
|
24
|
+
const nextMessageId = (0, react_1.useCallback)(() => {
|
|
25
|
+
idCounterRef.current += 1;
|
|
26
|
+
return `msg-${idCounterRef.current}-${Date.now()}`;
|
|
27
|
+
}, []);
|
|
28
|
+
const sendMessage = (0, react_1.useCallback)((text) => {
|
|
29
|
+
if (!text.trim() || isStreamingRef.current)
|
|
30
|
+
return;
|
|
31
|
+
const userMessage = {
|
|
32
|
+
id: nextMessageId(),
|
|
33
|
+
role: 'user',
|
|
34
|
+
text: text.trim(),
|
|
35
|
+
status: 'done',
|
|
36
|
+
};
|
|
37
|
+
const assistantMessage = {
|
|
38
|
+
id: nextMessageId(),
|
|
39
|
+
role: 'assistant',
|
|
40
|
+
text: '',
|
|
41
|
+
groups: [],
|
|
42
|
+
status: 'loading',
|
|
43
|
+
};
|
|
44
|
+
setMessages((prev) => [...prev, userMessage, assistantMessage]);
|
|
45
|
+
setIsStreaming(true);
|
|
46
|
+
isStreamingRef.current = true;
|
|
47
|
+
killSwitchRef.current = false;
|
|
48
|
+
const stream = cioClient.agent.getAgentResultsStream(text.trim(), Object.assign({ domain }, (threadIdRef.current && { threadId: threadIdRef.current })));
|
|
49
|
+
const reader = stream.getReader();
|
|
50
|
+
readerRef.current = reader;
|
|
51
|
+
(() => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
var _a, _b, _c, _d;
|
|
53
|
+
let pendingGroup = null;
|
|
54
|
+
try {
|
|
55
|
+
// eslint-disable-next-line no-await-in-loop
|
|
56
|
+
while (!killSwitchRef.current) {
|
|
57
|
+
// eslint-disable-next-line no-await-in-loop
|
|
58
|
+
const res = yield reader.read();
|
|
59
|
+
if (killSwitchRef.current)
|
|
60
|
+
break;
|
|
61
|
+
if (res.done) {
|
|
62
|
+
(0, asaStreamHandlers_1.handleStreamEnd)(assistantMessage.id, setMessages);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
const { type, data } = res.value;
|
|
66
|
+
if (type === 'start' && (data === null || data === void 0 ? void 0 : data.thread_id)) {
|
|
67
|
+
threadIdRef.current = data.thread_id;
|
|
68
|
+
}
|
|
69
|
+
else if (type === 'group') {
|
|
70
|
+
pendingGroup = {
|
|
71
|
+
display_name: (_b = (_a = data === null || data === void 0 ? void 0 : data.display_name) !== null && _a !== void 0 ? _a : data === null || data === void 0 ? void 0 : data.group) !== null && _b !== void 0 ? _b : '',
|
|
72
|
+
value: (_d = (_c = data === null || data === void 0 ? void 0 : data.value) !== null && _c !== void 0 ? _c : data === null || data === void 0 ? void 0 : data.group) !== null && _d !== void 0 ? _d : '',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
else if (type === 'search_result') {
|
|
76
|
+
pendingGroup = (0, asaStreamHandlers_1.handleSearchResult)(data, pendingGroup, assistantMessage.id, setMessages);
|
|
77
|
+
}
|
|
78
|
+
else if (type === 'message') {
|
|
79
|
+
(0, asaStreamHandlers_1.handleMessage)(data, assistantMessage.id, setMessages);
|
|
80
|
+
}
|
|
81
|
+
else if (type === 'server_error') {
|
|
82
|
+
(0, asaStreamHandlers_1.handleServerError)(assistantMessage.id, setMessages);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (_e) {
|
|
88
|
+
(0, asaStreamHandlers_1.handleStreamError)(assistantMessage.id, setMessages);
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
if (readerRef.current === reader) {
|
|
92
|
+
reader.cancel();
|
|
93
|
+
readerRef.current = null;
|
|
94
|
+
setIsStreaming(false);
|
|
95
|
+
isStreamingRef.current = false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}))();
|
|
99
|
+
}, [cioClient, domain, nextMessageId]);
|
|
100
|
+
(0, react_1.useEffect)(() => () => {
|
|
101
|
+
killSwitchRef.current = true;
|
|
102
|
+
if (readerRef.current) {
|
|
103
|
+
readerRef.current.cancel();
|
|
104
|
+
readerRef.current = null;
|
|
105
|
+
}
|
|
106
|
+
}, []);
|
|
107
|
+
const clearHistory = (0, react_1.useCallback)(() => {
|
|
108
|
+
killSwitchRef.current = true;
|
|
109
|
+
if (readerRef.current) {
|
|
110
|
+
readerRef.current.cancel();
|
|
111
|
+
readerRef.current = null;
|
|
112
|
+
}
|
|
113
|
+
threadIdRef.current = null;
|
|
114
|
+
setMessages([]);
|
|
115
|
+
setIsStreaming(false);
|
|
116
|
+
isStreamingRef.current = false;
|
|
117
|
+
}, []);
|
|
118
|
+
return { messages, sendMessage, isStreaming, clearHistory };
|
|
119
|
+
}
|
|
120
|
+
exports.default = useAsaResults;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useCioAsaContext = exports.AsaContext = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
exports.AsaContext = (0, react_1.createContext)(null);
|
|
6
|
+
exports.AsaContext.displayName = 'AsaContext';
|
|
7
|
+
/**
|
|
8
|
+
* React Hook to access state provided by CioAsa provider.
|
|
9
|
+
* Note: Should only be used by components nested under a CioAsa provider
|
|
10
|
+
*/
|
|
11
|
+
function useCioAsaContext() {
|
|
12
|
+
return (0, react_1.useContext)(exports.AsaContext);
|
|
13
|
+
}
|
|
14
|
+
exports.useCioAsaContext = useCioAsaContext;
|
|
15
|
+
exports.default = useCioAsaContext;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const constructorio_client_javascript_1 = tslib_1.__importDefault(require("@constructor-io/constructorio-client-javascript"));
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const version_1 = tslib_1.__importDefault(require("../version"));
|
|
7
|
+
const useCioClient = ({ apiKey, cioClient, cioClientOptions } = {}) => {
|
|
8
|
+
if (!apiKey && !cioClient) {
|
|
9
|
+
throw new Error('Api Key or Constructor Client required');
|
|
10
|
+
}
|
|
11
|
+
const memoizedCioClient = (0, react_1.useMemo)(() => {
|
|
12
|
+
if (cioClient)
|
|
13
|
+
return cioClient;
|
|
14
|
+
if (apiKey && typeof window !== 'undefined') {
|
|
15
|
+
return new constructorio_client_javascript_1.default(Object.assign({ apiKey, sendTrackingEvents: true, version: `cio-ui-asa-${version_1.default}` }, cioClientOptions));
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}, [apiKey, cioClient, cioClientOptions]);
|
|
19
|
+
return memoizedCioClient;
|
|
20
|
+
};
|
|
21
|
+
exports.default = useCioClient;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_1 = require("react");
|
|
4
|
+
const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
5
|
+
/**
|
|
6
|
+
* Traps Tab/Shift+Tab focus within the referenced container and invokes
|
|
7
|
+
* onEscape when Escape is pressed.
|
|
8
|
+
*/
|
|
9
|
+
function useFocusTrap(containerRef, { onEscape } = {}) {
|
|
10
|
+
(0, react_1.useEffect)(() => {
|
|
11
|
+
const container = containerRef.current;
|
|
12
|
+
if (!container)
|
|
13
|
+
return undefined;
|
|
14
|
+
const handleKeyDown = (e) => {
|
|
15
|
+
if (e.key === 'Escape' && onEscape) {
|
|
16
|
+
onEscape();
|
|
17
|
+
}
|
|
18
|
+
if (e.key === 'Tab') {
|
|
19
|
+
const focusableElements = container.querySelectorAll(FOCUSABLE_SELECTOR);
|
|
20
|
+
if (focusableElements.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
const first = focusableElements[0];
|
|
23
|
+
const last = focusableElements[focusableElements.length - 1];
|
|
24
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
last.focus();
|
|
27
|
+
}
|
|
28
|
+
else if (!e.shiftKey && document.activeElement === last) {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
first.focus();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
container.addEventListener('keydown', handleKeyDown);
|
|
35
|
+
return () => container.removeEventListener('keydown', handleKeyDown);
|
|
36
|
+
}, [containerRef, onEscape]);
|
|
37
|
+
}
|
|
38
|
+
exports.default = useFocusTrap;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_1 = require("react");
|
|
4
|
+
function useMessageInput({ onSend, isDisabled = false, submitOnEnterOnly = false, }) {
|
|
5
|
+
const [value, setValue] = (0, react_1.useState)('');
|
|
6
|
+
const handleSubmit = () => {
|
|
7
|
+
if (isDisabled || !value.trim())
|
|
8
|
+
return;
|
|
9
|
+
onSend(value.trim());
|
|
10
|
+
setValue('');
|
|
11
|
+
};
|
|
12
|
+
const handleKeyDown = (e) => {
|
|
13
|
+
if (e.key !== 'Enter')
|
|
14
|
+
return;
|
|
15
|
+
if (submitOnEnterOnly && e.shiftKey)
|
|
16
|
+
return;
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
handleSubmit();
|
|
19
|
+
};
|
|
20
|
+
return { value, setValue, handleSubmit, handleKeyDown };
|
|
21
|
+
}
|
|
22
|
+
exports.default = useMessageInput;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const useCioAsaContext_1 = require("./useCioAsaContext");
|
|
4
|
+
function useRequestConfigs() {
|
|
5
|
+
const context = (0, useCioAsaContext_1.useCioAsaContext)();
|
|
6
|
+
if (!context) {
|
|
7
|
+
throw new Error('This Hook needs to be called within the C.io ASA Context Provider.');
|
|
8
|
+
}
|
|
9
|
+
const { urlHelpers, staticRequestConfigs } = context;
|
|
10
|
+
const { getUrl, setUrl, getStateFromUrl, getUrlFromState } = urlHelpers;
|
|
11
|
+
const url = getUrl();
|
|
12
|
+
const urlRequestConfigs = url ? getStateFromUrl(url) : {};
|
|
13
|
+
const requestConfigs = Object.assign(Object.assign({}, staticRequestConfigs), urlRequestConfigs);
|
|
14
|
+
const setRequestConfigs = (configsToUpdate) => {
|
|
15
|
+
const oldUrl = getUrl();
|
|
16
|
+
if (!oldUrl) {
|
|
17
|
+
throw new Error('getUrl returns undefined when attempting to call setRequestConfigs');
|
|
18
|
+
}
|
|
19
|
+
const urlObj = new URL(oldUrl);
|
|
20
|
+
const oldRequestConfigs = getStateFromUrl(oldUrl);
|
|
21
|
+
const newRequestConfigs = Object.assign(Object.assign({}, oldRequestConfigs), configsToUpdate);
|
|
22
|
+
const newUrl = getUrlFromState(newRequestConfigs, urlObj);
|
|
23
|
+
setUrl(newUrl);
|
|
24
|
+
};
|
|
25
|
+
return { requestConfigs, setRequestConfigs };
|
|
26
|
+
}
|
|
27
|
+
exports.default = useRequestConfigs;
|
package/lib/cjs/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ConstructorIOClient = exports.normalizeItemToProduct = exports.useAsaResults = exports.Button = exports.ResultsBlock = exports.Chat = exports.CioAsaProvider = void 0;
|
|
7
|
+
// Components
|
|
8
|
+
var CioAsaProvider_1 = require("./components/CioAsaProvider/CioAsaProvider");
|
|
9
|
+
Object.defineProperty(exports, "CioAsaProvider", { enumerable: true, get: function () { return __importDefault(CioAsaProvider_1).default; } });
|
|
10
|
+
var Chat_1 = require("./components/Chat/Chat");
|
|
11
|
+
Object.defineProperty(exports, "Chat", { enumerable: true, get: function () { return __importDefault(Chat_1).default; } });
|
|
12
|
+
var ResultsBlock_1 = require("./components/ResultsBlock/ResultsBlock");
|
|
13
|
+
Object.defineProperty(exports, "ResultsBlock", { enumerable: true, get: function () { return __importDefault(ResultsBlock_1).default; } });
|
|
14
|
+
var Button_1 = require("./components/Button/Button");
|
|
15
|
+
Object.defineProperty(exports, "Button", { enumerable: true, get: function () { return __importDefault(Button_1).default; } });
|
|
16
|
+
// Hooks
|
|
17
|
+
var useAsaResults_1 = require("./hooks/useAsaResults");
|
|
18
|
+
Object.defineProperty(exports, "useAsaResults", { enumerable: true, get: function () { return __importDefault(useAsaResults_1).default; } });
|
|
19
|
+
// Utils
|
|
20
|
+
var productNormalizer_1 = require("./utils/productNormalizer");
|
|
21
|
+
Object.defineProperty(exports, "normalizeItemToProduct", { enumerable: true, get: function () { return productNormalizer_1.normalizeItemToProduct; } });
|
|
22
|
+
// Re-exported client value from the JS client (used to construct/type a cioClient)
|
|
23
|
+
var types_1 = require("./types");
|
|
24
|
+
Object.defineProperty(exports, "ConstructorIOClient", { enumerable: true, get: function () { return types_1.ConstructorIOClient; } });
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
@import '@constructor-io/constructorio-ui-components/styles.css';
|
|
2
|
+
@import './components/Button/Button.css';
|
|
3
|
+
@import './components/Chat/Chat.css';
|
|
4
|
+
@import './components/Chat/ChatHeader.css';
|
|
5
|
+
@import './components/Chat/WelcomeScreen.css';
|
|
6
|
+
@import './components/Chat/ChatMessageList.css';
|
|
7
|
+
@import './components/Chat/UserMessage.css';
|
|
8
|
+
@import './components/Chat/AiMessage.css';
|
|
9
|
+
@import './components/Chat/TypingIndicator.css';
|
|
10
|
+
@import './components/Chat/ChatInput.css';
|
|
11
|
+
@import './components/ResultsBlock/ResultsBlock.css';
|
|
12
|
+
|
|
13
|
+
.cio-asa,
|
|
14
|
+
.cio-asa * {
|
|
15
|
+
font-family: var(--cio-asa-font-family, 'Inter', sans-serif);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.cio-sr-only {
|
|
19
|
+
position: absolute;
|
|
20
|
+
width: 1px;
|
|
21
|
+
height: 1px;
|
|
22
|
+
padding: 0;
|
|
23
|
+
margin: -1px;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
clip: rect(0, 0, 0, 0);
|
|
26
|
+
white-space: nowrap;
|
|
27
|
+
border: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.cio-asa button:focus-visible,
|
|
31
|
+
.cio-asa [role='button']:focus-visible {
|
|
32
|
+
outline: 2px solid #A980FF;
|
|
33
|
+
outline-offset: 2px;
|
|
34
|
+
}
|
package/lib/cjs/types.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConstructorIOClient = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const constructorio_client_javascript_1 = tslib_1.__importDefault(require("@constructor-io/constructorio-client-javascript"));
|
|
6
|
+
exports.ConstructorIOClient = constructorio_client_javascript_1.default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatPrice = void 0;
|
|
4
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
5
|
+
function formatPrice(price) {
|
|
6
|
+
if (price) {
|
|
7
|
+
return `$${price.toFixed(2)}`;
|
|
8
|
+
}
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
exports.formatPrice = formatPrice;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeItemToProduct = void 0;
|
|
4
|
+
function normalizeItemToProduct(item, options) {
|
|
5
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
6
|
+
const data = item.data || {};
|
|
7
|
+
return {
|
|
8
|
+
id: String((_c = (_b = (_a = data.id) !== null && _a !== void 0 ? _a : data.result_id) !== null && _b !== void 0 ? _b : item.value) !== null && _c !== void 0 ? _c : ''),
|
|
9
|
+
name: String((_e = (_d = item.value) !== null && _d !== void 0 ? _d : data.item_name) !== null && _e !== void 0 ? _e : ''),
|
|
10
|
+
url: data.url,
|
|
11
|
+
imageUrl: (_f = data.image_url) !== null && _f !== void 0 ? _f : data.imageUrl,
|
|
12
|
+
price: data.price,
|
|
13
|
+
salePrice: (_g = data.sale_price) !== null && _g !== void 0 ? _g : data.salePrice,
|
|
14
|
+
description: data.description,
|
|
15
|
+
badge: data.sale_price != null || data.salePrice != null
|
|
16
|
+
? (_h = options === null || options === void 0 ? void 0 : options.saleBadgeText) !== null && _h !== void 0 ? _h : 'Sale'
|
|
17
|
+
: undefined,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
exports.normalizeItemToProduct = normalizeItemToProduct;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const defaultTranslations = {
|
|
4
|
+
'CioAsa.header.title': 'Shopping Assistant',
|
|
5
|
+
'CioAsa.header.close': 'Close',
|
|
6
|
+
'CioAsa.input.placeholder': 'Ask a question about this product',
|
|
7
|
+
'CioAsa.input.ariaLabel': 'Type your message',
|
|
8
|
+
'CioAsa.input.sendAriaLabel': 'Send message',
|
|
9
|
+
'CioAsa.welcome.title': 'Shopping Assistant',
|
|
10
|
+
'CioAsa.welcome.placeholder': 'Ask anything',
|
|
11
|
+
'CioAsa.welcome.sendButton': 'Chat',
|
|
12
|
+
'CioAsa.welcome.inputAriaLabel': 'Type your question',
|
|
13
|
+
'CioAsa.welcome.sendAriaLabel': 'Send message',
|
|
14
|
+
'CioAsa.welcome.suggestionsAriaLabel': 'Suggested questions',
|
|
15
|
+
'CioAsa.messageList.ariaLabel': 'Chat messages',
|
|
16
|
+
'CioAsa.typingIndicator.ariaLabel': 'Assistant is typing',
|
|
17
|
+
'CioAsa.userMessage.ariaLabel': 'You said',
|
|
18
|
+
'CioAsa.results.viewMore': 'View more products',
|
|
19
|
+
'CioAsa.results.addToCart': 'Add to cart',
|
|
20
|
+
'CioAsa.results.saleBadge': 'Sale',
|
|
21
|
+
'CioAsa.error.message': "I can't assist you with that request.",
|
|
22
|
+
};
|
|
23
|
+
function translate(key, translations) {
|
|
24
|
+
var _a;
|
|
25
|
+
if ((translations === null || translations === void 0 ? void 0 : translations[key]) !== undefined) {
|
|
26
|
+
return translations[key];
|
|
27
|
+
}
|
|
28
|
+
return (_a = defaultTranslations[key]) !== null && _a !== void 0 ? _a : key;
|
|
29
|
+
}
|
|
30
|
+
exports.default = translate;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getUrlFromState = exports.getStateFromUrl = exports.getFilterParamsFromState = exports.extractFiltersFromUrl = exports.setUrl = exports.getUrl = exports.defaultQueryStringMap = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
exports.defaultQueryStringMap = Object.freeze({
|
|
6
|
+
intent: 'q',
|
|
7
|
+
page: 'page',
|
|
8
|
+
numResultsPerPage: 'resultsPerPod',
|
|
9
|
+
domain: 'domain',
|
|
10
|
+
filters: 'filters', // do special encoding/decoding
|
|
11
|
+
});
|
|
12
|
+
function getUrl() {
|
|
13
|
+
if (typeof window === 'undefined')
|
|
14
|
+
return undefined;
|
|
15
|
+
return window.location.href;
|
|
16
|
+
}
|
|
17
|
+
exports.getUrl = getUrl;
|
|
18
|
+
function setUrl(newUrlWithEncodedState) {
|
|
19
|
+
if (typeof window === 'undefined')
|
|
20
|
+
return;
|
|
21
|
+
window.location.href = newUrlWithEncodedState;
|
|
22
|
+
}
|
|
23
|
+
exports.setUrl = setUrl;
|
|
24
|
+
function extractFiltersFromUrl(urlParams) {
|
|
25
|
+
const filters = {};
|
|
26
|
+
const filterRegex = new RegExp(`^${exports.defaultQueryStringMap.filters}\\[(.*)\\]`);
|
|
27
|
+
urlParams === null || urlParams === void 0 ? void 0 : urlParams.forEach((value, key) => {
|
|
28
|
+
var _a;
|
|
29
|
+
const filterKey = (_a = key === null || key === void 0 ? void 0 : key.match(filterRegex)) === null || _a === void 0 ? void 0 : _a[1];
|
|
30
|
+
if (filterKey) {
|
|
31
|
+
if (filters[filterKey]) {
|
|
32
|
+
filters[filterKey].push(value);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
filters[filterKey] = [value];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return Object.keys(filters).length ? filters : undefined;
|
|
40
|
+
}
|
|
41
|
+
exports.extractFiltersFromUrl = extractFiltersFromUrl;
|
|
42
|
+
function getFilterParamsFromState(urlParams, filters) {
|
|
43
|
+
var _a;
|
|
44
|
+
if (filters) {
|
|
45
|
+
(_a = Object.entries(filters)) === null || _a === void 0 ? void 0 : _a.forEach(([filterName, filterValues]) => {
|
|
46
|
+
if (Array.isArray(filterValues)) {
|
|
47
|
+
filterValues.forEach((filterValue) => {
|
|
48
|
+
urlParams.append(`filters[${filterName}]`, String(filterValue));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
urlParams.append(`filters[${filterName}]`, String(filterValues));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.getFilterParamsFromState = getFilterParamsFromState;
|
|
58
|
+
function getStateFromUrl(url) {
|
|
59
|
+
const urlObject = new URL(url);
|
|
60
|
+
const urlParams = urlObject.searchParams;
|
|
61
|
+
const rawState = {};
|
|
62
|
+
Object.entries(exports.defaultQueryStringMap).forEach(([key, val]) => {
|
|
63
|
+
const storedVal = urlParams.get(val);
|
|
64
|
+
if (storedVal != null) {
|
|
65
|
+
rawState[key] = storedVal;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const filters = extractFiltersFromUrl(urlParams);
|
|
69
|
+
const { page, numResultsPerPage } = rawState, rest = tslib_1.__rest(rawState, ["page", "numResultsPerPage"]);
|
|
70
|
+
const state = Object.assign({}, rest);
|
|
71
|
+
if (numResultsPerPage)
|
|
72
|
+
state.numResultsPerPage = Number(numResultsPerPage);
|
|
73
|
+
if (filters)
|
|
74
|
+
state.filters = filters;
|
|
75
|
+
return state;
|
|
76
|
+
}
|
|
77
|
+
exports.getStateFromUrl = getStateFromUrl;
|
|
78
|
+
function getUrlFromState(state, options = {}) {
|
|
79
|
+
const { baseUrl: url, origin, pathname } = options;
|
|
80
|
+
const baseUrl = url || `${origin}${pathname}`;
|
|
81
|
+
const params = new URLSearchParams();
|
|
82
|
+
Object.entries(state).forEach(([key, val]) => {
|
|
83
|
+
if (exports.defaultQueryStringMap[key] === undefined) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let encodedVal = '';
|
|
87
|
+
if (key === 'filters' && state.filters) {
|
|
88
|
+
getFilterParamsFromState(params, state.filters);
|
|
89
|
+
}
|
|
90
|
+
else if (typeof val !== 'string') {
|
|
91
|
+
encodedVal = JSON.stringify(val);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
encodedVal = val;
|
|
95
|
+
}
|
|
96
|
+
if (encodedVal) {
|
|
97
|
+
params.set(exports.defaultQueryStringMap[key], encodedVal);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return `${baseUrl}?${params.toString()}`;
|
|
101
|
+
}
|
|
102
|
+
exports.getUrlFromState = getUrlFromState;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
.cio-asa-button.cio-button {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: row;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: 4px;
|
|
7
|
+
border: none;
|
|
8
|
+
border-radius: 6px;
|
|
9
|
+
cursor: pointer;
|
|
10
|
+
box-shadow:
|
|
11
|
+
0px 6px 14px rgba(18, 18, 105, 0.11),
|
|
12
|
+
0px 2px 4px rgba(18, 18, 105, 0.0717),
|
|
13
|
+
0px 1px 2px rgba(18, 18, 105, 0.055);
|
|
14
|
+
transition: opacity 0.2s;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.cio-asa-button.cio-button:hover {
|
|
18
|
+
opacity: 0.9;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.cio-asa-button.cio-button:active {
|
|
22
|
+
opacity: 0.8;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Dark theme */
|
|
26
|
+
.cio-asa-button--dark.cio-button {
|
|
27
|
+
background: #14151A;
|
|
28
|
+
color: #ffffff;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Light theme */
|
|
32
|
+
.cio-asa-button--light.cio-button {
|
|
33
|
+
background: #ffffff;
|
|
34
|
+
color: #14151A;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Sizes */
|
|
38
|
+
.cio-asa-button--sm.cio-button {
|
|
39
|
+
height: 32px;
|
|
40
|
+
padding: 8px 12px 8px 8px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.cio-asa-button--lg.cio-button {
|
|
44
|
+
height: 40px;
|
|
45
|
+
padding: 10px 16px 10px 12px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.cio-asa-button__icon {
|
|
49
|
+
width: 16px;
|
|
50
|
+
height: 16px;
|
|
51
|
+
flex: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.cio-asa-button__label {
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
font-size: 13px;
|
|
57
|
+
line-height: 16px;
|
|
58
|
+
letter-spacing: -0.005em;
|
|
59
|
+
font-feature-settings: 'tnum' on, 'lnum' on;
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button as CioButton } from '@constructor-io/constructorio-ui-components';
|
|
3
|
+
import { ChatBubbleDarkIcon, ChatBubbleLightIcon } from '../icons';
|
|
4
|
+
export default function Button({ theme = 'dark', size = 'sm', onClick, label = 'Shopping assistant', }) {
|
|
5
|
+
return (React.createElement(CioButton, { className: `cio-asa-button cio-asa-button--${theme} cio-asa-button--${size}`, size: size === 'lg' ? 'default' : 'sm', onClick: onClick },
|
|
6
|
+
theme === 'dark' ? React.createElement(ChatBubbleDarkIcon, null) : React.createElement(ChatBubbleLightIcon, null),
|
|
7
|
+
React.createElement("span", { className: 'cio-asa-button__label' }, label)));
|
|
8
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.cio-asa-ai-message {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: flex-start;
|
|
5
|
+
gap: 12px;
|
|
6
|
+
padding-right: 12px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.cio-asa-ai-message__bubble {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
align-items: flex-start;
|
|
13
|
+
padding: 12px;
|
|
14
|
+
gap: 8px;
|
|
15
|
+
background: #ECEEF3;
|
|
16
|
+
border-radius: 6px;
|
|
17
|
+
width: 100%;
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.cio-asa-ai-message__bubble--error {
|
|
22
|
+
background: #FDECEC;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.cio-asa-ai-message__text {
|
|
26
|
+
font-weight: 400;
|
|
27
|
+
font-size: 14px;
|
|
28
|
+
line-height: 20px;
|
|
29
|
+
letter-spacing: -0.005em;
|
|
30
|
+
color: #0F1324;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.cio-asa-ai-message__bubble--error .cio-asa-ai-message__text {
|
|
34
|
+
color: #B42318;
|
|
35
|
+
}
|