@agentforge-io/chat-sdk 2.0.23 → 2.0.24
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/react.d.ts +30 -0
- package/dist/react.js +143 -15
- package/package.json +1 -1
package/dist/react.d.ts
CHANGED
|
@@ -97,6 +97,36 @@ export interface ChatWidgetProps {
|
|
|
97
97
|
* legitimately decide.
|
|
98
98
|
*/
|
|
99
99
|
readOnlyApprovals?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Chrome preset.
|
|
102
|
+
*
|
|
103
|
+
* - `'card'` (default): the historical look. Rounded card, header
|
|
104
|
+
* with the agent name, "Powered by AgentForge" footer, opaque
|
|
105
|
+
* background. Designed to drop into a customer's site as a
|
|
106
|
+
* visually distinct widget.
|
|
107
|
+
* - `'bare'`: no card, no header, no footer, transparent background.
|
|
108
|
+
* Lets the host page wrap the chat in its own template. The host
|
|
109
|
+
* drives the visible chrome via the surrounding layout + CSS
|
|
110
|
+
* variables (`--af-primary`, `--af-bubble-bg`, etc.). Use this on
|
|
111
|
+
* a dedicated agent page where the page IS the chat surface.
|
|
112
|
+
*/
|
|
113
|
+
variant?: 'card' | 'bare';
|
|
114
|
+
/**
|
|
115
|
+
* Initial assistant message rendered before the visitor types. Client-only —
|
|
116
|
+
* we don't send it to the server, so it costs zero tokens. Use it for
|
|
117
|
+
* "Hey, I'm Fabian, how can I help you?"-style openings.
|
|
118
|
+
*/
|
|
119
|
+
greeting?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Display name for the agent in the header / typing indicators. When
|
|
122
|
+
* unset we fall back to the agent's configured `name`. The visitor sees
|
|
123
|
+
* "personaName" alongside the avatar so a single workspace can run
|
|
124
|
+
* multiple agents that feel like distinct people.
|
|
125
|
+
*/
|
|
126
|
+
personaName?: string;
|
|
127
|
+
/** Input placeholder. Defaults to "Type a message…" — override when
|
|
128
|
+
* the persona speaks a different language. */
|
|
129
|
+
inputPlaceholder?: string;
|
|
100
130
|
}
|
|
101
131
|
/**
|
|
102
132
|
* Drop-in chat widget. Owns its own `ChatSession` and re-renders on every
|
package/dist/react.js
CHANGED
|
@@ -181,7 +181,8 @@ function fallbackCopy(ctx) {
|
|
|
181
181
|
* SDK event. Consumers don't need to read `session.getState()` themselves.
|
|
182
182
|
*/
|
|
183
183
|
function ChatWidget(props) {
|
|
184
|
-
const { token, apiBaseUrl, inline = false, position, browserSessionId, resumeConversationId, stream, className, style, onApprovalDecision, readOnlyApprovals = false, } = props;
|
|
184
|
+
const { token, apiBaseUrl, inline = false, position, browserSessionId, resumeConversationId, stream, className, style, onApprovalDecision, readOnlyApprovals = false, variant = 'card', greeting, personaName, inputPlaceholder, } = props;
|
|
185
|
+
const bare = variant === 'bare';
|
|
185
186
|
const [session, setSession] = (0, react_1.useState)(null);
|
|
186
187
|
const [status, setStatus] = (0, react_1.useState)('idle');
|
|
187
188
|
const [agent, setAgent] = (0, react_1.useState)();
|
|
@@ -281,6 +282,7 @@ function ChatWidget(props) {
|
|
|
281
282
|
'af-widget-root',
|
|
282
283
|
`af-pos-${resolvedPosition}`,
|
|
283
284
|
inline ? 'af-inline' : '',
|
|
285
|
+
bare ? 'af-variant-bare' : 'af-variant-card',
|
|
284
286
|
className ?? '',
|
|
285
287
|
]
|
|
286
288
|
.filter(Boolean)
|
|
@@ -299,21 +301,21 @@ function ChatWidget(props) {
|
|
|
299
301
|
}
|
|
300
302
|
: {}),
|
|
301
303
|
};
|
|
302
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: rootClass, style: rootStyle, "data-style-id": styleId, children: [!inline && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "af-toggle", "aria-label": open ? 'Close chat' : 'Open chat', "aria-expanded": open, onClick: () => setOpen((v) => !v), children: open ? (0, jsx_runtime_1.jsx)(CloseIcon, {}) : (0, jsx_runtime_1.jsx)(ChatIcon, {}) })), (0, jsx_runtime_1.jsxs)("div", { className: `af-panel ${open ? 'af-open' : ''}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "af-header", children: [theme?.avatarUrl ? (
|
|
304
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: rootClass, style: rootStyle, "data-style-id": styleId, children: [!inline && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "af-toggle", "aria-label": open ? 'Close chat' : 'Open chat', "aria-expanded": open, onClick: () => setOpen((v) => !v), children: open ? (0, jsx_runtime_1.jsx)(CloseIcon, {}) : (0, jsx_runtime_1.jsx)(ChatIcon, {}) })), (0, jsx_runtime_1.jsxs)("div", { className: `af-panel ${open ? 'af-open' : ''}`, children: [!bare && ((0, jsx_runtime_1.jsxs)("div", { className: "af-header", children: [theme?.avatarUrl ? (
|
|
303
305
|
// eslint-disable-next-line @next/next/no-img-element
|
|
304
|
-
(0, jsx_runtime_1.jsx)("img", { className: "af-header-avatar", src: theme.avatarUrl, alt: "" })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "af-header-info", children: [(0, jsx_runtime_1.jsx)("div", { className: "af-header-title", children: theme?.title ?? agent?.name ?? 'Chat' }), (0, jsx_runtime_1.jsx)("div", { className: "af-header-subtitle", children: status === 'loading' ? 'Loading…' : agent?.description ?? '' })] }), !inline && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "af-close", onClick: () => setOpen(false), "aria-label": "Close chat", children: (0, jsx_runtime_1.jsx)(CloseIcon, {}) }))] }), (0, jsx_runtime_1.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
306
|
+
(0, jsx_runtime_1.jsx)("img", { className: "af-header-avatar", src: theme.avatarUrl, alt: "" })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "af-header-info", children: [(0, jsx_runtime_1.jsx)("div", { className: "af-header-title", children: personaName ?? theme?.title ?? agent?.name ?? 'Chat' }), (0, jsx_runtime_1.jsx)("div", { className: "af-header-subtitle", children: status === 'loading' ? 'Loading…' : agent?.description ?? '' })] }), !inline && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "af-close", onClick: () => setOpen(false), "aria-label": "Close chat", children: (0, jsx_runtime_1.jsx)(CloseIcon, {}) }))] })), (0, jsx_runtime_1.jsxs)("div", { className: "af-messages", ref: messagesRef, children: [bare && greeting && messages.length === 0 && status !== 'loading' && ((0, jsx_runtime_1.jsx)("div", { className: "af-msg af-msg-assistant af-msg-greeting", dangerouslySetInnerHTML: { __html: renderMarkdown(greeting) } })), messages.map((m) => ((0, jsx_runtime_1.jsx)(MessageBubble, { message: m, session: session, readOnly: readOnlyApprovals, onDecision: onApprovalDecision, onContinue: () => {
|
|
307
|
+
// After a successful Approve, kick the next turn so the
|
|
308
|
+
// gate's fast-path consumes the approval and the tool
|
|
309
|
+
// actually runs. `silent: true` keeps the literal
|
|
310
|
+
// "continue" prompt out of the visible transcript —
|
|
311
|
+
// the visitor just sees the assistant's next reply
|
|
312
|
+
// appearing under the "Approved" pill.
|
|
313
|
+
if (!session)
|
|
314
|
+
return;
|
|
315
|
+
setTimeout(() => {
|
|
316
|
+
void session.send('continue', { silent: true });
|
|
317
|
+
}, 250);
|
|
318
|
+
} }, m.id)))] }), lastError && status === 'error' && ((0, jsx_runtime_1.jsx)("div", { className: "af-error", children: lastError })), (0, jsx_runtime_1.jsxs)("div", { className: "af-input-row", children: [(0, jsx_runtime_1.jsx)("textarea", { className: "af-input", value: draft, onChange: (e) => setDraft(e.target.value), onKeyDown: onKeyDown, placeholder: inputPlaceholder ?? 'Type a message…', rows: 1, disabled: status === 'ended' || status === 'loading' || status === 'idle' }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "af-send", onClick: handleSend, disabled: sendDisabled, "aria-label": "Send message", children: (0, jsx_runtime_1.jsx)(SendIcon, {}) })] }), !bare && (0, jsx_runtime_1.jsx)("div", { className: "af-footer", children: "Powered by AgentForge" })] })] }));
|
|
317
319
|
}
|
|
318
320
|
function MessageBubble({ message, session, readOnly, onDecision, onContinue, }) {
|
|
319
321
|
const kind = message.metadata?.kind;
|
|
@@ -546,4 +548,130 @@ const WIDGET_CSS = `
|
|
|
546
548
|
.af-msg-blocked { align-self: flex-start; max-width: 90%; padding: 10px 14px; border-radius: 14px; border-bottom-left-radius: 4px; font-size: 13px; line-height: 1.5; background: #fef2f2; border: 1px solid #fecaca; color: #7f1d1d; animation: af-msg-in 220ms cubic-bezier(0.2, 0.8, 0.2, 1); }
|
|
547
549
|
.af-blocked-title { font-weight: 600; font-size: 13px; margin-bottom: 4px; }
|
|
548
550
|
.af-blocked-body { font-size: 12px; line-height: 1.5; }
|
|
551
|
+
|
|
552
|
+
/* ─── variant="bare" ────────────────────────────────────────────────────
|
|
553
|
+
* Drops the card chrome and lets the host page own the surroundings.
|
|
554
|
+
* Inherits font-family and color from the parent so the chat blends
|
|
555
|
+
* into whatever wrapper the host renders (templates own those values
|
|
556
|
+
* via CSS variables on a parent div).
|
|
557
|
+
*
|
|
558
|
+
* Host-overridable variables — all optional, sensible defaults below:
|
|
559
|
+
* --af-primary accent color (send button, focus ring, links)
|
|
560
|
+
* --af-bubble-user-bg user message background (default: primary)
|
|
561
|
+
* --af-bubble-user-fg user message text (default: white)
|
|
562
|
+
* --af-bubble-agent-bg assistant message background (default: subtle)
|
|
563
|
+
* --af-bubble-agent-fg assistant message text (default: inherit)
|
|
564
|
+
* --af-bubble-radius bubble corner radius (default: 18px)
|
|
565
|
+
* --af-input-bg input background (default: subtle)
|
|
566
|
+
* --af-input-radius input corner radius (default: 24px — pill)
|
|
567
|
+
* --af-gap spacing between messages (default: 12px)
|
|
568
|
+
*/
|
|
569
|
+
.af-widget-root.af-variant-bare { font-family: inherit; color: inherit; }
|
|
570
|
+
.af-widget-root.af-variant-bare.af-inline .af-panel,
|
|
571
|
+
.af-widget-root.af-variant-bare .af-panel {
|
|
572
|
+
position: relative;
|
|
573
|
+
width: 100%;
|
|
574
|
+
height: 100%;
|
|
575
|
+
max-height: none;
|
|
576
|
+
background: transparent;
|
|
577
|
+
border-radius: 0;
|
|
578
|
+
box-shadow: none;
|
|
579
|
+
overflow: visible;
|
|
580
|
+
display: flex;
|
|
581
|
+
flex-direction: column;
|
|
582
|
+
}
|
|
583
|
+
.af-widget-root.af-variant-bare .af-messages {
|
|
584
|
+
background: transparent;
|
|
585
|
+
padding: 16px 14px 12px;
|
|
586
|
+
gap: var(--af-gap, 12px);
|
|
587
|
+
/* Let the parent flexbox give us a min-height; we'll grow into it
|
|
588
|
+
and scroll internally when the transcript gets longer. */
|
|
589
|
+
min-height: 0;
|
|
590
|
+
}
|
|
591
|
+
.af-widget-root.af-variant-bare .af-msg {
|
|
592
|
+
font-size: 15px;
|
|
593
|
+
line-height: 1.5;
|
|
594
|
+
border-radius: var(--af-bubble-radius, 18px);
|
|
595
|
+
padding: 10px 14px;
|
|
596
|
+
max-width: 86%;
|
|
597
|
+
}
|
|
598
|
+
.af-widget-root.af-variant-bare .af-msg-user {
|
|
599
|
+
background: var(--af-bubble-user-bg, var(--af-primary));
|
|
600
|
+
color: var(--af-bubble-user-fg, #ffffff);
|
|
601
|
+
border-bottom-right-radius: 6px;
|
|
602
|
+
}
|
|
603
|
+
.af-widget-root.af-variant-bare .af-msg-assistant {
|
|
604
|
+
background: var(--af-bubble-agent-bg, rgba(148, 163, 184, 0.12));
|
|
605
|
+
color: var(--af-bubble-agent-fg, inherit);
|
|
606
|
+
border: none;
|
|
607
|
+
border-bottom-left-radius: 6px;
|
|
608
|
+
}
|
|
609
|
+
.af-widget-root.af-variant-bare .af-msg-assistant a {
|
|
610
|
+
color: var(--af-primary);
|
|
611
|
+
}
|
|
612
|
+
.af-widget-root.af-variant-bare .af-msg-assistant code {
|
|
613
|
+
background: rgba(148, 163, 184, 0.2);
|
|
614
|
+
color: inherit;
|
|
615
|
+
}
|
|
616
|
+
.af-widget-root.af-variant-bare .af-msg-greeting { animation: af-msg-in 320ms cubic-bezier(0.2, 0.8, 0.2, 1); }
|
|
617
|
+
.af-widget-root.af-variant-bare .af-input-row {
|
|
618
|
+
position: sticky;
|
|
619
|
+
bottom: 0;
|
|
620
|
+
padding: 10px 12px 14px;
|
|
621
|
+
border-top: none;
|
|
622
|
+
background: transparent;
|
|
623
|
+
gap: 8px;
|
|
624
|
+
}
|
|
625
|
+
.af-widget-root.af-variant-bare .af-input {
|
|
626
|
+
background: var(--af-input-bg, rgba(148, 163, 184, 0.10));
|
|
627
|
+
color: inherit;
|
|
628
|
+
border: 1px solid var(--af-input-border, rgba(148, 163, 184, 0.25));
|
|
629
|
+
border-radius: var(--af-input-radius, 24px);
|
|
630
|
+
padding: 12px 16px;
|
|
631
|
+
font-size: 15px;
|
|
632
|
+
min-height: 48px;
|
|
633
|
+
line-height: 1.4;
|
|
634
|
+
}
|
|
635
|
+
.af-widget-root.af-variant-bare .af-input:focus {
|
|
636
|
+
border-color: var(--af-primary);
|
|
637
|
+
box-shadow: 0 0 0 3px var(--af-primary-soft);
|
|
638
|
+
}
|
|
639
|
+
.af-widget-root.af-variant-bare .af-input:disabled { background: rgba(148, 163, 184, 0.06); }
|
|
640
|
+
.af-widget-root.af-variant-bare .af-send {
|
|
641
|
+
background: var(--af-primary);
|
|
642
|
+
color: #fff;
|
|
643
|
+
border-radius: 50%;
|
|
644
|
+
width: 44px;
|
|
645
|
+
height: 44px;
|
|
646
|
+
min-height: 44px;
|
|
647
|
+
padding: 0;
|
|
648
|
+
align-self: flex-end;
|
|
649
|
+
box-shadow: 0 6px 16px var(--af-primary-soft);
|
|
650
|
+
}
|
|
651
|
+
.af-widget-root.af-variant-bare .af-error {
|
|
652
|
+
background: transparent;
|
|
653
|
+
border-top: none;
|
|
654
|
+
padding: 6px 14px;
|
|
655
|
+
}
|
|
656
|
+
.af-widget-root.af-variant-bare .af-typing-dots span { background: currentColor; opacity: 0.5; }
|
|
657
|
+
|
|
658
|
+
/* Mobile-first sticky input: on narrow viewports the input docks to
|
|
659
|
+
* the viewport's bottom edge so the visitor never has to scroll to
|
|
660
|
+
* find it (same affordance as iMessage / WhatsApp). On desktop the
|
|
661
|
+
* sticky behaviour scopes to the chat container, which is enough. */
|
|
662
|
+
@media (max-width: 640px) {
|
|
663
|
+
.af-widget-root.af-variant-bare .af-input-row {
|
|
664
|
+
position: sticky;
|
|
665
|
+
bottom: env(safe-area-inset-bottom, 0);
|
|
666
|
+
padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px));
|
|
667
|
+
background: var(--af-input-row-bg, rgba(255, 255, 255, 0.85));
|
|
668
|
+
backdrop-filter: saturate(140%) blur(8px);
|
|
669
|
+
-webkit-backdrop-filter: saturate(140%) blur(8px);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
@media (max-width: 640px) and (prefers-color-scheme: dark) {
|
|
673
|
+
.af-widget-root.af-variant-bare .af-input-row {
|
|
674
|
+
background: var(--af-input-row-bg, rgba(15, 23, 42, 0.7));
|
|
675
|
+
}
|
|
676
|
+
}
|
|
549
677
|
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/chat-sdk",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.24",
|
|
4
4
|
"description": "Framework-free chat session SDK for AgentForge public chat tokens. Headless — no DOM. Drop into any frontend (React, Vue, Svelte, vanilla) and listen for events.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|