@agentforge-io/chat-sdk 2.4.1 → 2.5.1
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 +243 -52
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -517,51 +517,153 @@ 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
|
+
// 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];
|
|
577
|
+
}
|
|
578
|
+
const finalIdxs = visibleIdxs.filter((i) => {
|
|
579
|
+
const m = messages[i];
|
|
580
|
+
if (m.role !== 'assistant')
|
|
581
|
+
return true;
|
|
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)
|
|
594
|
+
return false;
|
|
595
|
+
return true;
|
|
596
|
+
});
|
|
597
|
+
return finalIdxs.map((i) => {
|
|
598
|
+
const m = messages[i];
|
|
599
|
+
const prev = messages[i - 1];
|
|
600
|
+
const isAssistant = m.role === 'assistant';
|
|
601
|
+
const sameAssistantRun = prev?.role === 'assistant' &&
|
|
602
|
+
(prev?.actingAgentId ?? null) === (m.actingAgentId ?? null);
|
|
603
|
+
const showAvatar = bare && isAssistant && !sameAssistantRun;
|
|
604
|
+
// Per-bubble identity resolution.
|
|
605
|
+
const member = m.actingAgentId
|
|
606
|
+
? membersById.get(m.actingAgentId)
|
|
607
|
+
: undefined;
|
|
608
|
+
const bubbleAvatarTheme = member
|
|
609
|
+
? { ...theme, avatarUrl: member.avatarUrl }
|
|
610
|
+
: theme;
|
|
611
|
+
const bubbleAvatarName = member?.name ?? personaName ?? agent?.name;
|
|
612
|
+
const bubbleAgentId = m.actingAgentId ?? agent?.slug;
|
|
613
|
+
// Build the delegation-chain participants list when this
|
|
614
|
+
// bubble is the LAST in a chain (= the speaker that
|
|
615
|
+
// answered). Order: active speaker first, then the prior
|
|
616
|
+
// delegators in REVERSE chronological order so the most
|
|
617
|
+
// recent delegator sits closest to the active avatar.
|
|
618
|
+
let chainParticipants;
|
|
619
|
+
if (isAssistant && showAvatar) {
|
|
620
|
+
const head = chainHeadFor[i];
|
|
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()];
|
|
656
|
+
}
|
|
657
|
+
}
|
|
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: () => {
|
|
659
|
+
if (!session)
|
|
660
|
+
return;
|
|
661
|
+
setTimeout(() => {
|
|
662
|
+
void session.send('continue', { silent: true });
|
|
663
|
+
}, 250);
|
|
664
|
+
} }, m.id));
|
|
665
|
+
});
|
|
666
|
+
})() }), 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
667
|
if (onShortcutClick)
|
|
566
668
|
onShortcutClick(text, i);
|
|
567
669
|
else
|
|
@@ -592,16 +694,16 @@ function ChatWidget(props) {
|
|
|
592
694
|
status === 'idle' ||
|
|
593
695
|
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
696
|
}
|
|
595
|
-
function MessageBubble({ message, session, readOnly, onDecision, onContinue, bare = false, showAvatar = false, avatarTheme, avatarName, avatarAgentId, speakerLabel, }) {
|
|
697
|
+
function MessageBubble({ message, session, readOnly, onDecision, onContinue, bare = false, showAvatar = false, avatarTheme, avatarName, avatarAgentId, speakerLabel, chainParticipants, }) {
|
|
596
698
|
const kind = message.metadata?.kind;
|
|
597
699
|
if (kind === 'awaiting_approval') {
|
|
598
700
|
// Approval / blocked bubbles also count as "assistant-side" so we
|
|
599
701
|
// wrap them in the same row geometry — keeps the conversation
|
|
600
702
|
// 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);
|
|
703
|
+
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
704
|
}
|
|
603
705
|
if (kind === 'tool_blocked') {
|
|
604
|
-
return wrapAssistantRow(bare, showAvatar, avatarTheme, avatarName, avatarAgentId, (0, jsx_runtime_1.jsx)(BlockedBubble, { message: message }), speakerLabel);
|
|
706
|
+
return wrapAssistantRow(bare, showAvatar, avatarTheme, avatarName, avatarAgentId, (0, jsx_runtime_1.jsx)(BlockedBubble, { message: message }), speakerLabel, chainParticipants);
|
|
605
707
|
}
|
|
606
708
|
const cls = `af-msg af-msg-${message.role}${message.role === 'assistant' && message.isStreaming
|
|
607
709
|
? message.content
|
|
@@ -610,13 +712,13 @@ function MessageBubble({ message, session, readOnly, onDecision, onContinue, bar
|
|
|
610
712
|
: ''}`;
|
|
611
713
|
// Typing state (no content yet): render the three-dot indicator, no markdown.
|
|
612
714
|
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);
|
|
715
|
+
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
716
|
}
|
|
615
717
|
if (message.role === 'assistant') {
|
|
616
718
|
return wrapAssistantRow(bare, showAvatar, avatarTheme, avatarName, avatarAgentId, (0, jsx_runtime_1.jsx)("div", { className: cls,
|
|
617
719
|
// Output is sanitized by escapeHtml + a fixed tag whitelist in
|
|
618
720
|
// renderMarkdown — safe to inject as HTML.
|
|
619
|
-
dangerouslySetInnerHTML: { __html: renderMarkdown(message.content) } }), speakerLabel);
|
|
721
|
+
dangerouslySetInnerHTML: { __html: renderMarkdown(message.content) } }), speakerLabel, chainParticipants);
|
|
620
722
|
}
|
|
621
723
|
// User & system messages stay as plain text — they're typed verbatim.
|
|
622
724
|
// No avatar column on the user side; they align right.
|
|
@@ -631,10 +733,15 @@ function wrapAssistantRow(bare, showAvatar, theme, name, agentId, child,
|
|
|
631
733
|
/** When set + this is the first bubble of a speaker run (showAvatar
|
|
632
734
|
* is true), render the speaker's display name just above the
|
|
633
735
|
* bubble. Used by Team chats so members are visually attributed. */
|
|
634
|
-
speakerLabel
|
|
736
|
+
speakerLabel,
|
|
737
|
+
/** When set, replace the single avatar with a horizontal stack of
|
|
738
|
+
* every speaker in the delegation chain that produced this bubble.
|
|
739
|
+
* Index 0 is the active speaker (largest), the rest are upstream
|
|
740
|
+
* delegators in reverse-chronological order. */
|
|
741
|
+
chainParticipants) {
|
|
635
742
|
if (!bare)
|
|
636
743
|
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] })] }));
|
|
744
|
+
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
745
|
}
|
|
639
746
|
/**
|
|
640
747
|
* Small circular avatar for the assistant-side column. Prefers the
|
|
@@ -660,6 +767,36 @@ function AssistantAvatar({ theme, name, agentId, show, }) {
|
|
|
660
767
|
const seed = agentId || name || 'agent';
|
|
661
768
|
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
769
|
}
|
|
770
|
+
/**
|
|
771
|
+
* Horizontal stack of avatars representing the delegation chain that
|
|
772
|
+
* produced the current bubble. Index 0 is the active speaker — drawn
|
|
773
|
+
* at the regular avatar size on the LEFT (the speaker that actually
|
|
774
|
+
* answered, attention sits on them). The remaining participants are
|
|
775
|
+
* upstream delegators in reverse-chronological order: each one
|
|
776
|
+
* slightly smaller and overlapped to the right of the active avatar.
|
|
777
|
+
*
|
|
778
|
+
* Visually: [active] [delegator-1] [delegator-2] [...]
|
|
779
|
+
* Mental model: "this answer came from <active>, delegated by <…>".
|
|
780
|
+
*/
|
|
781
|
+
function DelegationAvatarStack({ participants, }) {
|
|
782
|
+
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) => {
|
|
783
|
+
const isActive = idx === 0;
|
|
784
|
+
const initial = (p.name ?? 'A').trim().charAt(0).toUpperCase() || 'A';
|
|
785
|
+
const seed = p.agentId || p.name || `agent-${idx}`;
|
|
786
|
+
const url = p.theme?.avatarUrl;
|
|
787
|
+
const className = 'af-msg-avatar-stack-item' +
|
|
788
|
+
(isActive ? ' af-msg-avatar-stack-active' : '');
|
|
789
|
+
// Inline z-index so the leftmost (active) lands on top of the
|
|
790
|
+
// overlapping siblings to its right.
|
|
791
|
+
const z = participants.length - idx;
|
|
792
|
+
return url ? (
|
|
793
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
794
|
+
(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: {
|
|
795
|
+
backgroundColor: hueFromSeed(seed),
|
|
796
|
+
zIndex: z,
|
|
797
|
+
}, children: (0, jsx_runtime_1.jsx)("span", { children: initial }) }, `${idx}-${seed}`));
|
|
798
|
+
}) }));
|
|
799
|
+
}
|
|
663
800
|
/**
|
|
664
801
|
* Deterministic per-agent hue. Hash the seed into the HSL hue space
|
|
665
802
|
* so an agent's avatar color stays stable across renders and looks
|
|
@@ -1229,6 +1366,60 @@ const WIDGET_CSS = `
|
|
|
1229
1366
|
* inline color and made every agent avatar look the same. */
|
|
1230
1367
|
background-color: var(--af-primary, #8b5cf6);
|
|
1231
1368
|
}
|
|
1369
|
+
/* Delegation-chain avatar stack. Override the default 26x26 size so
|
|
1370
|
+
* the row can host multiple overlapping items without clipping. */
|
|
1371
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack {
|
|
1372
|
+
width: auto;
|
|
1373
|
+
height: 26px;
|
|
1374
|
+
border-radius: 0;
|
|
1375
|
+
overflow: visible;
|
|
1376
|
+
display: flex;
|
|
1377
|
+
flex-direction: row;
|
|
1378
|
+
align-items: flex-end;
|
|
1379
|
+
/* Slight padding-right so the rightmost stacked item doesn't sit
|
|
1380
|
+
* flush against the bubble bevel. */
|
|
1381
|
+
padding-right: 4px;
|
|
1382
|
+
}
|
|
1383
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-item {
|
|
1384
|
+
width: 18px;
|
|
1385
|
+
height: 18px;
|
|
1386
|
+
border-radius: 50%;
|
|
1387
|
+
overflow: hidden;
|
|
1388
|
+
display: flex;
|
|
1389
|
+
align-items: center;
|
|
1390
|
+
justify-content: center;
|
|
1391
|
+
flex-shrink: 0;
|
|
1392
|
+
/* 2px ring matching the bubble background so overlapping reads
|
|
1393
|
+
* as distinct circles, not a continuous blob. */
|
|
1394
|
+
box-shadow: 0 0 0 2px var(--af-bg, #ffffff);
|
|
1395
|
+
/* Overlap with the prior sibling. */
|
|
1396
|
+
margin-left: -6px;
|
|
1397
|
+
}
|
|
1398
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-item:first-child {
|
|
1399
|
+
margin-left: 0;
|
|
1400
|
+
}
|
|
1401
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-active {
|
|
1402
|
+
/* The active speaker is rendered full-size so it visually anchors
|
|
1403
|
+
* the chain. Slightly larger ring keeps it clearly separated from
|
|
1404
|
+
* the smaller upstream delegators. */
|
|
1405
|
+
width: 26px;
|
|
1406
|
+
height: 26px;
|
|
1407
|
+
}
|
|
1408
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-img {
|
|
1409
|
+
object-fit: cover;
|
|
1410
|
+
display: block;
|
|
1411
|
+
width: 100%;
|
|
1412
|
+
height: 100%;
|
|
1413
|
+
}
|
|
1414
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-fallback {
|
|
1415
|
+
color: #fff;
|
|
1416
|
+
font-size: 9.5px;
|
|
1417
|
+
font-weight: 600;
|
|
1418
|
+
background-color: var(--af-primary, #8b5cf6);
|
|
1419
|
+
}
|
|
1420
|
+
.af-widget-root.af-variant-bare .af-msg-avatar-stack-active.af-msg-avatar-stack-fallback {
|
|
1421
|
+
font-size: 11.5px;
|
|
1422
|
+
}
|
|
1232
1423
|
@media (min-width: 768px) {
|
|
1233
1424
|
.af-widget-root.af-variant-bare .af-msg-avatar { width: 30px; height: 30px; }
|
|
1234
1425
|
.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.1",
|
|
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",
|