@iloveagents/foundry-web-ui 0.3.1 → 0.4.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 (223) 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 +93 -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 +103 -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 +4 -0
  51. package/dist/components/loading-indicator.js +22 -0
  52. package/dist/components/markdown-text.d.ts +13 -0
  53. package/dist/components/markdown-text.js +221 -0
  54. package/dist/components/name-dialog.d.ts +16 -0
  55. package/dist/components/name-dialog.js +23 -0
  56. package/dist/components/selection-popover.d.ts +12 -0
  57. package/dist/components/selection-popover.js +111 -0
  58. package/dist/components/show-document-tool-ui.d.ts +1 -0
  59. package/dist/components/show-document-tool-ui.js +53 -0
  60. package/dist/components/sidebar.d.ts +4 -0
  61. package/dist/components/sidebar.js +904 -0
  62. package/dist/components/surface-card.d.ts +9 -0
  63. package/dist/components/surface-card.js +33 -0
  64. package/dist/components/theme-runtime-provider.d.ts +15 -0
  65. package/dist/components/theme-runtime-provider.js +48 -0
  66. package/dist/components/theme-toggle.d.ts +2 -0
  67. package/dist/components/theme-toggle.js +19 -0
  68. package/dist/components/tool-call-card.d.ts +27 -0
  69. package/dist/components/tool-call-card.js +13 -0
  70. package/dist/components/tool-fallback.d.ts +26 -0
  71. package/dist/components/tool-fallback.js +129 -0
  72. package/dist/components/tool-panel-layout.d.ts +10 -0
  73. package/dist/components/tool-panel-layout.js +12 -0
  74. package/dist/components/tool-panel.d.ts +1 -0
  75. package/dist/components/tool-panel.js +89 -0
  76. package/dist/components/tooltip-icon-button.d.ts +7 -0
  77. package/dist/components/tooltip-icon-button.js +9 -0
  78. package/dist/components/user-menu.d.ts +2 -0
  79. package/dist/components/user-menu.js +14 -0
  80. package/dist/index.d.ts +68 -0
  81. package/dist/index.js +83 -0
  82. package/dist/lib/ag-ui-adapter.d.ts +29 -0
  83. package/dist/lib/ag-ui-adapter.js +373 -0
  84. package/dist/lib/app-store.d.ts +115 -0
  85. package/dist/lib/app-store.js +199 -0
  86. package/dist/lib/attachment-adapter.d.ts +20 -0
  87. package/dist/lib/attachment-adapter.js +99 -0
  88. package/dist/lib/auth-provider.d.ts +16 -0
  89. package/dist/lib/auth-provider.js +88 -0
  90. package/dist/lib/chat-bubble-store.d.ts +30 -0
  91. package/dist/lib/chat-bubble-store.js +23 -0
  92. package/dist/lib/chat-lifecycle-store.d.ts +10 -0
  93. package/{src/lib/chat-lifecycle-store.ts → dist/lib/chat-lifecycle-store.js} +3 -13
  94. package/dist/lib/client-tools.d.ts +11 -0
  95. package/dist/lib/client-tools.js +274 -0
  96. package/dist/lib/composer-submit-store.d.ts +12 -0
  97. package/dist/lib/composer-submit-store.js +17 -0
  98. package/dist/lib/dev-store.d.ts +38 -0
  99. package/dist/lib/dev-store.js +32 -0
  100. package/dist/lib/nav-config.d.ts +159 -0
  101. package/dist/lib/nav-config.js +162 -0
  102. package/dist/lib/nav-dnd.d.ts +8 -0
  103. package/dist/lib/nav-dnd.js +9 -0
  104. package/dist/lib/nav-store.d.ts +45 -0
  105. package/dist/lib/nav-store.js +49 -0
  106. package/dist/lib/selection-context.d.ts +21 -0
  107. package/dist/lib/selection-context.js +34 -0
  108. package/dist/lib/sidebar-store.d.ts +23 -0
  109. package/dist/lib/sidebar-store.js +98 -0
  110. package/dist/lib/theme-runtime.d.ts +130 -0
  111. package/dist/lib/theme-runtime.js +479 -0
  112. package/dist/lib/theme-store.d.ts +15 -0
  113. package/dist/lib/theme-store.js +53 -0
  114. package/dist/lib/tool-panel-store.d.ts +48 -0
  115. package/dist/lib/tool-panel-store.js +69 -0
  116. package/dist/lib/use-new-conversation.d.ts +8 -0
  117. package/dist/lib/use-new-conversation.js +62 -0
  118. package/dist/lib/use-page-tools.d.ts +27 -0
  119. package/{src/lib/use-page-tools.ts → dist/lib/use-page-tools.js} +11 -13
  120. package/dist/styles.css +142 -0
  121. package/dist/ui/collapsible.d.ts +5 -0
  122. package/{src/ui/collapsible.tsx → dist/ui/collapsible.js} +0 -2
  123. package/dist/ui/dialog.d.ts +15 -0
  124. package/dist/ui/dialog.js +25 -0
  125. package/dist/ui/dropdown-menu.d.ts +7 -0
  126. package/dist/ui/dropdown-menu.js +19 -0
  127. package/dist/ui/popover.d.ts +7 -0
  128. package/dist/ui/popover.js +10 -0
  129. package/dist/ui/select.d.ts +4 -0
  130. package/dist/ui/select.js +9 -0
  131. package/dist/ui/tooltip.d.ts +7 -0
  132. package/dist/ui/tooltip.js +10 -0
  133. package/package.json +31 -12
  134. package/AGENTS.md +0 -59
  135. package/CHANGELOG.md +0 -211
  136. package/CLAUDE.md +0 -1
  137. package/src/__tests__/no-spaces-imports.test.ts +0 -59
  138. package/src/__tests__/open-core-guards.test.ts +0 -307
  139. package/src/__tests__/theme-runtime.test.ts +0 -81
  140. package/src/__tests__/tool-fallback.test.tsx +0 -109
  141. package/src/components/__tests__/context-bar.test.ts +0 -112
  142. package/src/components/ag-ui-runtime-provider.tsx +0 -170
  143. package/src/components/app-brand.tsx +0 -38
  144. package/src/components/assistant-chat.tsx +0 -461
  145. package/src/components/chat-attachments.tsx +0 -68
  146. package/src/components/chat-bubble.tsx +0 -373
  147. package/src/components/chat-header.tsx +0 -44
  148. package/src/components/client-tool-executor.tsx +0 -230
  149. package/src/components/collection-empty-state.tsx +0 -37
  150. package/src/components/collection-filter-bar.tsx +0 -168
  151. package/src/components/collection-search-input.tsx +0 -41
  152. package/src/components/collection-skeleton.tsx +0 -55
  153. package/src/components/collection-sort-menu.tsx +0 -62
  154. package/src/components/collection-surface.tsx +0 -46
  155. package/src/components/collection-toolbar.tsx +0 -33
  156. package/src/components/collection-view-toggle.tsx +0 -61
  157. package/src/components/composer-submit-bridge.tsx +0 -19
  158. package/src/components/confirm-dialog.tsx +0 -49
  159. package/src/components/confirmation-card.tsx +0 -32
  160. package/src/components/context-badges.tsx +0 -136
  161. package/src/components/context-bar.tsx +0 -238
  162. package/src/components/form-dialog.tsx +0 -56
  163. package/src/components/global-selection-popover.tsx +0 -266
  164. package/src/components/infinite-scroll-sentinel.tsx +0 -48
  165. package/src/components/json-viewer.tsx +0 -232
  166. package/src/components/loading-indicator.tsx +0 -40
  167. package/src/components/markdown-text-citations.test.tsx +0 -108
  168. package/src/components/markdown-text.tsx +0 -508
  169. package/src/components/name-dialog.tsx +0 -87
  170. package/src/components/selection-popover.tsx +0 -156
  171. package/src/components/show-document-tool-ui.tsx +0 -90
  172. package/src/components/sidebar.test.tsx +0 -611
  173. package/src/components/sidebar.tsx +0 -1689
  174. package/src/components/surface-card.tsx +0 -45
  175. package/src/components/theme-runtime-provider.tsx +0 -93
  176. package/src/components/theme-toggle.tsx +0 -27
  177. package/src/components/tool-call-card.tsx +0 -126
  178. package/src/components/tool-fallback.tsx +0 -169
  179. package/src/components/tool-panel-layout.tsx +0 -36
  180. package/src/components/tool-panel.tsx +0 -233
  181. package/src/components/tooltip-icon-button.tsx +0 -28
  182. package/src/components/user-menu.tsx +0 -60
  183. package/src/index.ts +0 -185
  184. package/src/lib/__tests__/ag-ui-adapter.test.ts +0 -279
  185. package/src/lib/__tests__/app-store.test.ts +0 -405
  186. package/src/lib/__tests__/attachment-adapter.test.ts +0 -48
  187. package/src/lib/__tests__/chat-bubble-store.test.ts +0 -67
  188. package/src/lib/__tests__/client-tools.test.ts +0 -124
  189. package/src/lib/__tests__/dev-store.test.ts +0 -78
  190. package/src/lib/__tests__/nav-config.test.ts +0 -135
  191. package/src/lib/__tests__/nav-dnd.test.ts +0 -24
  192. package/src/lib/__tests__/nav-store.test.ts +0 -59
  193. package/src/lib/__tests__/selection-context.test.ts +0 -114
  194. package/src/lib/__tests__/sidebar-store.test.ts +0 -144
  195. package/src/lib/__tests__/theme-store.test.ts +0 -83
  196. package/src/lib/__tests__/tool-panel-store.test.ts +0 -61
  197. package/src/lib/__tests__/use-page-context.test.ts +0 -58
  198. package/src/lib/ag-ui-adapter.ts +0 -426
  199. package/src/lib/app-store.ts +0 -361
  200. package/src/lib/attachment-adapter.ts +0 -92
  201. package/src/lib/auth-provider.tsx +0 -144
  202. package/src/lib/chat-bubble-store.ts +0 -46
  203. package/src/lib/client-tools.ts +0 -293
  204. package/src/lib/composer-submit-store.ts +0 -32
  205. package/src/lib/dev-store.ts +0 -69
  206. package/src/lib/nav-config.ts +0 -329
  207. package/src/lib/nav-dnd.ts +0 -14
  208. package/src/lib/nav-store.ts +0 -89
  209. package/src/lib/selection-context.ts +0 -65
  210. package/src/lib/sidebar-store.ts +0 -113
  211. package/src/lib/theme-runtime.ts +0 -660
  212. package/src/lib/theme-store.ts +0 -67
  213. package/src/lib/tool-panel-store.ts +0 -121
  214. package/src/lib/use-new-conversation.test.tsx +0 -168
  215. package/src/lib/use-new-conversation.ts +0 -64
  216. package/src/ui/dialog.tsx +0 -109
  217. package/src/ui/dropdown-menu.tsx +0 -75
  218. package/src/ui/popover.tsx +0 -34
  219. package/src/ui/select.tsx +0 -15
  220. package/src/ui/tooltip.tsx +0 -30
  221. package/src/vite-env.d.ts +0 -1
  222. package/tsconfig.json +0 -16
  223. 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,4 @@
