@iloveagents/foundry-web-ui 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/README.md +13 -4
  2. package/dist/components/ag-ui-runtime-provider.d.ts +64 -0
  3. package/dist/components/ag-ui-runtime-provider.js +71 -0
  4. package/dist/components/app-brand.d.ts +6 -0
  5. package/dist/components/app-brand.js +13 -0
  6. package/dist/components/assistant-chat.d.ts +26 -0
  7. package/dist/components/assistant-chat.js +94 -0
  8. package/dist/components/chat-attachments.d.ts +25 -0
  9. package/dist/components/chat-attachments.js +20 -0
  10. package/dist/components/chat-bubble.d.ts +1 -0
  11. package/dist/components/chat-bubble.js +105 -0
  12. package/dist/components/chat-header.d.ts +2 -0
  13. package/dist/components/chat-header.js +16 -0
  14. package/dist/components/client-tool-executor.d.ts +12 -0
  15. package/dist/components/client-tool-executor.js +108 -0
  16. package/dist/components/collection-empty-state.d.ts +9 -0
  17. package/dist/components/collection-empty-state.js +6 -0
  18. package/dist/components/collection-filter-bar.d.ts +19 -0
  19. package/dist/components/collection-filter-bar.js +28 -0
  20. package/dist/components/collection-search-input.d.ts +6 -0
  21. package/dist/components/collection-search-input.js +8 -0
  22. package/dist/components/collection-skeleton.d.ts +6 -0
  23. package/dist/components/collection-skeleton.js +10 -0
  24. package/dist/components/collection-sort-menu.d.ts +10 -0
  25. package/dist/components/collection-sort-menu.js +9 -0
  26. package/dist/components/collection-surface.d.ts +8 -0
  27. package/dist/components/collection-surface.js +5 -0
  28. package/dist/components/collection-toolbar.d.ts +9 -0
  29. package/dist/components/collection-toolbar.js +5 -0
  30. package/dist/components/collection-view-toggle.d.ts +7 -0
  31. package/dist/components/collection-view-toggle.js +17 -0
  32. package/dist/components/composer-submit-bridge.d.ts +1 -0
  33. package/dist/components/composer-submit-bridge.js +15 -0
  34. package/dist/components/confirm-dialog.d.ts +11 -0
  35. package/dist/components/confirm-dialog.js +6 -0
  36. package/dist/components/confirmation-card.d.ts +14 -0
  37. package/dist/components/confirmation-card.js +12 -0
  38. package/dist/components/context-badges.d.ts +16 -0
  39. package/dist/components/context-badges.js +56 -0
  40. package/dist/components/context-bar.d.ts +41 -0
  41. package/dist/components/context-bar.js +123 -0
  42. package/dist/components/form-dialog.d.ts +12 -0
  43. package/dist/components/form-dialog.js +13 -0
  44. package/dist/components/global-selection-popover.d.ts +7 -0
  45. package/dist/components/global-selection-popover.js +167 -0
  46. package/dist/components/infinite-scroll-sentinel.d.ts +6 -0
  47. package/dist/components/infinite-scroll-sentinel.js +25 -0
  48. package/dist/components/json-viewer.d.ts +10 -0
  49. package/dist/components/json-viewer.js +80 -0
  50. package/dist/components/loading-indicator.d.ts +13 -0
  51. package/dist/components/loading-indicator.js +35 -0
  52. package/dist/components/markdown-text.d.ts +13 -0
  53. package/dist/components/markdown-text.js +221 -0
  54. package/dist/components/message-error.d.ts +10 -0
  55. package/dist/components/message-error.js +10 -0
  56. package/dist/components/message-timestamp.d.ts +10 -0
  57. package/dist/components/message-timestamp.js +18 -0
  58. package/dist/components/name-dialog.d.ts +16 -0
  59. package/dist/components/name-dialog.js +23 -0
  60. package/dist/components/selection-popover.d.ts +12 -0
  61. package/dist/components/selection-popover.js +111 -0
  62. package/dist/components/show-document-tool-ui.d.ts +1 -0
  63. package/dist/components/show-document-tool-ui.js +53 -0
  64. package/dist/components/sidebar.d.ts +4 -0
  65. package/dist/components/sidebar.js +904 -0
  66. package/dist/components/surface-card.d.ts +9 -0
  67. package/dist/components/surface-card.js +33 -0
  68. package/dist/components/theme-runtime-provider.d.ts +15 -0
  69. package/dist/components/theme-runtime-provider.js +48 -0
  70. package/dist/components/theme-toggle.d.ts +2 -0
  71. package/dist/components/theme-toggle.js +19 -0
  72. package/dist/components/tool-call-card.d.ts +27 -0
  73. package/dist/components/tool-call-card.js +13 -0
  74. package/dist/components/tool-fallback.d.ts +26 -0
  75. package/dist/components/tool-fallback.js +129 -0
  76. package/dist/components/tool-panel-layout.d.ts +10 -0
  77. package/dist/components/tool-panel-layout.js +12 -0
  78. package/dist/components/tool-panel.d.ts +1 -0
  79. package/dist/components/tool-panel.js +89 -0
  80. package/dist/components/tooltip-icon-button.d.ts +7 -0
  81. package/dist/components/tooltip-icon-button.js +9 -0
  82. package/dist/components/user-menu.d.ts +2 -0
  83. package/dist/components/user-menu.js +14 -0
  84. package/dist/index.d.ts +68 -0
  85. package/dist/index.js +83 -0
  86. package/dist/lib/ag-ui-adapter.d.ts +29 -0
  87. package/dist/lib/ag-ui-adapter.js +382 -0
  88. package/dist/lib/app-store.d.ts +115 -0
  89. package/dist/lib/app-store.js +199 -0
  90. package/dist/lib/attachment-adapter.d.ts +20 -0
  91. package/dist/lib/attachment-adapter.js +99 -0
  92. package/dist/lib/auth-provider.d.ts +16 -0
  93. package/dist/lib/auth-provider.js +88 -0
  94. package/dist/lib/chat-bubble-store.d.ts +30 -0
  95. package/dist/lib/chat-bubble-store.js +23 -0
  96. package/dist/lib/chat-lifecycle-store.d.ts +10 -0
  97. package/{src/lib/chat-lifecycle-store.ts → dist/lib/chat-lifecycle-store.js} +3 -13
  98. package/dist/lib/client-tools.d.ts +11 -0
  99. package/dist/lib/client-tools.js +274 -0
  100. package/dist/lib/composer-submit-store.d.ts +12 -0
  101. package/dist/lib/composer-submit-store.js +17 -0
  102. package/dist/lib/dev-store.d.ts +38 -0
  103. package/dist/lib/dev-store.js +32 -0
  104. package/dist/lib/nav-config.d.ts +159 -0
  105. package/dist/lib/nav-config.js +162 -0
  106. package/dist/lib/nav-dnd.d.ts +8 -0
  107. package/dist/lib/nav-dnd.js +9 -0
  108. package/dist/lib/nav-store.d.ts +45 -0
  109. package/dist/lib/nav-store.js +49 -0
  110. package/dist/lib/selection-context.d.ts +21 -0
  111. package/dist/lib/selection-context.js +34 -0
  112. package/dist/lib/sidebar-store.d.ts +23 -0
  113. package/dist/lib/sidebar-store.js +98 -0
  114. package/dist/lib/theme-runtime.d.ts +130 -0
  115. package/dist/lib/theme-runtime.js +479 -0
  116. package/dist/lib/theme-store.d.ts +15 -0
  117. package/dist/lib/theme-store.js +53 -0
  118. package/dist/lib/tool-panel-store.d.ts +48 -0
  119. package/dist/lib/tool-panel-store.js +69 -0
  120. package/dist/lib/use-new-conversation.d.ts +8 -0
  121. package/dist/lib/use-new-conversation.js +62 -0
  122. package/dist/lib/use-page-tools.d.ts +27 -0
  123. package/{src/lib/use-page-tools.ts → dist/lib/use-page-tools.js} +11 -13
  124. package/dist/styles.css +142 -0
  125. package/dist/ui/collapsible.d.ts +5 -0
  126. package/{src/ui/collapsible.tsx → dist/ui/collapsible.js} +0 -2
  127. package/dist/ui/dialog.d.ts +15 -0
  128. package/dist/ui/dialog.js +25 -0
  129. package/dist/ui/dropdown-menu.d.ts +7 -0
  130. package/dist/ui/dropdown-menu.js +19 -0
  131. package/dist/ui/popover.d.ts +7 -0
  132. package/dist/ui/popover.js +10 -0
  133. package/dist/ui/select.d.ts +4 -0
  134. package/dist/ui/select.js +9 -0
  135. package/dist/ui/tooltip.d.ts +7 -0
  136. package/dist/ui/tooltip.js +10 -0
  137. package/package.json +31 -12
  138. package/AGENTS.md +0 -59
  139. package/CHANGELOG.md +0 -211
  140. package/CLAUDE.md +0 -1
  141. package/src/__tests__/no-spaces-imports.test.ts +0 -59
  142. package/src/__tests__/open-core-guards.test.ts +0 -307
  143. package/src/__tests__/theme-runtime.test.ts +0 -81
  144. package/src/__tests__/tool-fallback.test.tsx +0 -109
  145. package/src/components/__tests__/context-bar.test.ts +0 -112
  146. package/src/components/ag-ui-runtime-provider.tsx +0 -170
  147. package/src/components/app-brand.tsx +0 -38
  148. package/src/components/assistant-chat.tsx +0 -461
  149. package/src/components/chat-attachments.tsx +0 -68
  150. package/src/components/chat-bubble.tsx +0 -373
  151. package/src/components/chat-header.tsx +0 -44
  152. package/src/components/client-tool-executor.tsx +0 -230
  153. package/src/components/collection-empty-state.tsx +0 -37
  154. package/src/components/collection-filter-bar.tsx +0 -168
  155. package/src/components/collection-search-input.tsx +0 -41
  156. package/src/components/collection-skeleton.tsx +0 -55
  157. package/src/components/collection-sort-menu.tsx +0 -62
  158. package/src/components/collection-surface.tsx +0 -46
  159. package/src/components/collection-toolbar.tsx +0 -33
  160. package/src/components/collection-view-toggle.tsx +0 -61
  161. package/src/components/composer-submit-bridge.tsx +0 -19
  162. package/src/components/confirm-dialog.tsx +0 -49
  163. package/src/components/confirmation-card.tsx +0 -32
  164. package/src/components/context-badges.tsx +0 -136
  165. package/src/components/context-bar.tsx +0 -238
  166. package/src/components/form-dialog.tsx +0 -56
  167. package/src/components/global-selection-popover.tsx +0 -266
  168. package/src/components/infinite-scroll-sentinel.tsx +0 -48
  169. package/src/components/json-viewer.tsx +0 -232
  170. package/src/components/loading-indicator.tsx +0 -40
  171. package/src/components/markdown-text-citations.test.tsx +0 -108
  172. package/src/components/markdown-text.tsx +0 -508
  173. package/src/components/name-dialog.tsx +0 -87
  174. package/src/components/selection-popover.tsx +0 -156
  175. package/src/components/show-document-tool-ui.tsx +0 -90
  176. package/src/components/sidebar.test.tsx +0 -611
  177. package/src/components/sidebar.tsx +0 -1689
  178. package/src/components/surface-card.tsx +0 -45
  179. package/src/components/theme-runtime-provider.tsx +0 -93
  180. package/src/components/theme-toggle.tsx +0 -27
  181. package/src/components/tool-call-card.tsx +0 -126
  182. package/src/components/tool-fallback.tsx +0 -169
  183. package/src/components/tool-panel-layout.tsx +0 -36
  184. package/src/components/tool-panel.tsx +0 -233
  185. package/src/components/tooltip-icon-button.tsx +0 -28
  186. package/src/components/user-menu.tsx +0 -60
  187. package/src/index.ts +0 -185
  188. package/src/lib/__tests__/ag-ui-adapter.test.ts +0 -279
  189. package/src/lib/__tests__/app-store.test.ts +0 -405
  190. package/src/lib/__tests__/attachment-adapter.test.ts +0 -48
  191. package/src/lib/__tests__/chat-bubble-store.test.ts +0 -67
  192. package/src/lib/__tests__/client-tools.test.ts +0 -124
  193. package/src/lib/__tests__/dev-store.test.ts +0 -78
  194. package/src/lib/__tests__/nav-config.test.ts +0 -135
  195. package/src/lib/__tests__/nav-dnd.test.ts +0 -24
  196. package/src/lib/__tests__/nav-store.test.ts +0 -59
  197. package/src/lib/__tests__/selection-context.test.ts +0 -114
  198. package/src/lib/__tests__/sidebar-store.test.ts +0 -144
  199. package/src/lib/__tests__/theme-store.test.ts +0 -83
  200. package/src/lib/__tests__/tool-panel-store.test.ts +0 -61
  201. package/src/lib/__tests__/use-page-context.test.ts +0 -58
  202. package/src/lib/ag-ui-adapter.ts +0 -426
  203. package/src/lib/app-store.ts +0 -361
  204. package/src/lib/attachment-adapter.ts +0 -92
  205. package/src/lib/auth-provider.tsx +0 -144
  206. package/src/lib/chat-bubble-store.ts +0 -46
  207. package/src/lib/client-tools.ts +0 -293
  208. package/src/lib/composer-submit-store.ts +0 -32
  209. package/src/lib/dev-store.ts +0 -69
  210. package/src/lib/nav-config.ts +0 -329
  211. package/src/lib/nav-dnd.ts +0 -14
  212. package/src/lib/nav-store.ts +0 -89
  213. package/src/lib/selection-context.ts +0 -65
  214. package/src/lib/sidebar-store.ts +0 -113
  215. package/src/lib/theme-runtime.ts +0 -660
  216. package/src/lib/theme-store.ts +0 -67
  217. package/src/lib/tool-panel-store.ts +0 -121
  218. package/src/lib/use-new-conversation.test.tsx +0 -168
  219. package/src/lib/use-new-conversation.ts +0 -64
  220. package/src/ui/dialog.tsx +0 -109
  221. package/src/ui/dropdown-menu.tsx +0 -75
  222. package/src/ui/popover.tsx +0 -34
  223. package/src/ui/select.tsx +0 -15
  224. package/src/ui/tooltip.tsx +0 -30
  225. package/src/vite-env.d.ts +0 -1
  226. package/tsconfig.json +0 -16
  227. package/vitest.config.ts +0 -8
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ export declare function FormDialog({ open, onOpenChange, title, description, children, footer, contentClassName, bodyClassName, size, }: {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ title: ReactNode;
6
+ description?: ReactNode;
7
+ children?: ReactNode;
8
+ footer?: ReactNode;
9
+ contentClassName?: string;
10
+ bodyClassName?: string;
11
+ size?: "sm" | "md" | "lg" | "xl" | "2xl";
12
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../ui/dialog.js";
3
+ import { cn } from "@iloveagents/foundry-web-primitives";
4
+ export function FormDialog({ open, onOpenChange, title, description, children, footer, contentClassName, bodyClassName, size = "md", }) {
5
+ const sizeClassName = {
6
+ sm: "max-w-sm",
7
+ md: "max-w-lg",
8
+ lg: "max-w-xl",
9
+ xl: "max-w-2xl",
10
+ "2xl": "max-w-3xl",
11
+ }[size];
12
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: cn(sizeClassName, contentClassName), children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { className: description ? undefined : "sr-only", children: description ?? "Complete the form below." })] }), children != null ? _jsx("div", { className: bodyClassName, children: children }) : null, footer ? _jsx(DialogFooter, { children: footer }) : null] }) }));
13
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Global selection command bar for text selection anywhere on the page.
3
+ *
4
+ * Appears near selected text when the user makes a selection outside of
5
+ * input elements and the tool panel (which has its own SelectionPopover).
6
+ */
7
+ export declare function GlobalSelectionPopover(): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,167 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Global selection command bar for text selection anywhere on the page.
4
+ *
5
+ * Appears near selected text when the user makes a selection outside of
6
+ * input elements and the tool panel (which has its own SelectionPopover).
7
+ */
8
+ import { useState, useEffect, useCallback, useRef } from "react";
9
+ import { PencilLine, Send, TextSelect, X } from "lucide-react";
10
+ import { cn } from "@iloveagents/foundry-web-primitives";
11
+ import { useAppStore } from "../lib/app-store.js";
12
+ import { useChatBubbleStore } from "../lib/chat-bubble-store.js";
13
+ import { submitComposerText } from "../lib/composer-submit-store.js";
14
+ import { resolveSelectionContext, } from "../lib/selection-context.js";
15
+ const ACTION_POPOVER_HEIGHT = 38;
16
+ const SIMPLE_POPOVER_WIDTH = 126;
17
+ const ACTION_POPOVER_WIDTH = 236;
18
+ const MODIFY_POPOVER_WIDTH = 340;
19
+ const OFFSET = 8;
20
+ const DEFAULT_MODIFY_PROMPT_PREFIX = "Modify the selected content:";
21
+ /** Elements where selection should NOT trigger the popover */
22
+ function isEditableTarget(node) {
23
+ const el = node instanceof HTMLElement ? node : node.parentElement;
24
+ if (!el)
25
+ return false;
26
+ const tag = el.tagName;
27
+ if (tag === "INPUT" || tag === "TEXTAREA")
28
+ return true;
29
+ if (el.isContentEditable)
30
+ return true;
31
+ return false;
32
+ }
33
+ export function GlobalSelectionPopover() {
34
+ const [position, setPosition] = useState(null);
35
+ const [selectedText, setSelectedText] = useState("");
36
+ const [selectionContext, setSelectionContext] = useState(null);
37
+ const [mode, setMode] = useState("actions");
38
+ const [modifyPrompt, setModifyPrompt] = useState("");
39
+ const addContextItem = useAppStore((s) => s.addContextItem);
40
+ const popoverRef = useRef(null);
41
+ const inputRef = useRef(null);
42
+ const setPopoverNode = useCallback((node) => {
43
+ popoverRef.current = node;
44
+ }, []);
45
+ const hidePopover = useCallback(() => {
46
+ setPosition(null);
47
+ setSelectedText("");
48
+ setSelectionContext(null);
49
+ setMode("actions");
50
+ setModifyPrompt("");
51
+ }, []);
52
+ const handleMouseUp = useCallback((event) => {
53
+ if (popoverRef.current?.contains(event.target))
54
+ return;
55
+ requestAnimationFrame(() => {
56
+ const selection = window.getSelection();
57
+ if (!selection || selection.isCollapsed || !selection.toString().trim())
58
+ return;
59
+ const anchorNode = selection.anchorNode;
60
+ if (!anchorNode)
61
+ return;
62
+ // Skip if inside editable element
63
+ if (isEditableTarget(anchorNode))
64
+ return;
65
+ // Skip if inside tool panel (it has its own SelectionPopover)
66
+ const el = anchorNode instanceof HTMLElement ? anchorNode : anchorNode.parentElement;
67
+ if (el?.closest("[data-tool-panel]"))
68
+ return;
69
+ const text = selection.toString().trim();
70
+ if (!text)
71
+ return;
72
+ const range = selection.getRangeAt(0);
73
+ const rect = range.getBoundingClientRect();
74
+ const sourcePage = useAppStore.getState().navContext.label;
75
+ const resolved = resolveSelectionContext({
76
+ text,
77
+ anchorNode,
78
+ focusNode: selection.focusNode,
79
+ range,
80
+ sourcePage,
81
+ });
82
+ const width = resolved.supportsModify ? ACTION_POPOVER_WIDTH : SIMPLE_POPOVER_WIDTH;
83
+ // Position above selection, fixed to viewport
84
+ let top = rect.top - ACTION_POPOVER_HEIGHT - OFFSET;
85
+ let left = rect.left + rect.width / 2 - width / 2;
86
+ // If would go above viewport, place below
87
+ if (top < OFFSET) {
88
+ top = rect.bottom + OFFSET;
89
+ }
90
+ // Clamp horizontal
91
+ left = Math.max(OFFSET, Math.min(left, window.innerWidth - width - OFFSET));
92
+ setSelectedText(text);
93
+ setSelectionContext(resolved);
94
+ setMode("actions");
95
+ setModifyPrompt("");
96
+ setPosition({ top, left });
97
+ });
98
+ }, []);
99
+ const addResolvedContext = useCallback(() => {
100
+ if (!selectionContext)
101
+ return false;
102
+ addContextItem(selectionContext.item);
103
+ return true;
104
+ }, [addContextItem, selectionContext]);
105
+ const handleAddToContext = useCallback(() => {
106
+ if (!selectedText || !addResolvedContext())
107
+ return;
108
+ window.getSelection()?.removeAllRanges();
109
+ hidePopover();
110
+ }, [selectedText, addResolvedContext, hidePopover]);
111
+ const handleStartModify = useCallback(() => {
112
+ setMode("modify");
113
+ setPosition((current) => {
114
+ if (!current)
115
+ return current;
116
+ const centeredLeft = current.left - (MODIFY_POPOVER_WIDTH - ACTION_POPOVER_WIDTH) / 2;
117
+ return {
118
+ ...current,
119
+ left: Math.max(OFFSET, Math.min(centeredLeft, window.innerWidth - MODIFY_POPOVER_WIDTH - OFFSET)),
120
+ };
121
+ });
122
+ }, []);
123
+ const handleModifySubmit = useCallback((event) => {
124
+ event.preventDefault();
125
+ const prompt = modifyPrompt.trim();
126
+ if (!prompt || !selectionContext || !addResolvedContext())
127
+ return;
128
+ const text = selectionContext.formatModifyPrompt?.(prompt) ??
129
+ `${DEFAULT_MODIFY_PROMPT_PREFIX} ${prompt}`;
130
+ useChatBubbleStore.getState().open();
131
+ submitComposerText(text);
132
+ window.getSelection()?.removeAllRanges();
133
+ hidePopover();
134
+ }, [addResolvedContext, hidePopover, modifyPrompt, selectionContext]);
135
+ useEffect(() => {
136
+ const handleMouseDown = (e) => {
137
+ if (popoverRef.current?.contains(e.target))
138
+ return;
139
+ hidePopover();
140
+ };
141
+ const handleKeyDown = (e) => {
142
+ if (e.key === "Escape")
143
+ hidePopover();
144
+ };
145
+ document.addEventListener("mouseup", handleMouseUp);
146
+ document.addEventListener("mousedown", handleMouseDown);
147
+ window.addEventListener("keydown", handleKeyDown);
148
+ return () => {
149
+ document.removeEventListener("mouseup", handleMouseUp);
150
+ document.removeEventListener("mousedown", handleMouseDown);
151
+ window.removeEventListener("keydown", handleKeyDown);
152
+ };
153
+ }, [handleMouseUp, hidePopover]);
154
+ useEffect(() => {
155
+ if (mode === "modify") {
156
+ inputRef.current?.focus();
157
+ }
158
+ }, [mode]);
159
+ if (!position || !selectionContext)
160
+ return null;
161
+ if (mode === "modify") {
162
+ return (_jsxs("form", { ref: setPopoverNode, className: cn("fixed z-50", "flex items-center gap-1.5", "rounded-2xl border border-border/70 bg-background/95 p-1.5 shadow-xl shadow-black/10 backdrop-blur-md", "animate-in fade-in-0 zoom-in-95 duration-150"), style: { top: position.top, left: position.left, width: MODIFY_POPOVER_WIDTH }, onSubmit: handleModifySubmit, children: [_jsx(PencilLine, { className: "ml-1 size-3.5 shrink-0 text-primary" }), _jsx("input", { ref: inputRef, value: modifyPrompt, onChange: (event) => setModifyPrompt(event.target.value), placeholder: "Describe change...", className: cn("min-w-0 flex-1 bg-transparent px-1 py-1.5 text-xs text-foreground outline-none", "placeholder:text-muted-foreground") }), _jsx("button", { type: "button", "aria-label": "Cancel modify", onClick: hidePopover, className: "rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: _jsx(X, { className: "size-3.5" }) }), _jsx("button", { type: "submit", "aria-label": "Send modify prompt", disabled: !modifyPrompt.trim(), className: cn("rounded-full p-1.5 transition-colors", modifyPrompt.trim()
163
+ ? "bg-primary text-primary-foreground hover:bg-primary/90"
164
+ : "text-muted-foreground opacity-50"), children: _jsx(Send, { className: "size-3.5" }) })] }));
165
+ }
166
+ return (_jsxs("div", { ref: setPopoverNode, role: "toolbar", "aria-label": "Selection actions", className: cn("fixed z-50", "inline-flex items-center gap-1", "rounded-full border border-border/70 bg-background/95 p-1 shadow-xl shadow-black/10 backdrop-blur-md", "animate-in fade-in-0 zoom-in-95 duration-150"), style: { top: position.top, left: position.left }, onMouseDown: (e) => e.preventDefault(), children: [_jsxs("button", { type: "button", className: "inline-flex h-7 items-center gap-1.5 rounded-full px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-muted", onClick: handleAddToContext, children: [_jsx(TextSelect, { className: "size-3.5 text-primary" }), "Add to chat"] }), selectionContext.supportsModify && (_jsxs("button", { type: "button", className: "inline-flex h-7 items-center gap-1.5 rounded-full px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-primary/10 hover:text-primary", onClick: handleStartModify, children: [_jsx(PencilLine, { className: "size-3.5 text-primary" }), "Modify"] }))] }));
167
+ }
@@ -0,0 +1,6 @@
1
+ export declare function InfiniteScrollSentinel({ enabled, loading, onVisible, className, }: {
2
+ enabled: boolean;
3
+ loading?: boolean;
4
+ onVisible: () => void;
5
+ className?: string;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { cn } from "@iloveagents/foundry-web-primitives";
4
+ export function InfiniteScrollSentinel({ enabled, loading, onVisible, className, }) {
5
+ const ref = useRef(null);
6
+ const pendingRef = useRef(false);
7
+ useEffect(() => {
8
+ if (!enabled || !loading) {
9
+ pendingRef.current = false;
10
+ }
11
+ }, [enabled, loading]);
12
+ useEffect(() => {
13
+ if (!enabled || !ref.current)
14
+ return;
15
+ const observer = new IntersectionObserver((entries) => {
16
+ if (entries.some((entry) => entry.isIntersecting) && !pendingRef.current) {
17
+ pendingRef.current = true;
18
+ onVisible();
19
+ }
20
+ }, { rootMargin: "240px 0px" });
21
+ observer.observe(ref.current);
22
+ return () => observer.disconnect();
23
+ }, [enabled, onVisible]);
24
+ return (_jsx("div", { ref: ref, className: cn("flex h-12 items-center justify-center text-sm text-muted-foreground", className), children: enabled ? (loading ? "Loading more..." : "Scroll for more") : null }));
25
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * JsonViewer — Collapsible JSON tree renderer.
3
+ *
4
+ * Fetches JSON from a URL and renders it as an interactive tree
5
+ * with syntax coloring and expand/collapse controls.
6
+ */
7
+ export declare function JsonViewer({ url, defaultExpanded, }: {
8
+ url: string;
9
+ defaultExpanded?: number;
10
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * JsonViewer — Collapsible JSON tree renderer.
4
+ *
5
+ * Fetches JSON from a URL and renders it as an interactive tree
6
+ * with syntax coloring and expand/collapse controls.
7
+ */
8
+ import { useEffect, useState } from "react";
9
+ import { ChevronDown, ChevronRight } from "lucide-react";
10
+ import { cn } from "@iloveagents/foundry-web-primitives";
11
+ export function JsonViewer({ url, defaultExpanded = 2, }) {
12
+ const [state, setState] = useState({ data: null, loading: true, error: false });
13
+ useEffect(() => {
14
+ let cancelled = false;
15
+ fetch(url)
16
+ .then((r) => {
17
+ if (!r.ok)
18
+ throw new Error("Failed to fetch");
19
+ return r.json();
20
+ })
21
+ .then((json) => {
22
+ if (!cancelled)
23
+ setState({ data: json, loading: false, error: false });
24
+ })
25
+ .catch(() => {
26
+ if (!cancelled)
27
+ setState({ data: null, loading: false, error: true });
28
+ });
29
+ return () => {
30
+ cancelled = true;
31
+ };
32
+ }, [url]);
33
+ const { data, loading, error } = state;
34
+ if (loading) {
35
+ return (_jsx("div", { className: "flex-1 flex items-center justify-center text-sm text-muted-foreground p-12", children: "Loading..." }));
36
+ }
37
+ if (error || data == null) {
38
+ return (_jsx("div", { className: "flex-1 flex items-center justify-center text-sm text-muted-foreground p-12", children: "Failed to load JSON" }));
39
+ }
40
+ return (_jsx("div", { className: "flex-1 overflow-auto p-6", children: _jsx("div", { className: "font-mono text-sm max-w-5xl", children: _jsx(JsonNode, { value: data, depth: 0, defaultExpanded: defaultExpanded }) }) }));
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // JsonNode — recursive tree node
44
+ // ---------------------------------------------------------------------------
45
+ function JsonNode({ label, value, depth, defaultExpanded, }) {
46
+ const [expanded, setExpanded] = useState(depth < defaultExpanded);
47
+ if (value === null || value === undefined) {
48
+ return (_jsxs("span", { children: [label != null && _jsx(KeyLabel, { text: label }), _jsx("span", { className: "text-muted-foreground italic", children: "null" })] }));
49
+ }
50
+ if (typeof value === "boolean") {
51
+ return (_jsxs("span", { children: [label != null && _jsx(KeyLabel, { text: label }), _jsx("span", { className: "text-[var(--syntax-boolean)]", children: String(value) })] }));
52
+ }
53
+ if (typeof value === "number") {
54
+ return (_jsxs("span", { children: [label != null && _jsx(KeyLabel, { text: label }), _jsx("span", { className: "text-[var(--syntax-number)]", children: value })] }));
55
+ }
56
+ if (typeof value === "string") {
57
+ const display = value.length > 200 ? value.slice(0, 200) + "..." : value;
58
+ return (_jsxs("span", { children: [label != null && _jsx(KeyLabel, { text: label }), _jsxs("span", { className: "text-[var(--syntax-string)]", children: ["\"", display, "\""] })] }));
59
+ }
60
+ if (Array.isArray(value)) {
61
+ if (value.length === 0) {
62
+ return (_jsxs("span", { children: [label != null && _jsx(KeyLabel, { text: label }), _jsx("span", { className: "text-muted-foreground", children: "[]" })] }));
63
+ }
64
+ return (_jsxs("div", { children: [_jsxs(ToggleButton, { expanded: expanded, onToggle: () => setExpanded(!expanded), children: [label != null && _jsx(KeyLabel, { text: label }), _jsxs("span", { className: "text-muted-foreground ml-1", children: ["[", value.length, " items]"] })] }), expanded && (_jsx("div", { className: cn("border-l border-border ml-2 pl-3", depth > 4 && "ml-1 pl-2"), children: value.map((item, i) => (_jsx("div", { className: "py-0.5", children: _jsx(JsonNode, { label: String(i), value: item, depth: depth + 1, defaultExpanded: defaultExpanded }) }, i))) }))] }));
65
+ }
66
+ if (typeof value === "object") {
67
+ const entries = Object.entries(value);
68
+ if (entries.length === 0) {
69
+ return (_jsxs("span", { children: [label != null && _jsx(KeyLabel, { text: label }), _jsx("span", { className: "text-muted-foreground", children: "{}" })] }));
70
+ }
71
+ return (_jsxs("div", { children: [_jsxs(ToggleButton, { expanded: expanded, onToggle: () => setExpanded(!expanded), children: [label != null && _jsx(KeyLabel, { text: label }), _jsxs("span", { className: "text-muted-foreground ml-1", children: ["{", entries.length, " keys", "}"] })] }), expanded && (_jsx("div", { className: cn("border-l border-border ml-2 pl-3", depth > 4 && "ml-1 pl-2"), children: entries.map(([key, val]) => (_jsx("div", { className: "py-0.5", children: _jsx(JsonNode, { label: key, value: val, depth: depth + 1, defaultExpanded: defaultExpanded }) }, key))) }))] }));
72
+ }
73
+ return (_jsxs("span", { children: [label != null && _jsx(KeyLabel, { text: label }), _jsx("span", { children: String(value) })] }));
74
+ }
75
+ function KeyLabel({ text }) {
76
+ return _jsxs("span", { className: "text-[var(--syntax-key)]", children: [text, ": "] });
77
+ }
78
+ function ToggleButton({ expanded, onToggle, children, }) {
79
+ return (_jsxs("button", { type: "button", onClick: onToggle, className: "inline-flex items-center gap-0.5 hover:bg-muted/50 rounded px-0.5 -ml-0.5", children: [expanded ? (_jsx(ChevronDown, { className: "size-3.5 text-muted-foreground" })) : (_jsx(ChevronRight, { className: "size-3.5 text-muted-foreground" })), children] }));
80
+ }
@@ -0,0 +1,13 @@
1
+ /** Shimmer bar shown above the composer while the agent is running. */
2
+ export declare function LoadingBar(): import("react/jsx-runtime").JSX.Element;
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
+ */
13
+ export declare function LoadingIndicator(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useStore } from "zustand";
3
+ import { streamingStatusStore } from "@iloveagents/foundry-agent";
4
+ function formatToolName(name) {
5
+ return name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
6
+ }
7
+ /** Shimmer bar shown above the composer while the agent is running. */
8
+ export function LoadingBar() {
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
+ }
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
+ */
21
+ export function LoadingIndicator() {
22
+ const streaming = useStore(streamingStatusStore, (s) => s.streamingStatus);
23
+ const stalled = streaming.status === "stalled";
24
+ let label = "Thinking";
25
+ if (stalled) {
26
+ label = "Still working — no signal from the agent";
27
+ }
28
+ else if (streaming.status === "calling" && streaming.toolName) {
29
+ label = `Running ${formatToolName(streaming.toolName)}`;
30
+ }
31
+ else if (streaming.status === "streaming") {
32
+ label = "Generating response";
33
+ }
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]` })] })] }) }));
35
+ }
@@ -0,0 +1,13 @@
1
+ import { type FC } from "react";
2
+ import type { Components } from "react-markdown";
3
+ import { type ReactNode } from "react";
4
+ /**
5
+ * Shared markdown component overrides used by both chat messages and the tool panel.
6
+ */
7
+ export declare const markdownComponents: Components;
8
+ /**
9
+ * Walk React children and replace [n] text fragments with CitationRef
10
+ * components. Only processes string children — leaves elements untouched.
11
+ */
12
+ export declare function injectCitations(children: ReactNode): ReactNode;
13
+ export declare const MarkdownText: FC;
@@ -0,0 +1,221 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cloneElement, isValidElement, useCallback, useState } from "react";
3
+ import { useMessagePartText } from "@assistant-ui/react";
4
+ import remarkGfm from "remark-gfm";
5
+ import ReactMarkdown from "react-markdown";
6
+ import { Children } from "react";
7
+ import { Check, Copy } from "lucide-react";
8
+ import { cn } from "@iloveagents/foundry-web-primitives";
9
+ import { useStore } from "zustand";
10
+ import { citationStore, linkStore, resolveLinkHandler, } from "@iloveagents/foundry-agent";
11
+ /* ---------- Shared copy-to-clipboard button ---------- */
12
+ const CopyButton = ({ text, className }) => {
13
+ const [copied, setCopied] = useState(false);
14
+ const handleCopy = useCallback(() => {
15
+ navigator.clipboard.writeText(text).then(() => {
16
+ setCopied(true);
17
+ setTimeout(() => setCopied(false), 2000);
18
+ });
19
+ }, [text]);
20
+ return (_jsx("button", { type: "button", onClick: handleCopy, className: cn("flex items-center justify-center size-7 rounded-md", "hover:bg-muted/50 transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", className), "aria-label": "Copy code", children: copied ? (_jsx(Check, { className: "size-3.5 text-success" })) : (_jsx(Copy, { className: "size-3.5 text-muted-foreground" })) }));
21
+ };
22
+ /* ---------- Inline code ---------- */
23
+ function textFromChildren(children) {
24
+ if (typeof children === "string")
25
+ return children;
26
+ if (typeof children === "number")
27
+ return String(children);
28
+ if (!Array.isArray(children))
29
+ return null;
30
+ let out = "";
31
+ for (const child of children) {
32
+ if (typeof child === "string" || typeof child === "number") {
33
+ out += String(child);
34
+ }
35
+ else {
36
+ return null;
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+ function resolveHandledHref(rawHref) {
42
+ return resolveLinkHandler(rawHref)?.href ?? null;
43
+ }
44
+ function openHandledHref(href) {
45
+ const resolved = resolveLinkHandler(href);
46
+ resolved?.handler.openLink(resolved.href);
47
+ }
48
+ const TRAILING_LINK_PUNCT_RE = /[.,;:!?)\]]+$/;
49
+ function trimLinkCandidate(value) {
50
+ const match = value.match(TRAILING_LINK_PUNCT_RE);
51
+ if (!match)
52
+ return { href: value, suffix: "" };
53
+ return {
54
+ href: value.slice(0, -match[0].length),
55
+ suffix: match[0],
56
+ };
57
+ }
58
+ function exactHandledHref(value) {
59
+ const trimmed = value.trim();
60
+ if (!trimmed || /\s/.test(trimmed))
61
+ return null;
62
+ const { href, suffix } = trimLinkCandidate(trimmed);
63
+ if (suffix)
64
+ return null;
65
+ return resolveHandledHref(href);
66
+ }
67
+ const HANDLED_LINK_RE = /(https?:\/\/[^\s<>"`]+|\/[A-Za-z0-9][^\s<>"`]*)/g;
68
+ const HandledLink = ({ href, children, className, ...props }) => {
69
+ const handledHref = useStore(linkStore, () => resolveHandledHref(href));
70
+ if (!handledHref) {
71
+ return (_jsx("a", { href: href, className: cn("text-primary hover:underline", className), target: "_blank", rel: "noopener noreferrer", ...props, children: children }));
72
+ }
73
+ return (_jsx("a", { href: handledHref, className: cn("text-primary hover:underline", className), onClick: (event) => {
74
+ event.preventDefault();
75
+ openHandledHref(handledHref);
76
+ }, ...props, children: children }));
77
+ };
78
+ const AutoLinkedText = ({ text }) => {
79
+ useStore(linkStore, (s) => s.handlers);
80
+ const parts = [];
81
+ let lastIndex = 0;
82
+ for (const match of text.matchAll(HANDLED_LINK_RE)) {
83
+ const raw = match[0];
84
+ const start = match.index ?? 0;
85
+ const { href, suffix } = trimLinkCandidate(raw);
86
+ const handledHref = resolveHandledHref(href);
87
+ if (!handledHref)
88
+ continue;
89
+ if (start > lastIndex)
90
+ parts.push(text.slice(lastIndex, start));
91
+ parts.push(_jsx(HandledLink, { href: handledHref, children: href }, `${start}-${href}`));
92
+ if (suffix)
93
+ parts.push(suffix);
94
+ lastIndex = start + raw.length;
95
+ }
96
+ if (lastIndex === 0)
97
+ return _jsx(_Fragment, { children: text });
98
+ if (lastIndex < text.length)
99
+ parts.push(text.slice(lastIndex));
100
+ return _jsx(_Fragment, { children: parts });
101
+ };
102
+ const InlineCode = ({ children, ...props }) => {
103
+ const value = textFromChildren(children);
104
+ const handledHref = useStore(linkStore, () => (value ? exactHandledHref(value) : null));
105
+ const className = "rounded bg-muted px-1.5 py-0.5 text-sm font-mono text-foreground";
106
+ if (handledHref) {
107
+ return (_jsx(HandledLink, { href: handledHref, className: className, children: children }));
108
+ }
109
+ return (_jsx("code", { className: className, ...props, children: children }));
110
+ };
111
+ /* ---------- Code block with header + copy action (used by ReactMarkdown / tool panel) ---------- */
112
+ const HighlightedCodeBlock = ({ language, code }) => {
113
+ return (_jsxs("div", { className: "relative group my-4", children: [_jsxs("div", { className: cn("flex items-center justify-between", "rounded-t-lg bg-code-block px-4 py-2", "border-b border-border/30"), children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: language || "text" }), _jsx(CopyButton, { text: code })] }), _jsx("pre", { className: "rounded-b-lg bg-code-block p-4 overflow-x-auto text-sm font-mono text-code-block-foreground", children: _jsx("code", { children: code }) })] }));
114
+ };
115
+ /**
116
+ * Extracts language and raw code from the <code> child that ReactMarkdown
117
+ * renders inside <pre>. Returns { language, code } or null for non-code pre.
118
+ */
119
+ function extractCodeInfo(children) {
120
+ const child = Array.isArray(children) ? children[0] : children;
121
+ if (child && typeof child === "object" && "props" in child && child.props) {
122
+ const className = child.props.className;
123
+ const lang = className?.match(/language-(\S+)/)?.[1];
124
+ const raw = child.props.children;
125
+ const code = typeof raw === "string" ? raw : Array.isArray(raw) ? raw.join("") : String(raw ?? "");
126
+ return { language: lang, code: code.replace(/\n$/, "") };
127
+ }
128
+ return null;
129
+ }
130
+ /* ---------- Shared markdown components (used by both chat + tool panel) ---------- */
131
+ /**
132
+ * Shared markdown component overrides used by both chat messages and the tool panel.
133
+ */
134
+ export const markdownComponents = {
135
+ h1: ({ children, ...props }) => (_jsx("h1", { className: "text-2xl font-bold mt-6 mb-4 text-foreground", ...props, children: children })),
136
+ h2: ({ children, ...props }) => (_jsx("h2", { className: "text-xl font-bold mt-5 mb-3 text-foreground", ...props, children: children })),
137
+ h3: ({ children, ...props }) => (_jsx("h3", { className: "text-lg font-semibold mt-4 mb-2 text-foreground", ...props, children: children })),
138
+ h4: ({ children, ...props }) => (_jsx("h4", { className: "text-base font-semibold mt-3 mb-1.5 text-foreground", ...props, children: children })),
139
+ p: ({ children, ...props }) => (_jsx("p", { className: "mb-2 leading-relaxed text-foreground", ...props, children: injectCitations(children) })),
140
+ ul: ({ children, ...props }) => (_jsx("ul", { className: "list-disc list-outside pl-6 mb-4 space-y-1 text-foreground", ...props, children: children })),
141
+ ol: ({ children, ...props }) => (_jsx("ol", { className: "list-decimal list-outside pl-6 mb-4 space-y-1 text-foreground", ...props, children: children })),
142
+ li: ({ children, ...props }) => (_jsx("li", { className: "text-foreground", ...props, children: injectCitations(children) })),
143
+ pre: ({ children }) => {
144
+ const info = extractCodeInfo(children);
145
+ if (info) {
146
+ return _jsx(HighlightedCodeBlock, { language: info.language, code: info.code });
147
+ }
148
+ return (_jsx("pre", { className: "rounded-lg bg-code-block p-4 overflow-x-auto text-sm font-mono text-code-block-foreground my-4", children: children }));
149
+ },
150
+ code: ({ className, children, ...props }) => {
151
+ // Fenced code blocks are wrapped in <pre><code> — the pre handles styling.
152
+ // Only inline code (no language class, not inside pre) gets the InlineCode style.
153
+ if (className?.includes("language-")) {
154
+ return _jsx("code", { ...props, children: children });
155
+ }
156
+ return _jsx(InlineCode, { ...props, children: children });
157
+ },
158
+ blockquote: ({ children, ...props }) => (_jsx("blockquote", { className: "border-l-4 border-primary pl-4 py-2 my-4 italic text-muted-foreground", ...props, children: children })),
159
+ a: ({ children, href, ...props }) => (_jsx(HandledLink, { href: href || "", ...props, children: children })),
160
+ hr: (props) => _jsx("hr", { className: "my-6 border-border", ...props }),
161
+ table: ({ children, ...props }) => (_jsx("div", { className: "overflow-x-auto my-4", children: _jsx("table", { className: "min-w-full divide-y divide-border", ...props, children: children }) })),
162
+ thead: ({ children, ...props }) => (_jsx("thead", { className: "bg-muted", ...props, children: children })),
163
+ tbody: ({ children, ...props }) => (_jsx("tbody", { className: "divide-y divide-border", ...props, children: children })),
164
+ tr: ({ children, ...props }) => _jsx("tr", { ...props, children: children }),
165
+ th: ({ children, ...props }) => (_jsx("th", { className: "px-4 py-2 text-left text-sm font-semibold text-foreground", ...props, children: children })),
166
+ td: ({ children, ...props }) => (_jsx("td", { className: "px-4 py-2 text-sm text-foreground", ...props, children: children })),
167
+ strong: ({ children, ...props }) => (_jsx("strong", { className: "font-bold", ...props, children: children })),
168
+ em: ({ children, ...props }) => (_jsx("em", { className: "italic", ...props, children: children })),
169
+ };
170
+ /* ---------- Inline [n] citations (React-level, no remark plugin) ---------- */
171
+ const CITE_RE = /(\[\d+\])/;
172
+ /** Open a citation via the registered handler (provided by feature modules). */
173
+ function openCitation(result) {
174
+ const handler = citationStore.getState().handler;
175
+ if (handler)
176
+ handler.openCitation(result);
177
+ }
178
+ /** Clickable superscript citation: [1] → opens via registered handler. */
179
+ const CitationRef = ({ index }) => {
180
+ const result = useStore(citationStore, (s) => s.results[index - 1]);
181
+ const hasHandler = useStore(citationStore, (s) => !!s.handler);
182
+ if (!result)
183
+ return _jsxs("span", { className: "text-muted-foreground", children: ["[", index, "]"] });
184
+ if (!hasHandler)
185
+ return _jsxs("span", { className: "text-muted-foreground", children: ["[", index, "]"] });
186
+ return (_jsx("span", { role: "button", tabIndex: 0, onClick: () => openCitation(result), onKeyDown: (e) => {
187
+ if (e.key === "Enter")
188
+ openCitation(result);
189
+ }, title: `${result.entity_name}, p. ${result.page_number}`, className: "inline-flex items-center text-primary font-semibold cursor-pointer hover:underline", children: _jsxs("sup", { className: "text-[0.7em] ml-0.5", children: ["[", index, "]"] }) }));
190
+ };
191
+ /**
192
+ * Walk React children and replace [n] text fragments with CitationRef
193
+ * components. Only processes string children — leaves elements untouched.
194
+ */
195
+ export function injectCitations(children) {
196
+ return Children.map(children, (child) => {
197
+ if (typeof child === "string") {
198
+ if (!CITE_RE.test(child))
199
+ return _jsx(AutoLinkedText, { text: child });
200
+ const parts = child.split(CITE_RE);
201
+ return parts.map((part, i) => {
202
+ const m = /^\[(\d+)\]$/.exec(part);
203
+ if (m)
204
+ return _jsx(CitationRef, { index: parseInt(m[1], 10) }, i);
205
+ return _jsx(AutoLinkedText, { text: part }, i);
206
+ });
207
+ }
208
+ if (!isValidElement(child))
209
+ return child;
210
+ const element = child;
211
+ const nested = element.props.children;
212
+ if (nested == null)
213
+ return child;
214
+ return cloneElement(element, undefined, injectCitations(nested));
215
+ });
216
+ }
217
+ /* ---------- MarkdownText (chat messages) ---------- */
218
+ export const MarkdownText = () => {
219
+ const part = useMessagePartText();
220
+ return (_jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], components: markdownComponents, children: part.text }));
221
+ };
@@ -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") })] }) }) }));