@iloveagents/foundry-web-ui 0.4.0 → 0.6.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/components/assistant-chat.js +7 -5
- package/dist/components/chat-bubble.js +7 -4
- package/dist/components/chat-slots.d.ts +37 -0
- package/dist/components/chat-slots.js +28 -0
- package/dist/components/loading-indicator.d.ts +10 -1
- package/dist/components/loading-indicator.js +16 -3
- package/dist/components/message-error.d.ts +10 -0
- package/dist/components/message-error.js +10 -0
- package/dist/components/message-timestamp.d.ts +10 -0
- package/dist/components/message-timestamp.js +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/ag-ui-adapter.js +9 -0
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useRef, useCallback } from "react";
|
|
3
|
-
import { ThreadPrimitive, ComposerPrimitive, MessagePrimitive, ActionBarPrimitive, BranchPickerPrimitive,
|
|
3
|
+
import { ThreadPrimitive, ComposerPrimitive, MessagePrimitive, ActionBarPrimitive, BranchPickerPrimitive, useAuiState, useComposerRuntime, useThreadViewport, } from "@assistant-ui/react";
|
|
4
4
|
import { ArrowUp, Copy, Check, RefreshCw, PencilIcon, ChevronLeft, ChevronRight, ChevronDown, Square, Bot, Paperclip, Minimize2, PanelRightClose, PanelRightOpen, } from "lucide-react";
|
|
5
5
|
import { cn } from "@iloveagents/foundry-web-primitives";
|
|
6
6
|
import { Button } from "@iloveagents/foundry-web-primitives";
|
|
@@ -9,22 +9,24 @@ import { userAttachmentComponents, composerAttachmentComponents } from "./chat-a
|
|
|
9
9
|
import { SentContextBadges, ComposerContextBadges } from "./context-badges.js";
|
|
10
10
|
import { ContextPins } from "./context-bar.js";
|
|
11
11
|
import { ComposerSubmitBridge } from "./composer-submit-bridge.js";
|
|
12
|
+
import { ChatComposerBannerSlot } from "./chat-slots.js";
|
|
12
13
|
import { useChatBubbleStore } from "../lib/chat-bubble-store.js";
|
|
13
14
|
import { MarkdownText } from "./markdown-text.js";
|
|
14
15
|
import { ToolFallback } from "./tool-fallback.js";
|
|
15
16
|
import { LoadingIndicator, LoadingBar } from "./loading-indicator.js";
|
|
17
|
+
import { MessageTimestamp } from "./message-timestamp.js";
|
|
18
|
+
import { MessageError } from "./message-error.js";
|
|
16
19
|
const BranchPicker = () => (_jsxs(BranchPickerPrimitive.Root, { hideWhenSingleBranch: true, className: "inline-flex items-center gap-1 text-xs", children: [_jsx(BranchPickerPrimitive.Previous, { asChild: true, children: _jsx(TooltipIconButton, { tooltip: "Previous", size: "icon", children: _jsx(ChevronLeft, { className: "size-4" }) }) }), _jsxs("span", { className: "text-muted-foreground", children: [_jsx(BranchPickerPrimitive.Number, {}), " / ", _jsx(BranchPickerPrimitive.Count, {})] }), _jsx(BranchPickerPrimitive.Next, { asChild: true, children: _jsx(TooltipIconButton, { tooltip: "Next", size: "icon", children: _jsx(ChevronRight, { className: "size-4" }) }) })] }));
|
|
17
20
|
const UserMessage = () => {
|
|
18
21
|
const messageId = useAuiState((s) => s.message.id);
|
|
19
|
-
return (_jsx(MessagePrimitive.Root, { className: "w-full mb-2 group", children: _jsxs("div", { className: "flex flex-col items-end gap-1", children: [_jsx(MessagePrimitive.Attachments, { components: userAttachmentComponents }), _jsx(SentContextBadges, { messageId: messageId }), _jsx("div", { className: cn("max-w-[80%]", "bg-muted rounded-2xl", "px-4 py-3", "text-foreground"), children: _jsx(MessagePrimitive.Content, {}) }), _jsxs("div", { className: "flex h-8 items-center justify-end gap-2 pr-2", children: [_jsx(BranchPicker, {}), _jsx("div", { className: "flex h-8 w-10 items-center justify-end", children: _jsx(UserActionBar, {}) })] })] }) }));
|
|
22
|
+
return (_jsx(MessagePrimitive.Root, { className: "w-full mb-2 group", children: _jsxs("div", { className: "flex flex-col items-end gap-1", children: [_jsx(MessagePrimitive.Attachments, { components: userAttachmentComponents }), _jsx(SentContextBadges, { messageId: messageId }), _jsx("div", { className: cn("max-w-[80%]", "bg-muted rounded-2xl", "px-4 py-3", "text-foreground"), children: _jsx(MessagePrimitive.Content, {}) }), _jsxs("div", { className: "flex h-8 items-center justify-end gap-2 pr-2", children: [_jsx(MessageTimestamp, {}), _jsx(BranchPicker, {}), _jsx("div", { className: "flex h-8 w-10 items-center justify-end", children: _jsx(UserActionBar, {}) })] })] }) }));
|
|
20
23
|
};
|
|
21
24
|
const UserActionBar = () => (_jsx(ActionBarPrimitive.Root, { hideWhenRunning: true, className: "flex items-center opacity-0 group-hover:opacity-100 transition-opacity", children: _jsx(ActionBarPrimitive.Edit, { asChild: true, children: _jsx(TooltipIconButton, { tooltip: "Edit", size: "icon", children: _jsx(PencilIcon, { className: "size-4" }) }) }) }));
|
|
22
25
|
const AssistantMessage = () => (_jsx(MessagePrimitive.Root, { className: "w-full mb-2 group", children: _jsxs("div", { className: "mx-auto max-w-3xl px-4", children: [_jsx("div", { className: "text-foreground leading-relaxed flex flex-col gap-4", children: _jsx(MessagePrimitive.Parts, { components: {
|
|
23
26
|
Text: MarkdownText,
|
|
24
27
|
tools: { Fallback: ToolFallback },
|
|
25
|
-
} }) }), _jsx(MessageError, {}), _jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx("div", { className: "flex h-8 w-[4.25rem] items-center", children: _jsx(AssistantActionBar, {}) }), _jsx(BranchPicker, {})] })] }) }));
|
|
28
|
+
} }) }), _jsx(MessageError, {}), _jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx("div", { className: "flex h-8 w-[4.25rem] items-center", children: _jsx(AssistantActionBar, {}) }), _jsx(BranchPicker, {}), _jsx(MessageTimestamp, {})] })] }) }));
|
|
26
29
|
const AssistantActionBar = () => (_jsxs(ActionBarPrimitive.Root, { hideWhenRunning: true, className: cn("pointer-events-none flex items-center gap-1 -ml-2 opacity-0 transition-opacity", "group-hover:pointer-events-auto group-hover:opacity-100", "group-focus-within:pointer-events-auto group-focus-within:opacity-100"), children: [_jsx(ActionBarPrimitive.Copy, { asChild: true, children: _jsxs(TooltipIconButton, { tooltip: "Copy", size: "icon", children: [_jsx(MessagePrimitive.If, { copied: false, children: _jsx(Copy, { className: "size-4" }) }), _jsx(MessagePrimitive.If, { copied: true, children: _jsx(Check, { className: "size-4 text-primary" }) })] }) }), _jsx(ActionBarPrimitive.Reload, { asChild: true, children: _jsx(TooltipIconButton, { tooltip: "Refresh", size: "icon", children: _jsx(RefreshCw, { className: "size-4" }) }) })] }));
|
|
27
|
-
const MessageError = () => (_jsx(MessagePrimitive.Error, { children: _jsx(ErrorPrimitive.Root, { className: "mt-2 rounded-lg border border-destructive bg-destructive/10 p-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium text-destructive", children: "Error:" }), _jsx(ErrorPrimitive.Message, { className: "text-sm text-destructive" })] }) }) }));
|
|
28
30
|
const EditComposer = () => (_jsx(MessagePrimitive.Root, { className: "w-full mb-2", children: _jsx("div", { className: "flex items-start justify-end", children: _jsx("div", { className: "w-full max-w-[80%]", children: _jsxs(ComposerPrimitive.Root, { className: cn("flex flex-col gap-2", "rounded-2xl border border-input", "bg-background shadow-sm", "px-4 py-3"), children: [_jsx(ComposerPrimitive.Input, { autoFocus: true, className: cn("w-full bg-transparent", "text-base text-foreground", "outline-none resize-none", "placeholder:text-muted-foreground") }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx(ComposerPrimitive.Cancel, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "sm", children: "Cancel" }) }), _jsx(ComposerPrimitive.Send, { asChild: true, children: _jsx(Button, { size: "sm", children: "Update" }) })] })] }) }) }) }));
|
|
29
31
|
const ComposerActionButton = () => {
|
|
30
32
|
return (_jsxs(_Fragment, { children: [_jsx(ThreadPrimitive.If, { running: true, children: _jsx(ComposerPrimitive.Cancel, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon", className: "rounded-full size-10 flex-shrink-0", "aria-label": "Stop generating", children: _jsx(Square, { className: "size-4" }) }) }) }), _jsx(ThreadPrimitive.If, { running: false, children: _jsx(ComposerPrimitive.Send, { asChild: true, children: _jsx(Button, { size: "icon", className: "rounded-full size-10 flex-shrink-0", "aria-label": "Send message", children: _jsx(ArrowUp, { className: "size-5" }) }) }) })] }));
|
|
@@ -89,5 +91,5 @@ export function ChatContent({ starterSuggestions = DEFAULT_STARTER_SUGGESTIONS,
|
|
|
89
91
|
UserMessage,
|
|
90
92
|
EditComposer,
|
|
91
93
|
AssistantMessage,
|
|
92
|
-
} }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(LoadingIndicator, {}) })] }) })] }), _jsx(ThreadPrimitive.If, { empty: false, children: _jsx(ScrollToBottomButton, {}) })] }), _jsxs("div", { className: "shrink-0 bg-background", children: [_jsx(ThreadPrimitive.If, { running: false, children: _jsx("div", { className: "border-t border-border" }) }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(LoadingBar, {}) }), _jsx("div", { className: "mx-auto max-w-3xl px-4 py-4", children: _jsxs(ComposerPrimitive.Root, { className: cn("flex flex-col gap-2", "rounded-3xl border border-input", "bg-background shadow-sm", "px-4 py-2", "focus-within:border-primary/50 focus-within:shadow-md", "transition-all duration-300"), children: [_jsx(ComposerSubmitBridge, {}), _jsx(ComposerContextBadges, {}), _jsx(ComposerPrimitive.Attachments, { components: composerAttachmentComponents }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "flex items-center -space-x-1 shrink-0", children: [_jsx(ComposerPrimitive.AddAttachment, { asChild: true, children: _jsx(TooltipIconButton, { tooltip: "Attach file", variant: "ghost", className: "shrink-0", children: _jsx(Paperclip, { className: "size-5" }) }) }), _jsx(ContextPins, { showPinButton: showPinButton })] }), _jsx(ComposerPrimitive.Input, { autoFocus: true, placeholder: "Message Assistant...", rows: 1, className: cn("flex-1 bg-transparent", "py-2 text-base", "text-foreground outline-none", "placeholder:text-muted-foreground", "resize-none max-h-32") }), _jsx(ComposerActionButton, {})] })] }) })] })] })] }));
|
|
94
|
+
} }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(LoadingIndicator, {}) })] }) })] }), _jsx(ThreadPrimitive.If, { empty: false, children: _jsx(ScrollToBottomButton, {}) })] }), _jsxs("div", { className: "shrink-0 bg-background", children: [_jsx(ChatComposerBannerSlot, {}), _jsx(ThreadPrimitive.If, { running: false, children: _jsx("div", { className: "border-t border-border" }) }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(LoadingBar, {}) }), _jsx("div", { className: "mx-auto max-w-3xl px-4 py-4", children: _jsxs(ComposerPrimitive.Root, { className: cn("flex flex-col gap-2", "rounded-3xl border border-input", "bg-background shadow-sm", "px-4 py-2", "focus-within:border-primary/50 focus-within:shadow-md", "transition-all duration-300"), children: [_jsx(ComposerSubmitBridge, {}), _jsx(ComposerContextBadges, {}), _jsx(ComposerPrimitive.Attachments, { components: composerAttachmentComponents }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "flex items-center -space-x-1 shrink-0", children: [_jsx(ComposerPrimitive.AddAttachment, { asChild: true, children: _jsx(TooltipIconButton, { tooltip: "Attach file", variant: "ghost", className: "shrink-0", children: _jsx(Paperclip, { className: "size-5" }) }) }), _jsx(ContextPins, { showPinButton: showPinButton })] }), _jsx(ComposerPrimitive.Input, { autoFocus: true, placeholder: "Message Assistant...", rows: 1, className: cn("flex-1 bg-transparent", "py-2 text-base", "text-foreground outline-none", "placeholder:text-muted-foreground", "resize-none max-h-32") }), _jsx(ComposerActionButton, {})] })] }) })] })] })] }));
|
|
93
95
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useState } from "react";
|
|
3
3
|
import { ThreadPrimitive, ComposerPrimitive, MessagePrimitive, ActionBarPrimitive, useAuiState, useThreadViewport, } from "@assistant-ui/react";
|
|
4
|
-
import { MessageSquare, X, Maximize2, SquarePen, ArrowUp, Square, ChevronDown, Copy, Check, Paperclip, } from "lucide-react";
|
|
4
|
+
import { MessageSquare, X, Maximize2, SquarePen, ArrowUp, Square, ChevronDown, Copy, Check, Paperclip, RefreshCw, } from "lucide-react";
|
|
5
5
|
import { cn } from "@iloveagents/foundry-web-primitives";
|
|
6
6
|
import { Button } from "@iloveagents/foundry-web-primitives";
|
|
7
7
|
import { TooltipIconButton } from "./tooltip-icon-button.js";
|
|
8
8
|
import { ContextPins } from "./context-bar.js";
|
|
9
9
|
import { ComposerContextBadges, SentContextBadges } from "./context-badges.js";
|
|
10
10
|
import { ComposerSubmitBridge } from "./composer-submit-bridge.js";
|
|
11
|
+
import { ChatComposerBannerSlot, ChatLauncherBadgeSlot } from "./chat-slots.js";
|
|
11
12
|
import { MarkdownText } from "./markdown-text.js";
|
|
12
13
|
import { LoadingIndicator } from "./loading-indicator.js";
|
|
14
|
+
import { MessageTimestamp } from "./message-timestamp.js";
|
|
15
|
+
import { MessageError } from "./message-error.js";
|
|
13
16
|
import { useAppStore } from "../lib/app-store.js";
|
|
14
17
|
import { useChatBubbleStore } from "../lib/chat-bubble-store.js";
|
|
15
18
|
import { useNewConversation } from "../lib/use-new-conversation.js";
|
|
@@ -18,7 +21,7 @@ const CHAT_BUBBLE_INSET = 8;
|
|
|
18
21
|
function BubbleAssistantMessage() {
|
|
19
22
|
return (_jsxs(MessagePrimitive.Root, { className: "w-full mb-2 group", children: [_jsx("div", { className: "text-foreground text-sm leading-relaxed flex flex-col gap-2", children: _jsx(MessagePrimitive.Parts, { components: {
|
|
20
23
|
Text: MarkdownText,
|
|
21
|
-
} }) }), _jsx("div", { className: "flex h-7
|
|
24
|
+
} }) }), _jsx(MessageError, { compact: true }), _jsxs("div", { className: "flex h-7 items-center gap-2 mt-1", children: [_jsxs(ActionBarPrimitive.Root, { hideWhenRunning: true, className: cn("pointer-events-none flex items-center gap-1 opacity-0 transition-opacity", "group-hover:pointer-events-auto group-hover:opacity-100", "group-focus-within:pointer-events-auto group-focus-within:opacity-100"), children: [_jsx(ActionBarPrimitive.Copy, { asChild: true, children: _jsxs(TooltipIconButton, { tooltip: "Copy", size: "icon", className: "size-7", children: [_jsx(MessagePrimitive.If, { copied: false, children: _jsx(Copy, { className: "size-3" }) }), _jsx(MessagePrimitive.If, { copied: true, children: _jsx(Check, { className: "size-3 text-primary" }) })] }) }), _jsx(ActionBarPrimitive.Reload, { asChild: true, children: _jsx(TooltipIconButton, { tooltip: "Retry", size: "icon", className: "size-7", children: _jsx(RefreshCw, { className: "size-3" }) }) })] }), _jsx(MessageTimestamp, {})] })] }));
|
|
22
25
|
}
|
|
23
26
|
function BubbleUserMessage() {
|
|
24
27
|
const messageId = useAuiState((s) => s.message.id);
|
|
@@ -94,10 +97,10 @@ export function ChatBubble() {
|
|
|
94
97
|
return (_jsxs(_Fragment, { children: [!isOpen && (_jsx("div", { "aria-hidden": "true", className: cn("pointer-events-none fixed bottom-0 left-0 z-40 hidden md:block", "border-r border-sidebar-border", "bg-gradient-to-b from-sidebar/0 via-sidebar/88 to-sidebar/96", "supports-[backdrop-filter]:backdrop-blur"), style: {
|
|
95
98
|
width: isSidebarOpen ? sidebarWidth : RAIL_WIDTH,
|
|
96
99
|
height: 64,
|
|
97
|
-
} })), !isOpen && (_jsx("button", { type: "button", onClick: openBubble, className: cn("
|
|
100
|
+
} })), !isOpen && (_jsxs("div", { className: "fixed z-50 group", style: { left: CHAT_BUBBLE_INSET, bottom: CHAT_BUBBLE_INSET }, children: [_jsx("button", { type: "button", onClick: openBubble, className: cn("size-10 rounded-full", "bg-primary text-primary-foreground shadow-lg", "flex items-center justify-center", "hover:bg-primary/90 transition-all duration-200", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"), "aria-label": "Open chat", children: _jsx(MessageSquare, { className: "size-4" }) }), _jsx(ChatLauncherBadgeSlot, {})] })), isOpen && (_jsxs("div", { ref: panelRef, className: cn("fixed z-50", "rounded-2xl border border-border", "bg-background shadow-2xl", "flex flex-col overflow-hidden", "w-95 h-150"), style: dragPos
|
|
98
101
|
? { left: dragPos.x, top: dragPos.y }
|
|
99
102
|
: { left: CHAT_BUBBLE_INSET, bottom: CHAT_BUBBLE_INSET }, children: [_jsxs("div", { className: cn("flex items-center justify-between px-4 py-2.5 border-b border-border shrink-0", "cursor-grab active:cursor-grabbing select-none"), style: { touchAction: "none" }, onPointerDown: onHeaderPointerDown, onPointerMove: onHeaderPointerMove, onPointerUp: onHeaderPointerUp, onLostPointerCapture: onHeaderLostPointerCapture, children: [_jsxs("div", { className: "flex flex-col min-w-0", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-sm", children: [_jsx(MessageSquare, { className: "size-3.5 text-primary shrink-0" }), _jsx("span", { className: "font-semibold text-foreground truncate", children: pageLabel })] }), _jsx("span", { className: "text-xs text-muted-foreground", children: threadActive ? "Active thread" : "New conversation" })] }), _jsxs("div", { className: "flex items-center gap-0.5", children: [_jsx(TooltipIconButton, { tooltip: "New Thread", size: "icon", className: "size-7", onClick: startNewThread, children: _jsx(SquarePen, { className: "size-3.5" }) }), _jsx(TooltipIconButton, { tooltip: "Expand chat", size: "icon", className: "size-7", onClick: expandChat, children: _jsx(Maximize2, { className: "size-3.5" }) }), _jsx(TooltipIconButton, { tooltip: "Close", size: "icon", className: "size-7", onClick: handleClose, children: _jsx(X, { className: "size-4" }) })] })] }), _jsxs(ThreadPrimitive.Root, { className: "flex flex-col flex-1 overflow-hidden", children: [_jsxs("div", { className: "relative flex-1 overflow-hidden", children: [_jsxs(ThreadPrimitive.Viewport, { className: "absolute inset-0 overflow-y-auto", children: [_jsx(ThreadPrimitive.Empty, { children: _jsx("div", { className: "flex h-full items-center justify-center p-4", children: _jsx("p", { className: "text-sm text-muted-foreground text-center", children: "Ask the agent about this page" }) }) }), _jsx(ThreadPrimitive.If, { empty: false, children: _jsxs("div", { className: "px-4 pt-4", children: [_jsx(ThreadPrimitive.Messages, { components: {
|
|
100
103
|
UserMessage: BubbleUserMessage,
|
|
101
104
|
AssistantMessage: BubbleAssistantMessage,
|
|
102
|
-
} }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(LoadingIndicator, {}) })] }) })] }), _jsx(ThreadPrimitive.If, { empty: false, children: _jsx(BubbleScrollToBottomButton, {}) })] }),
|
|
105
|
+
} }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(LoadingIndicator, {}) })] }) })] }), _jsx(ThreadPrimitive.If, { empty: false, children: _jsx(BubbleScrollToBottomButton, {}) })] }), _jsxs("div", { className: "shrink-0 border-t border-border", children: [_jsx(ChatComposerBannerSlot, {}), _jsx("div", { className: "px-3 py-3", children: _jsxs(ComposerPrimitive.Root, { className: cn("flex flex-col gap-1", "rounded-2xl border border-input", "bg-background shadow-sm", "px-3 py-2", "focus-within:border-primary/50", "transition-all duration-200"), children: [_jsx(ComposerSubmitBridge, {}), _jsx(ComposerContextBadges, {}), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "flex items-center -space-x-1 shrink-0", children: [_jsx(ComposerPrimitive.AddAttachment, { asChild: true, children: _jsx(TooltipIconButton, { tooltip: "Attach file", size: "icon", className: "size-7 shrink-0", children: _jsx(Paperclip, { className: "size-3.5" }) }) }), _jsx(ContextPins, { compact: true })] }), _jsx(ComposerPrimitive.Input, { autoFocus: true, placeholder: "Message...", rows: 1, className: cn("flex-1 bg-transparent", "py-1 text-sm", "text-foreground outline-none", "placeholder:text-muted-foreground", "resize-none max-h-20") }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(ComposerPrimitive.Cancel, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon", className: "rounded-full size-8 shrink-0", "aria-label": "Stop", children: _jsx(Square, { className: "size-3" }) }) }) }), _jsx(ThreadPrimitive.If, { running: false, children: _jsx(ComposerPrimitive.Send, { asChild: true, children: _jsx(Button, { size: "icon", className: "rounded-full size-8 shrink-0", "aria-label": "Send", children: _jsx(ArrowUp, { className: "size-4" }) }) }) })] })] }) })] })] })] }))] }));
|
|
103
106
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat extension slots — module-injectable UI anchored to the chat chrome.
|
|
3
|
+
*
|
|
4
|
+
* Modules (via ``bootstrapShell``) sometimes need to render inside the
|
|
5
|
+
* chat surfaces the shell owns — e.g. a background-task progress strip
|
|
6
|
+
* pinned above the composer, or an activity badge on the collapsed chat
|
|
7
|
+
* launcher. Threading those through props would couple the shell to
|
|
8
|
+
* feature modules, so this is a tiny registry instead: a module calls
|
|
9
|
+
* :func:`registerChatSlots` once (import time or ``useInit``) and the
|
|
10
|
+
* shell's chat components render the slots wherever they belong.
|
|
11
|
+
*
|
|
12
|
+
* Render points:
|
|
13
|
+
* - ``composerBanner`` — directly above the composer in BOTH the full
|
|
14
|
+
* chat (``ChatContent``) and the floating bubble panel. It renders
|
|
15
|
+
* inside a fixed (non-scrolling) footer, so it never scrolls away
|
|
16
|
+
* with the thread.
|
|
17
|
+
* - ``launcherBadge`` — inside the collapsed chat-bubble launcher's
|
|
18
|
+
* wrapper (a positioned ``group`` container around the round button),
|
|
19
|
+
* so a badge can position itself absolutely over the button and use
|
|
20
|
+
* ``group-hover:`` styles for a hover flyout.
|
|
21
|
+
*/
|
|
22
|
+
import type { ComponentType } from "react";
|
|
23
|
+
interface ChatSlotsState {
|
|
24
|
+
composerBanner: ComponentType | null;
|
|
25
|
+
launcherBadge: ComponentType | null;
|
|
26
|
+
}
|
|
27
|
+
export declare const useChatSlotsStore: import("zustand").UseBoundStore<import("zustand").StoreApi<ChatSlotsState>>;
|
|
28
|
+
/**
|
|
29
|
+
* Register chat slot components. Later calls overwrite only the keys
|
|
30
|
+
* they pass, so independent modules can each claim a different slot.
|
|
31
|
+
* Explicit ``null`` clears a slot; ``undefined`` values are ignored so
|
|
32
|
+
* an optional-spread caller can't corrupt the state shape.
|
|
33
|
+
*/
|
|
34
|
+
export declare function registerChatSlots(slots: Partial<ChatSlotsState>): void;
|
|
35
|
+
export declare function ChatComposerBannerSlot(): import("react/jsx-runtime").JSX.Element | null;
|
|
36
|
+
export declare function ChatLauncherBadgeSlot(): import("react/jsx-runtime").JSX.Element | null;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { create } from "zustand";
|
|
3
|
+
export const useChatSlotsStore = create(() => ({
|
|
4
|
+
composerBanner: null,
|
|
5
|
+
launcherBadge: null,
|
|
6
|
+
}));
|
|
7
|
+
/**
|
|
8
|
+
* Register chat slot components. Later calls overwrite only the keys
|
|
9
|
+
* they pass, so independent modules can each claim a different slot.
|
|
10
|
+
* Explicit ``null`` clears a slot; ``undefined`` values are ignored so
|
|
11
|
+
* an optional-spread caller can't corrupt the state shape.
|
|
12
|
+
*/
|
|
13
|
+
export function registerChatSlots(slots) {
|
|
14
|
+
const next = {};
|
|
15
|
+
if (slots.composerBanner !== undefined)
|
|
16
|
+
next.composerBanner = slots.composerBanner;
|
|
17
|
+
if (slots.launcherBadge !== undefined)
|
|
18
|
+
next.launcherBadge = slots.launcherBadge;
|
|
19
|
+
useChatSlotsStore.setState(next);
|
|
20
|
+
}
|
|
21
|
+
export function ChatComposerBannerSlot() {
|
|
22
|
+
const Banner = useChatSlotsStore((s) => s.composerBanner);
|
|
23
|
+
return Banner ? _jsx(Banner, {}) : null;
|
|
24
|
+
}
|
|
25
|
+
export function ChatLauncherBadgeSlot() {
|
|
26
|
+
const Badge = useChatSlotsStore((s) => s.launcherBadge);
|
|
27
|
+
return Badge ? _jsx(Badge, {}) : null;
|
|
28
|
+
}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
/** Shimmer bar shown above the composer while the agent is running. */
|
|
2
2
|
export declare function LoadingBar(): import("react/jsx-runtime").JSX.Element;
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Inline status shown in the message area while the agent is running.
|
|
5
|
+
*
|
|
6
|
+
* Visible for the whole run (assistant-ui `running` gate) with the current
|
|
7
|
+
* phase, plus a distinct amber "stalled" treatment when no event or server
|
|
8
|
+
* heartbeat has arrived for a while — so a user can always tell "still
|
|
9
|
+
* working" from "connection is dead". A dead stream is additionally
|
|
10
|
+
* surfaced as an explicit run error by the runner; this indicator covers
|
|
11
|
+
* the in-flight phases.
|
|
12
|
+
*/
|
|
4
13
|
export declare function LoadingIndicator(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -8,15 +8,28 @@ function formatToolName(name) {
|
|
|
8
8
|
export function LoadingBar() {
|
|
9
9
|
return (_jsx("div", { className: "w-full h-px bg-primary/30 overflow-hidden", children: _jsx("div", { className: "h-full w-1/4 bg-primary animate-[shimmer_1.5s_ease-in-out_infinite]" }) }));
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Inline status shown in the message area while the agent is running.
|
|
13
|
+
*
|
|
14
|
+
* Visible for the whole run (assistant-ui `running` gate) with the current
|
|
15
|
+
* phase, plus a distinct amber "stalled" treatment when no event or server
|
|
16
|
+
* heartbeat has arrived for a while — so a user can always tell "still
|
|
17
|
+
* working" from "connection is dead". A dead stream is additionally
|
|
18
|
+
* surfaced as an explicit run error by the runner; this indicator covers
|
|
19
|
+
* the in-flight phases.
|
|
20
|
+
*/
|
|
12
21
|
export function LoadingIndicator() {
|
|
13
22
|
const streaming = useStore(streamingStatusStore, (s) => s.streamingStatus);
|
|
23
|
+
const stalled = streaming.status === "stalled";
|
|
14
24
|
let label = "Thinking";
|
|
15
|
-
if (
|
|
25
|
+
if (stalled) {
|
|
26
|
+
label = "Still working — no signal from the agent";
|
|
27
|
+
}
|
|
28
|
+
else if (streaming.status === "calling" && streaming.toolName) {
|
|
16
29
|
label = `Running ${formatToolName(streaming.toolName)}`;
|
|
17
30
|
}
|
|
18
31
|
else if (streaming.status === "streaming") {
|
|
19
32
|
label = "Generating response";
|
|
20
33
|
}
|
|
21
|
-
return (_jsx("div", { className: "h-10 px-4", children: _jsxs("div", { className: "flex items-center gap-2 py-2 text-foreground leading-relaxed animate-in fade-in duration-300", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: label }), _jsxs("div", { className: "flex gap-1", children: [_jsx("span", { className:
|
|
34
|
+
return (_jsx("div", { className: "h-10 px-4", children: _jsxs("div", { className: "flex items-center gap-2 py-2 text-foreground leading-relaxed animate-in fade-in duration-300", children: [_jsx("span", { className: stalled ? "text-sm text-warning" : "text-sm text-muted-foreground", children: label }), _jsxs("div", { className: "flex gap-1", children: [_jsx("span", { className: `size-1.5 rounded-full ${stalled ? "bg-warning/70" : "bg-primary/60"} animate-[pulse_1.4s_ease-in-out_infinite]` }), _jsx("span", { className: `size-1.5 rounded-full ${stalled ? "bg-warning/70" : "bg-primary/60"} animate-[pulse_1.4s_ease-in-out_0.2s_infinite]` }), _jsx("span", { className: `size-1.5 rounded-full ${stalled ? "bg-warning/70" : "bg-primary/60"} animate-[pulse_1.4s_ease-in-out_0.4s_infinite]` })] })] }) }));
|
|
22
35
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FC } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Visible, styled run-error for an assistant message — rendered whenever the
|
|
4
|
+
* message settled with an error status (agent RUN_ERROR, lost connection,
|
|
5
|
+
* transport failure). Shared by the full-page thread and the compact chat
|
|
6
|
+
* bubble so a failed run is NEVER silent in either surface.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MessageError: FC<{
|
|
9
|
+
compact?: boolean;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { MessagePrimitive, ErrorPrimitive } from "@assistant-ui/react";
|
|
3
|
+
import { cn } from "@iloveagents/foundry-web-primitives";
|
|
4
|
+
/**
|
|
5
|
+
* Visible, styled run-error for an assistant message — rendered whenever the
|
|
6
|
+
* message settled with an error status (agent RUN_ERROR, lost connection,
|
|
7
|
+
* transport failure). Shared by the full-page thread and the compact chat
|
|
8
|
+
* bubble so a failed run is NEVER silent in either surface.
|
|
9
|
+
*/
|
|
10
|
+
export const MessageError = ({ compact }) => (_jsx(MessagePrimitive.Error, { children: _jsx(ErrorPrimitive.Root, { className: cn("mt-2 rounded-lg border border-destructive bg-destructive/10", compact ? "p-2" : "p-3"), children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: cn("font-medium text-destructive", compact ? "text-xs" : "text-sm"), children: "Error:" }), _jsx(ErrorPrimitive.Message, { className: cn("text-destructive", compact ? "text-xs" : "text-sm") })] }) }) }));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FC } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Message send/receive time — revealed on hover in the same row as the
|
|
4
|
+
* message actions (edit / copy). The short time is shown; the full date
|
|
5
|
+
* lives in the native tooltip. Shared by the full-page thread and the
|
|
6
|
+
* compact chat bubble.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MessageTimestamp: FC<{
|
|
9
|
+
className?: string;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useAuiState } from "@assistant-ui/react";
|
|
3
|
+
import { cn } from "@iloveagents/foundry-web-primitives";
|
|
4
|
+
/**
|
|
5
|
+
* Message send/receive time — revealed on hover in the same row as the
|
|
6
|
+
* message actions (edit / copy). The short time is shown; the full date
|
|
7
|
+
* lives in the native tooltip. Shared by the full-page thread and the
|
|
8
|
+
* compact chat bubble.
|
|
9
|
+
*/
|
|
10
|
+
export const MessageTimestamp = ({ className }) => {
|
|
11
|
+
const createdAt = useAuiState((s) => s.message.createdAt);
|
|
12
|
+
if (!createdAt)
|
|
13
|
+
return null;
|
|
14
|
+
const d = createdAt instanceof Date ? createdAt : new Date(createdAt);
|
|
15
|
+
if (Number.isNaN(d.getTime()))
|
|
16
|
+
return null;
|
|
17
|
+
return (_jsx("span", { title: d.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }), className: cn("text-xs text-muted-foreground/70 tabular-nums select-none", "opacity-0 transition-opacity group-hover:opacity-100", className), children: d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" }) }));
|
|
18
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type { AGUIChatConversationFactoryArgs, AGUIHistoryAdapterFactory, } from
|
|
|
8
8
|
export { ChatContent, DEFAULT_STARTER_SUGGESTIONS } from "./components/assistant-chat.js";
|
|
9
9
|
export type { ChatContentProps } from "./components/assistant-chat.js";
|
|
10
10
|
export { ChatBubble } from "./components/chat-bubble.js";
|
|
11
|
+
export { registerChatSlots, useChatSlotsStore, ChatComposerBannerSlot, ChatLauncherBadgeSlot, } from "./components/chat-slots.js";
|
|
11
12
|
export { ChatHeader } from "./components/chat-header.js";
|
|
12
13
|
export { ComposerAttachment, UserMessageAttachment, userAttachmentComponents, composerAttachmentComponents, } from "./components/chat-attachments.js";
|
|
13
14
|
export { ShowDocumentToolUI } from "./components/show-document-tool-ui.js";
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { ClientToolExecutor } from "./components/client-tool-executor.js";
|
|
|
7
7
|
export { AGUIRuntimeProvider, useAGUIAdapter } from "./components/ag-ui-runtime-provider.js";
|
|
8
8
|
export { ChatContent, DEFAULT_STARTER_SUGGESTIONS } from "./components/assistant-chat.js";
|
|
9
9
|
export { ChatBubble } from "./components/chat-bubble.js";
|
|
10
|
+
export { registerChatSlots, useChatSlotsStore, ChatComposerBannerSlot, ChatLauncherBadgeSlot, } from "./components/chat-slots.js";
|
|
10
11
|
export { ChatHeader } from "./components/chat-header.js";
|
|
11
12
|
export { ComposerAttachment, UserMessageAttachment, userAttachmentComponents, composerAttachmentComponents, } from "./components/chat-attachments.js";
|
|
12
13
|
export { ShowDocumentToolUI } from "./components/show-document-tool-ui.js";
|
|
@@ -48,6 +48,10 @@ export class AGUIAdapterSDK {
|
|
|
48
48
|
let currentText = "";
|
|
49
49
|
const toolCalls = new Map();
|
|
50
50
|
let runError = null;
|
|
51
|
+
// Liveness clock for the working indicator: every runner event (incl.
|
|
52
|
+
// server heartbeats) proves the run is alive; the indicator derives
|
|
53
|
+
// elapsed time from runStartedAt and staleness from lastSignalAt.
|
|
54
|
+
streamingStatusStore.getState().markRunStarted();
|
|
51
55
|
/**
|
|
52
56
|
* Build a `ChatModelRunResult` from the accumulated state.
|
|
53
57
|
*
|
|
@@ -103,7 +107,11 @@ export class AGUIAdapterSDK {
|
|
|
103
107
|
abortSignal,
|
|
104
108
|
})) {
|
|
105
109
|
let shouldYield = false;
|
|
110
|
+
streamingStatusStore.getState().touchSignal();
|
|
106
111
|
switch (event.type) {
|
|
112
|
+
case "heartbeat":
|
|
113
|
+
// Server liveness ping — already recorded via touchSignal above.
|
|
114
|
+
break;
|
|
107
115
|
case "request-sent":
|
|
108
116
|
if (import.meta.env.DEV) {
|
|
109
117
|
useDevStore.getState().captureRequest(event.input);
|
|
@@ -226,6 +234,7 @@ export class AGUIAdapterSDK {
|
|
|
226
234
|
}
|
|
227
235
|
finally {
|
|
228
236
|
streamingStatusStore.getState().setStreamingStatus({ status: "idle" });
|
|
237
|
+
streamingStatusStore.getState().markRunEnded();
|
|
229
238
|
}
|
|
230
239
|
}
|
|
231
240
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iloveagents/foundry-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"tailwind-merge": "^3.5.0",
|
|
54
54
|
"react-markdown": "^10.0.0",
|
|
55
55
|
"remark-gfm": "^4.0.0",
|
|
56
|
-
"@iloveagents/foundry-agent": "^0.
|
|
57
|
-
"@iloveagents/foundry-web-primitives": "^0.
|
|
56
|
+
"@iloveagents/foundry-agent": "^0.6.0",
|
|
57
|
+
"@iloveagents/foundry-web-primitives": "^0.6.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@ag-ui/client": "^0.0.52",
|