@agentforge-io/chat-sdk 2.0.22 → 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/entities.d.ts +4 -6
- package/dist/react.d.ts +32 -2
- package/dist/react.js +143 -15
- package/dist/session.js +2 -11
- package/package.json +1 -1
package/dist/entities.d.ts
CHANGED
|
@@ -131,12 +131,10 @@ export interface ChatSessionOptions {
|
|
|
131
131
|
token: string;
|
|
132
132
|
/**
|
|
133
133
|
* Base URL of the AgentForge API, including scheme. Trailing slash is
|
|
134
|
-
* stripped.
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* 3. The baked default that ships with the current SDK version.
|
|
139
|
-
* Hosts embedding into their own site can leave this unset.
|
|
134
|
+
* stripped. Optional — when omitted the SDK uses the baked default
|
|
135
|
+
* that ships with the current version. Pass this only when self-
|
|
136
|
+
* hosting the backend or pointing the SDK at a non-production
|
|
137
|
+
* deployment (e.g. localhost during development).
|
|
140
138
|
*/
|
|
141
139
|
apiBaseUrl?: string;
|
|
142
140
|
/** Stable id for this end-user's browser. Persist it (localStorage etc.)
|
package/dist/react.d.ts
CHANGED
|
@@ -64,8 +64,8 @@ export interface ChatWidgetProps {
|
|
|
64
64
|
/**
|
|
65
65
|
* AgentForge API origin. Optional — the SDK ships with a built-in
|
|
66
66
|
* default that points at the hosted AgentForge deployment. Override
|
|
67
|
-
* this when
|
|
68
|
-
*
|
|
67
|
+
* this only when self-hosting the backend or pointing at a non-
|
|
68
|
+
* production deployment.
|
|
69
69
|
*/
|
|
70
70
|
apiBaseUrl?: string;
|
|
71
71
|
/** Render inline (fills the parent) instead of as a floating bubble. */
|
|
@@ -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/dist/session.js
CHANGED
|
@@ -391,19 +391,10 @@ function generateBrowserSessionId() {
|
|
|
391
391
|
}
|
|
392
392
|
/**
|
|
393
393
|
* Build-time default API origin. Bumped together with the SDK when the
|
|
394
|
-
* hosted AgentForge deployment moves to a new domain.
|
|
395
|
-
*
|
|
396
|
-
* option, or at runtime via `window.AGENTFORGE_API_BASE_URL`.
|
|
394
|
+
* hosted AgentForge deployment moves to a new domain. Self-hosted
|
|
395
|
+
* deployments override this via the `apiBaseUrl` constructor option.
|
|
397
396
|
*/
|
|
398
397
|
const BAKED_API_BASE = 'https://api-agentforge.stupidmvp.com';
|
|
399
398
|
function defaultApiBase() {
|
|
400
|
-
// Runtime override for hosts that embed via a script tag and don't
|
|
401
|
-
// want to touch React props. Set once on `window` before the widget
|
|
402
|
-
// mounts and the SDK uses it for every transport call.
|
|
403
|
-
if (typeof window !== 'undefined') {
|
|
404
|
-
const override = window.AGENTFORGE_API_BASE_URL;
|
|
405
|
-
if (override)
|
|
406
|
-
return override;
|
|
407
|
-
}
|
|
408
399
|
return BAKED_API_BASE;
|
|
409
400
|
}
|
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",
|