@smart-cloud/ai-kit-ui 1.1.11 → 1.1.12
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 +24 -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 +2 -2
- package/src/ai-chatbot/AiChatbot.tsx +98 -29
- package/src/ai-chatbot/index.tsx +1 -1
- package/src/styles/ai-kit-ui.css +24 -7
- package/src/withAiKitShell.tsx +9 -1
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.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@emotion/cache": "^11.14.0",
|
|
21
21
|
"@emotion/react": "^11.14.0",
|
|
22
|
-
"@smart-cloud/ai-kit-core": "^1.1.
|
|
22
|
+
"@smart-cloud/ai-kit-core": "^1.1.5",
|
|
23
23
|
"@smart-cloud/wpsuite-core": "^2.0.5",
|
|
24
24
|
"@tabler/icons-react": "^3.36.1",
|
|
25
25
|
"react-markdown": "^10.1.0",
|
|
@@ -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">
|
|
@@ -1107,7 +1170,11 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1107
1170
|
</Group>
|
|
1108
1171
|
</Modal.Header>
|
|
1109
1172
|
|
|
1110
|
-
<Modal.Body
|
|
1173
|
+
<Modal.Body
|
|
1174
|
+
className="ai-chat-scroll"
|
|
1175
|
+
ref={setScrollerEl}
|
|
1176
|
+
data-scrollable={bodyScrollable ? "true" : "false"}
|
|
1177
|
+
>
|
|
1111
1178
|
{messages.map((msg) => {
|
|
1112
1179
|
const isUser = msg.role === "user";
|
|
1113
1180
|
const isLastCanceled =
|
|
@@ -1129,7 +1196,9 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1129
1196
|
>
|
|
1130
1197
|
<Stack
|
|
1131
1198
|
gap={4}
|
|
1132
|
-
style={{
|
|
1199
|
+
style={{
|
|
1200
|
+
alignItems: isUser ? "flex-end" : "flex-start",
|
|
1201
|
+
}}
|
|
1133
1202
|
>
|
|
1134
1203
|
<Stack className="ai-chat-bubble">
|
|
1135
1204
|
<Text className="ai-chat-header">
|
|
@@ -1389,7 +1458,7 @@ const AiChatbotBase: FC<AiChatbotProps & AiKitShellInjectedProps> = (props) => {
|
|
|
1389
1458
|
</Group>
|
|
1390
1459
|
)}
|
|
1391
1460
|
</Stack>
|
|
1392
|
-
</
|
|
1461
|
+
</div>
|
|
1393
1462
|
</Modal.Root>
|
|
1394
1463
|
)}
|
|
1395
1464
|
</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 */
|
|
@@ -601,8 +612,10 @@
|
|
|
601
612
|
display: flex;
|
|
602
613
|
align-items: center;
|
|
603
614
|
justify-content: space-between;
|
|
604
|
-
padding: 0 var(--ai-kit-space-3)
|
|
615
|
+
padding: 0 var(--ai-kit-space-3);
|
|
605
616
|
border-bottom: 1px solid var(--ai-kit-chat-border-color);
|
|
617
|
+
margin-left: calc(-1 * var(--ai-kit-button-padding-x));
|
|
618
|
+
margin-right: calc(-1 * var(--ai-kit-button-padding-x));
|
|
606
619
|
}
|
|
607
620
|
|
|
608
621
|
.ai-status-line {
|
|
@@ -655,6 +668,11 @@
|
|
|
655
668
|
flex: 1;
|
|
656
669
|
overflow-y: auto;
|
|
657
670
|
padding: var(--ai-kit-space-4);
|
|
671
|
+
overscroll-behavior: auto;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
.ai-chat-scroll[data-scrollable="true"] {
|
|
675
|
+
overscroll-behavior: contain;
|
|
658
676
|
}
|
|
659
677
|
|
|
660
678
|
.ai-chat-placeholder {
|
|
@@ -676,7 +694,6 @@
|
|
|
676
694
|
}
|
|
677
695
|
|
|
678
696
|
.ai-chat-bubble {
|
|
679
|
-
max-width: 85%;
|
|
680
697
|
padding: 10px 12px;
|
|
681
698
|
border-radius: var(--ai-kit-radius-sm);
|
|
682
699
|
background: var(--ai-kit-assistant-bubble-bg);
|
|
@@ -781,7 +798,7 @@
|
|
|
781
798
|
|
|
782
799
|
.ai-status-text {
|
|
783
800
|
font-size: 0.85rem;
|
|
784
|
-
color: var(--ai-kit-color-text
|
|
801
|
+
color: var(--ai-kit-color-text);
|
|
785
802
|
}
|
|
786
803
|
|
|
787
804
|
.ai-feedback {
|
package/src/withAiKitShell.tsx
CHANGED
|
@@ -183,16 +183,24 @@ export function withAiKitShell<P extends object>(
|
|
|
183
183
|
rootElement.setAttribute("lang", currentLanguage);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
const resolved =
|
|
187
|
+
colorMode === "auto"
|
|
188
|
+
? window.matchMedia?.("(prefers-color-scheme: dark)")?.matches
|
|
189
|
+
? "dark"
|
|
190
|
+
: "light"
|
|
191
|
+
: colorMode;
|
|
192
|
+
|
|
186
193
|
return (
|
|
187
194
|
<DirectionProvider initialDirection={currentDirection}>
|
|
188
195
|
<MantineProvider
|
|
189
|
-
|
|
196
|
+
forceColorScheme={resolved}
|
|
190
197
|
theme={theme}
|
|
191
198
|
cssVariablesSelector={`#${rootElement.id}`}
|
|
192
199
|
getRootElement={() => rootElement as unknown as HTMLElement}
|
|
193
200
|
>
|
|
194
201
|
<RootComponent
|
|
195
202
|
{...props}
|
|
203
|
+
colorMode={resolved}
|
|
196
204
|
language={currentLanguage}
|
|
197
205
|
rootElement={rootElement}
|
|
198
206
|
/>
|