@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smart-cloud/ai-kit-ui",
3
- "version": "1.1.12",
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",
@@ -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, innerCSS, innerCSSHash]);
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 fw="bolder" size="xs">
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
- <Text size="xs">
1217
+ &nbsp;
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
- withinPortal={false}
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 variant="default" onClick={cancelReset}>
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>
@@ -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 {
@@ -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
- customColors![c] = colorsTuple(colors[c]);
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