@iloveagents/foundry-web-ui 0.29.1 → 0.31.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/ag-ui-runtime-provider.d.ts +8 -3
- package/dist/components/ag-ui-runtime-provider.js +76 -33
- package/dist/components/assistant-chat.d.ts +1 -15
- package/dist/components/assistant-chat.js +4 -5
- package/dist/components/chat-bubble.js +3 -4
- package/dist/components/chat-context-items.d.ts +6 -0
- package/dist/components/chat-context-items.js +25 -0
- package/dist/components/chat-context.js +10 -2
- package/dist/components/chat-empty-state.js +1 -1
- package/dist/components/chat-header.js +2 -1
- package/dist/components/composer-add-menu.d.ts +0 -19
- package/dist/components/composer-add-menu.js +2 -10
- package/dist/components/context-badges.d.ts +0 -14
- package/dist/components/context-badges.js +2 -29
- package/dist/components/context-bar.d.ts +1 -21
- package/dist/components/context-bar.js +3 -74
- package/dist/components/focus-chat-pane.js +1 -1
- package/dist/components/markdown-text.js +6 -4
- package/dist/components/sidebar.js +51 -17
- package/dist/components/tool-panel.js +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +5 -4
- package/dist/lib/ag-ui-adapter.d.ts +3 -0
- package/dist/lib/ag-ui-adapter.js +75 -11
- package/dist/lib/auth-provider.js +1 -1
- package/dist/lib/chat-runs-store.d.ts +40 -0
- package/dist/lib/chat-runs-store.js +173 -0
- package/dist/lib/merge-chat-state.d.ts +3 -0
- package/dist/lib/merge-chat-state.js +21 -0
- package/dist/lib/nav-config.js +47 -20
- package/dist/lib/theme-runtime.d.ts +23 -0
- package/dist/lib/theme-runtime.js +210 -30
- package/dist/lib/theme-store.d.ts +3 -1
- package/dist/lib/theme-store.js +3 -2
- package/dist/lib/use-new-conversation.js +4 -8
- package/dist/styles.css +14 -0
- package/dist/workbench/chat-header.js +4 -3
- package/dist/workbench/conversation-history.d.ts +1 -1
- package/dist/workbench/conversation-history.js +75 -19
- package/dist/workbench/running-chats-menu.d.ts +3 -0
- package/dist/workbench/running-chats-menu.js +20 -0
- package/dist/workbench/selection-bridge.js +2 -5
- package/dist/workbench/use-workbench-state.d.ts +1 -2
- package/dist/workbench/use-workbench-state.js +15 -18
- package/dist/workbench/welcome-intro.js +1 -1
- package/dist/workbench/workbench-styles.js +54 -6
- package/dist/workbench/workbench.js +1 -1
- package/package.json +3 -3
|
@@ -1,23 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { PinStateIcon } from "./pin-state-icon.js";
|
|
3
|
-
/**
|
|
4
|
-
* Context Pins — pin button with count badge and hover flyout.
|
|
5
|
-
*
|
|
6
|
-
* Click: toggles pin/unpin current page.
|
|
7
|
-
* Hover (when pins exist): shows flyout to view, navigate, and remove pins.
|
|
8
|
-
*/
|
|
9
|
-
import { useState, useRef, useEffect, createElement } from "react";
|
|
10
|
-
import { useNavigate } from "react-router";
|
|
11
|
-
import { X, TextSelect, FileText, Link2, StickyNote } from "lucide-react";
|
|
12
|
-
import { cn } from "@iloveagents/foundry-web-primitives";
|
|
13
|
-
import { TooltipIconButton } from "./tooltip-icon-button.js";
|
|
14
1
|
import { useAppStore } from "../lib/app-store.js";
|
|
15
|
-
const CONTEXT_ICONS = {
|
|
16
|
-
selection: TextSelect,
|
|
17
|
-
page: FileText,
|
|
18
|
-
ref: Link2,
|
|
19
|
-
note: StickyNote,
|
|
20
|
-
};
|
|
21
2
|
/** Resolve the URL a context item navigates to when clicked.
|
|
22
3
|
*
|
|
23
4
|
* Two shapes the framework knows about:
|
|
@@ -59,15 +40,14 @@ export function getNavigablePath(item) {
|
|
|
59
40
|
return null;
|
|
60
41
|
}
|
|
61
42
|
/**
|
|
62
|
-
*
|
|
63
|
-
* pin badge here and the composer's add-menu. Two call sites deciding
|
|
64
|
-
* independently what "pinned" means is how they end up disagreeing.
|
|
43
|
+
* Pin the current navigation context from the With menu.
|
|
65
44
|
*/
|
|
66
45
|
export function usePagePin() {
|
|
67
46
|
const items = useAppStore((s) => s.contextItems);
|
|
68
47
|
const removeItem = useAppStore((s) => s.removeContextItem);
|
|
69
48
|
const addContextItem = useAppStore((s) => s.addContextItem);
|
|
70
|
-
const
|
|
49
|
+
const page = useAppStore((s) => s.currentPage);
|
|
50
|
+
const currentPage = page;
|
|
71
51
|
const pageLabel = useAppStore((s) => s.navContext.label);
|
|
72
52
|
const navMeta = useAppStore((s) => s.navContext.meta);
|
|
73
53
|
const currentPagePin = items.find((i) => i.persistence === "persistent" &&
|
|
@@ -91,54 +71,3 @@ export function usePagePin() {
|
|
|
91
71
|
},
|
|
92
72
|
};
|
|
93
73
|
}
|
|
94
|
-
export const ContextPins = ({ allowPageToggle = true, showPinButton = false, compact = false, flyoutDirection = "up", }) => {
|
|
95
|
-
const items = useAppStore((s) => s.contextItems);
|
|
96
|
-
const removeItem = useAppStore((s) => s.removeContextItem);
|
|
97
|
-
const currentPage = useAppStore((s) => s.currentPage);
|
|
98
|
-
const { isPinned: isCurrentPagePinned, toggle: handleTogglePin } = usePagePin();
|
|
99
|
-
const navigate = useNavigate();
|
|
100
|
-
const [flyoutOpen, setFlyoutOpen] = useState(false);
|
|
101
|
-
const containerRef = useRef(null);
|
|
102
|
-
const hoverTimeout = useRef(undefined);
|
|
103
|
-
const pinned = items.filter((i) => i.persistence === "persistent");
|
|
104
|
-
// Close flyout on outside click
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
if (!flyoutOpen)
|
|
107
|
-
return;
|
|
108
|
-
const handleClick = (e) => {
|
|
109
|
-
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
110
|
-
setFlyoutOpen(false);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
document.addEventListener("mousedown", handleClick);
|
|
114
|
-
return () => document.removeEventListener("mousedown", handleClick);
|
|
115
|
-
}, [flyoutOpen]);
|
|
116
|
-
const handleMouseEnter = () => {
|
|
117
|
-
clearTimeout(hoverTimeout.current);
|
|
118
|
-
if (pinned.length > 0)
|
|
119
|
-
setFlyoutOpen(true);
|
|
120
|
-
};
|
|
121
|
-
const handleMouseLeave = () => {
|
|
122
|
-
hoverTimeout.current = setTimeout(() => setFlyoutOpen(false), 200);
|
|
123
|
-
};
|
|
124
|
-
if (pinned.length === 0 && !showPinButton)
|
|
125
|
-
return null;
|
|
126
|
-
return (_jsxs("div", { className: "relative", ref: containerRef, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onFocus: handleMouseEnter, onBlur: (event) => {
|
|
127
|
-
if (!event.currentTarget.contains(event.relatedTarget))
|
|
128
|
-
setFlyoutOpen(false);
|
|
129
|
-
}, children: [_jsxs(TooltipIconButton, { tooltip: allowPageToggle
|
|
130
|
-
? isCurrentPagePinned
|
|
131
|
-
? "Unpin this page"
|
|
132
|
-
: "Pin this page"
|
|
133
|
-
: "Manage pinned context", size: "icon", className: cn("shrink-0 relative", compact ? "size-7" : "size-8", isCurrentPagePinned ? "text-foreground" : "text-muted-foreground"), "aria-pressed": allowPageToggle ? isCurrentPagePinned : undefined, "aria-expanded": !allowPageToggle ? flyoutOpen : undefined, onClick: allowPageToggle ? handleTogglePin : () => setFlyoutOpen(true), children: [_jsx(PinStateIcon, { pinned: isCurrentPagePinned, className: compact ? "size-3.5" : "size-4" }), pinned.length > 0 && (_jsx("span", { className: cn("absolute rounded-full bg-primary font-medium text-primary-foreground flex items-center justify-center", compact
|
|
134
|
-
? "-top-0.5 -right-0.5 size-3.5 text-[9px]"
|
|
135
|
-
: "top-0 right-0 size-3.5 text-[9px]"), children: pinned.length }))] }), flyoutOpen && pinned.length > 0 && (_jsxs("div", { className: cn("absolute z-50 w-64 rounded-lg border border-border bg-background shadow-xl p-2 space-y-1", flyoutDirection === "up" ? "bottom-full mb-2 left-0" : "top-9 right-0"), children: [_jsx("div", { className: "px-1 pb-1 border-b border-border/50", children: _jsx("span", { className: "text-[10px] font-medium text-muted-foreground uppercase tracking-wide", children: "Pinned Context" }) }), pinned.map((item) => {
|
|
136
|
-
const navPath = getNavigablePath(item);
|
|
137
|
-
return (_jsxs("div", { className: "flex items-center gap-1.5 px-1 py-1 rounded hover:bg-muted group/pin", children: [createElement(CONTEXT_ICONS[item.type] ?? TextSelect, {
|
|
138
|
-
className: "size-3 text-primary shrink-0",
|
|
139
|
-
}), navPath ? (_jsx("button", { type: "button", onClick: () => {
|
|
140
|
-
navigate(navPath);
|
|
141
|
-
setFlyoutOpen(false);
|
|
142
|
-
}, className: "text-xs text-foreground truncate flex-1 text-left hover:underline", children: item.label })) : (_jsx("span", { className: "text-xs text-foreground truncate flex-1", children: item.label })), _jsx("button", { type: "button", onClick: () => removeItem(item.id), className: cn("p-0.5 rounded hover:bg-primary/10 transition-opacity shrink-0", "opacity-0 group-hover/pin:opacity-100"), "aria-label": `Remove ${item.label}`, children: _jsx(X, { className: "size-3 text-muted-foreground" }) })] }, item.id));
|
|
143
|
-
})] }))] }));
|
|
144
|
-
};
|
|
@@ -8,5 +8,5 @@ import { FocusPaneResize } from "./data-table/focus-pane-resize.js";
|
|
|
8
8
|
import { ChatContent } from "./assistant-chat.js";
|
|
9
9
|
export function FocusChatPane({ chatOpen, setChatOpen, chatWidth, setChatWidth, source, onClose, }) {
|
|
10
10
|
const pageLabel = useAppStore((state) => state.navContext.label);
|
|
11
|
-
return (_jsxs("aside", { "aria-label": "Focused content chat", style: { "--focus-chat-width": `${chatWidth ?? 384}px` }, className: cn("relative flex min-h-0 shrink-0 flex-col border-r border-border bg-background", chatOpen ? "w-full md:w-[var(--focus-chat-width)]" : "w-11", source && "hidden xl:flex"), children: [_jsx("div", { className: cn("flex h-12 shrink-0 items-center", chatOpen ? "gap-1 border-b border-border px-2" : "justify-center"), children: chatOpen ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "min-w-0 flex-1 px-2 text-sm font-semibold", children: "Chat" }), _jsx(TooltipIconButton, { tooltip: "Collapse chat", "aria-label": "Collapse chat", className: "size-8", onClick: () => setChatOpen(false), children: _jsx(PanelLeft, { size: 18 }) }), _jsx(Button, { variant: "ghost", size: "icon", title: "Close", "aria-label": "Close", className: "size-8 md:hidden", onClick: () => onClose(), children: _jsx(X, { size: 18 }) })] })) : (_jsx(TooltipIconButton, { tooltip: "Expand chat", "aria-label": "Expand chat", "aria-expanded": false, className: "size-8", onClick: () => setChatOpen(true), children: _jsx(MessageSquare, { size: 18 }) })) }), chatOpen && (_jsx(FocusPaneResize, { label: "chat", side: "right", width: chatWidth, onResize: setChatWidth })), chatOpen && (_jsx(ChatContent, { emptyState: _jsx(ChatEmptyState, { pageLabel: pageLabel, homeStarterPriority: 20 }), compactComposer: true,
|
|
11
|
+
return (_jsxs("aside", { "aria-label": "Focused content chat", style: { "--focus-chat-width": `${chatWidth ?? 384}px` }, className: cn("relative flex min-h-0 shrink-0 flex-col border-r border-border bg-background", chatOpen ? "w-full md:w-[var(--focus-chat-width)]" : "w-11", source && "hidden xl:flex"), children: [_jsx("div", { className: cn("flex h-12 shrink-0 items-center", chatOpen ? "gap-1 border-b border-border px-2" : "justify-center"), children: chatOpen ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "min-w-0 flex-1 px-2 text-sm font-semibold", children: "Chat" }), _jsx(TooltipIconButton, { tooltip: "Collapse chat", "aria-label": "Collapse chat", className: "size-8", onClick: () => setChatOpen(false), children: _jsx(PanelLeft, { size: 18 }) }), _jsx(Button, { variant: "ghost", size: "icon", title: "Close", "aria-label": "Close", className: "size-8 md:hidden", onClick: () => onClose(), children: _jsx(X, { size: 18 }) })] })) : (_jsx(TooltipIconButton, { tooltip: "Expand chat", "aria-label": "Expand chat", "aria-expanded": false, className: "size-8", onClick: () => setChatOpen(true), children: _jsx(MessageSquare, { size: 18 }) })) }), chatOpen && (_jsx(FocusPaneResize, { label: "chat", side: "right", width: chatWidth, onResize: setChatWidth })), chatOpen && (_jsx(ChatContent, { emptyState: _jsx(ChatEmptyState, { pageLabel: pageLabel, homeStarterPriority: 20 }), compactComposer: true, starterSuggestions: [] }))] }));
|
|
12
12
|
}
|
|
@@ -132,10 +132,12 @@ function extractCodeInfo(children) {
|
|
|
132
132
|
* Shared markdown component overrides used by both chat messages and the tool panel.
|
|
133
133
|
*/
|
|
134
134
|
export const markdownComponents = {
|
|
135
|
-
h1: ({ children, ...props }) => (_jsx("h1", { className: "text-2xl font-bold mt-6 mb-4 text-
|
|
136
|
-
h2: ({ children, ...props }) => (_jsx("h2", { className: "text-xl font-bold mt-5 mb-3 text-
|
|
137
|
-
h3: ({ children, ...props }) => (_jsx("h3", { className: "text-lg font-semibold mt-4 mb-2 text-
|
|
138
|
-
h4: ({ children, ...props }) => (_jsx("h4", { className: "text-base font-semibold mt-3 mb-1.5 text-
|
|
135
|
+
h1: ({ children, ...props }) => (_jsx("h1", { className: "text-2xl font-bold mt-6 mb-4 text-content-heading-1", ...props, children: children })),
|
|
136
|
+
h2: ({ children, ...props }) => (_jsx("h2", { className: "text-xl font-bold mt-5 mb-3 text-content-heading-2", ...props, children: children })),
|
|
137
|
+
h3: ({ children, ...props }) => (_jsx("h3", { className: "text-lg font-semibold mt-4 mb-2 text-content-heading-3", ...props, children: children })),
|
|
138
|
+
h4: ({ children, ...props }) => (_jsx("h4", { className: "text-base font-semibold mt-3 mb-1.5 text-content-heading-4", ...props, children: children })),
|
|
139
|
+
h5: ({ children, ...props }) => (_jsx("h5", { className: "mt-3 mb-1.5 text-sm font-semibold text-content-heading-5", ...props, children: children })),
|
|
140
|
+
h6: ({ children, ...props }) => (_jsx("h6", { className: "mt-3 mb-1.5 text-xs font-semibold text-content-heading-6", ...props, children: children })),
|
|
139
141
|
p: ({ children, ...props }) => (_jsx("p", { className: "mb-2 leading-relaxed text-foreground", ...props, children: injectCitations(children) })),
|
|
140
142
|
ul: ({ children, ...props }) => (_jsx("ul", { className: "list-disc list-outside pl-6 mb-4 space-y-1 text-foreground", ...props, children: children })),
|
|
141
143
|
ol: ({ children, ...props }) => (_jsx("ol", { className: "list-decimal list-outside pl-6 mb-4 space-y-1 text-foreground", ...props, children: children })),
|
|
@@ -13,9 +13,9 @@ import { TooltipIconButton } from "./tooltip-icon-button.js";
|
|
|
13
13
|
import { Button } from "@iloveagents/foundry-web-primitives";
|
|
14
14
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "../ui/dropdown-menu.js";
|
|
15
15
|
import { AppBrand } from "./app-brand.js";
|
|
16
|
-
const ACTIVE_NAV_ROW_CLASS = "border border-transparent bg-sidebar-selected shadow-none";
|
|
17
|
-
const ACTIVE_NAV_TEXT_CLASS = "font-semibold text-sidebar-foreground";
|
|
18
|
-
const ACTIVE_NAV_ICON_CLASS = "text-
|
|
16
|
+
const ACTIVE_NAV_ROW_CLASS = "border border-transparent bg-sidebar-selected text-sidebar-selected-foreground shadow-none";
|
|
17
|
+
const ACTIVE_NAV_TEXT_CLASS = "font-semibold text-sidebar-selected-foreground";
|
|
18
|
+
const ACTIVE_NAV_ICON_CLASS = "text-sidebar-selected-foreground";
|
|
19
19
|
const ACTIVE_NAV_BADGE_CLASS = "border border-sidebar-border bg-background/60 text-sidebar-foreground";
|
|
20
20
|
// Indent ladder for the nav tree, indexed by depth (0 = top-level item).
|
|
21
21
|
//
|
|
@@ -437,13 +437,15 @@ function ContainerDropZone({ dnd }) {
|
|
|
437
437
|
function NavTwisty({ hasChildren, childrenUnloaded = false, isExpanded, onToggle, label, isActive, depth, inTree = true, }) {
|
|
438
438
|
const cell = cn(NAV_TWISTY_CLASS, getNavDepthOffsets(depth).column);
|
|
439
439
|
if (!hasChildren) {
|
|
440
|
-
return (_jsx("span", { "data-nav-twisty-cell": true, "aria-hidden": "true", className: cn(cell, "pointer-events-none"), children: inTree && !childrenUnloaded && (_jsx("span", { "data-nav-leaf-dot": true, className: cn("block size-1 shrink-0 rounded-full", isActive ? "bg-
|
|
440
|
+
return (_jsx("span", { "data-nav-twisty-cell": true, "aria-hidden": "true", className: cn(cell, "pointer-events-none"), children: inTree && !childrenUnloaded && (_jsx("span", { "data-nav-leaf-dot": true, className: cn("block size-1 shrink-0 rounded-full", isActive ? "bg-sidebar-selected-foreground/70" : "bg-sidebar-foreground/30") })) }));
|
|
441
441
|
}
|
|
442
442
|
return (_jsx("span", { "data-nav-twisty-cell": true, className: cell, children: _jsx("button", { type: "button", "data-nav-twisty": true, "aria-expanded": isExpanded, "aria-label": isExpanded ? `Collapse ${label}` : `Expand ${label}`, onClick: (e) => {
|
|
443
443
|
e.preventDefault();
|
|
444
444
|
e.stopPropagation();
|
|
445
445
|
onToggle?.();
|
|
446
|
-
}, className: cn("flex items-center justify-center rounded", NAV_TWISTY_HIT_CLASS, isActive
|
|
446
|
+
}, className: cn("flex items-center justify-center rounded", NAV_TWISTY_HIT_CLASS, isActive
|
|
447
|
+
? "text-sidebar-selected-foreground"
|
|
448
|
+
: "text-sidebar-foreground/50 hover:text-sidebar-foreground"), children: _jsx(ChevronRight, { className: cn("size-3.5 transition-transform duration-150", isExpanded && "rotate-90") }) }) }));
|
|
447
449
|
}
|
|
448
450
|
function NavItemActions({ actions }) {
|
|
449
451
|
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "size-7 rounded-md text-muted-foreground hover:bg-muted/80 hover:text-foreground", onClick: (e) => {
|
|
@@ -506,10 +508,11 @@ function NavLeafItem({ item, isActive, onClick, depth = 0, }) {
|
|
|
506
508
|
}
|
|
507
509
|
function SubNavItems({ items, parentPath, depth = 1, }) {
|
|
508
510
|
const navigate = useNavigate();
|
|
509
|
-
const
|
|
511
|
+
const location = useLocation();
|
|
512
|
+
const currentPath = location.pathname + location.search;
|
|
510
513
|
const setNavContext = useAppStore((s) => s.setNavContext);
|
|
511
514
|
const navContext = useAppStore((s) => s.navContext);
|
|
512
|
-
const isOnParent = currentPath
|
|
515
|
+
const isOnParent = matchesNavLocation(currentPath, parentPath);
|
|
513
516
|
const [activeLabel, setActiveLabel] = useState(items[0]?.label ?? "");
|
|
514
517
|
// Children with unique paths (e.g., SPACES documents) use URL matching;
|
|
515
518
|
// children sharing the parent path (e.g., Documents sub-filters) use label matching.
|
|
@@ -543,19 +546,40 @@ function SubNavItems({ items, parentPath, depth = 1, }) {
|
|
|
543
546
|
}
|
|
544
547
|
/** Check if current page is inside a nav item's subtree. */
|
|
545
548
|
function isInSubtree(currentPage, item) {
|
|
546
|
-
if (currentPage
|
|
549
|
+
if (matchesNavLocation(currentPage, item.to))
|
|
547
550
|
return true;
|
|
548
551
|
return item.children?.some((c) => isInSubtree(currentPage, c)) ?? false;
|
|
549
552
|
}
|
|
553
|
+
/** A navigation target may declare stable scope parameters while the page
|
|
554
|
+
* adds transient state such as a selected row or table sort. */
|
|
555
|
+
function matchesNavLocation(currentPage, target) {
|
|
556
|
+
const current = new URL(currentPage, "http://navigation.local");
|
|
557
|
+
const destination = new URL(target, current.origin);
|
|
558
|
+
if (current.pathname.replace(/\/+$/, "") !== destination.pathname.replace(/\/+$/, "")) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
if (destination.searchParams.size === 0)
|
|
562
|
+
return true;
|
|
563
|
+
return [...destination.searchParams].every(([key, value]) => current.searchParams.getAll(key).includes(value));
|
|
564
|
+
}
|
|
565
|
+
function hasMoreSpecificNavMatch(currentPage, target) {
|
|
566
|
+
const count = new URL(target, "http://navigation.local").searchParams.size;
|
|
567
|
+
const walk = (items) => items.some((item) => (matchesNavLocation(currentPage, item.to) &&
|
|
568
|
+
new URL(item.to, "http://navigation.local").searchParams.size > count) ||
|
|
569
|
+
walk(item.children ?? []));
|
|
570
|
+
return useNavStore.getState().config.some((group) => walk(group.items));
|
|
571
|
+
}
|
|
550
572
|
/** A list owns the sidebar selection of descendants it deliberately does not draw.
|
|
551
573
|
* The route lookup still resolves the actual descendant for chat and page actions. */
|
|
552
574
|
function isSelectedNavItem(currentPage, item) {
|
|
553
|
-
return currentPage
|
|
575
|
+
return ((matchesNavLocation(currentPage, item.to) && !hasMoreSpecificNavMatch(currentPage, item.to)) ||
|
|
576
|
+
Boolean(item.childrenHidden && isInSubtree(currentPage, item)));
|
|
554
577
|
}
|
|
555
578
|
/** Nested folder with collapsible children (used inside SubNavItems). */
|
|
556
579
|
function NestedFolderItem({ item, depth }) {
|
|
557
580
|
const navigate = useNavigate();
|
|
558
|
-
const
|
|
581
|
+
const location = useLocation();
|
|
582
|
+
const currentPath = location.pathname + location.search;
|
|
559
583
|
const setNavContext = useAppStore((s) => s.setNavContext);
|
|
560
584
|
const navContext = useAppStore((s) => s.navContext);
|
|
561
585
|
const isInsideSubtree = isInSubtree(currentPath, item);
|
|
@@ -624,7 +648,8 @@ function NestedFolderItem({ item, depth }) {
|
|
|
624
648
|
}
|
|
625
649
|
function CollapsibleNavItem({ item, inTree = true, heading = false, underHeader = false, }) {
|
|
626
650
|
const navigate = useNavigate();
|
|
627
|
-
const
|
|
651
|
+
const location = useLocation();
|
|
652
|
+
const currentPath = location.pathname + location.search;
|
|
628
653
|
const setNavContext = useAppStore((s) => s.setNavContext);
|
|
629
654
|
const navContext = useAppStore((s) => s.navContext);
|
|
630
655
|
const isInsideSubtree = isInSubtree(currentPath, item);
|
|
@@ -643,7 +668,9 @@ function CollapsibleNavItem({ item, inTree = true, heading = false, underHeader
|
|
|
643
668
|
: heading || !underHeader
|
|
644
669
|
? NAV_FLAT_BODY_CLASS
|
|
645
670
|
: getNavDepthOffsets(0).body;
|
|
646
|
-
const headingClass = heading &&
|
|
671
|
+
const headingClass = heading &&
|
|
672
|
+
!isDisabled &&
|
|
673
|
+
`font-semibold ${isActive ? "text-sidebar-selected-foreground" : "text-sidebar-primary"}`;
|
|
647
674
|
const { draggableProps, dropTargetProps, isDragOver, isFileDragOver } = useNavItemDnd(item.dnd);
|
|
648
675
|
const rendersRow = Boolean(hasChildren || hasActions);
|
|
649
676
|
const activeRowRef = useActiveNavRowScroll(isActive && rendersRow);
|
|
@@ -693,7 +720,12 @@ function CollapsibleNavItem({ item, inTree = true, heading = false, underHeader
|
|
|
693
720
|
// Transparent border for box-model parity — see the NavLink variant.
|
|
694
721
|
"rounded-lg border border-transparent pr-3 py-1.5 text-left text-sm text-sidebar-foreground/45"), children: [_jsx(NavTwisty, { hasChildren: false, childrenUnloaded: item.childrenUnloaded, inTree: rowInTree, label: item.label, depth: 0 }), _jsx(item.icon, { className: "size-4 shrink-0 text-sidebar-foreground/45" }), _jsx("span", { className: "truncate", children: item.label })] }));
|
|
695
722
|
}
|
|
696
|
-
return (_jsx(NavLink, { ref: activeLinkRef, "data-active-nav-row": isActive ? "true" : undefined, "data-nav-depth": 0, to: item.to,
|
|
723
|
+
return (_jsx(NavLink, { ref: activeLinkRef, "data-active-nav-row": isActive ? "true" : undefined, "data-nav-depth": 0, to: item.to, onClick: (event) => {
|
|
724
|
+
if (!isActive || !matchesNavLocation(currentPath, item.to))
|
|
725
|
+
return;
|
|
726
|
+
event.preventDefault();
|
|
727
|
+
useSidebarStore.getState().closeMobile();
|
|
728
|
+
}, ...draggableProps, ...dropTargetProps, className: () => cn("relative flex items-center gap-2 w-full", bodyClass,
|
|
697
729
|
// The transparent border is NOT decoration: it is what makes this
|
|
698
730
|
// row's box model match every other variant. An absolutely
|
|
699
731
|
// positioned twisty resolves against the PADDING box, so a row
|
|
@@ -766,7 +798,8 @@ function SidebarResizeHandle() {
|
|
|
766
798
|
// group is now the source of truth for "you're in a chat right now".
|
|
767
799
|
// Kept as a comment so future spelunkers know it was intentional.
|
|
768
800
|
function CollapsedNavShortcut({ to, label, icon: Icon, isActive, }) {
|
|
769
|
-
return (_jsx(NavLink, { to: to, children: _jsx(TooltipIconButton, { tooltip: label, side: "right", className: cn(isActive &&
|
|
801
|
+
return (_jsx(NavLink, { to: to, children: _jsx(TooltipIconButton, { tooltip: label, side: "right", className: cn(isActive &&
|
|
802
|
+
"border border-transparent bg-sidebar-selected text-sidebar-selected-foreground"), children: _jsx(Icon, { className: cn("size-4", isActive ? "text-sidebar-selected-foreground" : "text-sidebar-icon") }) }) }, to));
|
|
770
803
|
}
|
|
771
804
|
// ---------------------------------------------------------------------------
|
|
772
805
|
// Collapsible nav-group state — persisted per-label in localStorage so the
|
|
@@ -953,7 +986,7 @@ function NavGroupSection({ group }) {
|
|
|
953
986
|
// collapsible headers and item labels.
|
|
954
987
|
_jsxs("div", { "data-nav-group-header": true, className: cn("flex items-center justify-between py-1.5 transition-colors hover:bg-sidebar-accent/70", SIDEBAR_GROUP_HEADER_BOX, SIDEBAR_ITEM_GAP_CLASS), children: [_jsx("span", { "aria-hidden": "true", className: SIDEBAR_ITEM_ICON_CLASS }), _jsx(NavLink, { to: group.to, end: true, className: "min-w-0 flex-1", children: ({ isActive }) => (_jsx("span", { className: cn("block", SIDEBAR_GROUP_HEADER_LABEL_CLASS, isActive
|
|
955
988
|
? "text-sidebar-selected-foreground"
|
|
956
|
-
: "text-sidebar-foreground/75 hover:text-sidebar-foreground"), children: group.label })) }), group.createActions && group.createActions.length > 0 && (_jsx(CreateActionsMenu, { group: group }))] })) : (_jsxs("div", { "data-nav-group-header": true, className: cn("flex items-center", SIDEBAR_GROUP_HEADER_BOX, SIDEBAR_ITEM_GAP_CLASS), children: [_jsx("span", { "aria-hidden": "true", className: SIDEBAR_ITEM_ICON_CLASS }), _jsx("span", { className: cn("text-sidebar-foreground/75", SIDEBAR_GROUP_HEADER_LABEL_CLASS), children: group.label })] })) })), !collapsed && (_jsx("div", { className: cn(animateRows &&
|
|
989
|
+
: "text-sidebar-foreground/75 hover:text-sidebar-foreground"), children: group.label })) }), group.createActions && group.createActions.length > 0 && (_jsx(CreateActionsMenu, { group: group }))] })) : (_jsxs("div", { "data-nav-group-header": true, className: cn("flex items-center", SIDEBAR_GROUP_HEADER_BOX, SIDEBAR_ITEM_GAP_CLASS), children: [_jsx("span", { "aria-hidden": "true", className: SIDEBAR_ITEM_ICON_CLASS }), _jsx("span", { className: cn("text-sidebar-foreground/75", SIDEBAR_GROUP_HEADER_LABEL_CLASS), children: group.label })] })) })), !collapsed && (_jsx("div", { "data-nav-group-items": true, className: cn(animateRows &&
|
|
957
990
|
"motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-top-1 motion-safe:duration-200"), children: (focused ? [focusedItem] : group.items).map((item) => (_jsx(CollapsibleNavItem, { item: item,
|
|
958
991
|
// A leaf dot distinguishes "nothing under this" from a sibling
|
|
959
992
|
// that branches. Decided per GROUP: Workspaces has branches, so
|
|
@@ -996,7 +1029,8 @@ function SidebarContent({ collapsed, groups, onNewConversation, hideFooterAction
|
|
|
996
1029
|
// still resolving, so the skeleton never flashes over a populated tree.
|
|
997
1030
|
const hasWorkspacesGroup = navConfig.some((group) => group.label === "Workspaces" || group.meta?.type === "spaces-workspaces");
|
|
998
1031
|
const showNavSkeleton = navLoading && !hasWorkspacesGroup;
|
|
999
|
-
const
|
|
1032
|
+
const location = useLocation();
|
|
1033
|
+
const currentPage = location.pathname + location.search;
|
|
1000
1034
|
const navContext = useAppStore((s) => s.navContext);
|
|
1001
1035
|
useScrollActiveNavRow(currentPage, navConfig, collapsed);
|
|
1002
1036
|
const preventNativeFileDrop = useCallback((e) => {
|
|
@@ -1050,7 +1084,7 @@ function SidebarContent({ collapsed, groups, onNewConversation, hideFooterAction
|
|
|
1050
1084
|
to: it.to,
|
|
1051
1085
|
label: it.label,
|
|
1052
1086
|
icon: it.icon,
|
|
1053
|
-
isActive: currentPage
|
|
1087
|
+
isActive: isSelectedNavItem(currentPage, it),
|
|
1054
1088
|
}));
|
|
1055
1089
|
return { group, groupIndex, items };
|
|
1056
1090
|
})
|
|
@@ -141,5 +141,5 @@ export function ToolPanel({ contained = false }) {
|
|
|
141
141
|
else
|
|
142
142
|
return;
|
|
143
143
|
event.preventDefault();
|
|
144
|
-
}, className: cn("absolute left-0 top-0 z-10 h-full w-2 -translate-x-1/2", "cursor-col-resize touch-none", "before:absolute before:inset-y-0 before:left-1/2 before:w-px before:-translate-x-1/2", "before:bg-transparent hover:before:bg-primary/40 focus-visible:before:bg-primary", "focus:outline-none") })) : null, _jsx("div", { className: "flex-shrink-0 border-b border-border px-6 py-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h2", { className: "text-lg font-semibold text-
|
|
144
|
+
}, className: cn("absolute left-0 top-0 z-10 h-full w-2 -translate-x-1/2", "cursor-col-resize touch-none", "before:absolute before:inset-y-0 before:left-1/2 before:w-px before:-translate-x-1/2", "before:bg-transparent hover:before:bg-primary/40 focus-visible:before:bg-primary", "focus:outline-none") })) : null, _jsx("div", { className: "flex-shrink-0 border-b border-border px-6 py-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h2", { className: "text-lg font-semibold text-heading truncate pr-4", children: content.title }), _jsxs("div", { className: "flex items-center gap-1", children: [entityId && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: handleToggleEntityPin, className: cn("flex items-center justify-center", "size-8 rounded-md shrink-0", "hover:bg-muted transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", isEntityPinned ? "text-foreground" : "text-muted-foreground"), "aria-pressed": isEntityPinned, "aria-label": isEntityPinned ? "Unpin document" : "Pin document", title: isEntityPinned ? "Unpin document" : "Pin document", children: _jsx(PinStateIcon, { pinned: isEntityPinned }) }), _jsx("button", { type: "button", onClick: handleNavigateToEntity, className: cn("flex items-center justify-center", "size-8 rounded-md shrink-0", "hover:bg-muted transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"), "aria-label": "Open in navigation", title: "Open in navigation", children: _jsx(FolderOpen, { className: "size-4 text-muted-foreground" }) })] })), _jsx("button", { type: "button", onClick: handleCopyAll, className: cn("flex items-center justify-center", "size-8 rounded-md flex-shrink-0", "hover:bg-muted transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"), "aria-label": "Copy content", children: copied ? (_jsx(Check, { className: "size-4 text-success" })) : (_jsx(Copy, { className: "size-4 text-muted-foreground" })) }), !contained && (_jsx("button", { type: "button", onClick: toggleFullscreen, className: cn("flex items-center justify-center", "size-8 rounded-md flex-shrink-0", "hover:bg-muted transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"), "aria-label": isFullscreen ? "Exit fullscreen" : "Fullscreen", children: isFullscreen ? (_jsx(Minimize2, { className: "size-4 text-muted-foreground" })) : (_jsx(Maximize2, { className: "size-4 text-muted-foreground" })) })), _jsx("button", { type: "button", onClick: closePanel, className: cn("flex items-center justify-center", "size-8 rounded-md flex-shrink-0", "hover:bg-muted transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"), "aria-label": "Close panel", children: _jsx(X, { className: "size-5 text-muted-foreground" }) })] })] }) }), _jsxs("div", { className: "relative flex-1 overflow-y-auto", ref: contentRef, "data-testid": "tool-panel-content", children: [_jsx(PanelContentRenderer, { content: content }), _jsx(SelectionPopover, { containerRef: contentRef })] })] }));
|
|
145
145
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { ToolPanel } from "./components/tool-panel.js";
|
|
|
4
4
|
export { ToolPanelLayout } from "./components/tool-panel-layout.js";
|
|
5
5
|
export { MarkdownText, markdownComponents } from "./components/markdown-text.js";
|
|
6
6
|
export { ClientToolExecutor } from "./components/client-tool-executor.js";
|
|
7
|
-
export { AGUIRuntimeProvider, useAGUIAdapter } from "./components/ag-ui-runtime-provider.js";
|
|
7
|
+
export { AGUIRuntimeProvider, useAGUIAdapter, useIsForegroundChat, } from "./components/ag-ui-runtime-provider.js";
|
|
8
8
|
export type { AGUIChatConversationFactoryArgs, AGUIHistoryAdapterFactory, } from "./components/ag-ui-runtime-provider.js";
|
|
9
9
|
export { ChatContent, DEFAULT_STARTER_SUGGESTIONS, DEFAULT_COMPOSER_PLACEHOLDER, } from "./components/assistant-chat.js";
|
|
10
10
|
export { ComposerAddMenu, type ComposerAddMenuProps } from "./components/composer-add-menu.js";
|
|
@@ -19,8 +19,8 @@ export type { ToolCallStatus, ToolCallCardProps } from "./components/tool-call-c
|
|
|
19
19
|
export { ToolFallback } from "./components/tool-fallback.js";
|
|
20
20
|
export { ConfirmationCard } from "./components/confirmation-card.js";
|
|
21
21
|
export { TooltipIconButton } from "./components/tooltip-icon-button.js";
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
22
|
+
export { SentContextBadges } from "./components/context-badges.js";
|
|
23
|
+
export { usePagePin } from "./components/context-bar.js";
|
|
24
24
|
export { SelectionPopover } from "./components/selection-popover.js";
|
|
25
25
|
export { GlobalSelectionPopover } from "./components/global-selection-popover.js";
|
|
26
26
|
export { LoadingIndicator } from "./components/loading-indicator.js";
|
|
@@ -76,8 +76,8 @@ export { submitComposerText } from "./lib/composer-submit-store.js";
|
|
|
76
76
|
export { registerSelectionContextResolver, resolveSelectionContext, type SelectionContextInput, type SelectionContextItem, type SelectionContextResolver, type SelectionContextResult, } from "./lib/selection-context.js";
|
|
77
77
|
export { useThemeStore } from "./lib/theme-store.js";
|
|
78
78
|
export type { ThemeMode } from "./lib/theme-store.js";
|
|
79
|
-
export { mergeThemeDefinitions, resolveThemeRuntime, getEffectiveThemeMode, getThemePreset, } from "./lib/theme-runtime.js";
|
|
80
|
-
export type { ThemeDefinition, ThemeLayer, ResolvedThemeRuntime, ThemeColorSlots, ThemeSidebarSlots, ThemeStatusSlots, ThemeChartSlots, ThemeTypographySlots, ThemeRadiusSlots, ThemeAppSlots, ThemeBranding, } from "./lib/theme-runtime.js";
|
|
79
|
+
export { THEME_SLOTS, mergeThemeDefinitions, resolveThemeRuntime, getEffectiveThemeMode, getThemePreset, } from "./lib/theme-runtime.js";
|
|
80
|
+
export type { ThemeSlot, ThemeDefinition, ThemeLayer, ResolvedThemeRuntime, ThemeColorSlots, ThemeSidebarSlots, ThemeStatusSlots, ThemeChartSlots, ThemeTypographySlots, ThemeRadiusSlots, ThemeAppSlots, ThemeBranding, } from "./lib/theme-runtime.js";
|
|
81
81
|
export { useDevStore } from "./lib/dev-store.js";
|
|
82
82
|
export { DEFAULT_NAV_CONFIG, findNavItem } from "./lib/nav-config.js";
|
|
83
83
|
export type { NavItem, NavItemAction, NavItemDnd, NavGroup, NavGroupCreateAction, NavMatch, CollapsedRailItem, CollapsedRailContext, } from "./lib/nav-config.js";
|
|
@@ -91,3 +91,4 @@ export { ReasoningPart, ReasoningMessagePartComponent } from "./components/reaso
|
|
|
91
91
|
export { ReasoningEffortPicker } from "./components/reasoning-effort-picker.js";
|
|
92
92
|
export { AuthProvider } from "./lib/auth-provider.js";
|
|
93
93
|
export { createUiId } from "./lib/create-ui-id.js";
|
|
94
|
+
export { useChatRuns, isChatRunActive, removeChatRun, type ChatRun, } from "./lib/chat-runs-store.js";
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { ToolPanel } from "./components/tool-panel.js";
|
|
|
5
5
|
export { ToolPanelLayout } from "./components/tool-panel-layout.js";
|
|
6
6
|
export { MarkdownText, markdownComponents } from "./components/markdown-text.js";
|
|
7
7
|
export { ClientToolExecutor } from "./components/client-tool-executor.js";
|
|
8
|
-
export { AGUIRuntimeProvider, useAGUIAdapter } from "./components/ag-ui-runtime-provider.js";
|
|
8
|
+
export { AGUIRuntimeProvider, useAGUIAdapter, useIsForegroundChat, } from "./components/ag-ui-runtime-provider.js";
|
|
9
9
|
export { ChatContent, DEFAULT_STARTER_SUGGESTIONS, DEFAULT_COMPOSER_PLACEHOLDER, } from "./components/assistant-chat.js";
|
|
10
10
|
export { ComposerAddMenu } from "./components/composer-add-menu.js";
|
|
11
11
|
export { ChatBubble } from "./components/chat-bubble.js";
|
|
@@ -17,8 +17,8 @@ export { ToolCallCard } from "./components/tool-call-card.js";
|
|
|
17
17
|
export { ToolFallback } from "./components/tool-fallback.js";
|
|
18
18
|
export { ConfirmationCard } from "./components/confirmation-card.js";
|
|
19
19
|
export { TooltipIconButton } from "./components/tooltip-icon-button.js";
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
20
|
+
export { SentContextBadges } from "./components/context-badges.js";
|
|
21
|
+
export { usePagePin } from "./components/context-bar.js";
|
|
22
22
|
export { SelectionPopover } from "./components/selection-popover.js";
|
|
23
23
|
export { GlobalSelectionPopover } from "./components/global-selection-popover.js";
|
|
24
24
|
export { LoadingIndicator } from "./components/loading-indicator.js";
|
|
@@ -79,7 +79,7 @@ export { useChatLifecycleStore } from "./lib/chat-lifecycle-store.js";
|
|
|
79
79
|
export { submitComposerText } from "./lib/composer-submit-store.js";
|
|
80
80
|
export { registerSelectionContextResolver, resolveSelectionContext, } from "./lib/selection-context.js";
|
|
81
81
|
export { useThemeStore } from "./lib/theme-store.js";
|
|
82
|
-
export { mergeThemeDefinitions, resolveThemeRuntime, getEffectiveThemeMode, getThemePreset, } from "./lib/theme-runtime.js";
|
|
82
|
+
export { THEME_SLOTS, mergeThemeDefinitions, resolveThemeRuntime, getEffectiveThemeMode, getThemePreset, } from "./lib/theme-runtime.js";
|
|
83
83
|
// Stores moved to @iloveagents/foundry-agent (#95): authStore, citationStore,
|
|
84
84
|
// streamingStatusStore, clientToolRegistry. Consumers import directly from
|
|
85
85
|
// `@iloveagents/foundry-agent` / `@iloveagents/foundry-agent/msal` — no re-exports here per
|
|
@@ -106,3 +106,4 @@ export { AuthProvider } from "./lib/auth-provider.js";
|
|
|
106
106
|
// Re-exporting here would create two canonical sources for the same
|
|
107
107
|
// utility — banned per the "no shims" pre-1.0 rule (#90).
|
|
108
108
|
export { createUiId } from "./lib/create-ui-id.js";
|
|
109
|
+
export { useChatRuns, isChatRunActive, removeChatRun, } from "./lib/chat-runs-store.js";
|
|
@@ -19,9 +19,12 @@
|
|
|
19
19
|
import type { ChatModelAdapter, ChatModelRunOptions, ChatModelRunResult } from "@assistant-ui/react";
|
|
20
20
|
export declare class AGUIAdapterSDK implements ChatModelAdapter {
|
|
21
21
|
private readonly runner;
|
|
22
|
+
private previousAppState?;
|
|
23
|
+
private readonly conversationUrl?;
|
|
22
24
|
constructor(url?: string, options?: {
|
|
23
25
|
threadId?: string;
|
|
24
26
|
fetchFn?: typeof fetch;
|
|
27
|
+
conversationUrl?: string;
|
|
25
28
|
});
|
|
26
29
|
get threadId(): string;
|
|
27
30
|
get state(): unknown;
|
|
@@ -16,12 +16,15 @@
|
|
|
16
16
|
* @see https://docs.ag-ui.com/sdk/js/client/http-agent
|
|
17
17
|
* @see https://www.assistant-ui.com/docs/runtimes/custom/local
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
19
|
+
import { mergeChatState } from "./merge-chat-state.js";
|
|
20
|
+
import { AGUIRunner, clientToolRegistry } from "@iloveagents/foundry-agent";
|
|
20
21
|
import { tokenFetch } from "@iloveagents/foundry-agent/msal";
|
|
21
22
|
import { useAppStore } from "./app-store.js";
|
|
22
23
|
import { useDevStore } from "./dev-store.js";
|
|
24
|
+
import { publishChatAgentState, beginChatRun, touchChatRun, updateChatRun, getChatStateSnapshot, useChatRuns, retainChatProjection, waitForForegroundChat, } from "./chat-runs-store.js";
|
|
23
25
|
export class AGUIAdapterSDK {
|
|
24
26
|
constructor(url = "/api/agent", options) {
|
|
27
|
+
this.conversationUrl = options?.conversationUrl;
|
|
25
28
|
this.runner = new AGUIRunner({
|
|
26
29
|
url,
|
|
27
30
|
threadId: options?.threadId,
|
|
@@ -35,11 +38,22 @@ export class AGUIAdapterSDK {
|
|
|
35
38
|
return this.runner.state;
|
|
36
39
|
}
|
|
37
40
|
async *run({ messages, abortSignal }) {
|
|
41
|
+
if (useChatRuns.getState().deletedThreads[this.threadId]) {
|
|
42
|
+
throw new Error("This conversation was deleted. Start a new chat to continue.");
|
|
43
|
+
}
|
|
38
44
|
const aguiMessages = convertMessagesToAGUI(messages);
|
|
39
45
|
// App-level lifecycle: mark thread active, snapshot context, consume
|
|
40
46
|
// ephemeral context off the last user message.
|
|
41
47
|
useAppStore.getState().markThreadActive();
|
|
42
48
|
const appState = useAppStore.getState().getAgentState();
|
|
49
|
+
const projection = appState;
|
|
50
|
+
const retained = getChatStateSnapshot(this.threadId);
|
|
51
|
+
const serverState = retained.state ?? this.runner.state;
|
|
52
|
+
const requestState = mergeChatState(serverState && typeof serverState === "object"
|
|
53
|
+
? serverState
|
|
54
|
+
: {}, this.previousAppState ?? retained.projection, projection);
|
|
55
|
+
this.previousAppState = structuredClone(projection);
|
|
56
|
+
retainChatProjection(this.threadId, projection);
|
|
43
57
|
const context = buildAGUIContext(appState);
|
|
44
58
|
const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
|
|
45
59
|
if (lastUserMsg) {
|
|
@@ -55,7 +69,25 @@ export class AGUIAdapterSDK {
|
|
|
55
69
|
// Liveness clock for the working indicator: every runner event (incl.
|
|
56
70
|
// server heartbeats) proves the run is alive; the indicator derives
|
|
57
71
|
// elapsed time from runStartedAt and staleness from lastSignalAt.
|
|
58
|
-
|
|
72
|
+
const stopController = new AbortController();
|
|
73
|
+
const runSignal = AbortSignal.any([abortSignal, stopController.signal]);
|
|
74
|
+
beginChatRun({
|
|
75
|
+
threadId: this.threadId,
|
|
76
|
+
conversationUrl: this.conversationUrl,
|
|
77
|
+
title: lastUserMsg?.content
|
|
78
|
+
.filter((p) => p.type === "text")
|
|
79
|
+
.map((p) => p.text)
|
|
80
|
+
.join(" ")
|
|
81
|
+
.slice(0, 120) || "Conversation",
|
|
82
|
+
pageLabel: useAppStore.getState().navContext.label ?? undefined,
|
|
83
|
+
contextMeta: { ...useAppStore.getState().navContext.meta },
|
|
84
|
+
stop: () => stopController.abort(),
|
|
85
|
+
});
|
|
86
|
+
const registry = clientToolRegistry.getState();
|
|
87
|
+
const pageOwners = new Map(registry.listPageTools().map((tool) => [tool.name, tool]));
|
|
88
|
+
const ownerLocation = typeof window === "undefined" ? "" : window.location.href;
|
|
89
|
+
const schemas = registry.getActiveSchemas();
|
|
90
|
+
const advertised = new Set(schemas.map((tool) => tool.name));
|
|
59
91
|
/**
|
|
60
92
|
* Build a `ChatModelRunResult` from the accumulated state.
|
|
61
93
|
*
|
|
@@ -110,13 +142,35 @@ export class AGUIAdapterSDK {
|
|
|
110
142
|
try {
|
|
111
143
|
for await (const event of this.runner.run({
|
|
112
144
|
messages: aguiMessages,
|
|
113
|
-
state:
|
|
145
|
+
state: requestState,
|
|
114
146
|
context,
|
|
115
|
-
registry:
|
|
116
|
-
|
|
147
|
+
registry: {
|
|
148
|
+
...registry,
|
|
149
|
+
getActiveSchemas: () => schemas,
|
|
150
|
+
isRegistered: (name) => advertised.has(name),
|
|
151
|
+
executeTool: async (name, args) => {
|
|
152
|
+
await waitForForegroundChat(this.threadId, runSignal);
|
|
153
|
+
runSignal.throwIfAborted();
|
|
154
|
+
const owner = pageOwners.get(name);
|
|
155
|
+
if (owner) {
|
|
156
|
+
const current = clientToolRegistry
|
|
157
|
+
.getState()
|
|
158
|
+
.listPageTools()
|
|
159
|
+
.find((tool) => tool.name === name);
|
|
160
|
+
if (current !== owner ||
|
|
161
|
+
(typeof window !== "undefined" && window.location.href !== ownerLocation)) {
|
|
162
|
+
return JSON.stringify({
|
|
163
|
+
error: "The page changed since this action was requested. Open the original page and request the action again.",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return clientToolRegistry.getState().executeTool(name, args);
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
abortSignal: runSignal,
|
|
117
171
|
})) {
|
|
118
172
|
let shouldYield = false;
|
|
119
|
-
|
|
173
|
+
touchChatRun(this.threadId);
|
|
120
174
|
switch (event.type) {
|
|
121
175
|
case "heartbeat":
|
|
122
176
|
// Server liveness ping — already recorded via touchSignal above.
|
|
@@ -146,7 +200,7 @@ export class AGUIAdapterSDK {
|
|
|
146
200
|
shouldYield = true;
|
|
147
201
|
break;
|
|
148
202
|
case "streaming-status":
|
|
149
|
-
|
|
203
|
+
touchChatRun(this.threadId, event.status);
|
|
150
204
|
break;
|
|
151
205
|
case "text-delta":
|
|
152
206
|
currentText += event.delta;
|
|
@@ -162,7 +216,7 @@ export class AGUIAdapterSDK {
|
|
|
162
216
|
case "agent-state":
|
|
163
217
|
// Publish server state so pages can react to it. Not a chat
|
|
164
218
|
// message, so no yield.
|
|
165
|
-
|
|
219
|
+
publishChatAgentState(this.threadId, event.state, event.patch);
|
|
166
220
|
break;
|
|
167
221
|
case "messages-snapshot":
|
|
168
222
|
case "activity":
|
|
@@ -256,7 +310,10 @@ export class AGUIAdapterSDK {
|
|
|
256
310
|
// not a silent empty bubble. We yield the terminal status rather
|
|
257
311
|
// than throwing; an unhandled throw re-introduces the stuck-running
|
|
258
312
|
// bug (the runner generator has already returned here).
|
|
259
|
-
if (
|
|
313
|
+
if (runSignal.aborted) {
|
|
314
|
+
yield buildResult({ type: "incomplete", reason: "cancelled" });
|
|
315
|
+
}
|
|
316
|
+
else if (runError !== null) {
|
|
260
317
|
yield buildResult({
|
|
261
318
|
type: "incomplete",
|
|
262
319
|
reason: "error",
|
|
@@ -267,9 +324,16 @@ export class AGUIAdapterSDK {
|
|
|
267
324
|
yield buildResult({ type: "complete", reason: "stop" });
|
|
268
325
|
}
|
|
269
326
|
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
if (runSignal.aborted)
|
|
329
|
+
yield buildResult({ type: "incomplete", reason: "cancelled" });
|
|
330
|
+
else {
|
|
331
|
+
runError = error instanceof Error ? error.message : String(error);
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
270
335
|
finally {
|
|
271
|
-
|
|
272
|
-
streamingStatusStore.getState().markRunEnded();
|
|
336
|
+
updateChatRun(this.threadId, runSignal.aborted ? "cancelled" : runError !== null ? "failed" : "completed", runError ?? undefined);
|
|
273
337
|
}
|
|
274
338
|
}
|
|
275
339
|
}
|
|
@@ -42,7 +42,7 @@ export const AuthProvider = ({ config, children }) => {
|
|
|
42
42
|
if (!ready)
|
|
43
43
|
return null;
|
|
44
44
|
if (initError) {
|
|
45
|
-
return (_jsx("div", { className: "flex min-h-dvh items-center justify-center bg-background px-6 text-center", children: _jsxs("div", { className: "max-w-md rounded-2xl border border-border bg-card px-6 py-5 shadow-sm", children: [_jsx("h1", { className: "text-lg font-semibold text-
|
|
45
|
+
return (_jsx("div", { className: "flex min-h-dvh items-center justify-center bg-background px-6 text-center", children: _jsxs("div", { className: "max-w-md rounded-2xl border border-border bg-card px-6 py-5 shadow-sm", children: [_jsx("h1", { className: "text-lg font-semibold text-heading", children: "Authentication unavailable" }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: initError })] }) }));
|
|
46
46
|
}
|
|
47
47
|
if (!MsalProvider || !useMsalHook) {
|
|
48
48
|
return null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type StreamingStatus } from "@iloveagents/foundry-agent";
|
|
2
|
+
export type ChatRunStatus = "running" | "waiting" | "completed" | "failed" | "cancelled";
|
|
3
|
+
export interface ChatRun {
|
|
4
|
+
threadId: string;
|
|
5
|
+
conversationUrl?: string;
|
|
6
|
+
title: string;
|
|
7
|
+
pageLabel?: string;
|
|
8
|
+
/** Navigation snapshot at run start, interpreted by the owning feature module. */
|
|
9
|
+
contextMeta?: Record<string, unknown>;
|
|
10
|
+
status: ChatRunStatus;
|
|
11
|
+
startedAt: number;
|
|
12
|
+
finishedAt?: number;
|
|
13
|
+
error?: string;
|
|
14
|
+
stop: () => void;
|
|
15
|
+
}
|
|
16
|
+
export declare const isChatRunActive: (run: ChatRun) => boolean;
|
|
17
|
+
/** Browser-session activity. Message persistence remains owned by the history adapter. */
|
|
18
|
+
export declare const useChatRuns: import("zustand").UseBoundStore<import("zustand").StoreApi<{
|
|
19
|
+
foregroundId: string | null;
|
|
20
|
+
runs: Record<string, ChatRun>;
|
|
21
|
+
deletedThreads: Record<string, true>;
|
|
22
|
+
}>>;
|
|
23
|
+
export declare function getChatStateSnapshot(threadId: string): {
|
|
24
|
+
state: unknown;
|
|
25
|
+
projection: Record<string, unknown> | undefined;
|
|
26
|
+
};
|
|
27
|
+
export declare function retainChatProjection(threadId: string, projection: Record<string, unknown>): void;
|
|
28
|
+
export declare function publishChatAgentState(threadId: string, state: unknown, patch?: unknown[]): void;
|
|
29
|
+
export declare function isForegroundChat(threadId: string): boolean;
|
|
30
|
+
export declare function selectChatRun(threadId: string): void;
|
|
31
|
+
/** Call after a successful conversation deletion so session activity cannot restore it. */
|
|
32
|
+
export declare function removeChatRun(threadId: string): void;
|
|
33
|
+
export declare function beginChatRun(run: Omit<ChatRun, "status" | "startedAt">): void;
|
|
34
|
+
export declare function touchChatRun(threadId: string, status?: StreamingStatus): void;
|
|
35
|
+
export declare function updateChatRun(threadId: string, status: ChatRunStatus, error?: string): void;
|
|
36
|
+
/** Browser tools need the conversation in front, so they cannot act on another chat's page. */
|
|
37
|
+
export declare function waitForForegroundChat(threadId: string, signal: AbortSignal): Promise<void>;
|
|
38
|
+
export declare function warnBeforeChatUnload(event: BeforeUnloadEvent): void;
|
|
39
|
+
/** Dispose browser-owned runs when the authenticated runtime is torn down. */
|
|
40
|
+
export declare function disposeChatRuns(): void;
|