@agentforge-io/chat-react 2.0.17 → 4.0.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/index.d.ts CHANGED
@@ -1,24 +1,27 @@
1
1
  /**
2
2
  * @agentforge-io/chat-react — React + Next.js bindings for the headless chat SDK.
3
3
  *
4
- * Two ways to use this:
4
+ * Three ways to use this:
5
5
  *
6
6
  * // 1. Provider + hooks: build your own UI
7
7
  * <AgentChatProvider token={...} apiBaseUrl={...}>
8
8
  * <YourChatUi />
9
9
  * </AgentChatProvider>
10
10
  *
11
- * function YourChatUi() {
12
- * const { messages, send, isBusy } = useChat();
13
- * const { agent, theme } = useAgent();
14
- * ...
15
- * }
16
- *
17
- * // 2. Drop-in: use the reference panel
11
+ * // 2. Drop-in chat panel
18
12
  * <AgentChatProvider ...>
19
13
  * <ChatPanel />
20
14
  * </AgentChatProvider>
21
15
  *
16
+ * // 3. Full Messenger shell (Home/Messages/Help/News tabs, v4+)
17
+ * <MessengerShell
18
+ * token={...}
19
+ * apiBaseUrl={...}
20
+ * helpAdapter={myHelpAdapter}
21
+ * newsAdapter={myNewsAdapter}
22
+ * homeActions={[...]}
23
+ * />
24
+ *
22
25
  * All components are marked `'use client'` so they work with the Next.js
23
26
  * App Router out of the box.
24
27
  */
@@ -27,4 +30,20 @@ export type { AgentChatProviderProps, ChatUserContext, } from './provider';
27
30
  export { useChat, useAgent } from './hooks';
28
31
  export { ChatPanel } from './ChatPanel';
29
32
  export type { ChatPanelProps, ApprovalActionHandler } from './ChatPanel';
33
+ export { MessengerShell } from './messenger/MessengerShell';
34
+ export type { MessengerShellProps } from './messenger/MessengerShell';
35
+ export type { MessengerTab } from './messenger/MessengerFooter';
36
+ export type { HelpAdapter, HelpResult, HelpCollection, HelpCollectionDetail, NewsAdapter, Announcement, HomeAction, } from './messenger/adapters';
37
+ export { MessengerHeader } from './messenger/MessengerHeader';
38
+ export type { MessengerHeaderProps } from './messenger/MessengerHeader';
39
+ export { MessengerFooter } from './messenger/MessengerFooter';
40
+ export type { MessengerFooterProps } from './messenger/MessengerFooter';
41
+ export { HomeTab } from './messenger/tabs/HomeTab';
42
+ export type { HomeTabProps } from './messenger/tabs/HomeTab';
43
+ export { MessagesTab } from './messenger/tabs/MessagesTab';
44
+ export type { MessagesTabProps } from './messenger/tabs/MessagesTab';
45
+ export { HelpTab } from './messenger/tabs/HelpTab';
46
+ export type { HelpTabProps } from './messenger/tabs/HelpTab';
47
+ export { NewsTab } from './messenger/tabs/NewsTab';
48
+ export type { NewsTabProps } from './messenger/tabs/NewsTab';
30
49
  export type { ChatAgentSummary, ChatEvent, ChatMessage, ChatMessageMetadata, ChatRole, ChatSessionStatus, ChatTheme, } from '@agentforge-io/chat-sdk';
package/dist/index.js CHANGED
@@ -2,29 +2,32 @@
2
2
  /**
3
3
  * @agentforge-io/chat-react — React + Next.js bindings for the headless chat SDK.
4
4
  *
5
- * Two ways to use this:
5
+ * Three ways to use this:
6
6
  *
7
7
  * // 1. Provider + hooks: build your own UI
8
8
  * <AgentChatProvider token={...} apiBaseUrl={...}>
9
9
  * <YourChatUi />
10
10
  * </AgentChatProvider>
11
11
  *
12
- * function YourChatUi() {
13
- * const { messages, send, isBusy } = useChat();
14
- * const { agent, theme } = useAgent();
15
- * ...
16
- * }
17
- *
18
- * // 2. Drop-in: use the reference panel
12
+ * // 2. Drop-in chat panel
19
13
  * <AgentChatProvider ...>
20
14
  * <ChatPanel />
21
15
  * </AgentChatProvider>
22
16
  *
17
+ * // 3. Full Messenger shell (Home/Messages/Help/News tabs, v4+)
18
+ * <MessengerShell
19
+ * token={...}
20
+ * apiBaseUrl={...}
21
+ * helpAdapter={myHelpAdapter}
22
+ * newsAdapter={myNewsAdapter}
23
+ * homeActions={[...]}
24
+ * />
25
+ *
23
26
  * All components are marked `'use client'` so they work with the Next.js
24
27
  * App Router out of the box.
25
28
  */
