@smart-cloud/ai-kit-ui 1.1.12 → 1.1.14
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/ai-kit-ui.css +3 -0
- package/dist/index.cjs +9 -9
- package/dist/index.js +9 -9
- package/package.json +3 -1
- package/src/ShadowBoundary.tsx +1 -44
- package/src/ai-chatbot/AiChatbot.tsx +26 -5
- package/src/styles/ai-kit-ui.css +3 -0
- package/src/withAiKitShell.tsx +52 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smart-cloud/ai-kit-ui",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@emotion/cache": "^11.14.0",
|
|
21
21
|
"@emotion/react": "^11.14.0",
|
|
22
|
+
"@mantine/colors-generator": "^8.3.13",
|
|
22
23
|
"@smart-cloud/ai-kit-core": "^1.1.5",
|
|
23
24
|
"@smart-cloud/wpsuite-core": "^2.0.5",
|
|
24
25
|
"@tabler/icons-react": "^3.36.1",
|
|
26
|
+
"chroma-js": "^3.2.0",
|
|
25
27
|
"react-markdown": "^10.1.0",
|
|
26
28
|
"rehype-sanitize": "^6.0.0",
|
|
27
29
|
"rehype-stringify": "^10.0.1",
|
package/src/ShadowBoundary.tsx
CHANGED
|
@@ -12,9 +12,6 @@ export type ShadowBoundaryProps = {
|
|
|
12
12
|
/** Optional class name applied to the host element. */
|
|
13
13
|
className?: string;
|
|
14
14
|
|
|
15
|
-
/** Optional raw CSS text injected into the shadow root (as <style>). */
|
|
16
|
-
innerCSS?: string;
|
|
17
|
-
|
|
18
15
|
/** ID of the element inside the shadow root used as the portal target. */
|
|
19
16
|
rootElementId: string;
|
|
20
17
|
|
|
@@ -89,22 +86,9 @@ function installAiKitPropertyRegistry() {
|
|
|
89
86
|
doc.head.appendChild(style);
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
// tiny stable hash to detect innerCSS changes without forcing huge deps churn
|
|
93
|
-
function hashStringDjb2(str: string): string {
|
|
94
|
-
let hash = 5381;
|
|
95
|
-
for (let i = 0; i < str.length; i++) {
|
|
96
|
-
hash = ((hash << 5) + hash) ^ str.charCodeAt(i);
|
|
97
|
-
}
|
|
98
|
-
// unsigned + base36
|
|
99
|
-
return (hash >>> 0).toString(36);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const STYLE_TEXT_ID = "ai-kit-style-text";
|
|
103
|
-
|
|
104
89
|
export function ShadowBoundary({
|
|
105
90
|
stylesheets,
|
|
106
91
|
className,
|
|
107
|
-
innerCSS,
|
|
108
92
|
children,
|
|
109
93
|
rootElementId,
|
|
110
94
|
mode = "local",
|
|
@@ -121,10 +105,6 @@ export function ShadowBoundary({
|
|
|
121
105
|
return all.join("|");
|
|
122
106
|
}, [stylesheets]);
|
|
123
107
|
|
|
124
|
-
const innerCSSHash = useMemo(() => {
|
|
125
|
-
return innerCSS ? hashStringDjb2(innerCSS) : "";
|
|
126
|
-
}, [innerCSS]);
|
|
127
|
-
|
|
128
108
|
useLayoutEffect(() => {
|
|
129
109
|
if (!hostRef.current) return;
|
|
130
110
|
|
|
@@ -206,29 +186,6 @@ export function ShadowBoundary({
|
|
|
206
186
|
stylesKey ? stylesKey.split("|") : [],
|
|
207
187
|
);
|
|
208
188
|
|
|
209
|
-
// 6) Optional: inject raw style text into the shadow root
|
|
210
|
-
const existingStyle = shadow.getElementById(
|
|
211
|
-
STYLE_TEXT_ID,
|
|
212
|
-
) as HTMLStyleElement | null;
|
|
213
|
-
|
|
214
|
-
if (innerCSS) {
|
|
215
|
-
if (!existingStyle) {
|
|
216
|
-
const s = doc.createElement("style");
|
|
217
|
-
s.id = STYLE_TEXT_ID;
|
|
218
|
-
s.setAttribute("data-hash", innerCSSHash);
|
|
219
|
-
s.textContent = innerCSS;
|
|
220
|
-
rootEl.appendChild(s);
|
|
221
|
-
} else {
|
|
222
|
-
const prevHash = existingStyle.getAttribute("data-hash") || "";
|
|
223
|
-
if (prevHash !== innerCSSHash) {
|
|
224
|
-
existingStyle.setAttribute("data-hash", innerCSSHash);
|
|
225
|
-
existingStyle.textContent = innerCSS;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
} else if (existingStyle) {
|
|
229
|
-
existingStyle.remove();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
189
|
setShadowRoot(shadow);
|
|
233
190
|
setPortalTarget(rootEl);
|
|
234
191
|
|
|
@@ -236,7 +193,7 @@ export function ShadowBoundary({
|
|
|
236
193
|
mo.disconnect();
|
|
237
194
|
mq?.removeEventListener?.("change", onMq);
|
|
238
195
|
};
|
|
239
|
-
}, [mode, overlayRootId, rootElementId, stylesKey
|
|
196
|
+
}, [mode, overlayRootId, rootElementId, stylesKey]);
|
|
240
197
|
|
|
241
198
|
const emotionCache = useMemo(() => {
|
|
242
199
|
if (!portalTarget) return null;
|
|
@@ -734,6 +734,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
734
734
|
// mark last sent timestamp on successful request completion
|
|
735
735
|
setLastUserSentAt(userMessageCreatedAt);
|
|
736
736
|
} catch (e) {
|
|
737
|
+
console.error("Error during ask()", e);
|
|
737
738
|
// Cancel: treat as not sent, no error bubble
|
|
738
739
|
if (
|
|
739
740
|
cancelRequestedRef.current ||
|
|
@@ -1156,6 +1157,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1156
1157
|
? I18n.get(labels.restoreSizeLabel)
|
|
1157
1158
|
: I18n.get(labels.maximizeLabel)
|
|
1158
1159
|
}
|
|
1160
|
+
data-ai-kit-maximize-button
|
|
1159
1161
|
>
|
|
1160
1162
|
{isMaximized ? (
|
|
1161
1163
|
<IconMinimize size={16} />
|
|
@@ -1196,18 +1198,24 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1196
1198
|
>
|
|
1197
1199
|
<Stack
|
|
1198
1200
|
gap={4}
|
|
1201
|
+
w="100%"
|
|
1199
1202
|
style={{
|
|
1200
1203
|
alignItems: isUser ? "flex-end" : "flex-start",
|
|
1201
1204
|
}}
|
|
1202
1205
|
>
|
|
1203
1206
|
<Stack className="ai-chat-bubble">
|
|
1204
1207
|
<Text className="ai-chat-header">
|
|
1205
|
-
<Text
|
|
1208
|
+
<Text
|
|
1209
|
+
fw="bolder"
|
|
1210
|
+
size="xs"
|
|
1211
|
+
style={{ whiteSpace: "nowrap" }}
|
|
1212
|
+
>
|
|
1206
1213
|
{isUser
|
|
1207
1214
|
? I18n.get(labels.userLabel)
|
|
1208
1215
|
: I18n.get(labels.assistantLabel)}
|
|
1209
1216
|
</Text>
|
|
1210
|
-
|
|
1217
|
+
|
|
1218
|
+
<Text size="xs" style={{ whiteSpace: "nowrap" }}>
|
|
1211
1219
|
{new Date(msg.createdAt).toLocaleTimeString([], {
|
|
1212
1220
|
hour: "2-digit",
|
|
1213
1221
|
minute: "2-digit",
|
|
@@ -1238,6 +1246,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1238
1246
|
onClick={() => handleEditCanceled(msg)}
|
|
1239
1247
|
title={I18n.get(labels.editLabel)}
|
|
1240
1248
|
aria-label={I18n.get(labels.editLabel)}
|
|
1249
|
+
data-ai-kit-edit-button
|
|
1241
1250
|
>
|
|
1242
1251
|
<IconPencil size={14} />
|
|
1243
1252
|
</ActionIcon>
|
|
@@ -1291,6 +1300,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1291
1300
|
onClick={() => updateFeedback(msg.id, "accepted")}
|
|
1292
1301
|
aria-label={I18n.get(labels.acceptResponseLabel)}
|
|
1293
1302
|
disabled={ai.busy}
|
|
1303
|
+
data-ai-kit-feedback-accept-button
|
|
1294
1304
|
>
|
|
1295
1305
|
👍
|
|
1296
1306
|
</Button>
|
|
@@ -1302,6 +1312,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1302
1312
|
onClick={() => updateFeedback(msg.id, "rejected")}
|
|
1303
1313
|
aria-label={I18n.get(labels.rejectResponseLabel)}
|
|
1304
1314
|
disabled={ai.busy}
|
|
1315
|
+
data-ai-kit-feedback-reject-button
|
|
1305
1316
|
>
|
|
1306
1317
|
👎
|
|
1307
1318
|
</Button>
|
|
@@ -1350,19 +1361,25 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1350
1361
|
onClose={cancelReset}
|
|
1351
1362
|
centered
|
|
1352
1363
|
title={I18n.get("Reset conversation")}
|
|
1353
|
-
|
|
1364
|
+
style={{ position: "fixed" }}
|
|
1365
|
+
left={0}
|
|
1354
1366
|
>
|
|
1355
1367
|
<Text size="sm">
|
|
1356
1368
|
{I18n.get("Are you sure you want to reset the conversation?")}
|
|
1357
1369
|
</Text>
|
|
1358
1370
|
<Group justify="flex-end" mt="md">
|
|
1359
|
-
<Button
|
|
1371
|
+
<Button
|
|
1372
|
+
variant="default"
|
|
1373
|
+
onClick={cancelReset}
|
|
1374
|
+
data-ai-kit-no-button
|
|
1375
|
+
>
|
|
1360
1376
|
{I18n.get("No")}
|
|
1361
1377
|
</Button>
|
|
1362
1378
|
<Button
|
|
1363
|
-
color="red"
|
|
1379
|
+
color="var(--ai-kit-color-danger, red)"
|
|
1364
1380
|
onClick={confirmReset}
|
|
1365
1381
|
disabled={!hasMessages && !isChatBusy}
|
|
1382
|
+
data-ai-kit-yes-button
|
|
1366
1383
|
>
|
|
1367
1384
|
{I18n.get("Yes")}
|
|
1368
1385
|
</Button>
|
|
@@ -1390,6 +1407,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1390
1407
|
leftSection={<IconTrash size={18} />}
|
|
1391
1408
|
onClick={handleResetClick}
|
|
1392
1409
|
disabled={!hasMessages && !isChatBusy}
|
|
1410
|
+
data-ai-kit-reset-button
|
|
1393
1411
|
>
|
|
1394
1412
|
{I18n.get(labels.resetLabel)}
|
|
1395
1413
|
</Button>
|
|
@@ -1402,6 +1420,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1402
1420
|
onClick={() => fileInputRef.current?.click()}
|
|
1403
1421
|
disabled={images.length >= resolvedMaxImages}
|
|
1404
1422
|
title={I18n.get(labels.addImageLabel)}
|
|
1423
|
+
data-ai-kit-add-image-button
|
|
1405
1424
|
>
|
|
1406
1425
|
{I18n.get(labels.addLabel)}
|
|
1407
1426
|
</Button>
|
|
@@ -1420,6 +1439,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1420
1439
|
variant="filled"
|
|
1421
1440
|
onClick={onSendOrCancel}
|
|
1422
1441
|
disabled={!isChatBusy && !canSend}
|
|
1442
|
+
data-ai-kit-send-button
|
|
1423
1443
|
>
|
|
1424
1444
|
{sendOrCancelLabel}
|
|
1425
1445
|
</Button>
|
|
@@ -1450,6 +1470,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1450
1470
|
p={0}
|
|
1451
1471
|
className="remove-image-button"
|
|
1452
1472
|
title={I18n.get(labels.removeImageLabel)}
|
|
1473
|
+
data-ai-kit-remove-image-button
|
|
1453
1474
|
>
|
|
1454
1475
|
X
|
|
1455
1476
|
</Button>
|
package/src/styles/ai-kit-ui.css
CHANGED
|
@@ -609,6 +609,7 @@
|
|
|
609
609
|
|
|
610
610
|
/* Header / status */
|
|
611
611
|
.ai-chat-header-bar {
|
|
612
|
+
background: var(--ai-kit-chat-surface);
|
|
612
613
|
display: flex;
|
|
613
614
|
align-items: center;
|
|
614
615
|
justify-content: space-between;
|
|
@@ -704,6 +705,8 @@
|
|
|
704
705
|
word-break: break-word;
|
|
705
706
|
hyphens: auto;
|
|
706
707
|
line-height: var(--ai-kit-line-height);
|
|
708
|
+
width: fit-content;
|
|
709
|
+
max-width: 80%;
|
|
707
710
|
}
|
|
708
711
|
|
|
709
712
|
.ai-chat-row.user .ai-chat-bubble {
|
package/src/withAiKitShell.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { generateColors } from "@mantine/colors-generator";
|
|
1
2
|
import {
|
|
2
3
|
colorsTuple,
|
|
3
4
|
createTheme,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
} from "@smart-cloud/ai-kit-core";
|
|
14
15
|
import { useSelect } from "@wordpress/data";
|
|
15
16
|
import { I18n } from "aws-amplify/utils";
|
|
16
|
-
import { type ComponentType, useMemo, useState } from "react";
|
|
17
|
+
import { type ComponentType, useCallback, useMemo, useState } from "react";
|
|
17
18
|
import { ShadowBoundary } from "./ShadowBoundary";
|
|
18
19
|
|
|
19
20
|
export type AiKitShellInjectedProps = {
|
|
@@ -21,6 +22,17 @@ export type AiKitShellInjectedProps = {
|
|
|
21
22
|
rootElement: HTMLElement;
|
|
22
23
|
};
|
|
23
24
|
|
|
25
|
+
function hashStringDjb2(str: string): string {
|
|
26
|
+
let hash = 5381;
|
|
27
|
+
for (let i = 0; i < str.length; i++) {
|
|
28
|
+
hash = ((hash << 5) + hash) ^ str.charCodeAt(i);
|
|
29
|
+
}
|
|
30
|
+
// unsigned + base36
|
|
31
|
+
return (hash >>> 0).toString(36);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const STYLE_TEXT_ID = "ai-kit-style-text";
|
|
35
|
+
|
|
24
36
|
export function withAiKitShell<P extends object>(
|
|
25
37
|
RootComponent: ComponentType<P & AiKitShellInjectedProps>,
|
|
26
38
|
propOverrides?: Partial<AiWorkerProps>,
|
|
@@ -96,10 +108,16 @@ export function withAiKitShell<P extends object>(
|
|
|
96
108
|
if (colors) {
|
|
97
109
|
customColors = {};
|
|
98
110
|
Object.keys(colors).forEach((c) => {
|
|
99
|
-
|
|
111
|
+
try {
|
|
112
|
+
customColors![c] = generateColors(colors[c]);
|
|
113
|
+
} catch {
|
|
114
|
+
customColors![c] = colorsTuple(colors[c]);
|
|
115
|
+
}
|
|
100
116
|
});
|
|
101
117
|
}
|
|
102
118
|
|
|
119
|
+
console.log("withAiKitShell rendered", { customColors });
|
|
120
|
+
|
|
103
121
|
const theme = createTheme({
|
|
104
122
|
respectReducedMotion: true,
|
|
105
123
|
...(customColors && { colors: customColors }),
|
|
@@ -159,13 +177,43 @@ export function withAiKitShell<P extends object>(
|
|
|
159
177
|
},
|
|
160
178
|
});
|
|
161
179
|
|
|
180
|
+
const innerCSSHash = useMemo(() => {
|
|
181
|
+
return innerCSS ? hashStringDjb2(innerCSS) : "";
|
|
182
|
+
}, [innerCSS]);
|
|
183
|
+
|
|
184
|
+
const injectStyle = useCallback(
|
|
185
|
+
(rootElement: HTMLDivElement) => {
|
|
186
|
+
const existingStyle = rootElement.ownerDocument.getElementById(
|
|
187
|
+
STYLE_TEXT_ID,
|
|
188
|
+
) as HTMLStyleElement | null;
|
|
189
|
+
|
|
190
|
+
if (innerCSS) {
|
|
191
|
+
if (!existingStyle) {
|
|
192
|
+
const s = rootElement.ownerDocument.createElement("style");
|
|
193
|
+
s.id = STYLE_TEXT_ID;
|
|
194
|
+
s.setAttribute("data-hash", innerCSSHash);
|
|
195
|
+
s.textContent = innerCSS;
|
|
196
|
+
rootElement.appendChild(s);
|
|
197
|
+
} else {
|
|
198
|
+
const prevHash = existingStyle.getAttribute("data-hash") || "";
|
|
199
|
+
if (prevHash !== innerCSSHash) {
|
|
200
|
+
existingStyle.setAttribute("data-hash", innerCSSHash);
|
|
201
|
+
existingStyle.textContent = innerCSS;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} else if (existingStyle) {
|
|
205
|
+
existingStyle.remove();
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
[innerCSS, innerCSSHash],
|
|
209
|
+
);
|
|
210
|
+
|
|
162
211
|
return (
|
|
163
212
|
<ShadowBoundary
|
|
164
213
|
mode={variation === "modal" && !showOpenButton ? "overlay" : "local"}
|
|
165
214
|
variation={variation}
|
|
166
215
|
overlayRootId="ai-kit-overlay-root"
|
|
167
216
|
stylesheets={stylesheets}
|
|
168
|
-
innerCSS={innerCSS}
|
|
169
217
|
className={className}
|
|
170
218
|
rootElementId={
|
|
171
219
|
variation === "modal" && !showOpenButton
|
|
@@ -174,6 +222,7 @@ export function withAiKitShell<P extends object>(
|
|
|
174
222
|
}
|
|
175
223
|
>
|
|
176
224
|
{({ rootElement }) => {
|
|
225
|
+
injectStyle(rootElement);
|
|
177
226
|
rootElement.setAttribute(
|
|
178
227
|
"data-ai-kit-variation",
|
|
179
228
|
variation || "default",
|
|
@@ -195,7 +244,6 @@ export function withAiKitShell<P extends object>(
|
|
|
195
244
|
<MantineProvider
|
|
196
245
|
forceColorScheme={resolved}
|
|
197
246
|
theme={theme}
|
|
198
|
-
cssVariablesSelector={`#${rootElement.id}`}
|
|
199
247
|
getRootElement={() => rootElement as unknown as HTMLElement}
|
|
200
248
|
>
|
|
201
249
|
<RootComponent
|