@agentforge-io/chat-sdk 2.4.1 → 2.5.0
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 +233 -52
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -517,51 +517,143 @@ function ChatWidget(props) {
|
|
|
517
517
|
};
|
|
518
518
|
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 ? (
|
|
519
519
|
// eslint-disable-next-line @next/next/no-img-element
|
|
520
|
-
(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.jsx)("div", { className: "af-messages", ref: messagesRef, children:
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
//
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
|
|
520
|
+
(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.jsx)("div", { className: "af-messages", ref: messagesRef, children: (() => {
|
|
521
|
+
// Pre-walk the transcript to identify "delegation chains":
|
|
522
|
+
// a sequence of consecutive assistant bubbles where each has
|
|
523
|
+
// a different `actingAgentId` AND every intermediate bubble
|
|
524
|
+
// is empty (the orchestrator/member delegated immediately
|
|
525
|
+
// and never produced text of its own). The visible bubble
|
|
526
|
+
// is the LAST one — the speaker that actually answered.
|
|
527
|
+
//
|
|
528
|
+
// For each chain we render a single MessageBubble for the
|
|
529
|
+
// last entry, with the avatar slot replaced by a HORIZONTAL
|
|
530
|
+
// STACK of every participant in delegation order: the
|
|
531
|
+
// active speaker first (largest), the upstream delegators
|
|
532
|
+
// smaller and overlapped to the right.
|
|
533
|
+
//
|
|
534
|
+
// chainHeadFor[i] holds the head index of the chain that
|
|
535
|
+
// contains message i. visibleIdxs is the list of indices
|
|
536
|
+
// we actually render.
|
|
537
|
+
const chainHeadFor = new Array(messages.length);
|
|
538
|
+
const visibleIdxs = [];
|
|
539
|
+
for (let i = 0; i < messages.length; i++) {
|
|
540
|
+
const m = messages[i];
|
|
541
|
+
if (m.role !== 'assistant') {
|
|
542
|
+
chainHeadFor[i] = i;
|
|
543
|
+
visibleIdxs.push(i);
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
const prev = messages[i - 1];
|
|
547
|
+
const prevSameSpeaker = prev?.role === 'assistant' &&
|
|
548
|
+
(prev?.actingAgentId ?? null) === (m.actingAgentId ?? null);
|
|
549
|
+
// Walk back through the chain: pick the head of the
|
|
550
|
+
// previous bubble's chain if that bubble was an empty
|
|
551
|
+
// assistant with a DIFFERENT actingAgentId (delegation
|
|
552
|
+
// boundary). Same speaker -> head stays the same.
|
|
553
|
+
if (prev?.role === 'assistant' &&
|
|
554
|
+
(prev.actingAgentId ?? null) !== (m.actingAgentId ?? null) &&
|
|
555
|
+
!prev.content) {
|
|
556
|
+
chainHeadFor[i] = chainHeadFor[i - 1];
|
|
557
|
+
}
|
|
558
|
+
else if (prevSameSpeaker) {
|
|
559
|
+
chainHeadFor[i] = chainHeadFor[i - 1];
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
chainHeadFor[i] = i;
|
|
563
|
+
}
|
|
564
|
+
visibleIdxs.push(i);
|
|
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
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const finalIdxs = visibleIdxs.filter((i) => {
|
|
577
|
+
const m = messages[i];
|
|
578
|
+
if (m.role !== 'assistant')
|
|
579
|
+
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)
|
|
588
|
+
return false;
|
|
589
|
+
return true;
|
|
590
|
+
});
|
|
591
|
+
return finalIdxs.map((i) => {
|
|
592
|
+
const m = messages[i];
|
|
593
|
+
const prev = messages[i - 1];
|
|
594
|
+
const isAssistant = m.role === 'assistant';
|
|
595
|
+
const sameAssistantRun = prev?.role === 'assistant' &&
|
|
596
|
+
(prev?.actingAgentId ?? null) === (m.actingAgentId ?? null);
|
|
597
|
+
const showAvatar = bare && isAssistant && !sameAssistantRun;
|
|
598
|
+
// Per-bubble identity resolution.
|
|
599
|
+
const member = m.actingAgentId
|
|
600
|
+
? membersById.get(m.actingAgentId)
|
|
601
|
+
: undefined;
|
|
602
|
+
const bubbleAvatarTheme = member
|
|
603
|
+
? { ...theme, avatarUrl: member.avatarUrl }
|
|
604
|
+
: theme;
|
|
605
|
+
const bubbleAvatarName = member?.name ?? personaName ?? agent?.name;
|
|
606
|
+
const bubbleAgentId = m.actingAgentId ?? agent?.slug;
|
|
607
|
+
// Build the delegation-chain participants list when this
|
|
608
|
+
// bubble is the LAST in a chain (= the speaker that
|
|
609
|
+
// answered). Order: active speaker first, then the prior
|
|
610
|
+
// delegators in REVERSE chronological order so the most
|
|
611
|
+
// recent delegator sits closest to the active avatar.
|
|
612
|
+
let chainParticipants;
|
|
613
|
+
if (isAssistant && showAvatar) {
|
|
614
|
+
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
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
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: () => {
|
|
649
|
+
if (!session)
|
|
650
|
+
return;
|
|
651
|
+
setTimeout(() => {
|
|
652
|
+
void session.send('continue', { silent: true });
|
|
653
|
+
}, 250);
|
|
654
|
+
} }, m.id));
|
|
655
|
+
});
|
|
656
|
+
})() }), lastError && status === 'error' && ((0, jsx_runtime_1.jsx)("div", { className: "af-error", children: lastError })), bare && greeting && messages.length === 0 && status !== 'loading' && ((0, jsx_runtime_1.jsx)("div", { className: "af-greeting-slot", children: (0, jsx_runtime_1.jsxs)("div", { className: "af-msg-row af-msg-row-assistant af-msg-row-greeting", children: [(0, jsx_runtime_1.jsx)(AssistantAvatar, { theme: theme, name: personaName ?? agent?.name, agentId: agent?.slug, show: true }), (0, jsx_runtime_1.jsx)("div", { className: "af-msg af-msg-assistant af-msg-greeting", dangerouslySetInnerHTML: { __html: renderMarkdown(greeting) } })] }) })), shortcuts && shortcuts.length > 0 && messages.length === 0 && ((0, jsx_runtime_1.jsx)("div", { className: "af-shortcut-row", children: shortcuts.map((text, i) => ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "af-shortcut", onClick: () => {
|
|
565
657
|
if (onShortcutClick)
|
|
566
658
|
onShortcutClick(text, i);
|
|
567
659
|
else
|
|
@@ -592,16 +684,16 @@ function ChatWidget(props) {
|
|
|
592
684
|
status === 'idle' ||
|
|
593
685
|
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" })] })] }));
|
|
594
686
|
}
|
|
595
|
-
function MessageBubble({ message, session, readOnly, onDecision, onContinue, bare = false, showAvatar = false, avatarTheme, avatarName, avatarAgentId, speakerLabel, }) {
|
|
687
|
+
function MessageBubble({ message, session, readOnly, onDecision, onContinue, bare = false, showAvatar = false, avatarTheme, avatarName, avatarAgentId, speakerLabel, chainParticipants, }) {
|
|
596
688
|
const kind = message.metadata?.kind;
|
|
597
689
|
if (kind === 'awaiting_approval') {
|
|
598
690
|
// Approval / blocked bubbles also count as "assistant-side" so we
|
|
599
691
|
// wrap them in the same row geometry — keeps the conversation
|
|
600
692
|
// aligned even when a tool dispatch interrupts the regular flow.
|
|
601
|
-
return wrapAssistantRow(bare, showAvatar, avatarTheme, avatarName, avatarAgentId, (0, jsx_runtime_1.jsx)(ApprovalBubble, { message: message, session: session, readOnly: readOnly, onDecision: onDecision, onContinue: onContinue }), speakerLabel);
|
|
693
|
+
return wrapAssistantRow(bare, showAvatar, avatarTheme, avatarName, avatarAgentId, (0, jsx_runtime_1.jsx)(ApprovalBubble, { message: message, session: session, readOnly: readOnly, onDecision: onDecision, onContinue: onContinue }), speakerLabel, chainParticipants);
|
|
602
694
|
}
|
|
603
695
|
if (kind === 'tool_blocked') {
|
|
604
|
-
return wrapAssistantRow(bare, showAvatar, avatarTheme, avatarName, avatarAgentId, (0, jsx_runtime_1.jsx)(BlockedBubble, { message: message }), speakerLabel);
|
|
696
|
+
return wrapAssistantRow(bare, showAvatar, avatarTheme, avatarName, avatarAgentId, (0, jsx_runtime_1.jsx)(BlockedBubble, { message: message }), speakerLabel, chainParticipants);
|
|
605
697
|
}
|
|
606
698
|
const cls = `af-msg af-msg-${message.role}${message.role === 'assistant' && message.isStreaming
|
|
607
699
|
? message.content
|
|
@@ -610,13 +702,13 @@ function MessageBubble({ message, session, readOnly, onDecision, onContinue, bar
|
|
|
610
702
|
: ''}`;
|
|
611
703
|
// Typing state (no content yet): render the three-dot indicator, no markdown.
|
|
612
704
|
if (message.role === 'assistant' && message.isStreaming && !message.content) {
|
|
613
|
-
return wrapAssistantRow(bare, showAvatar, avatarTheme, avatarName, avatarAgentId, (0, jsx_runtime_1.jsx)("div", { className: cls, children: (0, jsx_runtime_1.jsxs)("span", { className: "af-typing-dots", "aria-label": "Assistant is typing", children: [(0, jsx_runtime_1.jsx)("span", {}), " ", (0, jsx_runtime_1.jsx)("span", {}), " ", (0, jsx_runtime_1.jsx)("span", {})] }) }), speakerLabel);
|
|
705
|
+
return wrapAssistantRow(bare, showAvatar, avatarTheme, avatarName, avatarAgentId, (0, jsx_runtime_1.jsx)("div", { className: cls, children: (0, jsx_runtime_1.jsxs)("span", { className: "af-typing-dots", "aria-label": "Assistant is typing", children: [(0, jsx_runtime_1.jsx)("span", {}), " ", (0, jsx_runtime_1.jsx)("span", {}), " ", (0, jsx_runtime_1.jsx)("span", {})] }) }), speakerLabel, chainParticipants);
|
|
614
706
|
}
|
|
615
707
|
if (message.role === 'assistant') {
|
|
616
708
|
return wrapAssistantRow(bare, showAvatar, avatarTheme, avatarName, avatarAgentId, (0, jsx_runtime_1.jsx)("div", { className: cls,
|
|
617
709
|
// Output is sanitized by escapeHtml + a fixed tag whitelist in
|
|
618
710
|
// renderMarkdown — safe to inject as HTML.
|
|
619
|
-
dangerouslySetInnerHTML: { __html: renderMarkdown(message.content) } }), speakerLabel);
|
|
711
|
+
dangerouslySetInnerHTML: { __html: renderMarkdown(message.content) } }), speakerLabel, chainParticipants);
|
|
620
712
|
}
|
|
621
713
|
// User & system messages stay as plain text — they're typed verbatim.
|
|
622
714
|
// No avatar column on the user side; they align right.
|
|
@@ -631,10 +723,15 @@ function wrapAssistantRow(bare, showAvatar, theme, name, agentId, child,
|
|
|
631
723
|
/** When set + this is the first bubble of a speaker run (showAvatar
|
|
632
724
|
* is true), render the speaker's display name just above the
|
|
633
725
|
* bubble. Used by Team chats so members are visually attributed. */
|
|
634
|
-
speakerLabel
|
|
726
|
+
speakerLabel,
|
|
727
|
+
/** When set, replace the single avatar with a horizontal stack of
|
|
728
|
+
* every speaker in the delegation chain that produced this bubble.
|
|
729
|
+
* Index 0 is the active speaker (largest), the rest are upstream
|
|
730
|
+
* delegators in reverse-chronological order. */
|
|
731
|
+
chainParticipants) {
|
|
635
732
|
if (!bare)
|
|
636
733
|
return child;
|
|
637
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "af-msg-row af-msg-row-assistant", children: [(0, jsx_runtime_1.jsx)(AssistantAvatar, { theme: theme, name: name, agentId: agentId, show: showAvatar }), (0, jsx_runtime_1.jsxs)("div", { className: "af-msg-col", children: [showAvatar && speakerLabel ? ((0, jsx_runtime_1.jsx)("div", { className: "af-msg-speaker", children: speakerLabel })) : null, child] })] }));
|
|
734
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "af-msg-row af-msg-row-assistant", children: [chainParticipants && chainParticipants.length > 1 ? ((0, jsx_runtime_1.jsx)(DelegationAvatarStack, { participants: chainParticipants })) : ((0, jsx_runtime_1.jsx)(AssistantAvatar, { theme: theme, name: name, agentId: agentId, show: showAvatar })), (0, jsx_runtime_1.jsxs)("div", { className: "af-msg-col", children: [showAvatar && speakerLabel ? ((0, jsx_runtime_1.jsx)("div", { className: "af-msg-speaker", children: speakerLabel })) : null, child] })] }));
|
|
638
735
|
}
|
|
639
736
|
/**
|
|
640
737
|
* Small circular avatar for the assistant-side column. Prefers the
|
|
@@ -660,6 +757,36 @@ function AssistantAvatar({ theme, name, agentId, show, }) {
|
|
|
660
757
|
const seed = agentId || name || 'agent';
|
|
661
758
|
return ((0, jsx_runtime_1.jsx)("div", { className: "af-msg-avatar af-msg-avatar-fallback", "aria-hidden": true, style: { backgroundColor: hueFromSeed(seed) }, children: (0, jsx_runtime_1.jsx)("span", { children: initial }) }));
|
|
662
759
|
}
|
|
760
|
+
/**
|
|
761
|
+
* Horizontal stack of avatars representing the delegation chain that
|
|
762
|
+
* produced the current bubble. Index 0 is the active speaker — drawn
|
|
763
|
+
* at the regular avatar size on the LEFT (the speaker that actually
|
|
764
|
+
* answered, attention sits on them). The remaining participants are
|
|
765
|
+
* upstream delegators in reverse-chronological order: each one
|
|
766
|
+
* slightly smaller and overlapped to the right of the active avatar.
|
|
767
|
+
*
|
|
768
|
+
* Visually: [active] [delegator-1] [delegator-2] [...]
|
|
769
|
+
* Mental model: "this answer came from <active>, delegated by <…>".
|
|
770
|
+
*/
|
|
771
|
+
function DelegationAvatarStack({ participants, }) {
|
|
772
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "af-msg-avatar af-msg-avatar-stack", role: "img", "aria-label": `Delegation chain: ${participants.map((p) => p.name).filter(Boolean).join(' → ')}`, children: participants.map((p, idx) => {
|
|
773
|
+
const isActive = idx === 0;
|
|
774
|
+
const initial = (p.name ?? 'A').trim().charAt(0).toUpperCase() || 'A';
|
|
775
|
+
const seed = p.agentId || p.name || `agent-${idx}`;
|
|
776
|
+
const url = p.theme?.avatarUrl;
|
|
777
|
+
const className = 'af-msg-avatar-stack-item' +
|
|
778
|
+
(isActive ? ' af-msg-avatar-stack-active' : '');
|
|
779
|
+
// Inline z-index so the leftmost (active) lands on top of the
|
|
780
|
+
// overlapping siblings to its right.
|
|
781
|
+
const z = participants.length - idx;
|
|
782
|
+
return url ? (
|
|
783
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
784
|
+
(0, jsx_runtime_1.jsx)("img", { className: className + ' af-msg-avatar-stack-img', src: url, alt: "", "aria-hidden": true, style: { zIndex: z } }, `${idx}-${seed}`)) : ((0, jsx_runtime_1.jsx)("div", { className: className + ' af-msg-avatar-stack-fallback', "aria-hidden": true, style: {
|
|
785
|
+
backgroundColor: hueFromSeed(seed),
|
|
786
|
+
zIndex: z,
|
|
787
|
+
}, children: (0, jsx_runtime_1.jsx)("span", { children: initial }) }, `${idx}-${seed}`));
|
|
788
|
+
}) }));
|
|
789
|
+
}
|
|
663
790
|
/**
|
|
664
791
|
* Deterministic per-agent hue. Hash the seed into the HSL hue space
|
|
665
792
|
* so an agent's avatar color stays stable across renders and looks
|
|
@@ -1229,6 +1356,60 @@ const WIDGET_CSS = `
|
|
|
1229
1356
|
* inline color and made every agent avatar look the same. */
|
|
1230
1357
|
background-color: var(--af-primary, #8b5cf6);
|
|
1231
1358
|
}
|
|
1359
|
+
/* Delegation-chain avatar stack. Override the default 26x26 size so
|
|
1360
|
+
* the row can host multiple overlapping items without clipping. */
|
|
1361
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack {
|
|
1362
|
+
width: auto;
|
|
1363
|
+
height: 26px;
|
|
1364
|
+
border-radius: 0;
|
|
1365
|
+
overflow: visible;
|
|
1366
|
+
display: flex;
|
|
1367
|
+
flex-direction: row;
|
|
1368
|
+
align-items: flex-end;
|
|
1369
|
+
/* Slight padding-right so the rightmost stacked item doesn't sit
|
|
1370
|
+
* flush against the bubble bevel. */
|
|
1371
|
+
padding-right: 4px;
|
|
1372
|
+
}
|
|
1373
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-item {
|
|
1374
|
+
width: 18px;
|
|
1375
|
+
height: 18px;
|
|
1376
|
+
border-radius: 50%;
|
|
1377
|
+
overflow: hidden;
|
|
1378
|
+
display: flex;
|
|
1379
|
+
align-items: center;
|
|
1380
|
+
justify-content: center;
|
|
1381
|
+
flex-shrink: 0;
|
|
1382
|
+
/* 2px ring matching the bubble background so overlapping reads
|
|
1383
|
+
* as distinct circles, not a continuous blob. */
|
|
1384
|
+
box-shadow: 0 0 0 2px var(--af-bg, #ffffff);
|
|
1385
|
+
/* Overlap with the prior sibling. */
|
|
1386
|
+
margin-left: -6px;
|
|
1387
|
+
}
|
|
1388
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-item:first-child {
|
|
1389
|
+
margin-left: 0;
|
|
1390
|
+
}
|
|
1391
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-active {
|
|
1392
|
+
/* 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. */
|
|
1395
|
+
width: 26px;
|
|
1396
|
+
height: 26px;
|
|
1397
|
+
}
|
|
1398
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-img {
|
|
1399
|
+
object-fit: cover;
|
|
1400
|
+
display: block;
|
|
1401
|
+
width: 100%;
|
|
1402
|
+
height: 100%;
|
|
1403
|
+
}
|
|
1404
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-fallback {
|
|
1405
|
+
color: #fff;
|
|
1406
|
+
font-size: 9.5px;
|
|
1407
|
+
font-weight: 600;
|
|
1408
|
+
background-color: var(--af-primary, #8b5cf6);
|
|
1409
|
+
}
|
|
1410
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-active.af-msg-avatar-stack-fallback {
|
|
1411
|
+
font-size: 11.5px;
|
|
1412
|
+
}
|
|
1232
1413
|
@media (min-width: 768px) {
|
|
1233
1414
|
.af-widget-root.af-variant-bare .af-msg-avatar { width: 30px; height: 30px; }
|
|
1234
1415
|
.af-widget-root.af-variant-bare .af-msg-avatar-fallback { font-size: 13px; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/chat-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
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",
|