1
+ /** Shimmer bar shown above the composer while the agent is running. */
2
+ export declare function LoadingBar(): import("react/jsx-runtime").JSX.Element;
3
+ /** Inline status text shown in the message area while the agent is running. */
4
+ export declare function LoadingIndicator(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,22 @@
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
+ /** Inline status text shown in the message area while the agent is running. */
12
+ export function LoadingIndicator() {
13
+ const streaming = useStore(streamingStatusStore, (s) => s.streamingStatus);
14
+ let label = "Thinking";
15
+ if (streaming.status === "calling" && streaming.toolName) {
16
+ label = `Running ${formatToolName(streaming.toolName)}`;
17
+ }
18
+ else if (streaming.status === "streaming") {
19
+ label = "Generating response";
20
+ }
21
+ return (_jsx("div", { className: "h-10 px-4", children: _jsxs("div", { className: "flex items-center gap-2 py-2 text-foreground leading-relaxed animate-in fade-in duration-300", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: label }), _jsxs("div", { className: "flex gap-1", children: [_jsx("span", { className: "size-1.5 rounded-full bg-primary/60 animate-[pulse_1.4s_ease-in-out_infinite]" }), _jsx("span", { className: "size-1.5 rounded-full bg-primary/60 animate-[pulse_1.4s_ease-in-out_0.2s_infinite]" }), _jsx("span", { className: "size-1.5 rounded-full bg-primary/60 animate-[pulse_1.4s_ease-in-out_0.4s_infinite]" })] })] }) }));
22
+ }
@@ -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,16 @@
1
+ import { type ReactNode } from "react";
2
+ export declare function NameDialog({ open, onOpenChange, title, description, placeholder, label, submitLabel, pendingLabel, initialValue, error, pending, extra, onSubmit, }: {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ title: ReactNode;
6
+ description?: ReactNode;
7
+ placeholder: string;
8
+ label?: ReactNode;
9
+ submitLabel?: string;
10
+ pendingLabel?: string;
11
+ initialValue?: string;
12
+ error?: ReactNode;
13
+ pending?: boolean;
14
+ extra?: ReactNode;
15
+ onSubmit: (value: string) => void | Promise<void>;
16
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useId, useState } from "react";
3
+ import { Button } from "@iloveagents/foundry-web-primitives";
4
+ import { Input } from "@iloveagents/foundry-web-primitives";
5
+ import { FormDialog } from "./form-dialog.js";
6
+ import { Field } from "@iloveagents/foundry-web-primitives";
7
+ export function NameDialog({ open, onOpenChange, title, description, placeholder, label = "Name", submitLabel = "Create", pendingLabel, initialValue = "", error, pending = false, extra, onSubmit, }) {
8
+ const inputId = useId();
9
+ const [value, setValue] = useState(initialValue);
10
+ useEffect(() => {
11
+ if (open)
12
+ setValue(initialValue);
13
+ }, [open, initialValue]);
14
+ return (_jsx(FormDialog, { open: open, onOpenChange: onOpenChange, title: title, description: description, size: "sm", footer: _jsxs(_Fragment, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), disabled: pending, children: "Cancel" }), _jsx(Button, { type: "submit", form: `${inputId}-form`, disabled: !value.trim() || pending, children: pending ? (pendingLabel ?? `${submitLabel}...`) : submitLabel })] }), children: _jsxs("form", { id: `${inputId}-form`, className: "space-y-4", onSubmit: (event) => {
15
+ event.preventDefault();
16
+ if (pending)
17
+ return;
18
+ const trimmed = value.trim();
19
+ if (!trimmed)
20
+ return;
21
+ void onSubmit(trimmed);
22
+ }, children: [_jsx(Field, { label: label, htmlFor: inputId, error: error, children: _jsx(Input, { id: inputId, value: value, onChange: (event) => setValue(event.target.value), placeholder: placeholder, disabled: pending, autoFocus: true }) }), extra] }) }));
23
+ }