@portablecore/chat 0.2.7 → 0.2.8
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/chat-interface-core.d.ts.map +1 -1
- package/dist/chat-interface-core.js +1 -2
- package/dist/chat-interface-core.js.map +1 -1
- package/dist/components/message-list.d.ts +2 -6
- package/dist/components/message-list.d.ts.map +1 -1
- package/dist/components/message-list.js +2 -10
- package/dist/components/message-list.js.map +1 -1
- package/dist/hooks/use-chat-scroll.d.ts +0 -5
- package/dist/hooks/use-chat-scroll.d.ts.map +1 -1
- package/dist/hooks/use-chat-scroll.js +14 -154
- package/dist/hooks/use-chat-scroll.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-interface-core.d.ts","sourceRoot":"","sources":["../src/chat-interface-core.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAa,MAAM,YAAY,CAAA;AAWhE,UAAU,sBAAsB;IAC9B,MAAM,EAAE,mBAAmB,CAAA;IAE3B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACzC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,kBAAkB,GACnB,EAAE,sBAAsB,
|
|
1
|
+
{"version":3,"file":"chat-interface-core.d.ts","sourceRoot":"","sources":["../src/chat-interface-core.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAa,MAAM,YAAY,CAAA;AAWhE,UAAU,sBAAsB;IAC9B,MAAM,EAAE,mBAAmB,CAAA;IAE3B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACzC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,kBAAkB,GACnB,EAAE,sBAAsB,2CAyGxB"}
|
|
@@ -33,7 +33,6 @@ function ChatInterfaceCore({ config, markdownComponents, }) {
|
|
|
33
33
|
const variant = config.variant || "full";
|
|
34
34
|
const isCompact = variant === "compact";
|
|
35
35
|
const handleSend = (content) => {
|
|
36
|
-
scroll.expandSpacer();
|
|
37
36
|
session.send(content);
|
|
38
37
|
attachmentState.clear();
|
|
39
38
|
};
|
|
@@ -50,7 +49,7 @@ function ChatInterfaceCore({ config, markdownComponents, }) {
|
|
|
50
49
|
}
|
|
51
50
|
return ((0, jsx_runtime_1.jsx)("div", { className: `${indicatorMaxWidth} mx-auto px-4`, children: (0, jsx_runtime_1.jsx)(bouncing_dots_js_1.BouncingDots, { expertName: config.expert?.name, expertAvatarUrl: config.expert?.avatarUrl, expertEmoji: config.expert?.emoji }) }));
|
|
52
51
|
};
|
|
53
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: rootClasses, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col flex-1 min-w-0", children: [(0, jsx_runtime_1.jsx)(message_list_js_1.MessageList, { messages: session.messages, streaming: session.streaming, config: config, markdownComponents: markdownComponents, scrollRef: scroll.scrollRef,
|
|
52
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: rootClasses, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col flex-1 min-w-0", children: [(0, jsx_runtime_1.jsx)(message_list_js_1.MessageList, { messages: session.messages, streaming: session.streaming, config: config, markdownComponents: markdownComponents, scrollRef: scroll.scrollRef, emptyState: slots.emptyState || ((0, jsx_runtime_1.jsx)(empty_state_js_1.EmptyState, { expertName: config.expert?.name })), beforeMessageList: slots.beforeMessageList, afterMessageList: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [renderIndicator(), slots.afterMessageList] }) }), (0, jsx_runtime_1.jsx)(input_area_js_1.InputArea, { onSend: handleSend, onAbort: session.abort, streaming: session.streaming, sending: session.sending, placeholder: config.expert
|
|
54
53
|
? `Message ${config.expert.name}...`
|
|
55
54
|
: "Send a message...", prefillText: config.prefillText, inputPrefix: slots.inputPrefix, inputSuffix: slots.inputSuffix, attachments: attachmentState.attachments, uploading: attachmentState.uploading, onAddFiles: attachmentState.addFiles, onRemoveAttachment: attachmentState.remove, hasDocumentResolver: !!config.resolvers?.documentResolver, variant: variant, classNames: config.classNames, mentionResolver: config.resolvers?.mentionResolver, skillResolver: config.resolvers?.skillResolver })] }), slots.sidePanel && ((0, jsx_runtime_1.jsx)("div", { className: "w-80 border-l border-border overflow-y-auto", children: slots.sidePanel }))] }));
|
|
56
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-interface-core.js","sourceRoot":"","sources":["../src/chat-interface-core.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AAiCZ,
|
|
1
|
+
{"version":3,"file":"chat-interface-core.js","sourceRoot":"","sources":["../src/chat-interface-core.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AAiCZ,8CA4GC;;AAzID,qEAA4D;AAC5D,mEAA0D;AAC1D,mEAA2D;AAC3D,kEAA0D;AAC1D,8DAAsD;AACtD,oEAA4D;AAC5D,8EAAsE;AACtE,wEAAgE;AAChE,gEAAwD;AAQxD;;;;;;;;;;;;GAYG;AACH,SAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,kBAAkB,GACK;IACvB,MAAM,OAAO,GAAG,IAAA,oCAAc,EAAC,MAAM,CAAC,CAAA;IACtC,MAAM,MAAM,GAAG,IAAA,kCAAa,EAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAA;IAC1F,MAAM,eAAe,GAAG,IAAA,mCAAc,EAAC,MAAM,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAA;IAC1E,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAc,CAAA;IAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAA;IACxC,MAAM,SAAS,GAAG,OAAO,KAAK,SAAS,CAAA;IAEvC,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,EAAE;QACrC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACrB,eAAe,CAAC,KAAK,EAAE,CAAA;IACzB,CAAC,CAAA;IAED,MAAM,iBAAiB,GAAG,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAA;IAC/D,MAAM,WAAW,GAAG,eAAe,MAAM,CAAC,UAAU,EAAE,IAAI,IAAI,EAAE,EAAE,CAAA;IAElE,MAAM,eAAe,GAAG,GAAG,EAAE;QAC3B,IAAI,CAAC,OAAO,CAAC,YAAY;YAAE,OAAO,IAAI,CAAA;QAEtC,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACxB,OAAO,CACL,gCAAK,SAAS,EAAE,GAAG,iBAAiB,eAAe,YACjD,uBAAC,mCAAc,IAAC,KAAK,EAAE,OAAO,CAAC,WAAW,GAAI,GAC1C,CACP,CAAA;QACH,CAAC;QAED,IAAI,OAAO,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtC,OAAO,CACL,gCAAK,SAAS,EAAE,GAAG,iBAAiB,eAAe,YACjD,uBAAC,yCAAiB,IAChB,MAAM,EAAE,OAAO,CAAC,cAAc,EAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAC/B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GACzC,GACE,CACP,CAAA;QACH,CAAC;QAED,OAAO,CACL,gCAAK,SAAS,EAAE,GAAG,iBAAiB,eAAe,YACjD,uBAAC,+BAAY,IACX,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAC/B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EACzC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,GACjC,GACE,CACP,CAAA;IACH,CAAC,CAAA;IAED,OAAO,CACL,iCAAK,SAAS,EAAE,WAAW,aACzB,iCAAK,SAAS,EAAC,8BAA8B,aAC3C,uBAAC,6BAAW,IACV,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAC1B,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB,EACtC,SAAS,EAAE,MAAM,CAAC,SAAS,EAC3B,UAAU,EACP,KAAK,CAAC,UAAwB,IAAI,CACjC,uBAAC,2BAAU,IAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,GAAI,CAChD,EAEH,iBAAiB,EAAE,KAAK,CAAC,iBAA8B,EACvD,gBAAgB,EACd,6DACG,eAAe,EAAE,EACjB,KAAK,CAAC,gBAA6B,IACnC,GAEL,EAEF,uBAAC,yBAAS,IACR,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,OAAO,CAAC,KAAK,EACtB,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,OAAO,EAAE,OAAO,CAAC,OAAO,EACxB,WAAW,EACT,MAAM,CAAC,MAAM;4BACX,CAAC,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK;4BACpC,CAAC,CAAC,mBAAmB,EAEzB,WAAW,EAAE,MAAM,CAAC,WAAW,EAC/B,WAAW,EAAE,KAAK,CAAC,WAAwB,EAC3C,WAAW,EAAE,KAAK,CAAC,WAAwB,EAC3C,WAAW,EAAE,eAAe,CAAC,WAAW,EACxC,SAAS,EAAE,eAAe,CAAC,SAAS,EACpC,UAAU,EAAE,eAAe,CAAC,QAAQ,EACpC,kBAAkB,EAAE,eAAe,CAAC,MAAM,EAC1C,mBAAmB,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,gBAAgB,EACzD,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,CAAC,UAAU,EAC7B,eAAe,EAAE,MAAM,CAAC,SAAS,EAAE,eAAe,EAClD,aAAa,EAAE,MAAM,CAAC,SAAS,EAAE,aAAa,GAC9C,IACE,EAEL,KAAK,CAAC,SAAS,IAAI,CAClB,gCAAK,SAAS,EAAC,6CAA6C,YACzD,KAAK,CAAC,SAAsB,GACzB,CACP,IACG,CACP,CAAA;AACH,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReactNode, type RefObject
|
|
1
|
+
import { type ReactNode, type RefObject } from "react";
|
|
2
2
|
import type { LiteMessage } from "@portablecore/types/chat";
|
|
3
3
|
import type { ChatInterfaceConfig } from "../types.js";
|
|
4
4
|
interface MessageListProps {
|
|
@@ -10,10 +10,6 @@ interface MessageListProps {
|
|
|
10
10
|
beforeMessageList?: ReactNode;
|
|
11
11
|
afterMessageList?: ReactNode;
|
|
12
12
|
scrollRef: RefObject<HTMLDivElement>;
|
|
13
|
-
messagesEndRef: RefObject<HTMLDivElement>;
|
|
14
|
-
lastUserMessageRef: MutableRefObject<HTMLDivElement | null>;
|
|
15
|
-
spacerExpanded: boolean;
|
|
16
|
-
spacerHeight: number;
|
|
17
13
|
}
|
|
18
14
|
/**
|
|
19
15
|
* Scrollable message list with auto-scroll, date separators,
|
|
@@ -23,6 +19,6 @@ interface MessageListProps {
|
|
|
23
19
|
* - "full": max-w-5xl, wider padding, extra bottom space for scroll
|
|
24
20
|
* - "compact": max-w-3xl, tighter padding
|
|
25
21
|
*/
|
|
26
|
-
export declare function MessageList({ messages, streaming, config, markdownComponents, emptyState, beforeMessageList, afterMessageList, scrollRef,
|
|
22
|
+
export declare function MessageList({ messages, streaming, config, markdownComponents, emptyState, beforeMessageList, afterMessageList, scrollRef, }: MessageListProps): import("react/jsx-runtime").JSX.Element;
|
|
27
23
|
export {};
|
|
28
24
|
//# sourceMappingURL=message-list.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-list.d.ts","sourceRoot":"","sources":["../../src/components/message-list.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"message-list.d.ts","sourceRoot":"","sources":["../../src/components/message-list.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAItD,UAAU,gBAAgB;IACxB,QAAQ,EAAE,WAAW,EAAE,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE,mBAAmB,CAAA;IAE3B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACxC,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB,iBAAiB,CAAC,EAAE,SAAS,CAAA;IAC7B,gBAAgB,CAAC,EAAE,SAAS,CAAA;IAC5B,SAAS,EAAE,SAAS,CAAC,cAAc,CAAC,CAAA;CACrC;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,MAAM,EACN,kBAAkB,EAClB,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,GACV,EAAE,gBAAgB,2CA4DlB"}
|
|
@@ -14,18 +14,10 @@ const message_bubble_js_1 = require("./message-bubble.js");
|
|
|
14
14
|
* - "full": max-w-5xl, wider padding, extra bottom space for scroll
|
|
15
15
|
* - "compact": max-w-3xl, tighter padding
|
|
16
16
|
*/
|
|
17
|
-
function MessageList({ messages, streaming, config, markdownComponents, emptyState, beforeMessageList, afterMessageList, scrollRef,
|
|
17
|
+
function MessageList({ messages, streaming, config, markdownComponents, emptyState, beforeMessageList, afterMessageList, scrollRef, }) {
|
|
18
18
|
const variant = config.variant || "full";
|
|
19
19
|
const isCompact = variant === "compact";
|
|
20
20
|
const renderableMessages = (0, react_1.useMemo)(() => (0, message_grouping_js_1.groupMessages)(messages), [messages]);
|
|
21
|
-
// Find the last user message ID (for attaching scroll ref)
|
|
22
|
-
const lastUserMessageId = (0, react_1.useMemo)(() => {
|
|
23
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
24
|
-
if (messages[i].role === "user")
|
|
25
|
-
return messages[i].id;
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}, [messages]);
|
|
29
21
|
const isEmpty = messages.length === 0;
|
|
30
22
|
const scrollClasses = isCompact
|
|
31
23
|
? "flex-1 overflow-y-auto px-4 py-4"
|
|
@@ -34,6 +26,6 @@ function MessageList({ messages, streaming, config, markdownComponents, emptySta
|
|
|
34
26
|
? `max-w-3xl mx-auto space-y-0 ${config.classNames?.messageListInner || ""}`
|
|
35
27
|
: `max-w-5xl mx-auto px-4 sm:px-6 pt-2 pb-12 sm:pb-4 space-y-0 ${config.classNames?.messageListInner || ""}`;
|
|
36
28
|
const listClasses = config.classNames?.messageList || "";
|
|
37
|
-
return ((0, jsx_runtime_1.jsxs)("div", { ref: scrollRef, className: `${scrollClasses} ${listClasses}`, children: [beforeMessageList, isEmpty && emptyState ? ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center justify-center h-full", children: emptyState })) : ((0, jsx_runtime_1.
|
|
29
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: scrollRef, className: `${scrollClasses} ${listClasses}`, children: [beforeMessageList, isEmpty && emptyState ? ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center justify-center h-full", children: emptyState })) : ((0, jsx_runtime_1.jsx)("div", { className: innerClasses, children: renderableMessages.map((rm) => ((0, jsx_runtime_1.jsxs)("div", { children: [rm.showDateSeparator && rm.dateSeparatorLabel && ((0, jsx_runtime_1.jsx)("div", { className: `flex items-center justify-center ${isCompact ? "py-4" : "my-6"}`, children: (0, jsx_runtime_1.jsx)("div", { className: `px-3 py-1 rounded-full text-xs font-medium bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 ${config.classNames?.dateSeparator || ""}`, children: rm.dateSeparatorLabel }) })), (0, jsx_runtime_1.jsx)(message_bubble_js_1.MessageBubble, { message: rm.message, isFirstInGroup: rm.isFirstInGroup, isLastInGroup: rm.isLastInGroup, config: config, markdownComponents: markdownComponents })] }, rm.message.id))) })), afterMessageList] }));
|
|
38
30
|
}
|
|
39
31
|
//# sourceMappingURL=message-list.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-list.js","sourceRoot":"","sources":["../../src/components/message-list.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;
|
|
1
|
+
{"version":3,"file":"message-list.js","sourceRoot":"","sources":["../../src/components/message-list.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AA4BZ,kCAqEC;;AA/FD,iCAA+D;AAG/D,sEAA4D;AAC5D,2DAAmD;AAcnD;;;;;;;GAOG;AACH,SAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,MAAM,EACN,kBAAkB,EAClB,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,GACQ;IACjB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAA;IACxC,MAAM,SAAS,GAAG,OAAO,KAAK,SAAS,CAAA;IAEvC,MAAM,kBAAkB,GAAG,IAAA,eAAO,EAChC,GAAG,EAAE,CAAC,IAAA,mCAAa,EAAC,QAAQ,CAAC,EAC7B,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAA;IAErC,MAAM,aAAa,GAAG,SAAS;QAC7B,CAAC,CAAC,kCAAkC;QACpC,CAAC,CAAC,wBAAwB,CAAA;IAE5B,MAAM,YAAY,GAAG,SAAS;QAC5B,CAAC,CAAC,+BAA+B,MAAM,CAAC,UAAU,EAAE,gBAAgB,IAAI,EAAE,EAAE;QAC5E,CAAC,CAAC,+DAA+D,MAAM,CAAC,UAAU,EAAE,gBAAgB,IAAI,EAAE,EAAE,CAAA;IAE9G,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,EAAE,WAAW,IAAI,EAAE,CAAA;IAExD,OAAO,CACL,iCACE,GAAG,EAAE,SAAS,EACd,SAAS,EAAE,GAAG,aAAa,IAAI,WAAW,EAAE,aAE3C,iBAAiB,EAEjB,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CACvB,gCAAK,SAAS,EAAC,yCAAyC,YACrD,UAAU,GACP,CACP,CAAC,CAAC,CAAC,CACF,gCAAK,SAAS,EAAE,YAAY,YACzB,kBAAkB,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAC9B,4CACG,EAAE,CAAC,iBAAiB,IAAI,EAAE,CAAC,kBAAkB,IAAI,CAChD,gCAAK,SAAS,EAAE,oCAAoC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,YAC/E,gCACE,SAAS,EAAE,gHAAgH,MAAM,CAAC,UAAU,EAAE,aAAa,IAAI,EAAE,EAAE,YAElK,EAAE,CAAC,kBAAkB,GAClB,GACF,CACP,EACD,uBAAC,iCAAa,IACZ,OAAO,EAAE,EAAE,CAAC,OAAO,EACnB,cAAc,EAAE,EAAE,CAAC,cAAc,EACjC,aAAa,EAAE,EAAE,CAAC,aAAa,EAC/B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB,GACtC,KAhBM,EAAE,CAAC,OAAO,CAAC,EAAE,CAiBjB,CACP,CAAC,GACE,CACP,EAEA,gBAAgB,IACb,CACP,CAAA;AACH,CAAC"}
|
|
@@ -5,13 +5,8 @@ interface UseChatScrollOptions {
|
|
|
5
5
|
}
|
|
6
6
|
interface UseChatScrollReturn {
|
|
7
7
|
scrollRef: React.RefObject<HTMLDivElement>;
|
|
8
|
-
messagesEndRef: React.RefObject<HTMLDivElement>;
|
|
9
|
-
lastUserMessageRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
10
8
|
scrollToBottom: (behavior?: ScrollBehavior) => void;
|
|
11
9
|
isNearBottom: () => boolean;
|
|
12
|
-
spacerExpanded: boolean;
|
|
13
|
-
spacerHeight: number;
|
|
14
|
-
expandSpacer: () => void;
|
|
15
10
|
}
|
|
16
11
|
export declare function useChatScroll({ messages, streaming, }: UseChatScrollOptions): UseChatScrollReturn;
|
|
17
12
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chat-scroll.d.ts","sourceRoot":"","sources":["../../src/hooks/use-chat-scroll.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-chat-scroll.d.ts","sourceRoot":"","sources":["../../src/hooks/use-chat-scroll.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAE3D,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,WAAW,EAAE,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,UAAU,mBAAmB;IAC3B,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;IAC1C,cAAc,EAAE,CAAC,QAAQ,CAAC,EAAE,cAAc,KAAK,IAAI,CAAA;IACnD,YAAY,EAAE,MAAM,OAAO,CAAA;CAC5B;AAED,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,SAAS,GACV,EAAE,oBAAoB,GAAG,mBAAmB,CA0D5C"}
|
|
@@ -3,32 +3,23 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.useChatScroll = useChatScroll;
|
|
5
5
|
/**
|
|
6
|
-
* useChatScroll —
|
|
6
|
+
* useChatScroll — Simple, reliable scroll management for chat.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 3. Locking to exact height when streaming ends
|
|
13
|
-
* 4. Collapsing when user scrolls up
|
|
8
|
+
* Auto-scrolls to bottom when:
|
|
9
|
+
* - Component mounts (instant)
|
|
10
|
+
* - A new message arrives (smooth, if user was near bottom)
|
|
11
|
+
* - Streaming content grows (smooth, if user was near bottom)
|
|
14
12
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* Respects user intent: if user scrolled up, don't pull them down.
|
|
14
|
+
*
|
|
15
|
+
* The spacer system from legacy ChatInterface is intentionally NOT
|
|
16
|
+
* included here. It depends on the virtualizer (item 9) and will
|
|
17
|
+
* be ported together with virtualization.
|
|
17
18
|
*/
|
|
18
19
|
const react_1 = require("react");
|
|
19
20
|
function useChatScroll({ messages, streaming, }) {
|
|
20
21
|
const scrollRef = (0, react_1.useRef)(null);
|
|
21
|
-
const messagesEndRef = (0, react_1.useRef)(null);
|
|
22
|
-
const lastUserMessageRef = (0, react_1.useRef)(null);
|
|
23
22
|
const wasNearBottom = (0, react_1.useRef)(true);
|
|
24
|
-
const wasStreamingRef = (0, react_1.useRef)(false);
|
|
25
|
-
const lockedScrollPosition = (0, react_1.useRef)(null);
|
|
26
|
-
const lastMessageCountRef = (0, react_1.useRef)(messages.length);
|
|
27
|
-
const lastProgrammaticScroll = (0, react_1.useRef)(0);
|
|
28
|
-
const lastUserMessageId = (0, react_1.useRef)(null);
|
|
29
|
-
const pendingScrollAction = (0, react_1.useRef)(null);
|
|
30
|
-
const [spacerExpanded, setSpacerExpanded] = (0, react_1.useState)(false);
|
|
31
|
-
const [spacerHeight, setSpacerHeight] = (0, react_1.useState)(0);
|
|
32
23
|
const isNearBottom = (0, react_1.useCallback)(() => {
|
|
33
24
|
const el = scrollRef.current;
|
|
34
25
|
if (!el)
|
|
@@ -40,89 +31,25 @@ function useChatScroll({ messages, streaming, }) {
|
|
|
40
31
|
const el = scrollRef.current;
|
|
41
32
|
if (!el)
|
|
42
33
|
return;
|
|
43
|
-
lastProgrammaticScroll.current = Date.now();
|
|
44
34
|
el.scrollTo({ top: el.scrollHeight, behavior });
|
|
45
35
|
}, []);
|
|
46
|
-
|
|
47
|
-
setSpacerExpanded(true);
|
|
48
|
-
setSpacerHeight(0);
|
|
49
|
-
lockedScrollPosition.current = null;
|
|
50
|
-
pendingScrollAction.current = "top";
|
|
51
|
-
}, []);
|
|
52
|
-
// -----------------------------------------------------------------------
|
|
53
|
-
// Scroll-on-send: when a new user message appears and we have a pending
|
|
54
|
-
// scroll action, scroll the user message to the top of the viewport.
|
|
55
|
-
// Copied from legacy lines 2932-3082.
|
|
56
|
-
// -----------------------------------------------------------------------
|
|
57
|
-
(0, react_1.useEffect)(() => {
|
|
58
|
-
if (messages.length <= lastMessageCountRef.current) {
|
|
59
|
-
lastMessageCountRef.current = messages.length;
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
lastMessageCountRef.current = messages.length;
|
|
63
|
-
const newestMessage = messages[messages.length - 1];
|
|
64
|
-
if (newestMessage?.role !== "user")
|
|
65
|
-
return;
|
|
66
|
-
if (newestMessage.id === lastUserMessageId.current)
|
|
67
|
-
return;
|
|
68
|
-
lastUserMessageId.current = newestMessage.id;
|
|
69
|
-
const scrollAction = pendingScrollAction.current;
|
|
70
|
-
pendingScrollAction.current = null;
|
|
71
|
-
if (scrollAction !== "top")
|
|
72
|
-
return;
|
|
73
|
-
// RAF loop to wait for the ref to attach (legacy lines 3045-3076)
|
|
74
|
-
let rafId;
|
|
75
|
-
let attempts = 0;
|
|
76
|
-
const maxAttempts = 30;
|
|
77
|
-
const attemptScroll = () => {
|
|
78
|
-
attempts++;
|
|
79
|
-
const container = scrollRef.current;
|
|
80
|
-
const userMessageEl = lastUserMessageRef.current;
|
|
81
|
-
if (container && userMessageEl) {
|
|
82
|
-
const topOffset = 16;
|
|
83
|
-
const containerRect = container.getBoundingClientRect();
|
|
84
|
-
const elementRect = userMessageEl.getBoundingClientRect();
|
|
85
|
-
const scrollOffset = elementRect.top - containerRect.top + container.scrollTop - topOffset;
|
|
86
|
-
lastProgrammaticScroll.current = Date.now();
|
|
87
|
-
container.scrollTo({
|
|
88
|
-
top: Math.max(0, scrollOffset),
|
|
89
|
-
behavior: "smooth",
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
else if (attempts < maxAttempts) {
|
|
93
|
-
rafId = requestAnimationFrame(attemptScroll);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
const timeoutId = setTimeout(() => {
|
|
97
|
-
rafId = requestAnimationFrame(attemptScroll);
|
|
98
|
-
}, 16);
|
|
99
|
-
return () => {
|
|
100
|
-
clearTimeout(timeoutId);
|
|
101
|
-
if (rafId)
|
|
102
|
-
cancelAnimationFrame(rafId);
|
|
103
|
-
};
|
|
104
|
-
}, [messages]);
|
|
105
|
-
// Track scroll position for auto-scroll gating
|
|
36
|
+
// Track whether user is near bottom
|
|
106
37
|
(0, react_1.useEffect)(() => {
|
|
107
38
|
const el = scrollRef.current;
|
|
108
39
|
if (!el)
|
|
109
40
|
return;
|
|
110
41
|
const onScroll = () => {
|
|
111
|
-
if (Date.now() - lastProgrammaticScroll.current < 200)
|
|
112
|
-
return;
|
|
113
42
|
wasNearBottom.current = isNearBottom();
|
|
114
43
|
};
|
|
115
44
|
el.addEventListener("scroll", onScroll, { passive: true });
|
|
116
45
|
return () => el.removeEventListener("scroll", onScroll);
|
|
117
46
|
}, [isNearBottom]);
|
|
118
|
-
// Auto-scroll on new messages
|
|
47
|
+
// Auto-scroll on new messages
|
|
119
48
|
(0, react_1.useEffect)(() => {
|
|
120
|
-
if (spacerExpanded)
|
|
121
|
-
return;
|
|
122
49
|
if (wasNearBottom.current) {
|
|
123
50
|
scrollToBottom("smooth");
|
|
124
51
|
}
|
|
125
|
-
}, [messages.length, scrollToBottom
|
|
52
|
+
}, [messages.length, scrollToBottom]);
|
|
126
53
|
// Auto-scroll during streaming if near bottom
|
|
127
54
|
(0, react_1.useEffect)(() => {
|
|
128
55
|
if (!streaming)
|
|
@@ -139,73 +66,6 @@ function useChatScroll({ messages, streaming, }) {
|
|
|
139
66
|
scrollToBottom("instant");
|
|
140
67
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
141
68
|
}, []);
|
|
142
|
-
|
|
143
|
-
// Lock messages in place when streaming ends (legacy lines 3084-3122)
|
|
144
|
-
// -----------------------------------------------------------------------
|
|
145
|
-
(0, react_1.useEffect)(() => {
|
|
146
|
-
if (streaming) {
|
|
147
|
-
wasStreamingRef.current = true;
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
if (wasStreamingRef.current && spacerExpanded) {
|
|
151
|
-
wasStreamingRef.current = false;
|
|
152
|
-
const container = scrollRef.current;
|
|
153
|
-
const endMarker = messagesEndRef.current;
|
|
154
|
-
if (container && endMarker) {
|
|
155
|
-
const containerRect = container.getBoundingClientRect();
|
|
156
|
-
const endRect = endMarker.getBoundingClientRect();
|
|
157
|
-
const spaceBelow = containerRect.bottom - endRect.bottom;
|
|
158
|
-
const newSpacerHeight = Math.max(0, spaceBelow);
|
|
159
|
-
setSpacerHeight(newSpacerHeight);
|
|
160
|
-
setSpacerExpanded(false);
|
|
161
|
-
lockedScrollPosition.current = container.scrollTop;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}, [streaming, spacerExpanded]);
|
|
165
|
-
// Fallback: collapse spacer if assistant message arrives without streaming
|
|
166
|
-
// (legacy lines 3124-3146)
|
|
167
|
-
(0, react_1.useEffect)(() => {
|
|
168
|
-
const messageCount = messages.length;
|
|
169
|
-
const hadNewMessage = messageCount > lastMessageCountRef.current;
|
|
170
|
-
lastMessageCountRef.current = messageCount;
|
|
171
|
-
if (spacerExpanded && !streaming && hadNewMessage) {
|
|
172
|
-
const lastMessage = messages[messages.length - 1];
|
|
173
|
-
if (lastMessage?.role === "assistant") {
|
|
174
|
-
const timeoutId = setTimeout(() => {
|
|
175
|
-
if (!streaming && spacerExpanded) {
|
|
176
|
-
setSpacerExpanded(false);
|
|
177
|
-
setSpacerHeight(16);
|
|
178
|
-
}
|
|
179
|
-
}, 500);
|
|
180
|
-
return () => clearTimeout(timeoutId);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}, [messages.length, spacerExpanded, streaming, messages]);
|
|
184
|
-
// Collapse spacer when user scrolls up past lock point (legacy lines 3148-3168)
|
|
185
|
-
(0, react_1.useEffect)(() => {
|
|
186
|
-
const container = scrollRef.current;
|
|
187
|
-
if (!container)
|
|
188
|
-
return;
|
|
189
|
-
const handleScroll = () => {
|
|
190
|
-
if (lockedScrollPosition.current === null || spacerHeight <= 16)
|
|
191
|
-
return;
|
|
192
|
-
if (container.scrollTop < lockedScrollPosition.current - 50) {
|
|
193
|
-
setSpacerHeight(16);
|
|
194
|
-
lockedScrollPosition.current = null;
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
container.addEventListener("scroll", handleScroll, { passive: true });
|
|
198
|
-
return () => container.removeEventListener("scroll", handleScroll);
|
|
199
|
-
}, [spacerHeight]);
|
|
200
|
-
return {
|
|
201
|
-
scrollRef,
|
|
202
|
-
messagesEndRef,
|
|
203
|
-
lastUserMessageRef,
|
|
204
|
-
scrollToBottom,
|
|
205
|
-
isNearBottom,
|
|
206
|
-
spacerExpanded,
|
|
207
|
-
spacerHeight,
|
|
208
|
-
expandSpacer,
|
|
209
|
-
};
|
|
69
|
+
return { scrollRef, scrollToBottom, isNearBottom };
|
|
210
70
|
}
|
|
211
71
|
//# sourceMappingURL=use-chat-scroll.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chat-scroll.js","sourceRoot":"","sources":["../../src/hooks/use-chat-scroll.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;
|
|
1
|
+
{"version":3,"file":"use-chat-scroll.js","sourceRoot":"","sources":["../../src/hooks/use-chat-scroll.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AA+BZ,sCA6DC;AA1FD;;;;;;;;;;;;;GAaG;AAEH,iCAAsD;AActD,SAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,SAAS,GACY;IACrB,MAAM,SAAS,GAAG,IAAA,cAAM,EAAiB,IAAK,CAAC,CAAA;IAC/C,MAAM,aAAa,GAAG,IAAA,cAAM,EAAC,IAAI,CAAC,CAAA;IAElC,MAAM,YAAY,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QACpC,MAAM,EAAE,GAAG,SAAS,CAAC,OAAO,CAAA;QAC5B,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAA;QACpB,MAAM,SAAS,GAAG,GAAG,CAAA;QACrB,OAAO,EAAE,CAAC,YAAY,GAAG,EAAE,CAAC,SAAS,GAAG,EAAE,CAAC,YAAY,GAAG,SAAS,CAAA;IACrE,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,cAAc,GAAG,IAAA,mBAAW,EAChC,CAAC,WAA2B,QAAQ,EAAE,EAAE;QACtC,MAAM,EAAE,GAAG,SAAS,CAAC,OAAO,CAAA;QAC5B,IAAI,CAAC,EAAE;YAAE,OAAM;QACf,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,YAAY,EAAE,QAAQ,EAAE,CAAC,CAAA;IACjD,CAAC,EACD,EAAE,CACH,CAAA;IAED,oCAAoC;IACpC,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,MAAM,EAAE,GAAG,SAAS,CAAC,OAAO,CAAA;QAC5B,IAAI,CAAC,EAAE;YAAE,OAAM;QAEf,MAAM,QAAQ,GAAG,GAAG,EAAE;YACpB,aAAa,CAAC,OAAO,GAAG,YAAY,EAAE,CAAA;QACxC,CAAC,CAAA;QAED,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QAC1D,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;IACzD,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAA;IAElB,8BAA8B;IAC9B,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;YAC1B,cAAc,CAAC,QAAQ,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;IAErC,8CAA8C;IAC9C,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS;YAAE,OAAM;QACtB,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;YAChC,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;gBAC1B,cAAc,CAAC,QAAQ,CAAC,CAAA;YAC1B,CAAC;QACH,CAAC,EAAE,GAAG,CAAC,CAAA;QACP,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;IACtC,CAAC,EAAE,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAA;IAE/B,0BAA0B;IAC1B,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,cAAc,CAAC,SAAS,CAAC,CAAA;QACzB,uDAAuD;IACzD,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,CAAA;AACpD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portablecore/chat",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Unified chat UI for Portable platforms — composable ChatInterface with extension points",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"remark-breaks": "^4.0.0",
|
|
28
28
|
"rehype-raw": "^7.0.0",
|
|
29
29
|
"rehype-highlight": "^7.0.2",
|
|
30
|
-
"@portablecore/
|
|
31
|
-
"@portablecore/
|
|
30
|
+
"@portablecore/chat-runtime": "0.1.0",
|
|
31
|
+
"@portablecore/types": "0.11.2"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"react": ">=18.0.0",
|