@promptbook/components 0.101.0-16 → 0.101.0-18
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/esm/index.es.js +220 -68
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/_packages/components.index.d.ts +6 -0
- package/esm/typings/src/_packages/types.index.d.ts +4 -0
- package/esm/typings/src/book-components/AvatarProfile/AvatarChip/AvatarChip.d.ts +2 -2
- package/esm/typings/src/book-components/Chat/LlmChat/LlmChatProps.d.ts +13 -0
- package/esm/typings/src/book-components/Chat/hooks/index.d.ts +2 -0
- package/esm/typings/src/book-components/Chat/hooks/useChatAutoScroll.d.ts +41 -0
- package/esm/typings/src/book-components/Chat/hooks/useSendMessageToLlmChat.d.ts +44 -0
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +221 -67
- package/umd/index.umd.js.map +1 -1
- package/esm/typings/src/book-2.0/utils/extractAgentMetadata.d.ts +0 -17
- package/esm/typings/src/book-2.0/utils/extractProfileImageFromSystemMessage.d.ts +0 -12
package/esm/index.es.js
CHANGED
|
@@ -21,7 +21,7 @@ const BOOK_LANGUAGE_VERSION = '1.0.0';
|
|
|
21
21
|
* @generated
|
|
22
22
|
* @see https://github.com/webgptorg/promptbook
|
|
23
23
|
*/
|
|
24
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.101.0-
|
|
24
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.101.0-18';
|
|
25
25
|
/**
|
|
26
26
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
27
27
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -4130,6 +4130,147 @@ const SendIcon = ({ size }) => (jsx("svg", { width: size, height: size, viewBox:
|
|
|
4130
4130
|
*/
|
|
4131
4131
|
const TemplateIcon = ({ size }) => (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "currentColor", children: jsx("path", { d: "M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z" }) }));
|
|
4132
4132
|
|
|
4133
|
+
/**
|
|
4134
|
+
* Hook for managing auto-scroll behavior in chat components
|
|
4135
|
+
*
|
|
4136
|
+
* This hook provides:
|
|
4137
|
+
* - Automatic scrolling to bottom when new messages arrive (if user is already at bottom)
|
|
4138
|
+
* - Detection of when user scrolls away from bottom
|
|
4139
|
+
* - Scroll-to-bottom functionality with smooth animation
|
|
4140
|
+
* - Mobile-optimized scrolling behavior
|
|
4141
|
+
*
|
|
4142
|
+
* @public exported from `@promptbook/components`
|
|
4143
|
+
*/
|
|
4144
|
+
function useChatAutoScroll(config = {}) {
|
|
4145
|
+
const { bottomThreshold = 100, smoothScroll = true, scrollCheckDelay = 100, } = config;
|
|
4146
|
+
const [isAutoScrolling, setIsAutoScrolling] = useState(true);
|
|
4147
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
4148
|
+
const chatMessagesRef = useRef(null);
|
|
4149
|
+
const scrollTimeoutRef = useRef(null);
|
|
4150
|
+
const lastScrollHeightRef = useRef(0);
|
|
4151
|
+
// Detect mobile device
|
|
4152
|
+
useEffect(() => {
|
|
4153
|
+
const checkMobile = () => {
|
|
4154
|
+
const isMobileDevice = window.innerWidth <= 768 ||
|
|
4155
|
+
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
4156
|
+
setIsMobile(isMobileDevice);
|
|
4157
|
+
};
|
|
4158
|
+
checkMobile();
|
|
4159
|
+
window.addEventListener('resize', checkMobile);
|
|
4160
|
+
return () => window.removeEventListener('resize', checkMobile);
|
|
4161
|
+
}, []);
|
|
4162
|
+
// Check if user is at the bottom of the chat
|
|
4163
|
+
const checkIfAtBottom = useCallback((element) => {
|
|
4164
|
+
const { scrollTop, scrollHeight, clientHeight } = element;
|
|
4165
|
+
return scrollTop + clientHeight >= scrollHeight - bottomThreshold;
|
|
4166
|
+
}, [bottomThreshold]);
|
|
4167
|
+
// Scroll to bottom function
|
|
4168
|
+
const scrollToBottom = useCallback((behavior = 'smooth') => {
|
|
4169
|
+
const chatMessagesElement = chatMessagesRef.current;
|
|
4170
|
+
if (!chatMessagesElement)
|
|
4171
|
+
return;
|
|
4172
|
+
if (isMobile) {
|
|
4173
|
+
// Mobile-optimized scrolling
|
|
4174
|
+
chatMessagesElement.scrollTo({
|
|
4175
|
+
top: chatMessagesElement.scrollHeight,
|
|
4176
|
+
behavior: smoothScroll ? behavior : 'auto',
|
|
4177
|
+
});
|
|
4178
|
+
}
|
|
4179
|
+
else {
|
|
4180
|
+
// Desktop scrolling
|
|
4181
|
+
if (smoothScroll && behavior === 'smooth') {
|
|
4182
|
+
chatMessagesElement.style.scrollBehavior = 'smooth';
|
|
4183
|
+
chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight;
|
|
4184
|
+
chatMessagesElement.style.scrollBehavior = 'auto';
|
|
4185
|
+
}
|
|
4186
|
+
else {
|
|
4187
|
+
chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight;
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
}, [isMobile, smoothScroll]);
|
|
4191
|
+
// Handle scroll events
|
|
4192
|
+
const handleScroll = useCallback((event) => {
|
|
4193
|
+
const element = event.target;
|
|
4194
|
+
if (!element)
|
|
4195
|
+
return;
|
|
4196
|
+
// Clear any pending scroll timeout
|
|
4197
|
+
if (scrollTimeoutRef.current) {
|
|
4198
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
4199
|
+
}
|
|
4200
|
+
// Debounce scroll position check to avoid too frequent updates
|
|
4201
|
+
scrollTimeoutRef.current = setTimeout(() => {
|
|
4202
|
+
const isAtBottom = checkIfAtBottom(element);
|
|
4203
|
+
setIsAutoScrolling(isAtBottom);
|
|
4204
|
+
}, 50);
|
|
4205
|
+
}, [checkIfAtBottom]);
|
|
4206
|
+
// Auto-scroll when messages change (if user is at bottom)
|
|
4207
|
+
const handleMessagesChange = useCallback(() => {
|
|
4208
|
+
const chatMessagesElement = chatMessagesRef.current;
|
|
4209
|
+
if (!chatMessagesElement)
|
|
4210
|
+
return;
|
|
4211
|
+
// Check if this is a new message (scroll height increased)
|
|
4212
|
+
const currentScrollHeight = chatMessagesElement.scrollHeight;
|
|
4213
|
+
const hasNewContent = currentScrollHeight > lastScrollHeightRef.current;
|
|
4214
|
+
lastScrollHeightRef.current = currentScrollHeight;
|
|
4215
|
+
if (!hasNewContent)
|
|
4216
|
+
return;
|
|
4217
|
+
// If user is set to auto-scroll, scroll to bottom
|
|
4218
|
+
if (isAutoScrolling) {
|
|
4219
|
+
// Delay scroll slightly to ensure DOM has updated
|
|
4220
|
+
setTimeout(() => {
|
|
4221
|
+
scrollToBottom('smooth');
|
|
4222
|
+
}, scrollCheckDelay);
|
|
4223
|
+
}
|
|
4224
|
+
}, [isAutoScrolling, scrollToBottom, scrollCheckDelay]);
|
|
4225
|
+
// Ref callback for chat messages container
|
|
4226
|
+
const chatMessagesRefCallback = useCallback((element) => {
|
|
4227
|
+
chatMessagesRef.current = element;
|
|
4228
|
+
if (element) {
|
|
4229
|
+
// Update last scroll height
|
|
4230
|
+
lastScrollHeightRef.current = element.scrollHeight;
|
|
4231
|
+
// If auto-scrolling is enabled, scroll to bottom
|
|
4232
|
+
if (isAutoScrolling) {
|
|
4233
|
+
// Use requestAnimationFrame for smoother initial scroll
|
|
4234
|
+
requestAnimationFrame(() => {
|
|
4235
|
+
scrollToBottom('auto');
|
|
4236
|
+
});
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
}, [isAutoScrolling, scrollToBottom]);
|
|
4240
|
+
// Manual scroll to bottom (for button click)
|
|
4241
|
+
const handleScrollToBottomClick = useCallback(() => {
|
|
4242
|
+
setIsAutoScrolling(true);
|
|
4243
|
+
scrollToBottom('smooth');
|
|
4244
|
+
}, [scrollToBottom]);
|
|
4245
|
+
// Force auto-scroll back on (useful for programmatic control)
|
|
4246
|
+
const enableAutoScroll = useCallback(() => {
|
|
4247
|
+
setIsAutoScrolling(true);
|
|
4248
|
+
scrollToBottom('smooth');
|
|
4249
|
+
}, [scrollToBottom]);
|
|
4250
|
+
// Disable auto-scroll (useful for programmatic control)
|
|
4251
|
+
const disableAutoScroll = useCallback(() => {
|
|
4252
|
+
setIsAutoScrolling(false);
|
|
4253
|
+
}, []);
|
|
4254
|
+
// Cleanup timeout on unmount
|
|
4255
|
+
useEffect(() => {
|
|
4256
|
+
return () => {
|
|
4257
|
+
if (scrollTimeoutRef.current) {
|
|
4258
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
4259
|
+
}
|
|
4260
|
+
};
|
|
4261
|
+
}, []);
|
|
4262
|
+
return {
|
|
4263
|
+
isAutoScrolling,
|
|
4264
|
+
chatMessagesRef: chatMessagesRefCallback,
|
|
4265
|
+
handleScroll,
|
|
4266
|
+
handleMessagesChange,
|
|
4267
|
+
scrollToBottom: handleScrollToBottomClick,
|
|
4268
|
+
enableAutoScroll,
|
|
4269
|
+
disableAutoScroll,
|
|
4270
|
+
isMobile,
|
|
4271
|
+
};
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4133
4274
|
/**
|
|
4134
4275
|
* Parses markdown buttons in the format [Button Text](?message=Message%20to%20send)
|
|
4135
4276
|
* Returns both the content without buttons and the extracted buttons
|
|
@@ -4318,9 +4459,9 @@ function Chat(props) {
|
|
|
4318
4459
|
// exportHeaderMarkdown,
|
|
4319
4460
|
participants = [], } = props;
|
|
4320
4461
|
const { onUseTemplate } = props;
|
|
4321
|
-
|
|
4462
|
+
// Use the auto-scroll hook
|
|
4463
|
+
const { isAutoScrolling, chatMessagesRef, handleScroll, handleMessagesChange, scrollToBottom, isMobile: isMobileFromHook, } = useChatAutoScroll();
|
|
4322
4464
|
const textareaRef = useRef(null);
|
|
4323
|
-
const chatMessagesRef = useRef(null);
|
|
4324
4465
|
const buttonSendRef = useRef(null);
|
|
4325
4466
|
const [ratingModalOpen, setRatingModalOpen] = useState(false);
|
|
4326
4467
|
const [selectedMessage, setSelectedMessage] = useState(null);
|
|
@@ -4333,18 +4474,8 @@ function Chat(props) {
|
|
|
4333
4474
|
// const [inputValue, setInputValue] = useState('');
|
|
4334
4475
|
const [mode] = useState('LIGHT'); // Simplified light/dark mode
|
|
4335
4476
|
const [ratingConfirmation, setRatingConfirmation] = useState(null);
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
useEffect(() => {
|
|
4339
|
-
const checkMobile = () => {
|
|
4340
|
-
const isMobileDevice = window.innerWidth <= 768 ||
|
|
4341
|
-
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
4342
|
-
setIsMobile(isMobileDevice);
|
|
4343
|
-
};
|
|
4344
|
-
checkMobile();
|
|
4345
|
-
window.addEventListener('resize', checkMobile);
|
|
4346
|
-
return () => window.removeEventListener('resize', checkMobile);
|
|
4347
|
-
}, []);
|
|
4477
|
+
// Use mobile detection from the hook
|
|
4478
|
+
const isMobile = isMobileFromHook;
|
|
4348
4479
|
useEffect(( /* Focus textarea on page load */) => {
|
|
4349
4480
|
if (!textareaRef.current) {
|
|
4350
4481
|
return;
|
|
@@ -4470,59 +4601,16 @@ function Chat(props) {
|
|
|
4470
4601
|
return { ...message, content: humanizeAiText(message.content) };
|
|
4471
4602
|
});
|
|
4472
4603
|
}, [messages, isAiTextHumanized]);
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
// Mobile-optimized scroll to bottom
|
|
4479
|
-
if (isMobile) {
|
|
4480
|
-
chatMessagesElement.scrollTo({
|
|
4481
|
-
top: chatMessagesElement.scrollHeight,
|
|
4482
|
-
behavior: 'smooth',
|
|
4483
|
-
});
|
|
4484
|
-
}
|
|
4485
|
-
else {
|
|
4486
|
-
chatMessagesElement.style.scrollBehavior = 'smooth';
|
|
4487
|
-
chatMessagesElement.scrollBy(0, 10000);
|
|
4488
|
-
chatMessagesElement.style.scrollBehavior = 'auto';
|
|
4489
|
-
}
|
|
4490
|
-
}, children: jsx(ArrowIcon, { direction: "DOWN", size: 33 }) }) })), isVoiceCalling && (jsx("div", { className: styles$1.voiceCallIndicatorBar, children: jsxs("div", { className: styles$1.voiceCallIndicator, children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsx("path", { d: "M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" }) }), jsx("span", { children: "Voice call active" }), jsx("div", { className: styles$1.voiceCallPulse })] }) })), jsxs("div", { className: classNames(actionsAlignmentClass), children: [onReset && postprocessedMessages.length !== 0 && (jsxs("button", { className: classNames(styles$1.resetButton), onClick: () => {
|
|
4604
|
+
// Trigger auto-scroll when messages change
|
|
4605
|
+
useEffect(() => {
|
|
4606
|
+
handleMessagesChange();
|
|
4607
|
+
}, [postprocessedMessages, handleMessagesChange]);
|
|
4608
|
+
return (jsxs(Fragment, { children: [ratingConfirmation && jsx("div", { className: styles$1.ratingConfirmation, children: ratingConfirmation }), jsx("div", { className: classNames(className, styles$1.Chat, useChatCssClassName('Chat')), style, children: jsxs("div", { className: classNames(className, styles$1.chatMainFlow, useChatCssClassName('chatMainFlow')), children: [children && jsx("div", { className: classNames(styles$1.chatBar, chatBarCssClassName), children: children }), !isAutoScrolling && (jsx("div", { className: styles$1.scrollToBottomContainer, children: jsx("button", { "data-button-type": "custom", className: classNames(styles$1.scrollToBottom, scrollToBottomCssClassName), onClick: scrollToBottom, children: jsx(ArrowIcon, { direction: "DOWN", size: 33 }) }) })), isVoiceCalling && (jsx("div", { className: styles$1.voiceCallIndicatorBar, children: jsxs("div", { className: styles$1.voiceCallIndicator, children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsx("path", { d: "M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" }) }), jsx("span", { children: "Voice call active" }), jsx("div", { className: styles$1.voiceCallPulse })] }) })), jsxs("div", { className: classNames(actionsAlignmentClass), children: [onReset && postprocessedMessages.length !== 0 && (jsxs("button", { className: classNames(styles$1.resetButton), onClick: () => {
|
|
4491
4609
|
if (!confirm(`Do you really want to reset the chat?`)) {
|
|
4492
4610
|
return;
|
|
4493
4611
|
}
|
|
4494
4612
|
onReset();
|
|
4495
|
-
}, children: [jsx(ResetIcon, {}), jsx("span", { className: styles$1.resetButtonText, children: "New chat" })] })), onUseTemplate && (jsxs("button", { className: classNames(styles$1.useTemplateButton), onClick: onUseTemplate, children: [jsx("span", { className: styles$1.resetButtonText, children: "Use this template" }), jsx(TemplateIcon, { size: 16 })] }))] }), jsx("div", { className: classNames(styles$1.chatMessages, useChatCssClassName('chatMessages')), ref: (
|
|
4496
|
-
chatMessagesRef.current = chatMessagesElement;
|
|
4497
|
-
if (chatMessagesElement === null) {
|
|
4498
|
-
return;
|
|
4499
|
-
}
|
|
4500
|
-
if (!isAutoScrolling) {
|
|
4501
|
-
return;
|
|
4502
|
-
}
|
|
4503
|
-
// Mobile-optimized scrolling
|
|
4504
|
-
if (isMobile) {
|
|
4505
|
-
// Delay scroll slightly on mobile for better performance
|
|
4506
|
-
requestAnimationFrame(() => {
|
|
4507
|
-
chatMessagesElement.scrollTo({
|
|
4508
|
-
top: chatMessagesElement.scrollHeight,
|
|
4509
|
-
behavior: 'smooth',
|
|
4510
|
-
});
|
|
4511
|
-
});
|
|
4512
|
-
}
|
|
4513
|
-
else {
|
|
4514
|
-
// Desktop smooth scrolling
|
|
4515
|
-
chatMessagesElement.style.scrollBehavior = 'smooth';
|
|
4516
|
-
chatMessagesElement.scrollBy(0, 1000);
|
|
4517
|
-
chatMessagesElement.style.scrollBehavior = 'auto';
|
|
4518
|
-
}
|
|
4519
|
-
}, onScroll: (event) => {
|
|
4520
|
-
const element = event.target;
|
|
4521
|
-
if (!(element instanceof HTMLDivElement)) {
|
|
4522
|
-
return;
|
|
4523
|
-
}
|
|
4524
|
-
setAutoScrolling(element.scrollTop + element.clientHeight > element.scrollHeight - 100);
|
|
4525
|
-
}, children: postprocessedMessages.map((message, i) => {
|
|
4613
|
+
}, children: [jsx(ResetIcon, {}), jsx("span", { className: styles$1.resetButtonText, children: "New chat" })] })), onUseTemplate && (jsxs("button", { className: classNames(styles$1.useTemplateButton), onClick: onUseTemplate, children: [jsx("span", { className: styles$1.resetButtonText, children: "Use this template" }), jsx(TemplateIcon, { size: 16 })] }))] }), jsx("div", { className: classNames(styles$1.chatMessages, useChatCssClassName('chatMessages')), ref: chatMessagesRef, onScroll: handleScroll, children: postprocessedMessages.map((message, i) => {
|
|
4526
4614
|
const participant = participants.find((participant) => participant.name === message.from);
|
|
4527
4615
|
const avatarSrc = (participant && participant.avatarSrc) || '';
|
|
4528
4616
|
const color = Color.from((participant && participant.color) || '#ccc');
|
|
@@ -5396,6 +5484,53 @@ function BookEditor(props) {
|
|
|
5396
5484
|
return (jsx("div", { "data-book-component": "BookEditor", ref: hostRef, className: classNames(styles.BookEditor, isVerbose && styles.isVerbose, className), style: style, children: shadowReady && shadowRootRef.current ? createPortal(editorInner, shadowRootRef.current) : jsx(Fragment, { children: "Loading..." }) }));
|
|
5397
5485
|
}
|
|
5398
5486
|
|
|
5487
|
+
/**
|
|
5488
|
+
* Hook to create a sendMessage function for an <LlmChat/> component WITHOUT needing any React Context.
|
|
5489
|
+
*
|
|
5490
|
+
* Usage pattern:
|
|
5491
|
+
* ```tsx
|
|
5492
|
+
* const sendMessage = useSendMessageToLlmChat();
|
|
5493
|
+
* return (
|
|
5494
|
+
* <>
|
|
5495
|
+
* <button onClick={() => sendMessage('Hello!')}>Hello</button>
|
|
5496
|
+
* <LlmChat llmTools={llmTools} sendMessage={sendMessage} />
|
|
5497
|
+
* </>
|
|
5498
|
+
* );
|
|
5499
|
+
* ```
|
|
5500
|
+
*
|
|
5501
|
+
* - No provider wrapping needed.
|
|
5502
|
+
* - Safe to call before the <LlmChat/> mounts (messages will be queued).
|
|
5503
|
+
* - Keeps DRY by letting <LlmChat/> reuse its internal `handleMessage` logic.
|
|
5504
|
+
*
|
|
5505
|
+
* @public exported from `@promptbook/components`
|
|
5506
|
+
*/
|
|
5507
|
+
function useSendMessageToLlmChat() {
|
|
5508
|
+
const ref = useRef(null);
|
|
5509
|
+
if (!ref.current) {
|
|
5510
|
+
let handler = null;
|
|
5511
|
+
const queue = [];
|
|
5512
|
+
const sendMessage = (message) => {
|
|
5513
|
+
if (handler) {
|
|
5514
|
+
// Fire and forget
|
|
5515
|
+
void handler(message);
|
|
5516
|
+
}
|
|
5517
|
+
else {
|
|
5518
|
+
queue.push(message);
|
|
5519
|
+
}
|
|
5520
|
+
};
|
|
5521
|
+
sendMessage._attach = (attachedHandler) => {
|
|
5522
|
+
handler = attachedHandler;
|
|
5523
|
+
// Flush queued messages
|
|
5524
|
+
while (queue.length > 0) {
|
|
5525
|
+
const next = queue.shift();
|
|
5526
|
+
void handler(next);
|
|
5527
|
+
}
|
|
5528
|
+
};
|
|
5529
|
+
ref.current = sendMessage;
|
|
5530
|
+
}
|
|
5531
|
+
return ref.current;
|
|
5532
|
+
}
|
|
5533
|
+
|
|
5399
5534
|
/**
|
|
5400
5535
|
* Utility functions for persisting chat conversations in localStorage
|
|
5401
5536
|
*
|
|
@@ -5482,16 +5617,22 @@ ChatPersistence.STORAGE_PREFIX = 'promptbook_chat_';
|
|
|
5482
5617
|
* @public exported from `@promptbook/components`
|
|
5483
5618
|
*/
|
|
5484
5619
|
function LlmChat(props) {
|
|
5485
|
-
const { llmTools, persistenceKey, onChange, onReset, ...restProps } = props;
|
|
5620
|
+
const { llmTools, persistenceKey, onChange, onReset, initialMessages, sendMessage, ...restProps } = props;
|
|
5486
5621
|
// Internal state management
|
|
5487
|
-
const [messages, setMessages] = useState([]);
|
|
5622
|
+
const [messages, setMessages] = useState(() => (initialMessages ? [...initialMessages] : []));
|
|
5488
5623
|
const [tasksProgress, setTasksProgress] = useState([]);
|
|
5624
|
+
/**
|
|
5625
|
+
* Tracks whether the user (or system via persistence restoration) has interacted.
|
|
5626
|
+
* We do NOT persist purely initialMessages until the user sends something.
|
|
5627
|
+
*/
|
|
5628
|
+
const hasUserInteractedRef = useRef(false);
|
|
5489
5629
|
// Load persisted messages on component mount
|
|
5490
5630
|
useEffect(() => {
|
|
5491
5631
|
if (persistenceKey && ChatPersistence.isAvailable()) {
|
|
5492
5632
|
const persistedMessages = ChatPersistence.loadMessages(persistenceKey);
|
|
5493
5633
|
if (persistedMessages.length > 0) {
|
|
5494
5634
|
setMessages(persistedMessages);
|
|
5635
|
+
hasUserInteractedRef.current = true; // Persisted conversation exists; allow saving next changes
|
|
5495
5636
|
// Notify about loaded messages
|
|
5496
5637
|
if (onChange) {
|
|
5497
5638
|
onChange(persistedMessages, participants);
|
|
@@ -5501,7 +5642,10 @@ function LlmChat(props) {
|
|
|
5501
5642
|
}, [persistenceKey]); // Only depend on persistenceKey, not participants or onChange to avoid infinite loops
|
|
5502
5643
|
// Save messages to localStorage whenever messages change (and persistence is enabled)
|
|
5503
5644
|
useEffect(() => {
|
|
5504
|
-
if (persistenceKey &&
|
|
5645
|
+
if (persistenceKey &&
|
|
5646
|
+
ChatPersistence.isAvailable() &&
|
|
5647
|
+
messages.length > 0 &&
|
|
5648
|
+
hasUserInteractedRef.current) {
|
|
5505
5649
|
ChatPersistence.saveMessages(persistenceKey, messages);
|
|
5506
5650
|
}
|
|
5507
5651
|
}, [messages, persistenceKey]);
|
|
@@ -5522,6 +5666,7 @@ function LlmChat(props) {
|
|
|
5522
5666
|
], [llmTools.profile, llmTools.title]);
|
|
5523
5667
|
// Handle user messages and LLM responses
|
|
5524
5668
|
const handleMessage = useCallback(async (messageContent) => {
|
|
5669
|
+
hasUserInteractedRef.current = true;
|
|
5525
5670
|
// Add user message
|
|
5526
5671
|
const userMessage = {
|
|
5527
5672
|
id: `user_${Date.now()}`,
|
|
@@ -5609,6 +5754,7 @@ function LlmChat(props) {
|
|
|
5609
5754
|
const handleReset = useCallback(async () => {
|
|
5610
5755
|
setMessages([]);
|
|
5611
5756
|
setTasksProgress([]);
|
|
5757
|
+
hasUserInteractedRef.current = false;
|
|
5612
5758
|
// Clear persisted messages if persistence is enabled
|
|
5613
5759
|
if (persistenceKey && ChatPersistence.isAvailable()) {
|
|
5614
5760
|
ChatPersistence.clearMessages(persistenceKey);
|
|
@@ -5621,8 +5767,14 @@ function LlmChat(props) {
|
|
|
5621
5767
|
onChange([], participants);
|
|
5622
5768
|
}
|
|
5623
5769
|
}, [persistenceKey, onReset, onChange, participants]);
|
|
5770
|
+
// Attach internal handler to external sendMessage (from useSendMessageToLlmChat) if provided
|
|
5771
|
+
useEffect(() => {
|
|
5772
|
+
if (sendMessage && sendMessage._attach) {
|
|
5773
|
+
sendMessage._attach(handleMessage);
|
|
5774
|
+
}
|
|
5775
|
+
}, [sendMessage, handleMessage]);
|
|
5624
5776
|
return (jsx(Chat, { ...restProps, messages, onReset, tasksProgress, participants, onMessage: handleMessage, onReset: handleReset }));
|
|
5625
5777
|
}
|
|
5626
5778
|
|
|
5627
|
-
export { ArrowIcon, AvatarChip, AvatarChipFromSource, AvatarProfile, AvatarProfileFromSource, BOOK_LANGUAGE_VERSION, BookEditor, Chat, DEFAULT_BOOK_FONT_CLASS, LlmChat, MockedChat, PROMPTBOOK_ENGINE_VERSION, ResetIcon, SendIcon, TemplateIcon, isMarkdownContent, parseMessageButtons, renderMarkdown };
|
|
5779
|
+
export { ArrowIcon, AvatarChip, AvatarChipFromSource, AvatarProfile, AvatarProfileFromSource, BOOK_LANGUAGE_VERSION, BookEditor, Chat, DEFAULT_BOOK_FONT_CLASS, LlmChat, MockedChat, PROMPTBOOK_ENGINE_VERSION, ResetIcon, SendIcon, TemplateIcon, isMarkdownContent, parseMessageButtons, renderMarkdown, useChatAutoScroll, useSendMessageToLlmChat };
|
|
5628
5780
|
//# sourceMappingURL=index.es.js.map
|