@portablecore/chat 0.1.0
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 +27 -0
- package/dist/chat-interface-core.d.ts.map +1 -0
- package/dist/chat-interface-core.js +35 -0
- package/dist/chat-interface-core.js.map +1 -0
- package/dist/components/empty-state.d.ts +10 -0
- package/dist/components/empty-state.d.ts.map +1 -0
- package/dist/components/empty-state.js +16 -0
- package/dist/components/empty-state.js.map +1 -0
- package/dist/components/input-area.d.ts +26 -0
- package/dist/components/input-area.d.ts.map +1 -0
- package/dist/components/input-area.js +66 -0
- package/dist/components/input-area.js.map +1 -0
- package/dist/components/message-bubble.d.ts +20 -0
- package/dist/components/message-bubble.d.ts.map +1 -0
- package/dist/components/message-bubble.js +36 -0
- package/dist/components/message-bubble.js.map +1 -0
- package/dist/components/message-list.d.ts +23 -0
- package/dist/components/message-list.d.ts.map +1 -0
- package/dist/components/message-list.js +24 -0
- package/dist/components/message-list.js.map +1 -0
- package/dist/components/streaming-text.d.ts +14 -0
- package/dist/components/streaming-text.d.ts.map +1 -0
- package/dist/components/streaming-text.js +41 -0
- package/dist/components/streaming-text.js.map +1 -0
- package/dist/components/thinking-indicator.d.ts +18 -0
- package/dist/components/thinking-indicator.d.ts.map +1 -0
- package/dist/components/thinking-indicator.js +29 -0
- package/dist/components/thinking-indicator.js.map +1 -0
- package/dist/hooks/use-chat-scroll.d.ts +23 -0
- package/dist/hooks/use-chat-scroll.d.ts.map +1 -0
- package/dist/hooks/use-chat-scroll.js +66 -0
- package/dist/hooks/use-chat-scroll.js.map +1 -0
- package/dist/hooks/use-chat-session.d.ts +15 -0
- package/dist/hooks/use-chat-session.d.ts.map +1 -0
- package/dist/hooks/use-chat-session.js +224 -0
- package/dist/hooks/use-chat-session.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/markdown-config.d.ts +9 -0
- package/dist/utils/markdown-config.d.ts.map +1 -0
- package/dist/utils/markdown-config.js +21 -0
- package/dist/utils/markdown-config.js.map +1 -0
- package/dist/utils/message-grouping.d.ts +10 -0
- package/dist/utils/message-grouping.d.ts.map +1 -0
- package/dist/utils/message-grouping.js +36 -0
- package/dist/utils/message-grouping.js.map +1 -0
- package/dist/utils/time.d.ts +8 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +34 -0
- package/dist/utils/time.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ChatInterfaceConfig } from "./types.js";
|
|
2
|
+
interface ChatInterfaceCoreProps {
|
|
3
|
+
config: ChatInterfaceConfig;
|
|
4
|
+
/**
|
|
5
|
+
* Optional custom markdown components passed to ReactMarkdown.
|
|
6
|
+
* Allows platforms to override rendering of specific elements
|
|
7
|
+
* (code blocks, links, images, tables, etc.).
|
|
8
|
+
*/
|
|
9
|
+
markdownComponents?: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* ChatInterfaceCore — The unified chat experience.
|
|
13
|
+
*
|
|
14
|
+
* Accepts a ChatInterfaceConfig and renders a complete chat interface:
|
|
15
|
+
* message list, input area, streaming indicators, and empty state.
|
|
16
|
+
*
|
|
17
|
+
* Platform-specific behavior is injected via the config's features,
|
|
18
|
+
* slots, resolvers, and handlers. The component itself has zero
|
|
19
|
+
* knowledge of Expert vs. Team.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* <ChatInterfaceCore config={teamChatConfig} />
|
|
23
|
+
* <ChatInterfaceCore config={expertChatConfig} />
|
|
24
|
+
*/
|
|
25
|
+
export declare function ChatInterfaceCore({ config, markdownComponents, }: ChatInterfaceCoreProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=chat-interface-core.d.ts.map
|
|
@@ -0,0 +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;AAOhE,UAAU,sBAAsB;IAC9B,MAAM,EAAE,mBAAmB,CAAA;IAC3B;;;;OAIG;IAEH,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACzC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,kBAAkB,GACnB,EAAE,sBAAsB,2CAkExB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ChatInterfaceCore = ChatInterfaceCore;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const use_chat_session_js_1 = require("./hooks/use-chat-session.js");
|
|
7
|
+
const message_list_js_1 = require("./components/message-list.js");
|
|
8
|
+
const input_area_js_1 = require("./components/input-area.js");
|
|
9
|
+
const thinking_indicator_js_1 = require("./components/thinking-indicator.js");
|
|
10
|
+
const empty_state_js_1 = require("./components/empty-state.js");
|
|
11
|
+
/**
|
|
12
|
+
* ChatInterfaceCore — The unified chat experience.
|
|
13
|
+
*
|
|
14
|
+
* Accepts a ChatInterfaceConfig and renders a complete chat interface:
|
|
15
|
+
* message list, input area, streaming indicators, and empty state.
|
|
16
|
+
*
|
|
17
|
+
* Platform-specific behavior is injected via the config's features,
|
|
18
|
+
* slots, resolvers, and handlers. The component itself has zero
|
|
19
|
+
* knowledge of Expert vs. Team.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* <ChatInterfaceCore config={teamChatConfig} />
|
|
23
|
+
* <ChatInterfaceCore config={expertChatConfig} />
|
|
24
|
+
*/
|
|
25
|
+
function ChatInterfaceCore({ config, markdownComponents, }) {
|
|
26
|
+
const session = (0, use_chat_session_js_1.useChatSession)(config);
|
|
27
|
+
const slots = (config.slots || {});
|
|
28
|
+
const showThinking = session.streaming &&
|
|
29
|
+
session.thinkingStages.length > 0 &&
|
|
30
|
+
!session.activeSkill;
|
|
31
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "flex h-full", 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, 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: [showThinking && ((0, jsx_runtime_1.jsx)("div", { className: "max-w-3xl mx-auto px-4", children: (0, jsx_runtime_1.jsx)(thinking_indicator_js_1.ThinkingIndicator, { stages: session.thinkingStages, expertName: config.expert?.name, expertAvatarUrl: config.expert?.avatarUrl }) })), slots.afterMessageList] }) }), (0, jsx_runtime_1.jsx)(input_area_js_1.InputArea, { onSend: session.send, onAbort: session.abort, streaming: session.streaming, sending: session.sending, placeholder: config.expert
|
|
32
|
+
? `Message ${config.expert.name}...`
|
|
33
|
+
: "Send a message...", prefillText: config.prefillText, inputPrefix: slots.inputPrefix, inputSuffix: slots.inputSuffix })] }), slots.sidePanel && ((0, jsx_runtime_1.jsx)("div", { className: "w-80 border-l border-stone-200 dark:border-slate-700 overflow-y-auto", children: slots.sidePanel }))] }));
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=chat-interface-core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-interface-core.js","sourceRoot":"","sources":["../src/chat-interface-core.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AAmCZ,8CAqEC;;AApGD,qEAA4D;AAC5D,kEAA0D;AAC1D,8DAAsD;AACtD,8EAAsE;AACtE,gEAAwD;AAaxD;;;;;;;;;;;;;GAaG;AACH,SAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,kBAAkB,GACK;IACvB,MAAM,OAAO,GAAG,IAAA,oCAAc,EAAC,MAAM,CAAC,CAAA;IACtC,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAc,CAAA;IAE/C,MAAM,YAAY,GAChB,OAAO,CAAC,SAAS;QACjB,OAAO,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC;QACjC,CAAC,OAAO,CAAC,WAAW,CAAA;IAEtB,OAAO,CACL,iCAAK,SAAS,EAAC,aAAa,aAE1B,iCAAK,SAAS,EAAC,8BAA8B,aAE3C,uBAAC,6BAAW,IACV,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAC1B,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB,EACtC,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,YAAY,IAAI,CACf,gCAAK,SAAS,EAAC,wBAAwB,YACrC,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,EACA,KAAK,CAAC,gBAA6B,IACnC,GAEL,EAGF,uBAAC,yBAAS,IACR,MAAM,EAAE,OAAO,CAAC,IAAI,EACpB,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,GAC3C,IACE,EAGL,KAAK,CAAC,SAAS,IAAI,CAClB,gCAAK,SAAS,EAAC,sEAAsE,YAClF,KAAK,CAAC,SAAsB,GACzB,CACP,IACG,CACP,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface EmptyStateProps {
|
|
2
|
+
expertName?: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Default empty chat state shown when no messages exist.
|
|
6
|
+
* Platforms can override this entirely via the `emptyState` slot.
|
|
7
|
+
*/
|
|
8
|
+
export declare function EmptyState({ expertName }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=empty-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../../src/components/empty-state.tsx"],"names":[],"mappings":"AAIA,UAAU,eAAe;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EAAE,UAAU,EAAE,EAAE,eAAe,2CAkBzD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.EmptyState = EmptyState;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const lucide_react_1 = require("lucide-react");
|
|
7
|
+
/**
|
|
8
|
+
* Default empty chat state shown when no messages exist.
|
|
9
|
+
* Platforms can override this entirely via the `emptyState` slot.
|
|
10
|
+
*/
|
|
11
|
+
function EmptyState({ expertName }) {
|
|
12
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-center justify-center gap-3 text-center py-16 px-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-12 h-12 rounded-full bg-stone-100 dark:bg-slate-800 flex items-center justify-center", children: (0, jsx_runtime_1.jsx)(lucide_react_1.MessageSquare, { className: "w-6 h-6 text-stone-400 dark:text-slate-500" }) }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-sm font-medium text-stone-700 dark:text-slate-300", children: expertName
|
|
13
|
+
? `Start a conversation with ${expertName}`
|
|
14
|
+
: "Start a conversation" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-stone-500 dark:text-slate-400 mt-1", children: "Type a message below to begin." })] })] }));
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=empty-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-state.js","sourceRoot":"","sources":["../../src/components/empty-state.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AAYZ,gCAkBC;;AA5BD,+CAA4C;AAM5C;;;GAGG;AACH,SAAgB,UAAU,CAAC,EAAE,UAAU,EAAmB;IACxD,OAAO,CACL,iCAAK,SAAS,EAAC,wEAAwE,aACrF,gCAAK,SAAS,EAAC,wFAAwF,YACrG,uBAAC,4BAAa,IAAC,SAAS,EAAC,4CAA4C,GAAG,GACpE,EACN,4CACE,+BAAI,SAAS,EAAC,wDAAwD,YACnE,UAAU;4BACT,CAAC,CAAC,6BAA6B,UAAU,EAAE;4BAC3C,CAAC,CAAC,sBAAsB,GACvB,EACL,8BAAG,SAAS,EAAC,iDAAiD,+CAE1D,IACA,IACF,CACP,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
interface InputAreaProps {
|
|
3
|
+
onSend: (content: string) => void;
|
|
4
|
+
onAbort?: () => void;
|
|
5
|
+
streaming: boolean;
|
|
6
|
+
sending: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
prefillText?: string;
|
|
10
|
+
inputPrefix?: ReactNode;
|
|
11
|
+
inputSuffix?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Chat input area with textarea, send/stop button, and slot anchors
|
|
15
|
+
* for platform-specific prefix/suffix content.
|
|
16
|
+
*
|
|
17
|
+
* Supports:
|
|
18
|
+
* - Enter to send, Shift+Enter for newline
|
|
19
|
+
* - Auto-resize textarea
|
|
20
|
+
* - Stop generation button during streaming
|
|
21
|
+
* - Slot for inputPrefix (e.g., project badge, whisper indicator)
|
|
22
|
+
* - Slot for inputSuffix (e.g., provider selector, voice button)
|
|
23
|
+
*/
|
|
24
|
+
export declare function InputArea({ onSend, onAbort, streaming, sending, disabled, placeholder, prefillText, inputPrefix, inputSuffix, }: InputAreaProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=input-area.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-area.d.ts","sourceRoot":"","sources":["../../src/components/input-area.tsx"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,SAAS,EAEf,MAAM,OAAO,CAAA;AAGd,UAAU,cAAc;IACtB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,WAAW,CAAC,EAAE,SAAS,CAAA;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,EACxB,MAAM,EACN,OAAO,EACP,SAAS,EACT,OAAO,EACP,QAAgB,EAChB,WAAiC,EACjC,WAAW,EACX,WAAW,EACX,WAAW,GACZ,EAAE,cAAc,2CAmGhB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.InputArea = InputArea;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const lucide_react_1 = require("lucide-react");
|
|
8
|
+
/**
|
|
9
|
+
* Chat input area with textarea, send/stop button, and slot anchors
|
|
10
|
+
* for platform-specific prefix/suffix content.
|
|
11
|
+
*
|
|
12
|
+
* Supports:
|
|
13
|
+
* - Enter to send, Shift+Enter for newline
|
|
14
|
+
* - Auto-resize textarea
|
|
15
|
+
* - Stop generation button during streaming
|
|
16
|
+
* - Slot for inputPrefix (e.g., project badge, whisper indicator)
|
|
17
|
+
* - Slot for inputSuffix (e.g., provider selector, voice button)
|
|
18
|
+
*/
|
|
19
|
+
function InputArea({ onSend, onAbort, streaming, sending, disabled = false, placeholder = "Send a message...", prefillText, inputPrefix, inputSuffix, }) {
|
|
20
|
+
const [text, setText] = (0, react_1.useState)(prefillText || "");
|
|
21
|
+
const textareaRef = (0, react_1.useRef)(null);
|
|
22
|
+
const composingRef = (0, react_1.useRef)(false);
|
|
23
|
+
(0, react_1.useEffect)(() => {
|
|
24
|
+
if (prefillText) {
|
|
25
|
+
setText(prefillText);
|
|
26
|
+
}
|
|
27
|
+
}, [prefillText]);
|
|
28
|
+
const adjustHeight = (0, react_1.useCallback)(() => {
|
|
29
|
+
const el = textareaRef.current;
|
|
30
|
+
if (!el)
|
|
31
|
+
return;
|
|
32
|
+
el.style.height = "auto";
|
|
33
|
+
el.style.height = `${Math.min(el.scrollHeight, 200)}px`;
|
|
34
|
+
}, []);
|
|
35
|
+
(0, react_1.useEffect)(() => {
|
|
36
|
+
adjustHeight();
|
|
37
|
+
}, [text, adjustHeight]);
|
|
38
|
+
const handleSend = (0, react_1.useCallback)(() => {
|
|
39
|
+
const trimmed = text.trim();
|
|
40
|
+
if (!trimmed || sending || streaming || disabled)
|
|
41
|
+
return;
|
|
42
|
+
onSend(trimmed);
|
|
43
|
+
setText("");
|
|
44
|
+
requestAnimationFrame(() => {
|
|
45
|
+
const el = textareaRef.current;
|
|
46
|
+
if (el) {
|
|
47
|
+
el.style.height = "auto";
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}, [text, sending, streaming, disabled, onSend]);
|
|
51
|
+
const handleKeyDown = (0, react_1.useCallback)((e) => {
|
|
52
|
+
if (composingRef.current)
|
|
53
|
+
return;
|
|
54
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
handleSend();
|
|
57
|
+
}
|
|
58
|
+
}, [handleSend]);
|
|
59
|
+
const canSend = text.trim().length > 0 && !sending && !streaming && !disabled;
|
|
60
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "border-t border-stone-200 dark:border-slate-700 bg-white dark:bg-slate-900 px-4 py-3", children: [inputPrefix && ((0, jsx_runtime_1.jsx)("div", { className: "mb-2", children: inputPrefix })), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-end gap-2 max-w-3xl mx-auto", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex-1 relative", children: (0, jsx_runtime_1.jsx)("textarea", { ref: textareaRef, value: text, onChange: (e) => setText(e.target.value), onKeyDown: handleKeyDown, onCompositionStart: () => {
|
|
61
|
+
composingRef.current = true;
|
|
62
|
+
}, onCompositionEnd: () => {
|
|
63
|
+
composingRef.current = false;
|
|
64
|
+
}, placeholder: placeholder, disabled: disabled || sending, rows: 1, className: "w-full resize-none rounded-xl border border-stone-300 dark:border-slate-600 bg-stone-50 dark:bg-slate-800 px-4 py-2.5 text-sm text-stone-900 dark:text-slate-100 placeholder:text-stone-400 dark:placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 transition-colors" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5", children: [inputSuffix, streaming ? ((0, jsx_runtime_1.jsx)("button", { onClick: onAbort, className: "p-2 rounded-lg bg-red-500 text-white hover:bg-red-600 transition-colors", "aria-label": "Stop generating", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Square, { className: "w-4 h-4" }) })) : ((0, jsx_runtime_1.jsx)("button", { onClick: handleSend, disabled: !canSend, className: "p-2 rounded-lg bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors", "aria-label": "Send message", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Send, { className: "w-4 h-4" }) }))] })] })] }));
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=input-area.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-area.js","sourceRoot":"","sources":["../../src/components/input-area.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { LiteMessage } from "@portablecore/types/chat";
|
|
2
|
+
import type { ChatInterfaceConfig } from "../types.js";
|
|
3
|
+
interface MessageBubbleProps {
|
|
4
|
+
message: LiteMessage;
|
|
5
|
+
isFirstInGroup: boolean;
|
|
6
|
+
isLastInGroup: boolean;
|
|
7
|
+
config: ChatInterfaceConfig;
|
|
8
|
+
markdownComponents?: Record<string, any>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A single message bubble. Handles both user and assistant roles,
|
|
12
|
+
* markdown rendering, streaming text animation, and timestamps.
|
|
13
|
+
*
|
|
14
|
+
* Platform-specific chrome (reactions, edit/delete, copy, etc.)
|
|
15
|
+
* is NOT handled here; platforms inject that via slots or wrap
|
|
16
|
+
* this component.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MessageBubble: import("react").NamedExoticComponent<MessageBubbleProps>;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=message-bubble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-bubble.d.ts","sourceRoot":"","sources":["../../src/components/message-bubble.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAKtD,UAAU,kBAAkB;IAC1B,OAAO,EAAE,WAAW,CAAA;IACpB,cAAc,EAAE,OAAO,CAAA;IACvB,aAAa,EAAE,OAAO,CAAA;IACtB,MAAM,EAAE,mBAAmB,CAAA;IAE3B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACzC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,0DA8GxB,CAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.MessageBubble = void 0;
|
|
8
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const react_markdown_1 = __importDefault(require("react-markdown"));
|
|
11
|
+
const markdown_config_js_1 = require("../utils/markdown-config.js");
|
|
12
|
+
const time_js_1 = require("../utils/time.js");
|
|
13
|
+
const streaming_text_js_1 = require("./streaming-text.js");
|
|
14
|
+
/**
|
|
15
|
+
* A single message bubble. Handles both user and assistant roles,
|
|
16
|
+
* markdown rendering, streaming text animation, and timestamps.
|
|
17
|
+
*
|
|
18
|
+
* Platform-specific chrome (reactions, edit/delete, copy, etc.)
|
|
19
|
+
* is NOT handled here; platforms inject that via slots or wrap
|
|
20
|
+
* this component.
|
|
21
|
+
*/
|
|
22
|
+
exports.MessageBubble = (0, react_1.memo)(function MessageBubble({ message, isFirstInGroup, isLastInGroup, config, markdownComponents, }) {
|
|
23
|
+
const isUser = message.role === "user";
|
|
24
|
+
const isStreaming = message.streaming === true;
|
|
25
|
+
const expertInfo = config.expert;
|
|
26
|
+
const currentUser = config.currentUser;
|
|
27
|
+
const senderName = isUser ? currentUser.name : expertInfo?.name;
|
|
28
|
+
const avatarUrl = isUser ? currentUser.avatarUrl : expertInfo?.avatarUrl;
|
|
29
|
+
const timestamp = (0, react_1.useMemo)(() => (message.createdAt ? (0, time_js_1.formatTime)(message.createdAt) : null), [message.createdAt]);
|
|
30
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: `flex ${isUser ? "justify-end" : "justify-start"} ${isFirstInGroup ? "mt-4" : "mt-0.5"}`, children: (0, jsx_runtime_1.jsxs)("div", { className: `flex gap-2.5 max-w-[85%] ${isUser ? "flex-row-reverse" : "flex-row"}`, children: [(0, jsx_runtime_1.jsx)("div", { className: "w-7 h-7 shrink-0", children: isFirstInGroup && (avatarUrl ? ((0, jsx_runtime_1.jsx)("img", { src: avatarUrl, alt: senderName || "", className: "w-7 h-7 rounded-full object-cover" })) : ((0, jsx_runtime_1.jsx)("div", { className: "w-7 h-7 rounded-full bg-stone-200 dark:bg-slate-700 flex items-center justify-center text-xs font-medium text-stone-600 dark:text-slate-300", children: (senderName || "?").charAt(0).toUpperCase() }))) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-0.5", children: [isFirstInGroup && senderName && ((0, jsx_runtime_1.jsx)("span", { className: `text-xs font-medium text-stone-500 dark:text-slate-400 ${isUser ? "text-right" : "text-left"}`, children: senderName })), (0, jsx_runtime_1.jsx)("div", { className: `px-4 py-2.5 ${isUser
|
|
31
|
+
? "bg-blue-600 text-white rounded-2xl rounded-tr-md"
|
|
32
|
+
: "bg-white dark:bg-slate-800 border border-stone-200 dark:border-slate-700 text-stone-900 dark:text-slate-100 rounded-2xl rounded-tl-md"} ${!isFirstInGroup && isUser ? "rounded-tr-2xl" : ""} ${!isFirstInGroup && !isUser ? "rounded-tl-2xl" : ""}`, children: (0, jsx_runtime_1.jsx)("div", { className: `prose prose-sm max-w-none ${isUser
|
|
33
|
+
? "prose-invert"
|
|
34
|
+
: "dark:prose-invert"}`, children: isStreaming ? ((0, jsx_runtime_1.jsx)(streaming_text_js_1.StreamingText, { content: message.content, isStreaming: true, markdownComponents: markdownComponents })) : ((0, jsx_runtime_1.jsx)(react_markdown_1.default, { remarkPlugins: markdown_config_js_1.remarkPlugins, rehypePlugins: markdown_config_js_1.rehypePlugins, components: markdownComponents, children: message.content })) }) }), isLastInGroup && timestamp && ((0, jsx_runtime_1.jsx)("span", { className: `text-[10px] text-stone-400 dark:text-slate-500 mt-0.5 ${isUser ? "text-right" : "text-left"}`, children: timestamp }))] })] }) }));
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=message-bubble.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-bubble.js","sourceRoot":"","sources":["../../src/components/message-bubble.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { LiteMessage } from "@portablecore/types/chat";
|
|
3
|
+
import type { ChatInterfaceConfig } from "../types.js";
|
|
4
|
+
interface MessageListProps {
|
|
5
|
+
messages: LiteMessage[];
|
|
6
|
+
streaming: boolean;
|
|
7
|
+
config: ChatInterfaceConfig;
|
|
8
|
+
markdownComponents?: Record<string, any>;
|
|
9
|
+
emptyState?: ReactNode;
|
|
10
|
+
beforeMessageList?: ReactNode;
|
|
11
|
+
afterMessageList?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Scrollable message list with auto-scroll, date separators,
|
|
15
|
+
* and message grouping.
|
|
16
|
+
*
|
|
17
|
+
* v1 uses simple overflow-scroll (no virtualization). Virtualization
|
|
18
|
+
* via @tanstack/react-virtual can be added later without changing
|
|
19
|
+
* the public API.
|
|
20
|
+
*/
|
|
21
|
+
export declare function MessageList({ messages, streaming, config, markdownComponents, emptyState, beforeMessageList, afterMessageList, }: MessageListProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=message-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-list.d.ts","sourceRoot":"","sources":["../../src/components/message-list.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAKtD,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;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,MAAM,EACN,kBAAkB,EAClB,UAAU,EACV,iBAAiB,EACjB,gBAAgB,GACjB,EAAE,gBAAgB,2CA+ClB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.MessageList = MessageList;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const message_grouping_js_1 = require("../utils/message-grouping.js");
|
|
8
|
+
const use_chat_scroll_js_1 = require("../hooks/use-chat-scroll.js");
|
|
9
|
+
const message_bubble_js_1 = require("./message-bubble.js");
|
|
10
|
+
/**
|
|
11
|
+
* Scrollable message list with auto-scroll, date separators,
|
|
12
|
+
* and message grouping.
|
|
13
|
+
*
|
|
14
|
+
* v1 uses simple overflow-scroll (no virtualization). Virtualization
|
|
15
|
+
* via @tanstack/react-virtual can be added later without changing
|
|
16
|
+
* the public API.
|
|
17
|
+
*/
|
|
18
|
+
function MessageList({ messages, streaming, config, markdownComponents, emptyState, beforeMessageList, afterMessageList, }) {
|
|
19
|
+
const { scrollRef } = (0, use_chat_scroll_js_1.useChatScroll)({ messages, streaming });
|
|
20
|
+
const renderableMessages = (0, react_1.useMemo)(() => (0, message_grouping_js_1.groupMessages)(messages), [messages]);
|
|
21
|
+
const isEmpty = messages.length === 0;
|
|
22
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: scrollRef, className: "flex-1 overflow-y-auto px-4 py-4", 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: "max-w-3xl mx-auto space-y-0", 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 py-4", children: (0, jsx_runtime_1.jsx)("div", { className: "bg-stone-100 dark:bg-slate-800 text-stone-500 dark:text-slate-400 text-xs font-medium px-3 py-1 rounded-full", 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] }));
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=message-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-list.js","sourceRoot":"","sources":["../../src/components/message-list.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AA4BZ,kCAuDC;;AAjFD,iCAA+C;AAG/C,sEAA4D;AAC5D,oEAA2D;AAC3D,2DAAmD;AAanD;;;;;;;GAOG;AACH,SAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,MAAM,EACN,kBAAkB,EAClB,UAAU,EACV,iBAAiB,EACjB,gBAAgB,GACC;IACjB,MAAM,EAAE,SAAS,EAAE,GAAG,IAAA,kCAAa,EAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAA;IAE5D,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,OAAO,CACL,iCACE,GAAG,EAAE,SAAS,EACd,SAAS,EAAC,kCAAkC,aAE3C,iBAAiB,EAEjB,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CACvB,gCAAK,SAAS,EAAC,yCAAyC,YACrD,UAAU,GACP,CACP,CAAC,CAAC,CAAC,CACF,gCAAK,SAAS,EAAC,6BAA6B,YACzC,kBAAkB,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAC9B,4CACG,EAAE,CAAC,iBAAiB,IAAI,EAAE,CAAC,kBAAkB,IAAI,CAChD,gCAAK,SAAS,EAAC,uCAAuC,YACpD,gCAAK,SAAS,EAAC,8GAA8G,YAC1H,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,KAdM,EAAE,CAAC,OAAO,CAAC,EAAE,CAejB,CACP,CAAC,GACE,CACP,EAEA,gBAAgB,IACb,CACP,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "@nvq/flowtoken/dist/styles.css";
|
|
2
|
+
interface StreamingTextProps {
|
|
3
|
+
content: string;
|
|
4
|
+
isStreaming: boolean;
|
|
5
|
+
markdownComponents?: Record<string, any>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* StreamingText renders content with smooth word-by-word fade-in
|
|
9
|
+
* during streaming (via FlowToken), then switches to full markdown
|
|
10
|
+
* rendering once the stream completes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function StreamingText({ content, isStreaming, markdownComponents, }: StreamingTextProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=streaming-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming-text.d.ts","sourceRoot":"","sources":["../../src/components/streaming-text.tsx"],"names":[],"mappings":"AAIA,OAAO,gCAAgC,CAAA;AAIvC,UAAU,kBAAkB;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,OAAO,CAAA;IAEpB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACzC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,WAAW,EACX,kBAAkB,GACnB,EAAE,kBAAkB,2CAuCpB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.StreamingText = StreamingText;
|
|
8
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const flowtoken_1 = require("@nvq/flowtoken");
|
|
11
|
+
require("@nvq/flowtoken/dist/styles.css");
|
|
12
|
+
const react_markdown_1 = __importDefault(require("react-markdown"));
|
|
13
|
+
const markdown_config_js_1 = require("../utils/markdown-config.js");
|
|
14
|
+
/**
|
|
15
|
+
* StreamingText renders content with smooth word-by-word fade-in
|
|
16
|
+
* during streaming (via FlowToken), then switches to full markdown
|
|
17
|
+
* rendering once the stream completes.
|
|
18
|
+
*/
|
|
19
|
+
function StreamingText({ content, isStreaming, markdownComponents, }) {
|
|
20
|
+
(0, react_1.useEffect)(() => {
|
|
21
|
+
if (!isStreaming)
|
|
22
|
+
return;
|
|
23
|
+
const originalError = console.error;
|
|
24
|
+
console.error = (...args) => {
|
|
25
|
+
const message = args[0];
|
|
26
|
+
if (typeof message === "string" && message.includes("same key, `NaN`")) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
originalError.apply(console, args);
|
|
30
|
+
};
|
|
31
|
+
return () => {
|
|
32
|
+
console.error = originalError;
|
|
33
|
+
};
|
|
34
|
+
}, [isStreaming]);
|
|
35
|
+
const safeContent = content || "";
|
|
36
|
+
if (!isStreaming) {
|
|
37
|
+
return ((0, jsx_runtime_1.jsx)(react_markdown_1.default, { remarkPlugins: markdown_config_js_1.remarkPlugins, rehypePlugins: markdown_config_js_1.rehypePlugins, components: markdownComponents, children: safeContent }));
|
|
38
|
+
}
|
|
39
|
+
return ((0, jsx_runtime_1.jsx)(flowtoken_1.AnimatedMarkdown, { content: safeContent, sep: "word", animation: "fadeIn", animationDuration: "0.3s", animationTimingFunction: "ease-out" }));
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=streaming-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming-text.js","sourceRoot":"","sources":["../../src/components/streaming-text.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;;;;AAoBZ,sCA2CC;;AA7DD,iCAAiC;AACjC,8CAAiD;AACjD,0CAAuC;AACvC,oEAA0C;AAC1C,oEAA0E;AAS1E;;;;GAIG;AACH,SAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,WAAW,EACX,kBAAkB,GACC;IACnB,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,CAAC,WAAW;YAAE,OAAM;QACxB,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,CAAA;QACnC,OAAO,CAAC,KAAK,GAAG,CAAC,GAAG,IAAe,EAAE,EAAE;YACrC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;YACvB,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;gBACvE,OAAM;YACR,CAAC;YACD,aAAa,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QACpC,CAAC,CAAA;QACD,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,KAAK,GAAG,aAAa,CAAA;QAC/B,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAA;IAEjB,MAAM,WAAW,GAAG,OAAO,IAAI,EAAE,CAAA;IAEjC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CACL,uBAAC,wBAAa,IACZ,aAAa,EAAE,kCAAa,EAC5B,aAAa,EAAE,kCAAa,EAC5B,UAAU,EAAE,kBAAkB,YAE7B,WAAW,GACE,CACjB,CAAA;IACH,CAAC;IAED,OAAO,CACL,uBAAC,4BAAgB,IACf,OAAO,EAAE,WAAW,EACpB,GAAG,EAAC,MAAM,EACV,SAAS,EAAC,QAAQ,EAClB,iBAAiB,EAAC,MAAM,EACxB,uBAAuB,EAAC,UAAU,GAClC,CACH,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ThinkingStage {
|
|
2
|
+
stage: "memories" | "knowledge" | "context" | "skills" | "thinking";
|
|
3
|
+
detail?: string;
|
|
4
|
+
showIcon?: boolean;
|
|
5
|
+
}
|
|
6
|
+
interface ThinkingIndicatorProps {
|
|
7
|
+
stages: ThinkingStage[];
|
|
8
|
+
expertName?: string;
|
|
9
|
+
expertAvatarUrl?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Shows what processing the expert is doing during a response.
|
|
13
|
+
* Simplified from Expert's version: accepts flat props instead of
|
|
14
|
+
* an expert object, making it usable across platforms.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ThinkingIndicator({ stages, expertName, expertAvatarUrl, }: ThinkingIndicatorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=thinking-indicator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thinking-indicator.d.ts","sourceRoot":"","sources":["../../src/components/thinking-indicator.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAA;IACnE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,UAAU,sBAAsB;IAC9B,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAUD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,UAAU,EACV,eAAe,GAChB,EAAE,sBAAsB,kDAuCxB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ThinkingIndicator = ThinkingIndicator;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const lucide_react_1 = require("lucide-react");
|
|
7
|
+
const STAGE_CONFIG = {
|
|
8
|
+
memories: { icon: lucide_react_1.Brain, text: "Checking memories" },
|
|
9
|
+
knowledge: { icon: lucide_react_1.BookOpen, text: "Searching knowledge" },
|
|
10
|
+
context: { icon: lucide_react_1.Layers, text: "Building context" },
|
|
11
|
+
skills: { icon: lucide_react_1.Zap, text: "Using skill" },
|
|
12
|
+
thinking: { icon: lucide_react_1.Sparkles, text: "Thinking" },
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Shows what processing the expert is doing during a response.
|
|
16
|
+
* Simplified from Expert's version: accepts flat props instead of
|
|
17
|
+
* an expert object, making it usable across platforms.
|
|
18
|
+
*/
|
|
19
|
+
function ThinkingIndicator({ stages, expertName, expertAvatarUrl, }) {
|
|
20
|
+
const latestStage = stages[stages.length - 1];
|
|
21
|
+
if (!latestStage)
|
|
22
|
+
return null;
|
|
23
|
+
const config = STAGE_CONFIG[latestStage.stage] ?? STAGE_CONFIG.thinking;
|
|
24
|
+
const Icon = config.icon;
|
|
25
|
+
const displayText = latestStage.detail || config.text;
|
|
26
|
+
const showIcon = latestStage.showIcon !== false;
|
|
27
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "flex justify-start mt-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [expertAvatarUrl && ((0, jsx_runtime_1.jsx)("div", { className: "absolute -top-2 -left-2 z-10", children: (0, jsx_runtime_1.jsx)("img", { src: expertAvatarUrl, alt: expertName || "", className: "w-6 h-6 rounded-full object-cover ring-2 ring-stone-100 dark:ring-slate-800 shadow-sm" }) })), (0, jsx_runtime_1.jsx)("div", { className: "bg-white dark:bg-slate-800 border border-stone-200 dark:border-slate-700 px-4 py-3 rounded-2xl shadow-sm", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 animate-pulse", children: [showIcon && ((0, jsx_runtime_1.jsx)(Icon, { className: "w-4 h-4 text-stone-400 dark:text-slate-500" })), (0, jsx_runtime_1.jsxs)("span", { className: "text-sm text-stone-500 dark:text-slate-400", children: [displayText, "..."] }), (0, jsx_runtime_1.jsxs)("span", { className: "relative flex h-1.5 w-1.5", children: [(0, jsx_runtime_1.jsx)("span", { className: "absolute inline-flex h-full w-full rounded-full bg-stone-400 dark:bg-slate-500 opacity-75 animate-ping" }), (0, jsx_runtime_1.jsx)("span", { className: "relative inline-flex rounded-full h-1.5 w-1.5 bg-stone-400 dark:bg-slate-500" })] })] }) })] }) }));
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=thinking-indicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thinking-indicator.js","sourceRoot":"","sources":["../../src/components/thinking-indicator.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AA6BZ,8CA2CC;;AAtED,+CAAqE;AAcrE,MAAM,YAAY,GAAyD;IACzE,QAAQ,EAAE,EAAE,IAAI,EAAE,oBAAK,EAAE,IAAI,EAAE,mBAAmB,EAAE;IACpD,SAAS,EAAE,EAAE,IAAI,EAAE,uBAAQ,EAAE,IAAI,EAAE,qBAAqB,EAAE;IAC1D,OAAO,EAAE,EAAE,IAAI,EAAE,qBAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE;IACnD,MAAM,EAAE,EAAE,IAAI,EAAE,kBAAG,EAAE,IAAI,EAAE,aAAa,EAAE;IAC1C,QAAQ,EAAE,EAAE,IAAI,EAAE,uBAAQ,EAAE,IAAI,EAAE,UAAU,EAAE;CAC/C,CAAA;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,UAAU,EACV,eAAe,GACQ;IACvB,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC7C,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAA;IAE7B,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,QAAS,CAAA;IACxE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAA;IACxB,MAAM,WAAW,GAAG,WAAW,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,CAAA;IACrD,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ,KAAK,KAAK,CAAA;IAE/C,OAAO,CACL,gCAAK,SAAS,EAAC,yBAAyB,YACtC,iCAAK,SAAS,EAAC,UAAU,aACtB,eAAe,IAAI,CAClB,gCAAK,SAAS,EAAC,8BAA8B,YAC3C,gCACE,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,UAAU,IAAI,EAAE,EACrB,SAAS,EAAC,uFAAuF,GACjG,GACE,CACP,EAED,gCAAK,SAAS,EAAC,0GAA0G,YACvH,iCAAK,SAAS,EAAC,yCAAyC,aACrD,QAAQ,IAAI,CACX,uBAAC,IAAI,IAAC,SAAS,EAAC,4CAA4C,GAAG,CAChE,EACD,kCAAM,SAAS,EAAC,4CAA4C,aACzD,WAAW,WACP,EACP,kCAAM,SAAS,EAAC,2BAA2B,aACzC,iCAAM,SAAS,EAAC,wGAAwG,GAAG,EAC3H,iCAAM,SAAS,EAAC,8EAA8E,GAAG,IAC5F,IACH,GACF,IACF,GACF,CACP,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { LiteMessage } from "@portablecore/types/chat";
|
|
2
|
+
interface UseChatScrollOptions {
|
|
3
|
+
messages: LiteMessage[];
|
|
4
|
+
streaming: boolean;
|
|
5
|
+
}
|
|
6
|
+
interface UseChatScrollReturn {
|
|
7
|
+
scrollRef: React.RefObject<HTMLDivElement>;
|
|
8
|
+
scrollToBottom: (behavior?: ScrollBehavior) => void;
|
|
9
|
+
isNearBottom: () => boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Manages auto-scroll behavior for a chat message list.
|
|
13
|
+
*
|
|
14
|
+
* Auto-scrolls to bottom when:
|
|
15
|
+
* - A new message arrives (user or assistant)
|
|
16
|
+
* - The streaming message grows (if user is near the bottom)
|
|
17
|
+
*
|
|
18
|
+
* Respects user intent: if the user scrolled up to read history,
|
|
19
|
+
* new content does NOT pull them down.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useChatScroll({ messages, streaming, }: UseChatScrollOptions): UseChatScrollReturn;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=use-chat-scroll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-chat-scroll.d.ts","sourceRoot":"","sources":["../../src/hooks/use-chat-scroll.ts"],"names":[],"mappings":"AAGA,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;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,SAAS,GACV,EAAE,oBAAoB,GAAG,mBAAmB,CAyD5C"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.useChatScroll = useChatScroll;
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
/**
|
|
7
|
+
* Manages auto-scroll behavior for a chat message list.
|
|
8
|
+
*
|
|
9
|
+
* Auto-scrolls to bottom when:
|
|
10
|
+
* - A new message arrives (user or assistant)
|
|
11
|
+
* - The streaming message grows (if user is near the bottom)
|
|
12
|
+
*
|
|
13
|
+
* Respects user intent: if the user scrolled up to read history,
|
|
14
|
+
* new content does NOT pull them down.
|
|
15
|
+
*/
|
|
16
|
+
function useChatScroll({ messages, streaming, }) {
|
|
17
|
+
const scrollRef = (0, react_1.useRef)(null);
|
|
18
|
+
const wasNearBottom = (0, react_1.useRef)(true);
|
|
19
|
+
const isNearBottom = (0, react_1.useCallback)(() => {
|
|
20
|
+
const el = scrollRef.current;
|
|
21
|
+
if (!el)
|
|
22
|
+
return true;
|
|
23
|
+
const threshold = 100;
|
|
24
|
+
return el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
|
|
25
|
+
}, []);
|
|
26
|
+
const scrollToBottom = (0, react_1.useCallback)((behavior = "smooth") => {
|
|
27
|
+
const el = scrollRef.current;
|
|
28
|
+
if (!el)
|
|
29
|
+
return;
|
|
30
|
+
el.scrollTo({ top: el.scrollHeight, behavior });
|
|
31
|
+
}, []);
|
|
32
|
+
(0, react_1.useEffect)(() => {
|
|
33
|
+
const el = scrollRef.current;
|
|
34
|
+
if (!el)
|
|
35
|
+
return;
|
|
36
|
+
const onScroll = () => {
|
|
37
|
+
wasNearBottom.current = isNearBottom();
|
|
38
|
+
};
|
|
39
|
+
el.addEventListener("scroll", onScroll, { passive: true });
|
|
40
|
+
return () => el.removeEventListener("scroll", onScroll);
|
|
41
|
+
}, [isNearBottom]);
|
|
42
|
+
// Auto-scroll on new messages
|
|
43
|
+
(0, react_1.useEffect)(() => {
|
|
44
|
+
if (wasNearBottom.current) {
|
|
45
|
+
scrollToBottom("smooth");
|
|
46
|
+
}
|
|
47
|
+
}, [messages.length, scrollToBottom]);
|
|
48
|
+
// Auto-scroll during streaming if near bottom
|
|
49
|
+
(0, react_1.useEffect)(() => {
|
|
50
|
+
if (!streaming)
|
|
51
|
+
return;
|
|
52
|
+
const interval = setInterval(() => {
|
|
53
|
+
if (wasNearBottom.current) {
|
|
54
|
+
scrollToBottom("smooth");
|
|
55
|
+
}
|
|
56
|
+
}, 200);
|
|
57
|
+
return () => clearInterval(interval);
|
|
58
|
+
}, [streaming, scrollToBottom]);
|
|
59
|
+
// Instant scroll on mount
|
|
60
|
+
(0, react_1.useEffect)(() => {
|
|
61
|
+
scrollToBottom("instant");
|
|
62
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
63
|
+
}, []);
|
|
64
|
+
return { scrollRef, scrollToBottom, isNearBottom };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=use-chat-scroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-chat-scroll.js","sourceRoot":"","sources":["../../src/hooks/use-chat-scroll.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AA0BZ,sCA4DC;AApFD,iCAAsD;AActD;;;;;;;;;GASG;AACH,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,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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LiteMessage, ChatInterfaceConfig } from "../types.js";
|
|
2
|
+
import type { SkillThinkingStage, ActiveSkillState } from "@portablecore/chat-runtime";
|
|
3
|
+
export interface ChatSession {
|
|
4
|
+
messages: LiteMessage[];
|
|
5
|
+
streaming: boolean;
|
|
6
|
+
sending: boolean;
|
|
7
|
+
error: string | null;
|
|
8
|
+
thinkingStages: SkillThinkingStage[];
|
|
9
|
+
activeSkill: ActiveSkillState | null;
|
|
10
|
+
send: (content: string) => Promise<void>;
|
|
11
|
+
abort: () => void;
|
|
12
|
+
clear: () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function useChatSession(config: ChatInterfaceConfig): ChatSession;
|
|
15
|
+
//# sourceMappingURL=use-chat-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-chat-session.d.ts","sourceRoot":"","sources":["../../src/hooks/use-chat-session.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACV,WAAW,EAEX,mBAAmB,EACpB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,4BAA4B,CAAA;AAMnC,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,WAAW,EAAE,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,cAAc,EAAE,kBAAkB,EAAE,CAAA;IACpC,WAAW,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACpC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAMD,wBAAgB,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,WAAW,CAuRvE"}
|