26
29
  Object.defineProperty(exports, "__esModule", { value: true });
27
- exports.ChatPanel = exports.useAgent = exports.useChat = exports.useChatSessionState = exports.useAgentChatContext = exports.AgentChatProvider = void 0;
30
+ exports.NewsTab = exports.HelpTab = exports.MessagesTab = exports.HomeTab = exports.MessengerFooter = exports.MessengerHeader = exports.MessengerShell = exports.ChatPanel = exports.useAgent = exports.useChat = exports.useChatSessionState = exports.useAgentChatContext = exports.AgentChatProvider = void 0;
28
31
  var provider_1 = require("./provider");
29
32
  Object.defineProperty(exports, "AgentChatProvider", { enumerable: true, get: function () { return provider_1.AgentChatProvider; } });
30
33
  Object.defineProperty(exports, "useAgentChatContext", { enumerable: true, get: function () { return provider_1.useAgentChatContext; } });
@@ -34,3 +37,20 @@ Object.defineProperty(exports, "useChat", { enumerable: true, get: function () {
34
37
  Object.defineProperty(exports, "useAgent", { enumerable: true, get: function () { return hooks_1.useAgent; } });
35
38
  var ChatPanel_1 = require("./ChatPanel");
36
39
  Object.defineProperty(exports, "ChatPanel", { enumerable: true, get: function () { return ChatPanel_1.ChatPanel; } });
40
+ // — Messenger shell (v4+) —
41
+ var MessengerShell_1 = require("./messenger/MessengerShell");
42
+ Object.defineProperty(exports, "MessengerShell", { enumerable: true, get: function () { return MessengerShell_1.MessengerShell; } });
43
+ // Public sub-components for consumers that want to compose their own
44
+ // shell (e.g. swap one tab while keeping the rest).
45
+ var MessengerHeader_1 = require("./messenger/MessengerHeader");
46
+ Object.defineProperty(exports, "MessengerHeader", { enumerable: true, get: function () { return MessengerHeader_1.MessengerHeader; } });
47
+ var MessengerFooter_1 = require("./messenger/MessengerFooter");
48
+ Object.defineProperty(exports, "MessengerFooter", { enumerable: true, get: function () { return MessengerFooter_1.MessengerFooter; } });
49
+ var HomeTab_1 = require("./messenger/tabs/HomeTab");
50
+ Object.defineProperty(exports, "HomeTab", { enumerable: true, get: function () { return HomeTab_1.HomeTab; } });
51
+ var MessagesTab_1 = require("./messenger/tabs/MessagesTab");
52
+ Object.defineProperty(exports, "MessagesTab", { enumerable: true, get: function () { return MessagesTab_1.MessagesTab; } });
53
+ var HelpTab_1 = require("./messenger/tabs/HelpTab");
54
+ Object.defineProperty(exports, "HelpTab", { enumerable: true, get: function () { return HelpTab_1.HelpTab; } });
55
+ var NewsTab_1 = require("./messenger/tabs/NewsTab");
56
+ Object.defineProperty(exports, "NewsTab", { enumerable: true, get: function () { return NewsTab_1.NewsTab; } });
@@ -0,0 +1,22 @@
1
+ import type { ResolvedTheme } from './theme';
2
+ export type MessengerTab = 'home' | 'messages' | 'help' | 'news';
3
+ /**
4
+ * Bottom tab bar. Always sticky so it survives scroll inside each tab.
5
+ *
6
+ * Icons are inline SVGs rather than a lucide-react dep — keeps the
7
+ * messenger bundle from pulling a 30kb icon library when consumers
8
+ * already ship their own. The shapes mirror the Intercom mental model:
9
+ * home → soft chat-bubble outline
10
+ * messages → square message bubble with line
11
+ * help → circle + question mark
12
+ * news → megaphone
13
+ */
14
+ export interface MessengerFooterProps {
15
+ theme: ResolvedTheme;
16
+ active: MessengerTab;
17
+ onChange: (tab: MessengerTab) => void;
18
+ tabs: MessengerTab[];
19
+ /** Optional unread counts per tab. Renders a small dot when > 0. */
20
+ unread?: Partial<Record<MessengerTab, number>>;
21
+ }
22
+ export declare function MessengerFooter({ theme, active, onChange, tabs, unread, }: MessengerFooterProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.MessengerFooter = MessengerFooter;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ function MessengerFooter({ theme, active, onChange, tabs, unread, }) {
7
+ const rootStyle = {
8
+ display: 'flex',
9
+ borderTop: '1px solid #e2e8f0',
10
+ background: theme.surface,
11
+ padding: '8px 4px',
12
+ flexShrink: 0,
13
+ };
14
+ return ((0, jsx_runtime_1.jsx)("nav", { style: rootStyle, "aria-label": "Messenger sections", children: tabs.map((t) => ((0, jsx_runtime_1.jsx)(TabButton, { tab: t, active: t === active, onClick: () => onChange(t), accent: theme.accent, unread: unread?.[t] ?? 0 }, t))) }));
15
+ }
16
+ function TabButton({ tab, active, onClick, accent, unread, }) {
17
+ const label = LABELS[tab];
18
+ const Icon = ICONS[tab];
19
+ const color = active ? accent : '#64748b';
20
+ return ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: onClick, "aria-pressed": active, style: {
21
+ flex: 1,
22
+ display: 'flex',
23
+ flexDirection: 'column',
24
+ alignItems: 'center',
25
+ gap: 2,
26
+ padding: '6px 4px',
27
+ background: 'transparent',
28
+ border: 'none',
29
+ cursor: 'pointer',
30
+ color,
31
+ position: 'relative',
32
+ fontFamily: 'inherit',
33
+ }, children: [(0, jsx_runtime_1.jsx)(Icon, { filled: active }), (0, jsx_runtime_1.jsx)("span", { style: { fontSize: 11, fontWeight: active ? 600 : 500 }, children: label }), unread > 0 && ((0, jsx_runtime_1.jsx)("span", { "aria-label": `${unread} unread`, style: {
34
+ position: 'absolute',
35
+ top: 4,
36
+ right: '38%',
37
+ minWidth: 8,
38
+ height: 8,
39
+ borderRadius: 4,
40
+ background: '#ef4444',
41
+ } }))] }));
42
+ }
43
+ const LABELS = {
44
+ home: 'Home',
45
+ messages: 'Messages',
46
+ help: 'Help',
47
+ news: 'News',
48
+ };
49
+ const ICONS = {
50
+ home: ({ filled }) => ((0, jsx_runtime_1.jsx)("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: (0, jsx_runtime_1.jsx)("path", { d: "M4 11.5C4 9.8 5 8.3 6.5 7.5L11 5c.6-.3 1.4-.3 2 0l4.5 2.5c1.5.8 2.5 2.3 2.5 4V18a2 2 0 0 1-2 2h-2.5a1 1 0 0 1-1-1V15a1 1 0 0 0-1-1h-3a1 1 0 0 0-1 1v4a1 1 0 0 1-1 1H6a2 2 0 0 1-2-2v-6.5Z", stroke: "currentColor", strokeWidth: "1.6", fill: filled ? 'currentColor' : 'none' }) })),
51
+ messages: ({ filled }) => ((0, jsx_runtime_1.jsxs)("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [(0, jsx_runtime_1.jsx)("rect", { x: "3", y: "4", width: "18", height: "14", rx: "3", stroke: "currentColor", strokeWidth: "1.6", fill: filled ? 'currentColor' : 'none' }), (0, jsx_runtime_1.jsx)("path", { d: "M7 9h10M7 13h6", stroke: filled ? '#fff' : 'currentColor', strokeWidth: "1.6", strokeLinecap: "round" }), (0, jsx_runtime_1.jsx)("path", { d: "M9 18l-2 3", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })] })),
52
+ help: ({ filled }) => ((0, jsx_runtime_1.jsxs)("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [(0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "1.6", fill: filled ? 'currentColor' : 'none' }), (0, jsx_runtime_1.jsx)("path", { d: "M9.5 9.5a2.5 2.5 0 0 1 5 0c0 1.5-2.5 2-2.5 3.5", stroke: filled ? '#fff' : 'currentColor', strokeWidth: "1.6", strokeLinecap: "round" }), (0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "17", r: "1", fill: filled ? '#fff' : 'currentColor' })] })),
53
+ news: ({ filled }) => ((0, jsx_runtime_1.jsxs)("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [(0, jsx_runtime_1.jsx)("path", { d: "M3 10v4l11 5V5L3 10Z", stroke: "currentColor", strokeWidth: "1.6", strokeLinejoin: "round", fill: filled ? 'currentColor' : 'none' }), (0, jsx_runtime_1.jsx)("path", { d: "M17 9a3 3 0 0 1 0 6", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })] })),
54
+ };
@@ -0,0 +1,36 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ResolvedTheme } from './theme';
3
+ /**
4
+ * Header band sitting at the top of Home and Messages tabs. Help and
5
+ * News use a simpler title-only header (rendered inline in their tabs)
6
+ * because their content is denser.
7
+ *
8
+ * Three slots:
9
+ * - left: brand mark (logo / icon)
10
+ * - right: action group (avatars stack + close button)
11
+ * - bottom: greeting block (collapses on tab=messages where the chat
12
+ * needs the height for the message list)
13
+ */
14
+ export interface MessengerHeaderProps {
15
+ theme: ResolvedTheme;
16
+ /** Brand mark on the top-left. Consumer-provided so we don't ship a
17
+ * default that needs to be overridden in 100% of installs. Pass a
18
+ * small img / svg / lucide icon. */
19
+ brand?: ReactNode;
20
+ /** Up to 3 avatars stacked on the top-right. Adding more silently
21
+ * truncates to the first 3 because the stack runs out of room. */
22
+ avatars?: Array<{
23
+ src: string;
24
+ alt: string;
25
+ }>;
26
+ /** Close-button click. When omitted, the close button is hidden —
27
+ * embeds that have their own chrome (e.g. fullscreen modal) opt out. */
28
+ onClose?: () => void;
29
+ /** Greeting block content. When omitted, the bottom row collapses
30
+ * cleanly (Messages tab does this so the chat gets the height). */
31
+ greeting?: ReactNode;
32
+ /** Extra height on the bottom of the band. Used to round into the
33
+ * scrollable content below. */
34
+ bottomRadius?: number;
35
+ }
36
+ export declare function MessengerHeader({ theme, brand, avatars, onClose, greeting, bottomRadius, }: MessengerHeaderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.MessengerHeader = MessengerHeader;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ function MessengerHeader({ theme, brand, avatars, onClose, greeting, bottomRadius = 0, }) {
7
+ const rootStyle = {
8
+ background: theme.headerBg,
9
+ color: theme.onHeader,
10
+ padding: '16px 16px 20px',
11
+ // The shell content scrolls under the header bottom radius so the
12
+ // gradient stays visible when the user scrolls — the negative
13
+ // marginBottom + matching radius creates the effect without a
14
+ // sticky element.
15
+ marginBottom: bottomRadius ? -bottomRadius : 0,
16
+ borderBottomLeftRadius: bottomRadius,
17
+ borderBottomRightRadius: bottomRadius,
18
+ };
19
+ return ((0, jsx_runtime_1.jsxs)("div", { style: rootStyle, children: [(0, jsx_runtime_1.jsxs)("div", { style: {
20
+ display: 'flex',
21
+ alignItems: 'center',
22
+ justifyContent: 'space-between',
23
+ marginBottom: greeting ? 18 : 0,
24
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: {
25
+ display: 'flex',
26
+ alignItems: 'center',
27
+ gap: 10,
28
+ opacity: brand ? 1 : 0,
29
+ }, children: brand ?? (0, jsx_runtime_1.jsx)("div", { style: { width: 36, height: 36 } }) }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [avatars && avatars.length > 0 && ((0, jsx_runtime_1.jsx)(AvatarStack, { avatars: avatars, onHeader: theme.onHeader })), onClose && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onClose, "aria-label": "Close", style: {
30
+ background: 'transparent',
31
+ border: 'none',
32
+ color: theme.onHeader,
33
+ width: 32,
34
+ height: 32,
35
+ borderRadius: 6,
36
+ cursor: 'pointer',
37
+ fontSize: 18,
38
+ lineHeight: 1,
39
+ opacity: 0.85,
40
+ }, children: "\u00D7" }))] })] }), greeting] }));
41
+ }
42
+ /**
43
+ * Trio of overlapping circular avatars. Caps at 3 so the rendering is
44
+ * deterministic — adding a "+N" pill would require another design
45
+ * decision we'd rather defer until a customer asks for it.
46
+ */
47
+ function AvatarStack({ avatars, onHeader, }) {
48
+ const visible = avatars.slice(0, 3);
49
+ return ((0, jsx_runtime_1.jsx)("div", { style: { display: 'flex' }, children: visible.map((a, i) => ((0, jsx_runtime_1.jsx)("img", { src: a.src, alt: a.alt,
50
+ // marginLeft on every avatar except the first creates the
51
+ // overlap. Border in the header foreground color punches the
52
+ // avatar out of the gradient so each circle stays distinct.
53
+ style: {
54
+ width: 32,
55
+ height: 32,
56
+ borderRadius: '50%',
57
+ border: `2px solid ${onHeader}`,
58
+ marginLeft: i === 0 ? 0 : -10,
59
+ objectFit: 'cover',
60
+ } }, a.src))) }));
61
+ }
@@ -0,0 +1,59 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type AgentChatProviderProps } from '../provider';
3
+ import { type MessengerTab } from './MessengerFooter';
4
+ import type { HelpAdapter, HomeAction, NewsAdapter } from './adapters';
5
+ import type { ChatPanelProps } from '../ChatPanel';
6
+ /**
7
+ * Multi-tab Messenger shell (Home / Messages / Help / News).
8
+ *
9
+ * Composition over inheritance: the shell *contains* an
10
+ * AgentChatProvider for the Messages tab, but Home / Help / News don't
11
+ * depend on the chat session at all — they pull from the adapters the
12
+ * consumer passes.
13
+ *
14
+ * The shell renders inside whatever the host provides. It does NOT
15
+ * portal itself or apply fixed positioning. Wrap it in a sized
16
+ * container (FAB drawer, full-page modal, sidebar) and it fills.
17
+ *
18
+ * Two display modes — pure CSS, no portal:
19
+ * - `inline`: fills the parent (default; matches ChatPanel's behavior).
20
+ * - `floating`: anchors itself to a corner with `position: fixed`.
21
+ * Use this when the host doesn't manage placement and
22
+ * you want a drop-in launcher experience.
23
+ */
24
+ export interface MessengerShellProps extends Omit<AgentChatProviderProps, 'children'> {
25
+ /** Which tabs to render, in order. Default: all 4. */
26
+ tabs?: MessengerTab[];
27
+ /** Tab shown on first render. Default: 'home'. Controlled mode is
28
+ * supported via `activeTab` + `onTabChange`. */
29
+ defaultTab?: MessengerTab;
30
+ /** Controlled active tab. When set, `defaultTab` is ignored. */
31
+ activeTab?: MessengerTab;
32
+ onTabChange?: (tab: MessengerTab) => void;
33
+ homeGreeting?: {
34
+ name?: string;
35
+ emoji?: string;
36
+ };
37
+ homeQuestion?: string;
38
+ homeActions?: HomeAction[];
39
+ homeAvatars?: Array<{
40
+ src: string;
41
+ alt: string;
42
+ }>;
43
+ homeFooter?: ReactNode;
44
+ /** Brand mark in the header (logo). */
45
+ brand?: ReactNode;
46
+ helpAdapter?: HelpAdapter;
47
+ newsAdapter?: NewsAdapter;
48
+ /** Forwarded to ChatPanel inside the Messages tab. */
49
+ panelProps?: Omit<ChatPanelProps, 'style' | 'className'>;
50
+ /** `inline` fills the parent (default). `floating` pins to a corner. */
51
+ display?: 'inline' | 'floating';
52
+ /** Width when `display='floating'`. Default 380. */
53
+ width?: number;
54
+ /** Max height when `display='floating'`. Default min(640, viewport-80). */
55
+ maxHeight?: number;
56
+ onClose?: () => void;
57
+ unread?: Partial<Record<MessengerTab, number>>;
58
+ }
59
+ export declare function MessengerShell({ token, apiBaseUrl, browserSessionId, stream, userContext, theme: themeOverride, autoStart, tabs, defaultTab, activeTab, onTabChange, homeGreeting, homeQuestion, homeActions, homeAvatars, homeFooter, brand, helpAdapter, newsAdapter, panelProps, display, width, maxHeight, onClose, unread, }: MessengerShellProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.MessengerShell = MessengerShell;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ const provider_1 = require("../provider");
8
+ const hooks_1 = require("../hooks");
9
+ const MessengerFooter_1 = require("./MessengerFooter");
10
+ const HomeTab_1 = require("./tabs/HomeTab");
11
+ const MessagesTab_1 = require("./tabs/MessagesTab");
12
+ const HelpTab_1 = require("./tabs/HelpTab");
13
+ const NewsTab_1 = require("./tabs/NewsTab");
14
+ const theme_1 = require("./theme");
15
+ const DEFAULT_TABS = ['home', 'messages', 'help', 'news'];
16
+ function MessengerShell({
17
+ // chat provider props
18
+ token, apiBaseUrl, browserSessionId, stream, userContext, theme: themeOverride, autoStart,
19
+ // shell props
20
+ tabs = DEFAULT_TABS, defaultTab = 'home', activeTab, onTabChange, homeGreeting, homeQuestion, homeActions, homeAvatars, homeFooter, brand, helpAdapter, newsAdapter, panelProps, display = 'inline', width = 380, maxHeight, onClose, unread, }) {
21
+ // Controlled / uncontrolled active tab. When the parent passes
22
+ // activeTab, we never touch internal state.
23
+ const [internalTab, setInternalTab] = (0, react_1.useState)(defaultTab);
24
+ const tab = activeTab ?? internalTab;
25
+ const setTab = (next) => {
26
+ if (activeTab === undefined)
27
+ setInternalTab(next);
28
+ onTabChange?.(next);
29
+ };
30
+ // We mount AgentChatProvider once so the session warms up regardless
31
+ // of which tab is active — that way clicking Messages doesn't trigger
32
+ // a cold "Loading…". Provider is cheap; the wire only starts on the
33
+ // first useChat() call.
34
+ return ((0, jsx_runtime_1.jsx)(provider_1.AgentChatProvider, { token: token, apiBaseUrl: apiBaseUrl, browserSessionId: browserSessionId, stream: stream, userContext: userContext, theme: themeOverride, autoStart: autoStart, children: (0, jsx_runtime_1.jsx)(ShellChrome, { display: display, width: width, maxHeight: maxHeight, themeOverride: themeOverride, children: (0, jsx_runtime_1.jsx)(TabSurface, { tab: tab, tabs: tabs, setTab: setTab, onClose: onClose, brand: brand, homeGreeting: homeGreeting, homeQuestion: homeQuestion, homeActions: homeActions, homeAvatars: homeAvatars, homeFooter: homeFooter, helpAdapter: helpAdapter, newsAdapter: newsAdapter, panelProps: panelProps, unread: unread }) }) }));
35
+ }
36
+ /**
37
+ * Container that handles the `floating` vs `inline` mode and merges
38
+ * the resolved theme onto the root so children can read CSS vars.
39
+ *
40
+ * Split out from `MessengerShell` so the chrome can read `useAgent()`
41
+ * (which is only available inside the provider). The theme on the
42
+ * agent token wins over a static override — but the consumer's
43
+ * `themeOverride` wins over both (existing v3 behavior, preserved).
44
+ */
45
+ function ShellChrome({ display, width, maxHeight, themeOverride, children, }) {
46
+ const { theme: agentTheme } = (0, hooks_1.useAgent)();
47
+ const merged = (0, react_1.useMemo)(() => ({ ...(agentTheme ?? {}), ...(themeOverride ?? {}) }), [agentTheme, themeOverride]);
48
+ const theme = (0, react_1.useMemo)(() => (0, theme_1.resolveTheme)(merged), [merged]);
49
+ const [computedMaxHeight, setComputedMaxHeight] = (0, react_1.useState)(maxHeight ?? 640);
50
+ (0, react_1.useEffect)(() => {
51
+ if (maxHeight !== undefined) {
52
+ setComputedMaxHeight(maxHeight);
53
+ return;
54
+ }
55
+ const update = () => {
56
+ const vh = typeof window === 'undefined' ? 720 : window.innerHeight;
57
+ setComputedMaxHeight(Math.min(640, vh - 80));
58
+ };
59
+ update();
60
+ window.addEventListener('resize', update);
61
+ return () => window.removeEventListener('resize', update);
62
+ }, [maxHeight]);
63
+ const baseStyle = {
64
+ display: 'flex',
65
+ flexDirection: 'column',
66
+ background: theme.surface,
67
+ borderRadius: 18,
68
+ overflow: 'hidden',
69
+ border: '1px solid #e2e8f0',
70
+ boxShadow: '0 12px 40px rgba(15, 23, 42, 0.18)',
71
+ fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
72
+ color: theme.onSurface,
73
+ minHeight: 0,
74
+ };
75
+ const style = display === 'floating'
76
+ ? {
77
+ ...baseStyle,
78
+ position: 'fixed',
79
+ right: 20,
80
+ bottom: 20,
81
+ width,
82
+ height: computedMaxHeight,
83
+ maxHeight: '90vh',
84
+ zIndex: 2147483000,
85
+ }
86
+ : { ...baseStyle, height: '100%' };
87
+ // Provide the theme via context — but we don't need React.Context
88
+ // here because each tab is rendered with `theme` as a prop already.
89
+ // Splitting out a ThemeContext would be over-engineering for v1.
90
+ return (0, jsx_runtime_1.jsx)("div", { style: style, children: children });
91
+ }
92
+ function TabSurface({ tab, tabs, setTab, onClose, brand, homeGreeting, homeQuestion, homeActions, homeAvatars, homeFooter, helpAdapter, newsAdapter, panelProps, unread, }) {
93
+ const { theme: agentTheme } = (0, hooks_1.useAgent)();
94
+ const theme = (0, react_1.useMemo)(() => (0, theme_1.resolveTheme)(agentTheme), [agentTheme]);
95
+ // Render the active tab. We deliberately do NOT keep mounted tabs in
96
+ // the tree — switching is cheap and remounting Help/News refetches,
97
+ // which is the desired behavior (especially News).
98
+ const content = (() => {
99
+ switch (tab) {
100
+ case 'home':
101
+ return ((0, jsx_runtime_1.jsx)(HomeTab_1.HomeTab, { theme: theme, brand: brand, onClose: onClose, greeting: homeGreeting, question: homeQuestion, avatars: homeAvatars, actions: homeActions, footer: homeFooter, onSendMessage: tabs.includes('messages')
102
+ ? () => setTab('messages')
103
+ : undefined }));
104
+ case 'messages':
105
+ return ((0, jsx_runtime_1.jsx)(MessagesTab_1.MessagesTab, { theme: theme, panelProps: panelProps, topBarAction: onClose && ((0, jsx_runtime_1.jsx)(CloseButton, { onClick: onClose, color: theme.onSurface })) }));
106
+ case 'help':
107
+ if (!helpAdapter)
108
+ return (0, jsx_runtime_1.jsx)(MissingAdapter, { name: "helpAdapter" });
109
+ return (0, jsx_runtime_1.jsx)(HelpTab_1.HelpTab, { theme: theme, adapter: helpAdapter });
110
+ case 'news':
111
+ if (!newsAdapter)
112
+ return (0, jsx_runtime_1.jsx)(MissingAdapter, { name: "newsAdapter" });
113
+ return (0, jsx_runtime_1.jsx)(NewsTab_1.NewsTab, { theme: theme, adapter: newsAdapter });
114
+ }
115
+ })();
116
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { style: { flex: 1, minHeight: 0, display: 'flex' }, children: (0, jsx_runtime_1.jsx)("div", { style: { flex: 1, minWidth: 0 }, children: content }) }), (0, jsx_runtime_1.jsx)(MessengerFooter_1.MessengerFooter, { theme: theme, active: tab, onChange: setTab, tabs: tabs, unread: unread })] }));
117
+ }
118
+ function CloseButton({ onClick, color, }) {
119
+ return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onClick, "aria-label": "Close", style: {
120
+ background: 'transparent',
121
+ border: 'none',
122
+ color,
123
+ cursor: 'pointer',
124
+ fontSize: 20,
125
+ lineHeight: 1,
126
+ padding: 4,
127
+ opacity: 0.7,
128
+ }, children: "\u00D7" }));
129
+ }
130
+ function MissingAdapter({ name }) {
131
+ return ((0, jsx_runtime_1.jsxs)("div", { style: {
132
+ padding: 24,
133
+ fontSize: 13,
134
+ color: '#dc2626',
135
+ background: '#fff',
136
+ height: '100%',
137
+ }, children: ["Missing ", (0, jsx_runtime_1.jsx)("code", { children: name }), " prop on ", (0, jsx_runtime_1.jsx)("code", { children: "MessengerShell" }), "."] }));
138
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Adapter contracts the consumer wires to feed the Help and News tabs.
3
+ *
4
+ * The SDK does NOT bundle a transport for these — they're host-specific:
5
+ * AgentForge platform queries `/walkthroughs/search`, but a third-party
6
+ * embedder might point them at their own KB. Keeping the adapter
7
+ * boundary thin means consumers don't depend on platform internals.
8
+ *
9
+ * All adapter methods may throw; the tabs render an error state and let
10
+ * the user retry. Consumers should NOT swallow errors — surface them.
11
+ */
12
+ export interface HelpResult {
13
+ id: string;
14
+ /** Renders as the primary line in the search result. */
15
+ title: string;
16
+ /** Optional one-line context (e.g. collection name, summary). */
17
+ excerpt?: string;
18
+ /** Where this content lives. Drives the click behavior:
19
+ * - `walkthrough`: clicking calls `onWalkthroughOpen(id)` so the host
20
+ * can hand off to the playback engine (no navigation).
21
+ * - `article`: clicking opens `href` in a new tab.
22
+ * - `external`: same as article but tagged so we render an external-link
23
+ * icon (consumer disambiguates internal docs from external links). */
24
+ kind: 'walkthrough' | 'article' | 'external';
25
+ href?: string;
26
+ }
27
+ export interface HelpCollection {
28
+ id: string;
29
+ name: string;
30
+ description?: string;
31
+ /** Used in the list-view "N articles" tail label. */
32
+ itemCount: number;
33
+ /** Lucide-react icon name or a custom URL. Free-form on purpose — the
34
+ * consumer controls icon vocabulary. */
35
+ iconUrl?: string;
36
+ }
37
+ export interface HelpCollectionDetail extends HelpCollection {
38
+ items: HelpResult[];
39
+ }
40
+ export interface HelpAdapter {
41
+ search(query: string): Promise<HelpResult[]>;
42
+ listCollections(): Promise<HelpCollection[]>;
43
+ getCollection(id: string): Promise<HelpCollectionDetail>;
44
+ /**
45
+ * Called when the user clicks a result with `kind === 'walkthrough'`.
46
+ * The host wires this to its in-app playback engine — the SDK never
47
+ * touches the host DOM. When omitted, walkthrough results fall back
48
+ * to a no-op + a console warning so the developer notices.
49
+ */
50
+ onWalkthroughOpen?: (walkthroughId: string) => void;
51
+ }
52
+ export interface Announcement {
53
+ id: string;
54
+ title: string;
55
+ body: string;
56
+ /** ISO timestamp; the tab renders relative time. */
57
+ publishedAt: string;
58
+ /** Operator override to stick an item at the top. Render order:
59
+ * pinned first (by publishedAt desc), then non-pinned by publishedAt desc. */
60
+ pinned?: boolean;
61
+ /** Read state is consumer-tracked — the SDK just renders the badge. */
62
+ read?: boolean;
63
+ /** Optional CTA: when set, the card becomes a link. */
64
+ href?: string;
65
+ /** Optional cover image rendered above the title. */
66
+ imageUrl?: string;
67
+ }
68
+ export interface NewsAdapter {
69
+ listAnnouncements(): Promise<Announcement[]>;
70
+ /** Mark an announcement as read. Best-effort — the tab updates the
71
+ * badge optimistically and ignores failures. */
72
+ markRead?: (announcementId: string) => Promise<void>;
73
+ }
74
+ /**
75
+ * Default Home action shape. Consumers pass an array of these to the
76
+ * shell; we render them as a vertical list of card rows under the
77
+ * greeting + "Send us a message" CTA.
78
+ */
79
+ export interface HomeAction {
80
+ id: string;
81
+ /** Leading emoji or icon component. Strings are rendered raw so
82
+ * emojis work out of the box (no font dependency). */
83
+ icon?: React.ReactNode | string;
84
+ label: string;
85
+ /** When provided, clicking opens the link in a new tab. */
86
+ href?: string;
87
+ /** Alternative to `href` — fires a callback (e.g. switching tabs). */
88
+ onClick?: () => void;
89
+ /** Visual hint on the trailing edge. Defaults to 'external' when
90
+ * `href` is set, 'arrow' otherwise. Set explicitly to override. */
91
+ trailingIcon?: 'external' | 'arrow' | 'none';
92
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /**
3
+ * Adapter contracts the consumer wires to feed the Help and News tabs.
4
+ *
5
+ * The SDK does NOT bundle a transport for these — they're host-specific:
6
+ * AgentForge platform queries `/walkthroughs/search`, but a third-party
7
+ * embedder might point them at their own KB. Keeping the adapter
8
+ * boundary thin means consumers don't depend on platform internals.
9
+ *
10
+ * All adapter methods may throw; the tabs render an error state and let
11
+ * the user retry. Consumers should NOT swallow errors — surface them.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,21 @@
1
+ import type { ResolvedTheme } from '../theme';
2
+ import type { HelpAdapter } from '../adapters';
3
+ /**
4
+ * Help tab. Two states:
5
+ * 1. Default: search input + collection list (browse)
6
+ * 2. Query active: search input + result list (search)
7
+ *
8
+ * We debounce the search so each keystroke doesn't fire a request;
9
+ * 250ms feels live enough without hammering the adapter when the user
10
+ * is typing fast.
11
+ *
12
+ * When the user clicks a `kind: 'walkthrough'` result, we route through
13
+ * `adapter.onWalkthroughOpen?.(id)`. The SDK never touches the host
14
+ * DOM — playback is the host's responsibility.
15
+ */
16
+ export interface HelpTabProps {
17
+ theme: ResolvedTheme;
18
+ adapter: HelpAdapter;
19
+ title?: string;
20
+ }
21
+ export declare function HelpTab({ theme, adapter, title }: HelpTabProps): import("react/jsx-runtime").JSX.Element;