@iloveagents/foundry-web-ui 0.11.1 → 0.12.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/components/ag-ui-runtime-provider.js +5 -1
- package/dist/components/assistant-chat.js +32 -4
- package/dist/components/chat-bubble.js +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/lib/attachment-adapter.d.ts +4 -0
- package/dist/lib/attachment-adapter.js +7 -2
- package/dist/lib/attachment-registry.d.ts +34 -0
- package/dist/lib/attachment-registry.js +34 -0
- package/package.json +3 -3
|
@@ -4,6 +4,7 @@ import { AssistantRuntimeProvider, useLocalRuntime } from "@assistant-ui/react";
|
|
|
4
4
|
import { TooltipProvider } from "../ui/tooltip.js";
|
|
5
5
|
import { AGUIAdapterSDK } from "../lib/ag-ui-adapter.js";
|
|
6
6
|
import { FileAttachmentAdapter } from "../lib/attachment-adapter.js";
|
|
7
|
+
import { useAttachmentAdapterStore } from "../lib/attachment-registry.js";
|
|
7
8
|
import { ShowDocumentToolUI } from "./show-document-tool-ui.js";
|
|
8
9
|
import { ClientToolExecutor } from "./client-tool-executor.js";
|
|
9
10
|
const AGUIAdapterContext = createContext(null);
|
|
@@ -33,7 +34,10 @@ function AGUIRuntimeInner({ children, fetchFn, threadId, urlMatch, historyAdapte
|
|
|
33
34
|
// Only pass options if at least one is set, mirroring the prior
|
|
34
35
|
// single-arg semantics for the "fresh chat, no overrides" case.
|
|
35
36
|
fetchFn || threadId ? { fetchFn, threadId } : undefined), [fetchFn, threadId]);
|
|
36
|
-
|
|
37
|
+
// Hosts may register a custom adapter (see ``registerAttachmentAdapter``);
|
|
38
|
+
// the shell's inline-base64 adapter is the default.
|
|
39
|
+
const registeredAdapter = useAttachmentAdapterStore((s) => s.adapter);
|
|
40
|
+
const attachmentAdapter = useMemo(() => registeredAdapter ?? new FileAttachmentAdapter(), [registeredAdapter]);
|
|
37
41
|
// Build the history adapter ONCE per inner mount, after the AG-UI
|
|
38
42
|
// adapter exists so we know its thread id. The keyed-remount in the
|
|
39
43
|
// outer wrapper ensures threadId changes trigger a fresh adapter +
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useRef, useCallback } from "react";
|
|
2
|
+
import { useState, useRef, useCallback, useEffect } from "react";
|
|
3
3
|
import { ThreadPrimitive, ComposerPrimitive, MessagePrimitive, ActionBarPrimitive, BranchPickerPrimitive, useAui, useAuiState, 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";
|
|
@@ -53,8 +53,22 @@ const ScrollToBottomButton = () => {
|
|
|
53
53
|
};
|
|
54
54
|
const DropZone = ({ children }) => {
|
|
55
55
|
const [isDragging, setIsDragging] = useState(false);
|
|
56
|
+
const [dropError, setDropError] = useState(null);
|
|
57
|
+
const dropErrorTimer = useRef(null);
|
|
56
58
|
const dragCounter = useRef(0);
|
|
57
59
|
const aui = useAui();
|
|
60
|
+
const showDropError = useCallback((message) => {
|
|
61
|
+
setDropError(message);
|
|
62
|
+
if (dropErrorTimer.current)
|
|
63
|
+
clearTimeout(dropErrorTimer.current);
|
|
64
|
+
dropErrorTimer.current = setTimeout(() => setDropError(null), 4000);
|
|
65
|
+
}, []);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
return () => {
|
|
68
|
+
if (dropErrorTimer.current)
|
|
69
|
+
clearTimeout(dropErrorTimer.current);
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
58
72
|
const handleDragEnter = useCallback((e) => {
|
|
59
73
|
e.preventDefault();
|
|
60
74
|
dragCounter.current++;
|
|
@@ -75,11 +89,25 @@ const DropZone = ({ children }) => {
|
|
|
75
89
|
dragCounter.current = 0;
|
|
76
90
|
setIsDragging(false);
|
|
77
91
|
const files = Array.from(e.dataTransfer.files);
|
|
92
|
+
// Drops bypass the file picker's ``accept`` filter, so the adapter's
|
|
93
|
+
// add() is the real gate here — surface its rejections instead of
|
|
94
|
+
// leaving an unhandled promise rejection and a silently missing chip.
|
|
95
|
+
const rejected = [];
|
|
78
96
|
for (const file of files) {
|
|
79
|
-
|
|
97
|
+
try {
|
|
98
|
+
await aui.composer.addAttachment(file);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
rejected.push(`${file.name}: ${err instanceof Error ? err.message : "unsupported file type"}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (rejected.length > 0) {
|
|
105
|
+
showDropError(rejected.length === 1
|
|
106
|
+
? `Couldn't attach ${rejected[0]}`
|
|
107
|
+
: `Couldn't attach ${rejected.length} files (${rejected.join("; ")})`);
|
|
80
108
|
}
|
|
81
|
-
}, [aui]);
|
|
82
|
-
return (_jsxs("div", { className: "relative flex flex-col flex-1 overflow-hidden", onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, children: [children, isDragging && (_jsx("div", { className: cn("absolute inset-0 z-50 flex items-center justify-center pointer-events-none", "bg-background/80 backdrop-blur-sm", "border-2 border-dashed border-primary rounded-xl"), children: _jsx("p", { className: "text-lg text-primary font-medium", children: "Drop files here" }) }))] }));
|
|
109
|
+
}, [aui, showDropError]);
|
|
110
|
+
return (_jsxs("div", { className: "relative flex flex-col flex-1 overflow-hidden", onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, children: [children, isDragging && (_jsx("div", { className: cn("absolute inset-0 z-50 flex items-center justify-center pointer-events-none", "bg-background/80 backdrop-blur-sm", "border-2 border-dashed border-primary rounded-xl"), children: _jsx("p", { className: "text-lg text-primary font-medium", children: "Drop files here" }) })), dropError && (_jsx("div", { role: "alert", className: cn("absolute bottom-4 left-1/2 -translate-x-1/2 z-50 max-w-[90%]", "rounded-md border border-destructive/50 bg-background px-3 py-2", "text-sm text-destructive shadow-md pointer-events-none truncate"), children: dropError }))] }));
|
|
83
111
|
};
|
|
84
112
|
/**
|
|
85
113
|
* Reusable chat content — used by ChatPage and ChatBubble.
|
|
@@ -1,7 +1,7 @@
|
|
|
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 {
|
|
4
|
+
import { Sparkles, 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";
|
|
@@ -99,9 +99,9 @@ export function ChatBubble() {
|
|
|
99
99
|
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: {
|
|
100
100
|
width: isSidebarOpen ? sidebarWidth : RAIL_WIDTH,
|
|
101
101
|
height: 64,
|
|
102
|
-
} })), !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": "
|
|
102
|
+
} })), !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": "Ask the AI", children: _jsx(Sparkles, { 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
|
|
103
103
|
? { left: dragPos.x, top: dragPos.y }
|
|
104
|
-
: { 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(
|
|
104
|
+
: { 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(Sparkles, { 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: {
|
|
105
105
|
UserMessage: BubbleUserMessage,
|
|
106
106
|
AssistantMessage: BubbleAssistantMessage,
|
|
107
107
|
} }), _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" }) }) }) })] })] }) })] })] })] }))] }));
|
package/dist/index.d.ts
CHANGED
|
@@ -65,7 +65,8 @@ export type { NavItem, NavItemAction, NavItemDnd, NavGroup, NavGroupCreateAction
|
|
|
65
65
|
export { usePageTools } from "./lib/use-page-tools.js";
|
|
66
66
|
export { useNewConversation } from "./lib/use-new-conversation.js";
|
|
67
67
|
export { AGUIAdapterSDK } from "./lib/ag-ui-adapter.js";
|
|
68
|
-
export { FileAttachmentAdapter } from "./lib/attachment-adapter.js";
|
|
68
|
+
export { FileAttachmentAdapter, resolveMimeType } from "./lib/attachment-adapter.js";
|
|
69
|
+
export { registerAttachmentAdapter, useAttachmentAdapterStore, } from "./lib/attachment-registry.js";
|
|
69
70
|
export { ReasoningPart, ReasoningMessagePartComponent } from "./components/reasoning-part.js";
|
|
70
71
|
export { ReasoningEffortPicker } from "./components/reasoning-effort-picker.js";
|
|
71
72
|
export { AuthProvider } from "./lib/auth-provider.js";
|
package/dist/index.js
CHANGED
|
@@ -73,7 +73,8 @@ export { usePageTools } from "./lib/use-page-tools.js";
|
|
|
73
73
|
export { useNewConversation } from "./lib/use-new-conversation.js";
|
|
74
74
|
// --- Adapters ---
|
|
75
75
|
export { AGUIAdapterSDK } from "./lib/ag-ui-adapter.js";
|
|
76
|
-
export { FileAttachmentAdapter } from "./lib/attachment-adapter.js";
|
|
76
|
+
export { FileAttachmentAdapter, resolveMimeType } from "./lib/attachment-adapter.js";
|
|
77
|
+
export { registerAttachmentAdapter, useAttachmentAdapterStore, } from "./lib/attachment-registry.js";
|
|
77
78
|
export { ReasoningPart, ReasoningMessagePartComponent } from "./components/reasoning-part.js";
|
|
78
79
|
export { ReasoningEffortPicker } from "./components/reasoning-effort-picker.js";
|
|
79
80
|
// --- Auth ---
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
* lastspace's `_elide_inline_media` for the pattern.
|
|
21
21
|
*/
|
|
22
22
|
import type { AttachmentAdapter, PendingAttachment, CompleteAttachment, Attachment } from "@assistant-ui/react";
|
|
23
|
+
/** Resolve a file's MIME type, falling back to extension inference when
|
|
24
|
+
* the browser reports an empty ``file.type`` (common for Office files).
|
|
25
|
+
* Exported for host attachment adapters that need the same resolution. */
|
|
26
|
+
export declare function resolveMimeType(file: File): string;
|
|
23
27
|
export interface FileAttachmentAdapterOptions {
|
|
24
28
|
/**
|
|
25
29
|
* How non-image documents (PDF, DOCX) are sent.
|
|
@@ -20,17 +20,22 @@
|
|
|
20
20
|
* lastspace's `_elide_inline_media` for the pattern.
|
|
21
21
|
*/
|
|
22
22
|
const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20 MB
|
|
23
|
-
/** Infer MIME type from extension when browser reports empty file.type (common for
|
|
23
|
+
/** Infer MIME type from extension when browser reports empty file.type (common for Office files) */
|
|
24
24
|
const EXTENSION_MIME = {
|
|
25
25
|
".pdf": "application/pdf",
|
|
26
26
|
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
27
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
28
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
27
29
|
".png": "image/png",
|
|
28
30
|
".jpg": "image/jpeg",
|
|
29
31
|
".jpeg": "image/jpeg",
|
|
30
32
|
".gif": "image/gif",
|
|
31
33
|
".webp": "image/webp",
|
|
32
34
|
};
|
|
33
|
-
|
|
35
|
+
/** Resolve a file's MIME type, falling back to extension inference when
|
|
36
|
+
* the browser reports an empty ``file.type`` (common for Office files).
|
|
37
|
+
* Exported for host attachment adapters that need the same resolution. */
|
|
38
|
+
export function resolveMimeType(file) {
|
|
34
39
|
if (file.type)
|
|
35
40
|
return file.type;
|
|
36
41
|
const ext = file.name.slice(file.name.lastIndexOf(".")).toLowerCase();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attachment adapter registry — host-injectable composer attachment handling.
|
|
3
|
+
*
|
|
4
|
+
* The shell's AG-UI runtime builds its attachment adapter internally
|
|
5
|
+
* (``FileAttachmentAdapter``), which reads files client-side and inlines
|
|
6
|
+
* them as base64 message parts. Hosts sometimes need a different
|
|
7
|
+
* strategy — e.g. uploading Office documents into a backend workspace
|
|
8
|
+
* and handing the agent a reference instead of bytes. Threading an
|
|
9
|
+
* adapter through every chat mount would couple the shell to feature
|
|
10
|
+
* modules, so this is a tiny registry instead — the exact pattern of
|
|
11
|
+
* ``registerChatSlots``: a module calls :func:`registerAttachmentAdapter`
|
|
12
|
+
* once (import time or ``useInit``) and the runtime provider picks it up
|
|
13
|
+
* on its next mount.
|
|
14
|
+
*
|
|
15
|
+
* Compose, don't replace: hosts typically wrap their special-case
|
|
16
|
+
* adapter together with the default in assistant-ui's
|
|
17
|
+
* ``CompositeAttachmentAdapter`` so images/PDF keep the stock inline
|
|
18
|
+
* behavior while selected types get the custom path. The composite also
|
|
19
|
+
* closes the drag-and-drop loophole — its ``add()`` rejects files no
|
|
20
|
+
* member adapter accepts, whereas drops bypass the file picker's
|
|
21
|
+
* ``accept`` filter.
|
|
22
|
+
*/
|
|
23
|
+
import type { AttachmentAdapter } from "@assistant-ui/react";
|
|
24
|
+
interface AttachmentAdapterState {
|
|
25
|
+
adapter: AttachmentAdapter | null;
|
|
26
|
+
}
|
|
27
|
+
export declare const useAttachmentAdapterStore: import("zustand").UseBoundStore<import("zustand").StoreApi<AttachmentAdapterState>>;
|
|
28
|
+
/**
|
|
29
|
+
* Register the composer attachment adapter. ``null`` restores the
|
|
30
|
+
* shell's default (``FileAttachmentAdapter``). Later calls overwrite
|
|
31
|
+
* earlier ones — last registration wins.
|
|
32
|
+
*/
|
|
33
|
+
export declare function registerAttachmentAdapter(adapter: AttachmentAdapter | null): void;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attachment adapter registry — host-injectable composer attachment handling.
|
|
3
|
+
*
|
|
4
|
+
* The shell's AG-UI runtime builds its attachment adapter internally
|
|
5
|
+
* (``FileAttachmentAdapter``), which reads files client-side and inlines
|
|
6
|
+
* them as base64 message parts. Hosts sometimes need a different
|
|
7
|
+
* strategy — e.g. uploading Office documents into a backend workspace
|
|
8
|
+
* and handing the agent a reference instead of bytes. Threading an
|
|
9
|
+
* adapter through every chat mount would couple the shell to feature
|
|
10
|
+
* modules, so this is a tiny registry instead — the exact pattern of
|
|
11
|
+
* ``registerChatSlots``: a module calls :func:`registerAttachmentAdapter`
|
|
12
|
+
* once (import time or ``useInit``) and the runtime provider picks it up
|
|
13
|
+
* on its next mount.
|
|
14
|
+
*
|
|
15
|
+
* Compose, don't replace: hosts typically wrap their special-case
|
|
16
|
+
* adapter together with the default in assistant-ui's
|
|
17
|
+
* ``CompositeAttachmentAdapter`` so images/PDF keep the stock inline
|
|
18
|
+
* behavior while selected types get the custom path. The composite also
|
|
19
|
+
* closes the drag-and-drop loophole — its ``add()`` rejects files no
|
|
20
|
+
* member adapter accepts, whereas drops bypass the file picker's
|
|
21
|
+
* ``accept`` filter.
|
|
22
|
+
*/
|
|
23
|
+
import { create } from "zustand";
|
|
24
|
+
export const useAttachmentAdapterStore = create(() => ({
|
|
25
|
+
adapter: null,
|
|
26
|
+
}));
|
|
27
|
+
/**
|
|
28
|
+
* Register the composer attachment adapter. ``null`` restores the
|
|
29
|
+
* shell's default (``FileAttachmentAdapter``). Later calls overwrite
|
|
30
|
+
* earlier ones — last registration wins.
|
|
31
|
+
*/
|
|
32
|
+
export function registerAttachmentAdapter(adapter) {
|
|
33
|
+
useAttachmentAdapterStore.setState({ adapter });
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iloveagents/foundry-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React agent UI core for Foundry UI — chat, composer, AG-UI adapter for assistant-ui, tool cards, panels, sidebar, theme runtime, and UI stores.",
|
|
6
6
|
"keywords": [
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"tailwind-merge": "^3.5.0",
|
|
71
71
|
"react-markdown": "^10.0.0",
|
|
72
72
|
"remark-gfm": "^4.0.0",
|
|
73
|
-
"@iloveagents/foundry-agent": "^0.
|
|
74
|
-
"@iloveagents/foundry-web-primitives": "^0.
|
|
73
|
+
"@iloveagents/foundry-agent": "^0.12.1",
|
|
74
|
+
"@iloveagents/foundry-web-primitives": "^0.12.1"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@ag-ui/client": "^0.0.52",
|