@agentforge-io/chat-sdk 2.4.0-dev.11 → 2.4.0-dev.13
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.js +34 -5
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -291,14 +291,31 @@ function ChatWidget(props) {
|
|
|
291
291
|
el.focus({ preventScroll: true });
|
|
292
292
|
focusedOnceRef.current = true;
|
|
293
293
|
}, [status]);
|
|
294
|
-
// ── Session lifecycle. Recreate when token / apiBaseUrl
|
|
294
|
+
// ── Session lifecycle. Recreate when token / apiBaseUrl / browserSessionId
|
|
295
|
+
// / stream changes. NOTE: `resumeConversationId` is intentionally NOT a
|
|
296
|
+
// dependency.
|
|
297
|
+
//
|
|
298
|
+
// Why: hosts typically wire `resumeConversationId={conversationId}`
|
|
299
|
+
// and call `setConversationId(id)` in `onConversationStart`. The
|
|
300
|
+
// conversation_started event fires AFTER the SSE stream opens and
|
|
301
|
+
// the first chunks already started flowing. If we treated
|
|
302
|
+
// resumeConversationId as a reactive dep, this effect would
|
|
303
|
+
// re-run mid-stream, destroy the live ChatSession, and the
|
|
304
|
+
// remaining chunks would land in a corpse — the visitor sees
|
|
305
|
+
// an empty assistant bubble even though the backend completed
|
|
306
|
+
// the turn successfully.
|
|
307
|
+
//
|
|
308
|
+
// We capture the resume id at mount via a ref. Subsequent host
|
|
309
|
+
// updates to it are ignored — the SDK already owns the live
|
|
310
|
+
// conversation id internally via session.state.conversationId.
|
|
311
|
+
const resumeRef = (0, react_1.useRef)(resumeConversationId);
|
|
295
312
|
(0, react_1.useEffect)(() => {
|
|
296
313
|
let cancelled = false;
|
|
297
314
|
const s = new session_1.ChatSession({
|
|
298
315
|
token,
|
|
299
316
|
apiBaseUrl,
|
|
300
317
|
browserSessionId,
|
|
301
|
-
resumeConversationId,
|
|
318
|
+
resumeConversationId: resumeRef.current,
|
|
302
319
|
stream,
|
|
303
320
|
});
|
|
304
321
|
setSession(s);
|
|
@@ -336,7 +353,10 @@ function ChatWidget(props) {
|
|
|
336
353
|
unsubscribe();
|
|
337
354
|
s.destroy();
|
|
338
355
|
};
|
|
339
|
-
|
|
356
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- resumeConversationId
|
|
357
|
+
// is captured via resumeRef on first mount; reactive updates are
|
|
358
|
+
// ignored by design. See block comment above.
|
|
359
|
+
}, [token, apiBaseUrl, browserSessionId, stream]);
|
|
340
360
|
// Auto-scroll on new tokens.
|
|
341
361
|
//
|
|
342
362
|
// We defer the scroll into a requestAnimationFrame so the DOM has
|
|
@@ -546,7 +566,9 @@ function ChatWidget(props) {
|
|
|
546
566
|
onShortcutClick(text, i);
|
|
547
567
|
else
|
|
548
568
|
setDraft(text);
|
|
549
|
-
}, children: text }, `${i}-${text}`))) })), (0, jsx_runtime_1.jsxs)("div", { className: "af-input-row", children: [composerLeftSlot && ((0, jsx_runtime_1.jsx)("div", { className: "af-input-left", children: composerLeftSlot })), (0, jsx_runtime_1.jsx)("textarea", { ref: inputRef, className: "af-input", value: draft, onChange: (e) => setDraft(e.target.value), onKeyDown: onKeyDown, placeholder:
|
|
569
|
+
}, children: text }, `${i}-${text}`))) })), (0, jsx_runtime_1.jsxs)("div", { className: "af-input-row", "data-loading": status === 'loading' || status === 'idle' ? '' : undefined, children: [composerLeftSlot && ((0, jsx_runtime_1.jsx)("div", { className: "af-input-left", children: composerLeftSlot })), (0, jsx_runtime_1.jsx)("textarea", { ref: inputRef, className: "af-input", value: draft, onChange: (e) => setDraft(e.target.value), onKeyDown: onKeyDown, placeholder: status === 'idle' || status === 'loading'
|
|
570
|
+
? 'Preparing chat…'
|
|
571
|
+
: inputPlaceholder ?? 'Type a message…', rows: 1,
|
|
550
572
|
// The textarea stays editable while the agent is
|
|
551
573
|
// streaming so the visitor can compose their next
|
|
552
574
|
// message without waiting. Only block when the
|
|
@@ -563,7 +585,12 @@ function ChatWidget(props) {
|
|
|
563
585
|
// jumps to <body>, and the on-screen keyboard collapses.
|
|
564
586
|
onPointerDown: (e) => e.preventDefault(), onClick: handleSend, disabled: sendDisabled, "aria-label": status === 'sending' || status === 'streaming'
|
|
565
587
|
? 'Sending message'
|
|
566
|
-
:
|
|
588
|
+
: status === 'idle' || status === 'loading'
|
|
589
|
+
? 'Preparing chat'
|
|
590
|
+
: 'Send message', children: status === 'sending' ||
|
|
591
|
+
status === 'streaming' ||
|
|
592
|
+
status === 'idle' ||
|
|
593
|
+
status === 'loading' ? ((0, jsx_runtime_1.jsx)(SpinnerIcon, {})) : ((0, jsx_runtime_1.jsx)(SendIcon, {})) })] }), !bare && (0, jsx_runtime_1.jsx)("div", { className: "af-footer", children: "Powered by AgentForge" })] })] }));
|
|
567
594
|
}
|
|
568
595
|
function MessageBubble({ message, session, readOnly, onDecision, onContinue, bare = false, showAvatar = false, avatarTheme, avatarName, avatarAgentId, speakerLabel, }) {
|
|
569
596
|
const kind = message.metadata?.kind;
|
|
@@ -825,6 +852,8 @@ const WIDGET_CSS = `
|
|
|
825
852
|
to { opacity: 1; transform: translateY(0); }
|
|
826
853
|
}
|
|
827
854
|
.af-input-row { padding: 12px; border-top: 1px solid var(--af-border); background: var(--af-bg); display: flex; gap: 8px; align-items: flex-end; }
|
|
855
|
+
.af-input-row[data-loading] .af-input { cursor: progress; opacity: 0.7; }
|
|
856
|
+
.af-input-row[data-loading] .af-input::placeholder { font-style: italic; }
|
|
828
857
|
/* Composer left slot — hosts use this for affordance buttons that
|
|
829
858
|
scope the next turn (member picker, tools menu, attachments).
|
|
830
859
|
align-items: center keeps a single-line chip vertically centered
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/chat-sdk",
|
|
3
|
-
"version": "2.4.0-dev.
|
|
3
|
+
"version": "2.4.0-dev.13",
|
|
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",
|