@agentforge-io/chat-sdk 2.4.0-dev.10 → 2.4.0-dev.12

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 CHANGED
@@ -205,6 +205,19 @@ export interface ChatWidgetHandle {
205
205
  * the textarea hasn't mounted yet or is disabled.
206
206
  */
207
207
  focus(): void;
208
+ /**
209
+ * Warm up the underlying session — fires the initial
210
+ * `GET /agent` (and optionally a `resumeConversation` lookup) so
211
+ * the first `send()` doesn't pay the round-trip. Idempotent:
212
+ * subsequent calls join the same in-flight promise.
213
+ *
214
+ * Hosts call this when the visitor signals INTENT to chat (e.g.
215
+ * tapping a fake-composer pill that opens a drawer) so the
216
+ * session boot overlaps with the visitor finding the keyboard
217
+ * and tapping out their first message. Safe no-op if the session
218
+ * is already past `loading`.
219
+ */
220
+ warmup(): void;
208
221
  }
209
222
  /**
210
223
  * Drop-in chat widget. Owns its own `ChatSession` and re-renders on every
package/dist/react.js CHANGED
@@ -448,6 +448,14 @@ function ChatWidget(props) {
448
448
  return;
449
449
  el.focus({ preventScroll: true });
450
450
  },
451
+ warmup: () => {
452
+ // session.start() is idempotent — joins the in-flight
453
+ // promise if one exists, resolves immediately if start
454
+ // already completed.
455
+ if (!session)
456
+ return;
457
+ void session.start();
458
+ },
451
459
  };
452
460
  return () => {
453
461
  // Drop the handle on unmount so a stale ref can't fire send
@@ -538,7 +546,9 @@ function ChatWidget(props) {
538
546
  onShortcutClick(text, i);
539
547
  else
540
548
  setDraft(text);
541
- }, 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: inputPlaceholder ?? 'Type a message…', rows: 1,
549
+ }, 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'
550
+ ? 'Preparing chat…'
551
+ : inputPlaceholder ?? 'Type a message…', rows: 1,
542
552
  // The textarea stays editable while the agent is
543
553
  // streaming so the visitor can compose their next
544
554
  // message without waiting. Only block when the
@@ -553,7 +563,14 @@ function ChatWidget(props) {
553
563
  // before `handleSend` runs, the button then disables
554
564
  // (sendDisabled flips true on status change), focus
555
565
  // jumps to <body>, and the on-screen keyboard collapses.
556
- onPointerDown: (e) => e.preventDefault(), 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" })] })] }));
566
+ onPointerDown: (e) => e.preventDefault(), onClick: handleSend, disabled: sendDisabled, "aria-label": status === 'sending' || status === 'streaming'
567
+ ? 'Sending message'
568
+ : status === 'idle' || status === 'loading'
569
+ ? 'Preparing chat'
570
+ : 'Send message', children: status === 'sending' ||
571
+ status === 'streaming' ||
572
+ status === 'idle' ||
573
+ 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" })] })] }));
557
574
  }
558
575
  function MessageBubble({ message, session, readOnly, onDecision, onContinue, bare = false, showAvatar = false, avatarTheme, avatarName, avatarAgentId, speakerLabel, }) {
559
576
  const kind = message.metadata?.kind;
@@ -726,6 +743,11 @@ function CloseIcon() {
726
743
  function SendIcon() {
727
744
  return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [(0, jsx_runtime_1.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }), (0, jsx_runtime_1.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })] }));
728
745
  }
746
+ function SpinnerIcon() {
747
+ // Inline SVG spinner — no external dep. Stroke-dasharray
748
+ // arc rotates via the CSS keyframes injected by WIDGET_CSS.
749
+ return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", className: "af-spinner", "aria-hidden": true, children: [(0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeOpacity: "0.25", strokeWidth: "2.5" }), (0, jsx_runtime_1.jsx)("path", { d: "M21 12a9 9 0 0 0-9-9", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })] }));
750
+ }
729
751
  // Stylesheet kept verbatim from the standalone widget.js so the React
730
752
  // component is visually indistinguishable from the script-injected one.
731
753
  const WIDGET_CSS = `
@@ -810,6 +832,8 @@ const WIDGET_CSS = `
810
832
  to { opacity: 1; transform: translateY(0); }
811
833
  }
812
834
  .af-input-row { padding: 12px; border-top: 1px solid var(--af-border); background: var(--af-bg); display: flex; gap: 8px; align-items: flex-end; }
835
+ .af-input-row[data-loading] .af-input { cursor: progress; opacity: 0.7; }
836
+ .af-input-row[data-loading] .af-input::placeholder { font-style: italic; }
813
837
  /* Composer left slot — hosts use this for affordance buttons that
814
838
  scope the next turn (member picker, tools menu, attachments).
815
839
  align-items: center keeps a single-line chip vertically centered
@@ -869,6 +893,8 @@ const WIDGET_CSS = `
869
893
  .af-send:active:not(:disabled) { transform: translateY(0); }
870
894
  .af-send:disabled { opacity: 0.5; cursor: not-allowed; }
871
895
  .af-send svg { width: 16px; height: 16px; }
896
+ .af-send .af-spinner { animation: af-spin 720ms linear infinite; }
897
+ @keyframes af-spin { to { transform: rotate(360deg); } }
872
898
  .af-error { padding: 10px 16px; font-size: 12px; color: #b91c1c; background: #fef2f2; border-top: 1px solid #fee2e2; }
873
899
  .af-footer { padding: 6px 12px; font-size: 10px; color: var(--af-muted); text-align: center; background: var(--af-bubble-bg); border-top: 1px solid var(--af-border); }
874
900
  /* Approval and blocked bubbles. Amber for needs-decision, red for
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge-io/chat-sdk",
3
- "version": "2.4.0-dev.10",
3
+ "version": "2.4.0-dev.12",
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",