@smart-cloud/ai-kit-ui 1.1.11 → 1.1.13
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 +27 -7
- package/dist/index.cjs +9 -9
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +9 -9
- package/package.json +4 -2
- package/src/ShadowBoundary.tsx +1 -44
- package/src/ai-chatbot/AiChatbot.tsx +120 -33
- package/src/ai-chatbot/index.tsx +1 -1
- package/src/styles/ai-kit-ui.css +27 -7
- package/src/withAiKitShell.tsx +61 -5
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.13",
|
|
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
|
-
"@
|
|
22
|
+
"@mantine/colors-generator": "^8.3.13",
|
|
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;
|
|
@@ -57,7 +57,7 @@ const HISTORY_STORAGE_KEY = `ai-kit-chatbot-history-v1:${
|
|
|
57
57
|
typeof window !== "undefined" ? window.location.hostname : "unknown"
|
|
58
58
|
}`;
|
|
59
59
|
|
|
60
|
-
const
|
|
60
|
+
export const DEFAULT_CHATBOT_LABELS: Required<AiChatbotLabels> = {
|
|
61
61
|
modalTitle: "AI Assistant",
|
|
62
62
|
|
|
63
63
|
userLabel: "User",
|
|
@@ -234,7 +234,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
234
234
|
} = props;
|
|
235
235
|
|
|
236
236
|
const labels = useMemo(
|
|
237
|
-
() => ({ ...
|
|
237
|
+
() => ({ ...DEFAULT_CHATBOT_LABELS, ...(labelsOverride || {}) }),
|
|
238
238
|
[labelsOverride],
|
|
239
239
|
);
|
|
240
240
|
|
|
@@ -253,6 +253,10 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
253
253
|
const [opened, setOpened] = useState(false);
|
|
254
254
|
const [stickToBottom, setStickToBottom] = useState(true);
|
|
255
255
|
|
|
256
|
+
const [wheelHostEl, setWheelHostEl] = useState<HTMLDivElement | null>(null);
|
|
257
|
+
const [scrollerEl, setScrollerEl] = useState<HTMLDivElement | null>(null);
|
|
258
|
+
const [bodyScrollable, setBodyScrollable] = useState(false);
|
|
259
|
+
|
|
256
260
|
const [activeOp, setActiveOp] = useState<ActiveOp>(null);
|
|
257
261
|
const activeOpRef = useRef<ActiveOp>(activeOp);
|
|
258
262
|
useEffect(() => {
|
|
@@ -267,7 +271,6 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
267
271
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
268
272
|
const questionInputRef = useRef<HTMLTextAreaElement>(null);
|
|
269
273
|
const sessionRef = useRef<{ id: string; storedAt: number } | null>(null);
|
|
270
|
-
const chatScrollRef = useRef<HTMLDivElement>(null);
|
|
271
274
|
const chatContainerRef = useRef<HTMLDivElement>(null);
|
|
272
275
|
|
|
273
276
|
// New: persist timestamp of last actually-sent user message
|
|
@@ -356,18 +359,13 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
356
359
|
const viewportMax = Math.floor(vh * 0.8);
|
|
357
360
|
const target = Math.max(minHeight, Math.min(viewportMax, maxHeightCap));
|
|
358
361
|
el.style.height = `${target}px`;
|
|
359
|
-
const scrollEl = chatScrollRef.current;
|
|
360
|
-
if (scrollEl) {
|
|
361
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
362
|
-
scrollEl.offsetHeight;
|
|
363
|
-
}
|
|
364
362
|
} catch {
|
|
365
363
|
// ignore
|
|
366
364
|
}
|
|
367
365
|
}, []);
|
|
368
366
|
|
|
369
367
|
const scrollToBottom = useCallback(() => {
|
|
370
|
-
const el =
|
|
368
|
+
const el = scrollerEl;
|
|
371
369
|
if (!el) return;
|
|
372
370
|
window.setTimeout(() => {
|
|
373
371
|
try {
|
|
@@ -376,7 +374,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
376
374
|
// ignore
|
|
377
375
|
}
|
|
378
376
|
}, 50);
|
|
379
|
-
}, []);
|
|
377
|
+
}, [scrollerEl]);
|
|
380
378
|
|
|
381
379
|
const closeModal = useCallback(() => {
|
|
382
380
|
setOpened(false);
|
|
@@ -393,19 +391,12 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
393
391
|
}, [opened, adjustChatHeight]);
|
|
394
392
|
|
|
395
393
|
useEffect(() => {
|
|
396
|
-
if (!opened) return;
|
|
394
|
+
if (!opened || !isMaximized) return;
|
|
397
395
|
document.body.style.overflow = "hidden";
|
|
398
|
-
document.body.onkeydown = (e: KeyboardEvent) => {
|
|
399
|
-
if (e.key === "Escape") {
|
|
400
|
-
e.preventDefault();
|
|
401
|
-
closeModal();
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
396
|
return () => {
|
|
405
397
|
document.body.style.overflow = "";
|
|
406
|
-
document.body.onkeydown = null;
|
|
407
398
|
};
|
|
408
|
-
}, [opened, closeModal]);
|
|
399
|
+
}, [opened, isMaximized, closeModal]);
|
|
409
400
|
|
|
410
401
|
const imagePreviews = useMemo(() => {
|
|
411
402
|
if (
|
|
@@ -435,7 +426,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
435
426
|
}, [imagePreviews]);
|
|
436
427
|
|
|
437
428
|
useEffect(() => {
|
|
438
|
-
const el =
|
|
429
|
+
const el = scrollerEl;
|
|
439
430
|
if (!el) return;
|
|
440
431
|
const handleScroll = () => {
|
|
441
432
|
const distanceFromBottom =
|
|
@@ -446,16 +437,16 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
446
437
|
return () => {
|
|
447
438
|
el.removeEventListener("scroll", handleScroll);
|
|
448
439
|
};
|
|
449
|
-
}, [opened]);
|
|
440
|
+
}, [opened, scrollerEl]);
|
|
450
441
|
|
|
451
442
|
useEffect(() => {
|
|
452
443
|
if (!stickToBottom) return;
|
|
453
|
-
const el =
|
|
444
|
+
const el = scrollerEl;
|
|
454
445
|
if (!el) return;
|
|
455
446
|
if (el.scrollHeight > el.clientHeight) {
|
|
456
447
|
el.scrollTop = el.scrollHeight;
|
|
457
448
|
}
|
|
458
|
-
}, [messages, ai.busy, stickToBottom]);
|
|
449
|
+
}, [messages, ai.busy, stickToBottom, scrollerEl]);
|
|
459
450
|
|
|
460
451
|
const statusText = useMemo(() => {
|
|
461
452
|
if (!ai.busy) return null;
|
|
@@ -849,10 +840,16 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
849
840
|
const renderOpenButtonIcon = useMemo(() => {
|
|
850
841
|
if (!showOpenButtonIcon) return null;
|
|
851
842
|
if (openButtonIcon) {
|
|
852
|
-
return
|
|
843
|
+
return (
|
|
844
|
+
<img
|
|
845
|
+
src={openButtonIcon}
|
|
846
|
+
className="ai-open-btn-icon"
|
|
847
|
+
alt={I18n.get(labels.askMeLabel || openButtonLabel)}
|
|
848
|
+
/>
|
|
849
|
+
);
|
|
853
850
|
}
|
|
854
851
|
return <IconMessage size={18} />;
|
|
855
|
-
}, [showOpenButtonIcon, openButtonIcon]);
|
|
852
|
+
}, [showOpenButtonIcon, openButtonIcon, labels, openButtonLabel, language]);
|
|
856
853
|
|
|
857
854
|
const openButtonContent = useMemo(() => {
|
|
858
855
|
const iconEl = renderOpenButtonIcon;
|
|
@@ -942,6 +939,69 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
942
939
|
void ask();
|
|
943
940
|
}, [isChatBusy, cancelChat, ask]);
|
|
944
941
|
|
|
942
|
+
useEffect(() => {
|
|
943
|
+
if (!opened) return;
|
|
944
|
+
if (!wheelHostEl || !scrollerEl) return;
|
|
945
|
+
|
|
946
|
+
const isScrollableNow = () => {
|
|
947
|
+
const cs = window.getComputedStyle(scrollerEl);
|
|
948
|
+
const overflowY = cs.overflowY;
|
|
949
|
+
const canOverflow = overflowY === "auto" || overflowY === "scroll";
|
|
950
|
+
|
|
951
|
+
const sh = Math.ceil(scrollerEl.scrollHeight);
|
|
952
|
+
const ch = Math.floor(scrollerEl.clientHeight);
|
|
953
|
+
const hasOverflow = sh > ch;
|
|
954
|
+
return canOverflow && hasOverflow;
|
|
955
|
+
};
|
|
956
|
+
let enabled = false;
|
|
957
|
+
|
|
958
|
+
const onWheel = (e: WheelEvent) => {
|
|
959
|
+
if (!isScrollableNow()) return;
|
|
960
|
+
|
|
961
|
+
e.preventDefault();
|
|
962
|
+
|
|
963
|
+
const max = scrollerEl.scrollHeight - scrollerEl.clientHeight;
|
|
964
|
+
scrollerEl.scrollTop = Math.max(
|
|
965
|
+
0,
|
|
966
|
+
Math.min(max, scrollerEl.scrollTop + e.deltaY),
|
|
967
|
+
);
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
const sync = () => {
|
|
971
|
+
const shouldEnable = isScrollableNow();
|
|
972
|
+
setBodyScrollable(shouldEnable);
|
|
973
|
+
if (shouldEnable === enabled) return;
|
|
974
|
+
enabled = shouldEnable;
|
|
975
|
+
if (enabled) {
|
|
976
|
+
wheelHostEl.addEventListener("wheel", onWheel, { passive: false });
|
|
977
|
+
} else {
|
|
978
|
+
wheelHostEl.removeEventListener("wheel", onWheel as EventListener);
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
sync();
|
|
983
|
+
|
|
984
|
+
const ro = new ResizeObserver(sync);
|
|
985
|
+
ro.observe(scrollerEl);
|
|
986
|
+
|
|
987
|
+
const mo = new MutationObserver(sync);
|
|
988
|
+
mo.observe(scrollerEl, {
|
|
989
|
+
childList: true,
|
|
990
|
+
subtree: true,
|
|
991
|
+
characterData: true,
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
window.addEventListener("resize", sync);
|
|
995
|
+
|
|
996
|
+
return () => {
|
|
997
|
+
if (enabled)
|
|
998
|
+
wheelHostEl.removeEventListener("wheel", onWheel as EventListener);
|
|
999
|
+
ro.disconnect();
|
|
1000
|
+
mo.disconnect();
|
|
1001
|
+
window.removeEventListener("resize", sync);
|
|
1002
|
+
};
|
|
1003
|
+
}, [opened, wheelHostEl, scrollerEl]);
|
|
1004
|
+
|
|
945
1005
|
// -----------------------------
|
|
946
1006
|
// History persistence
|
|
947
1007
|
// -----------------------------
|
|
@@ -1063,6 +1123,9 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1063
1123
|
<Modal.Root
|
|
1064
1124
|
ref={chatContainerRef}
|
|
1065
1125
|
opened={opened}
|
|
1126
|
+
lockScroll={false}
|
|
1127
|
+
trapFocus={false}
|
|
1128
|
+
closeOnEscape={true}
|
|
1066
1129
|
onClose={closeModal}
|
|
1067
1130
|
className={
|
|
1068
1131
|
rootClassName +
|
|
@@ -1074,7 +1137,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1074
1137
|
data-ai-kit-theme={colorMode}
|
|
1075
1138
|
data-ai-kit-variation="modal"
|
|
1076
1139
|
>
|
|
1077
|
-
<
|
|
1140
|
+
<div className="ai-chat-container-internal" ref={setWheelHostEl}>
|
|
1078
1141
|
<Modal.Header className="ai-chat-header-bar">
|
|
1079
1142
|
<Modal.Title className="ai-chat-title">{modalTitle}</Modal.Title>
|
|
1080
1143
|
<Group gap="4px" align="center" justify="center">
|
|
@@ -1093,6 +1156,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1093
1156
|
? I18n.get(labels.restoreSizeLabel)
|
|
1094
1157
|
: I18n.get(labels.maximizeLabel)
|
|
1095
1158
|
}
|
|
1159
|
+
data-ai-kit-maximize-button
|
|
1096
1160
|
>
|
|
1097
1161
|
{isMaximized ? (
|
|
1098
1162
|
<IconMinimize size={16} />
|
|
@@ -1107,7 +1171,11 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1107
1171
|
</Group>
|
|
1108
1172
|
</Modal.Header>
|
|
1109
1173
|
|
|
1110
|
-
<Modal.Body
|
|
1174
|
+
<Modal.Body
|
|
1175
|
+
className="ai-chat-scroll"
|
|
1176
|
+
ref={setScrollerEl}
|
|
1177
|
+
data-scrollable={bodyScrollable ? "true" : "false"}
|
|
1178
|
+
>
|
|
1111
1179
|
{messages.map((msg) => {
|
|
1112
1180
|
const isUser = msg.role === "user";
|
|
1113
1181
|
const isLastCanceled =
|
|
@@ -1129,16 +1197,23 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1129
1197
|
>
|
|
1130
1198
|
<Stack
|
|
1131
1199
|
gap={4}
|
|
1132
|
-
style={{
|
|
1200
|
+
style={{
|
|
1201
|
+
alignItems: isUser ? "flex-end" : "flex-start",
|
|
1202
|
+
}}
|
|
1133
1203
|
>
|
|
1134
1204
|
<Stack className="ai-chat-bubble">
|
|
1135
1205
|
<Text className="ai-chat-header">
|
|
1136
|
-
<Text
|
|
1206
|
+
<Text
|
|
1207
|
+
fw="bolder"
|
|
1208
|
+
size="xs"
|
|
1209
|
+
style={{ whiteSpace: "nowrap" }}
|
|
1210
|
+
>
|
|
1137
1211
|
{isUser
|
|
1138
1212
|
? I18n.get(labels.userLabel)
|
|
1139
1213
|
: I18n.get(labels.assistantLabel)}
|
|
1140
1214
|
</Text>
|
|
1141
|
-
|
|
1215
|
+
|
|
1216
|
+
<Text size="xs" style={{ whiteSpace: "nowrap" }}>
|
|
1142
1217
|
{new Date(msg.createdAt).toLocaleTimeString([], {
|
|
1143
1218
|
hour: "2-digit",
|
|
1144
1219
|
minute: "2-digit",
|
|
@@ -1169,6 +1244,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1169
1244
|
onClick={() => handleEditCanceled(msg)}
|
|
1170
1245
|
title={I18n.get(labels.editLabel)}
|
|
1171
1246
|
aria-label={I18n.get(labels.editLabel)}
|
|
1247
|
+
data-ai-kit-edit-button
|
|
1172
1248
|
>
|
|
1173
1249
|
<IconPencil size={14} />
|
|
1174
1250
|
</ActionIcon>
|
|
@@ -1222,6 +1298,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1222
1298
|
onClick={() => updateFeedback(msg.id, "accepted")}
|
|
1223
1299
|
aria-label={I18n.get(labels.acceptResponseLabel)}
|
|
1224
1300
|
disabled={ai.busy}
|
|
1301
|
+
data-ai-kit-feedback-accept-button
|
|
1225
1302
|
>
|
|
1226
1303
|
👍
|
|
1227
1304
|
</Button>
|
|
@@ -1233,6 +1310,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1233
1310
|
onClick={() => updateFeedback(msg.id, "rejected")}
|
|
1234
1311
|
aria-label={I18n.get(labels.rejectResponseLabel)}
|
|
1235
1312
|
disabled={ai.busy}
|
|
1313
|
+
data-ai-kit-feedback-reject-button
|
|
1236
1314
|
>
|
|
1237
1315
|
👎
|
|
1238
1316
|
</Button>
|
|
@@ -1287,13 +1365,18 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1287
1365
|
{I18n.get("Are you sure you want to reset the conversation?")}
|
|
1288
1366
|
</Text>
|
|
1289
1367
|
<Group justify="flex-end" mt="md">
|
|
1290
|
-
<Button
|
|
1368
|
+
<Button
|
|
1369
|
+
variant="default"
|
|
1370
|
+
onClick={cancelReset}
|
|
1371
|
+
data-ai-kit-no-button
|
|
1372
|
+
>
|
|
1291
1373
|
{I18n.get("No")}
|
|
1292
1374
|
</Button>
|
|
1293
1375
|
<Button
|
|
1294
|
-
color="red"
|
|
1376
|
+
color="var(--ai-kit-color-danger, red)"
|
|
1295
1377
|
onClick={confirmReset}
|
|
1296
1378
|
disabled={!hasMessages && !isChatBusy}
|
|
1379
|
+
data-ai-kit-yes-button
|
|
1297
1380
|
>
|
|
1298
1381
|
{I18n.get("Yes")}
|
|
1299
1382
|
</Button>
|
|
@@ -1321,6 +1404,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1321
1404
|
leftSection={<IconTrash size={18} />}
|
|
1322
1405
|
onClick={handleResetClick}
|
|
1323
1406
|
disabled={!hasMessages && !isChatBusy}
|
|
1407
|
+
data-ai-kit-reset-button
|
|
1324
1408
|
>
|
|
1325
1409
|
{I18n.get(labels.resetLabel)}
|
|
1326
1410
|
</Button>
|
|
@@ -1333,6 +1417,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1333
1417
|
onClick={() => fileInputRef.current?.click()}
|
|
1334
1418
|
disabled={images.length >= resolvedMaxImages}
|
|
1335
1419
|
title={I18n.get(labels.addImageLabel)}
|
|
1420
|
+
data-ai-kit-add-image-button
|
|
1336
1421
|
>
|
|
1337
1422
|
{I18n.get(labels.addLabel)}
|
|
1338
1423
|
</Button>
|
|
@@ -1351,6 +1436,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1351
1436
|
variant="filled"
|
|
1352
1437
|
onClick={onSendOrCancel}
|
|
1353
1438
|
disabled={!isChatBusy && !canSend}
|
|
1439
|
+
data-ai-kit-send-button
|
|
1354
1440
|
>
|
|
1355
1441
|
{sendOrCancelLabel}
|
|
1356
1442
|
</Button>
|
|
@@ -1381,6 +1467,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1381
1467
|
p={0}
|
|
1382
1468
|
className="remove-image-button"
|
|
1383
1469
|
title={I18n.get(labels.removeImageLabel)}
|
|
1470
|
+
data-ai-kit-remove-image-button
|
|
1384
1471
|
>
|
|
1385
1472
|
X
|
|
1386
1473
|
</Button>
|
|
@@ -1389,7 +1476,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1389
1476
|
</Group>
|
|
1390
1477
|
)}
|
|
1391
1478
|
</Stack>
|
|
1392
|
-
</
|
|
1479
|
+
</div>
|
|
1393
1480
|
</Modal.Root>
|
|
1394
1481
|
)}
|
|
1395
1482
|
</Group>
|
package/src/ai-chatbot/index.tsx
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { AiChatbot } from "./AiChatbot";
|
|
1
|
+
export { AiChatbot, DEFAULT_CHATBOT_LABELS } from "./AiChatbot";
|
package/src/styles/ai-kit-ui.css
CHANGED
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
/* Sizing */
|
|
130
130
|
--ai-kit-chat-launcher-size: 60px;
|
|
131
131
|
|
|
132
|
-
--ai-kit-chat-width:
|
|
132
|
+
--ai-kit-chat-width: 480px;
|
|
133
133
|
--ai-kit-chat-height-min: 360px;
|
|
134
134
|
--ai-kit-chat-height-max: 80vh;
|
|
135
135
|
--ai-kit-chat-height-cap: 1000px;
|
|
@@ -376,6 +376,16 @@
|
|
|
376
376
|
display: contents;
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
.ai-docs-ask[data-ai-kit-theme="dark"] {
|
|
380
|
+
--ai-kit-chat-status-fg: var(--ai-kit-color-text);
|
|
381
|
+
--ai-kit-chat-status-bg: transparent;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.ai-open-btn-icon {
|
|
385
|
+
width: 24px;
|
|
386
|
+
height: 24px;
|
|
387
|
+
}
|
|
388
|
+
|
|
379
389
|
.ai-docs-ask.ai-open-btn--bottom-right {
|
|
380
390
|
--ai-kit-pos-top: auto;
|
|
381
391
|
--ai-kit-pos-right: var(--ai-kit-open-button-offset-x);
|
|
@@ -428,8 +438,8 @@
|
|
|
428
438
|
|
|
429
439
|
z-index: var(--ai-kit-position-z-index);
|
|
430
440
|
|
|
431
|
-
width:
|
|
432
|
-
height:
|
|
441
|
+
width: auto;
|
|
442
|
+
height: auto;
|
|
433
443
|
|
|
434
444
|
/* Radius: apply both border-radius and Mantine variable */
|
|
435
445
|
border-radius: var(--ai-kit-launcher-radius) !important;
|
|
@@ -453,7 +463,7 @@
|
|
|
453
463
|
box-shadow 0.2s ease,
|
|
454
464
|
background 0.2s ease;
|
|
455
465
|
|
|
456
|
-
padding:
|
|
466
|
+
padding: var(--ai-kit-space-3);
|
|
457
467
|
pointer-events: auto;
|
|
458
468
|
}
|
|
459
469
|
|
|
@@ -541,6 +551,7 @@
|
|
|
541
551
|
margin-right: auto;
|
|
542
552
|
margin-left: auto;
|
|
543
553
|
flex-direction: column;
|
|
554
|
+
padding: 0 var(--ai-kit-space-3) var(--ai-kit-space-3) var(--ai-kit-space-3);
|
|
544
555
|
}
|
|
545
556
|
|
|
546
557
|
/* Input Box */
|
|
@@ -598,11 +609,14 @@
|
|
|
598
609
|
|
|
599
610
|
/* Header / status */
|
|
600
611
|
.ai-chat-header-bar {
|
|
612
|
+
background: var(--ai-kit-chat-surface);
|
|
601
613
|
display: flex;
|
|
602
614
|
align-items: center;
|
|
603
615
|
justify-content: space-between;
|
|
604
|
-
padding: 0 var(--ai-kit-space-3)
|
|
616
|
+
padding: 0 var(--ai-kit-space-3);
|
|
605
617
|
border-bottom: 1px solid var(--ai-kit-chat-border-color);
|
|
618
|
+
margin-left: calc(-1 * var(--ai-kit-button-padding-x));
|
|
619
|
+
margin-right: calc(-1 * var(--ai-kit-button-padding-x));
|
|
606
620
|
}
|
|
607
621
|
|
|
608
622
|
.ai-status-line {
|
|
@@ -655,6 +669,11 @@
|
|
|
655
669
|
flex: 1;
|
|
656
670
|
overflow-y: auto;
|
|
657
671
|
padding: var(--ai-kit-space-4);
|
|
672
|
+
overscroll-behavior: auto;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.ai-chat-scroll[data-scrollable="true"] {
|
|
676
|
+
overscroll-behavior: contain;
|
|
658
677
|
}
|
|
659
678
|
|
|
660
679
|
.ai-chat-placeholder {
|
|
@@ -676,7 +695,6 @@
|
|
|
676
695
|
}
|
|
677
696
|
|
|
678
697
|
.ai-chat-bubble {
|
|
679
|
-
max-width: 85%;
|
|
680
698
|
padding: 10px 12px;
|
|
681
699
|
border-radius: var(--ai-kit-radius-sm);
|
|
682
700
|
background: var(--ai-kit-assistant-bubble-bg);
|
|
@@ -687,6 +705,8 @@
|
|
|
687
705
|
word-break: break-word;
|
|
688
706
|
hyphens: auto;
|
|
689
707
|
line-height: var(--ai-kit-line-height);
|
|
708
|
+
width: fit-content;
|
|
709
|
+
max-width: 80%;
|
|
690
710
|
}
|
|
691
711
|
|
|
692
712
|
.ai-chat-row.user .ai-chat-bubble {
|
|
@@ -781,7 +801,7 @@
|
|
|
781
801
|
|
|
782
802
|
.ai-status-text {
|
|
783
803
|
font-size: 0.85rem;
|
|
784
|
-
color: var(--ai-kit-color-text
|
|
804
|
+
color: var(--ai-kit-color-text);
|
|
785
805
|
}
|
|
786
806
|
|
|
787
807
|
.ai-feedback {
|
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",
|
|
@@ -183,16 +232,23 @@ export function withAiKitShell<P extends object>(
|
|
|
183
232
|
rootElement.setAttribute("lang", currentLanguage);
|
|
184
233
|
}
|
|
185
234
|
|
|
235
|
+
const resolved =
|
|
236
|
+
colorMode === "auto"
|
|
237
|
+
? window.matchMedia?.("(prefers-color-scheme: dark)")?.matches
|
|
238
|
+
? "dark"
|
|
239
|
+
: "light"
|
|
240
|
+
: colorMode;
|
|
241
|
+
|
|
186
242
|
return (
|
|
187
243
|
<DirectionProvider initialDirection={currentDirection}>
|
|
188
244
|
<MantineProvider
|
|
189
|
-
|
|
245
|
+
forceColorScheme={resolved}
|
|
190
246
|
theme={theme}
|
|
191
|
-
cssVariablesSelector={`#${rootElement.id}`}
|
|
192
247
|
getRootElement={() => rootElement as unknown as HTMLElement}
|
|
193
248
|
>
|
|
194
249
|
<RootComponent
|
|
195
250
|
{...props}
|
|
251
|
+
colorMode={resolved}
|
|
196
252
|
language={currentLanguage}
|
|
197
253
|
rootElement={rootElement}
|
|
198
254
|
/>
|