@agentforge-io/chat-sdk 2.5.0 → 2.5.2

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.
Files changed (2) hide show
  1. package/dist/react.js +79 -52
  2. package/package.json +1 -1
package/dist/react.js CHANGED
@@ -563,28 +563,34 @@ function ChatWidget(props) {
563
563
  }
564
564
  visibleIdxs.push(i);
565
565
  }
566
- // Drop the empty intermediaries: every assistant bubble
567
- // that has no content AND whose chain ends at a later
568
- // bubble is hidden. The chain head will collect their
569
- // identities and surface them as an avatar group.
570
- const lastInChain = new Map(); // head -> last idx
571
- for (const i of visibleIdxs) {
572
- if (messages[i].role === 'assistant') {
573
- lastInChain.set(chainHeadFor[i], i);
574
- }
566
+ // Find the index of the next assistant message after each
567
+ // position. Used to decide whether an empty bubble has been
568
+ // superseded by a later speaker (in which case the visitor
569
+ // should not still see it typing).
570
+ const nextAssistantIdx = new Array(messages.length).fill(-1);
571
+ for (let k = messages.length - 2; k >= 0; k--) {
572
+ const nextI = k + 1;
573
+ nextAssistantIdx[k] =
574
+ messages[nextI].role === 'assistant'
575
+ ? nextI
576
+ : nextAssistantIdx[nextI];
575
577
  }
576
578
  const finalIdxs = visibleIdxs.filter((i) => {
577
579
  const m = messages[i];
578
580
  if (m.role !== 'assistant')
579
581
  return true;
580
- const head = chainHeadFor[i];
581
- const last = lastInChain.get(head);
582
- // Hide intermediates: assistant bubbles that are part of
583
- // a chain whose LAST element is a later bubble AND that
584
- // are themselves empty. The last bubble in the chain
585
- // always renders; non-empty intermediates also render so
586
- // we never lose visible text.
587
- if (last !== undefined && i !== last && !m.content)
582
+ // Any empty assistant bubble that has been overtaken by
583
+ // a LATER assistant bubble is an intermediate — hide it.
584
+ // Covers two cases at once:
585
+ // 1. The classic "chain head/intermediate" path: bubble
586
+ // i is empty and there's a final bubble after it.
587
+ // 2. The "live delegation" path: while a chain streams,
588
+ // the previous member sits at content='' isStreaming=
589
+ // true (the SDK only flips isStreaming when the
590
+ // bubble produced text). A naive filter would keep
591
+ // showing its typing dots; we drop it as soon as
592
+ // a fresher speaker exists.
593
+ if (!m.content && nextAssistantIdx[i] !== -1)
588
594
  return false;
589
595
  return true;
590
596
  });
@@ -612,37 +618,41 @@ function ChatWidget(props) {
612
618
  let chainParticipants;
613
619
  if (isAssistant && showAvatar) {
614
620
  const head = chainHeadFor[i];
615
- const last = lastInChain.get(head);
616
- if (last === i && head !== i) {
617
- // Collect all distinct speakers in [head…i] in
618
- // chronological order, then surface them with the
619
- // active one (the last) at the front.
620
- const seen = new Set();
621
- const ordered = [];
622
- for (let j = head; j <= i; j++) {
623
- const mj = messages[j];
624
- if (mj.role !== 'assistant')
625
- continue;
626
- const key = mj.actingAgentId ?? null;
627
- if (seen.has(key))
628
- continue;
629
- seen.add(key);
630
- const mem = mj.actingAgentId
631
- ? membersById.get(mj.actingAgentId)
632
- : undefined;
633
- ordered.push({
634
- agentId: mj.actingAgentId ?? agent?.slug,
635
- name: mem?.name ?? personaName ?? agent?.name,
636
- theme: mem
637
- ? { ...theme, avatarUrl: mem.avatarUrl }
638
- : theme,
639
- });
640
- }
641
- if (ordered.length > 1) {
642
- // Move the LAST (active speaker) to the front.
643
- const active = ordered.pop();
644
- chainParticipants = [active, ...ordered.reverse()];
645
- }
621
+ // Collect all distinct speakers in [head…i] in
622
+ // chronological order. The bubble that's rendering now
623
+ // (i) is the most recent speaker so it becomes the
624
+ // "active" one at the front of the stack; everything
625
+ // earlier in the chain trails behind in reverse-
626
+ // chronological order. Works the same whether the
627
+ // chain has finished or is still streaming — visitor
628
+ // sees one bubble with a live-updating header.
629
+ const seen = new Set();
630
+ const ordered = [];
631
+ for (let j = head; j <= i; j++) {
632
+ const mj = messages[j];
633
+ if (mj.role !== 'assistant')
634
+ continue;
635
+ const key = mj.actingAgentId ?? null;
636
+ if (seen.has(key))
637
+ continue;
638
+ seen.add(key);
639
+ const mem = mj.actingAgentId
640
+ ? membersById.get(mj.actingAgentId)
641
+ : undefined;
642
+ ordered.push({
643
+ agentId: mj.actingAgentId ?? agent?.slug,
644
+ name: mem?.name ?? personaName ?? agent?.name,
645
+ theme: mem
646
+ ? { ...theme, avatarUrl: mem.avatarUrl }
647
+ : theme,
648
+ });
649
+ }
650
+ if (ordered.length > 1) {
651
+ // The visible bubble's speaker (i) sits at the END of
652
+ // `ordered`. Move them to the front so the active
653
+ // avatar leads the stack.
654
+ const active = ordered.pop();
655
+ chainParticipants = [active, ...ordered.reverse()];
646
656
  }
647
657
  }
648
658
  return ((0, jsx_runtime_1.jsx)(MessageBubble, { message: m, session: session, readOnly: readOnlyApprovals, onDecision: onApprovalDecision, bare: bare, showAvatar: showAvatar, avatarTheme: bubbleAvatarTheme, avatarName: bubbleAvatarName, avatarAgentId: bubbleAgentId, speakerLabel: member?.name, chainParticipants: chainParticipants, onContinue: () => {
@@ -1357,22 +1367,38 @@ const WIDGET_CSS = `
1357
1367
  background-color: var(--af-primary, #8b5cf6);
1358
1368
  }
1359
1369
  /* Delegation-chain avatar stack. Override the default 26x26 size so
1360
- * the row can host multiple overlapping items without clipping. */
1370
+ * the row can host multiple overlapping items without clipping.
1371
+ * `, align;
1372
+ -items;
1373
+ center ` is critical: with `;
1374
+ flex - end ` the smaller
1375
+ * items glue to the bottom of the row and the column they sit in
1376
+ * looks unbalanced — circles read as ovals because the bounding box
1377
+ * isn't square anymore. Centering keeps every item axis-aligned and
1378
+ * the ring + size cap together force a 1:1 aspect ratio. */
1361
1379
  .af-widget-root.af-variant-bare .af-msg-avatar-stack {
1362
1380
  width: auto;
1363
1381
  height: 26px;
1364
1382
  border-radius: 0;
1365
1383
  overflow: visible;
1384
+ background: transparent;
1366
1385
  display: flex;
1367
1386
  flex-direction: row;
1368
- align-items: flex-end;
1387
+ align-items: center;
1369
1388
  /* Slight padding-right so the rightmost stacked item doesn't sit
1370
1389
  * flush against the bubble bevel. */
1371
1390
  padding-right: 4px;
1372
1391
  }
1373
1392
  .af-widget-root.af-variant-bare .af-msg-avatar-stack-item {
1393
+ /* Hard-pin the aspect ratio so the box never deforms into an
1394
+ * ellipse when the parent flexes — `;
1395
+ aspect - ratio;
1396
+ 1 ` plus explicit
1397
+ * width AND min-width guarantees a square box on every flex child. */
1374
1398
  width: 18px;
1399
+ min-width: 18px;
1375
1400
  height: 18px;
1401
+ aspect-ratio: 1;
1376
1402
  border-radius: 50%;
1377
1403
  overflow: hidden;
1378
1404
  display: flex;
@@ -1390,9 +1416,9 @@ const WIDGET_CSS = `
1390
1416
  }
1391
1417
  .af-widget-root.af-variant-bare .af-msg-avatar-stack-active {
1392
1418
  /* The active speaker is rendered full-size so it visually anchors
1393
- * the chain. Slightly larger ring keeps it clearly separated from
1394
- * the smaller upstream delegators. */
1419
+ * the chain. */
1395
1420
  width: 26px;
1421
+ min-width: 26px;
1396
1422
  height: 26px;
1397
1423
  }
1398
1424
  .af-widget-root.af-variant-bare .af-msg-avatar-stack-img {
@@ -1406,6 +1432,7 @@ const WIDGET_CSS = `
1406
1432
  font-size: 9.5px;
1407
1433
  font-weight: 600;
1408
1434
  background-color: var(--af-primary, #8b5cf6);
1435
+ line-height: 1;
1409
1436
  }
1410
1437
  .af-widget-root.af-variant-bare .af-msg-avatar-stack-active.af-msg-avatar-stack-fallback {
1411
1438
  font-size: 11.5px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge-io/chat-sdk",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
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",