@iloveagents/foundry-web-ui 0.3.0 → 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 +14 -5
  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 -203
  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 -273
  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 -412
  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
@@ -1,508 +0,0 @@
1
- import { cloneElement, isValidElement, useCallback, useEffect, useState, type FC } from "react";
2
- import {
3
- MarkdownTextPrimitive,
4
- type SyntaxHighlighterProps,
5
- type CodeHeaderProps,
6
- } from "@assistant-ui/react-markdown";
7
- import remarkGfm from "remark-gfm";
8
- import type { Components } from "react-markdown";
9
- import type { Highlighter } from "shiki/bundle/web";
10
- import { Children, type ReactElement, type ReactNode } from "react";
11
- import { Check, Copy } from "lucide-react";
12
- import { cn } from "@iloveagents/foundry-web-primitives";
13
- import { useStore } from "zustand";
14
- import {
15
- citationStore,
16
- linkStore,
17
- resolveLinkHandler,
18
- type CitationResult,
19
- } from "@iloveagents/foundry-agent";
20
-
21
- /* ---------- Shiki syntax highlighter (lazy-loaded singleton) ---------- */
22
-
23
- let highlighterPromise: Promise<Highlighter> | null = null;
24
-
25
- function getHighlighter(): Promise<Highlighter> {
26
- if (!highlighterPromise) {
27
- highlighterPromise = import("shiki/bundle/web").then((mod) =>
28
- mod.createHighlighter({
29
- themes: ["github-dark"],
30
- langs: [],
31
- }),
32
- );
33
- }
34
- return highlighterPromise;
35
- }
36
-
37
- /**
38
- * Shared hook: returns highlighted HTML for a code string, or null while loading.
39
- * Always uses github-dark because code blocks have a dark background (bg-code-block)
40
- * regardless of the app's light/dark mode.
41
- */
42
- function useShikiHighlight(code: string, language: string | undefined): string | null {
43
- const [html, setHtml] = useState<string | null>(null);
44
-
45
- useEffect(() => {
46
- if (!language) return;
47
- let cancelled = false;
48
-
49
- getHighlighter().then(async (hl) => {
50
- if (cancelled) return;
51
- const loaded = hl.getLoadedLanguages();
52
- if (!loaded.includes(language as never)) {
53
- try {
54
- await hl.loadLanguage(language as never);
55
- } catch {
56
- // Language not supported -- keep html null (fallback rendering)
57
- return;
58
- }
59
- }
60
- if (cancelled) return;
61
- setHtml(hl.codeToHtml(code, { lang: language, theme: "github-dark" }));
62
- });
63
- return () => {
64
- cancelled = true;
65
- };
66
- }, [language, code]);
67
-
68
- return html;
69
- }
70
-
71
- /* ---------- Shared copy-to-clipboard button ---------- */
72
-
73
- const CopyButton: FC<{ text: string; className?: string }> = ({ text, className }) => {
74
- const [copied, setCopied] = useState(false);
75
-
76
- const handleCopy = useCallback(() => {
77
- navigator.clipboard.writeText(text).then(() => {
78
- setCopied(true);
79
- setTimeout(() => setCopied(false), 2000);
80
- });
81
- }, [text]);
82
-
83
- return (
84
- <button
85
- type="button"
86
- onClick={handleCopy}
87
- className={cn(
88
- "flex items-center justify-center size-7 rounded-md",
89
- "hover:bg-muted/50 transition-colors",
90
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
91
- className,
92
- )}
93
- aria-label="Copy code"
94
- >
95
- {copied ? (
96
- <Check className="size-3.5 text-success" />
97
- ) : (
98
- <Copy className="size-3.5 text-muted-foreground" />
99
- )}
100
- </button>
101
- );
102
- };
103
-
104
- /* ---------- Inline code ---------- */
105
-
106
- function textFromChildren(children: ReactNode): string | null {
107
- if (typeof children === "string") return children;
108
- if (typeof children === "number") return String(children);
109
- if (!Array.isArray(children)) return null;
110
- let out = "";
111
- for (const child of children) {
112
- if (typeof child === "string" || typeof child === "number") {
113
- out += String(child);
114
- } else {
115
- return null;
116
- }
117
- }
118
- return out;
119
- }
120
-
121
- function resolveHandledHref(rawHref: string): string | null {
122
- return resolveLinkHandler(rawHref)?.href ?? null;
123
- }
124
-
125
- function openHandledHref(href: string) {
126
- const resolved = resolveLinkHandler(href);
127
- resolved?.handler.openLink(resolved.href);
128
- }
129
-
130
- const TRAILING_LINK_PUNCT_RE = /[.,;:!?)\]]+$/;
131
-
132
- function trimLinkCandidate(value: string): { href: string; suffix: string } {
133
- const match = value.match(TRAILING_LINK_PUNCT_RE);
134
- if (!match) return { href: value, suffix: "" };
135
- return {
136
- href: value.slice(0, -match[0].length),
137
- suffix: match[0],
138
- };
139
- }
140
-
141
- function exactHandledHref(value: string): string | null {
142
- const trimmed = value.trim();
143
- if (!trimmed || /\s/.test(trimmed)) return null;
144
- const { href, suffix } = trimLinkCandidate(trimmed);
145
- if (suffix) return null;
146
- return resolveHandledHref(href);
147
- }
148
-
149
- const HANDLED_LINK_RE = /(https?:\/\/[^\s<>"`]+|\/[A-Za-z0-9][^\s<>"`]*)/g;
150
-
151
- const HandledLink: FC<
152
- {
153
- href: string;
154
- children: ReactNode;
155
- className?: string;
156
- } & React.AnchorHTMLAttributes<HTMLAnchorElement>
157
- > = ({ href, children, className, ...props }) => {
158
- const handledHref = useStore(linkStore, () => resolveHandledHref(href));
159
-
160
- if (!handledHref) {
161
- return (
162
- <a
163
- href={href}
164
- className={cn("text-primary hover:underline", className)}
165
- target="_blank"
166
- rel="noopener noreferrer"
167
- {...props}
168
- >
169
- {children}
170
- </a>
171
- );
172
- }
173
-
174
- return (
175
- <a
176
- href={handledHref}
177
- className={cn("text-primary hover:underline", className)}
178
- onClick={(event) => {
179
- event.preventDefault();
180
- openHandledHref(handledHref);
181
- }}
182
- {...props}
183
- >
184
- {children}
185
- </a>
186
- );
187
- };
188
-
189
- const AutoLinkedText: FC<{ text: string }> = ({ text }) => {
190
- useStore(linkStore, (s) => s.handlers);
191
-
192
- const parts: ReactNode[] = [];
193
- let lastIndex = 0;
194
- for (const match of text.matchAll(HANDLED_LINK_RE)) {
195
- const raw = match[0];
196
- const start = match.index ?? 0;
197
- const { href, suffix } = trimLinkCandidate(raw);
198
- const handledHref = resolveHandledHref(href);
199
- if (!handledHref) continue;
200
- if (start > lastIndex) parts.push(text.slice(lastIndex, start));
201
- parts.push(
202
- <HandledLink key={`${start}-${href}`} href={handledHref}>
203
- {href}
204
- </HandledLink>,
205
- );
206
- if (suffix) parts.push(suffix);
207
- lastIndex = start + raw.length;
208
- }
209
- if (lastIndex === 0) return <>{text}</>;
210
- if (lastIndex < text.length) parts.push(text.slice(lastIndex));
211
- return <>{parts}</>;
212
- };
213
-
214
- const InlineCode = ({ children, ...props }: React.HTMLAttributes<HTMLElement>) => {
215
- const value = textFromChildren(children);
216
- const handledHref = useStore(linkStore, () => (value ? exactHandledHref(value) : null));
217
- const className = "rounded bg-muted px-1.5 py-0.5 text-sm font-mono text-foreground";
218
-
219
- if (handledHref) {
220
- return (
221
- <HandledLink href={handledHref} className={className}>
222
- {children}
223
- </HandledLink>
224
- );
225
- }
226
-
227
- return (
228
- <code className={className} {...props}>
229
- {children}
230
- </code>
231
- );
232
- };
233
-
234
- /* ---------- Code block with header + highlighting (used by ReactMarkdown / tool panel) ---------- */
235
-
236
- const HighlightedCodeBlock: FC<{ language?: string; code: string }> = ({ language, code }) => {
237
- const html = useShikiHighlight(code, language);
238
-
239
- return (
240
- <div className="relative group my-4">
241
- <div
242
- className={cn(
243
- "flex items-center justify-between",
244
- "rounded-t-lg bg-code-block px-4 py-2",
245
- "border-b border-border/30",
246
- )}
247
- >
248
- <span className="text-xs font-medium text-muted-foreground">{language || "text"}</span>
249
- <CopyButton text={code} />
250
- </div>
251
- {html ? (
252
- <div
253
- dangerouslySetInnerHTML={{ __html: html }}
254
- className="[&_pre]:bg-code-block! [&_pre]:rounded-b-lg [&_pre]:rounded-t-none [&_pre]:p-4 [&_pre]:overflow-x-auto [&_pre]:text-sm [&_pre]:font-mono"
255
- />
256
- ) : (
257
- <pre className="rounded-b-lg bg-code-block p-4 overflow-x-auto text-sm font-mono text-code-block-foreground">
258
- <code>{code}</code>
259
- </pre>
260
- )}
261
- </div>
262
- );
263
- };
264
-
265
- /**
266
- * Extracts language and raw code from the <code> child that ReactMarkdown
267
- * renders inside <pre>. Returns { language, code } or null for non-code pre.
268
- */
269
- function extractCodeInfo(children: React.ReactNode): { language?: string; code: string } | null {
270
- const child = Array.isArray(children) ? children[0] : children;
271
- if (child && typeof child === "object" && "props" in child && child.props) {
272
- const className: string | undefined = child.props.className;
273
- const lang = className?.match(/language-(\S+)/)?.[1];
274
- const raw = child.props.children;
275
- const code =
276
- typeof raw === "string" ? raw : Array.isArray(raw) ? raw.join("") : String(raw ?? "");
277
- return { language: lang, code: code.replace(/\n$/, "") };
278
- }
279
- return null;
280
- }
281
-
282
- /* ---------- MarkdownTextPrimitive components (chat messages) ---------- */
283
-
284
- const ShikiHighlighter: FC<SyntaxHighlighterProps> = ({ language, code, components: C }) => {
285
- const html = useShikiHighlight(code, language);
286
-
287
- if (html) {
288
- return (
289
- <div
290
- dangerouslySetInnerHTML={{ __html: html }}
291
- className="[&_pre]:bg-code-block! [&_pre]:rounded-b-lg [&_pre]:p-4 [&_pre]:overflow-x-auto [&_pre]:text-sm [&_pre]:font-mono"
292
- />
293
- );
294
- }
295
-
296
- return (
297
- <C.Pre className="rounded-b-lg bg-code-block p-4 overflow-x-auto text-sm font-mono text-code-block-foreground">
298
- <C.Code>{code}</C.Code>
299
- </C.Pre>
300
- );
301
- };
302
-
303
- const CodeBlockHeader: FC<CodeHeaderProps> = ({ language, code }) => (
304
- <div
305
- className={cn(
306
- "flex items-center justify-between",
307
- "rounded-t-lg bg-code-block px-4 py-2 mt-4",
308
- "border-b border-border/30",
309
- )}
310
- >
311
- <span className="text-xs font-medium text-muted-foreground">{language || "text"}</span>
312
- <CopyButton text={code} />
313
- </div>
314
- );
315
-
316
- /* ---------- Shared markdown components (used by both chat + tool panel) ---------- */
317
-
318
- /**
319
- * Shared markdown component overrides used by both the chat messages
320
- * (MarkdownTextPrimitive) and the tool panel (ReactMarkdown).
321
- */
322
- export const markdownComponents: Components = {
323
- h1: ({ children, ...props }) => (
324
- <h1 className="text-2xl font-bold mt-6 mb-4 text-foreground" {...props}>
325
- {children}
326
- </h1>
327
- ),
328
- h2: ({ children, ...props }) => (
329
- <h2 className="text-xl font-bold mt-5 mb-3 text-foreground" {...props}>
330
- {children}
331
- </h2>
332
- ),
333
- h3: ({ children, ...props }) => (
334
- <h3 className="text-lg font-semibold mt-4 mb-2 text-foreground" {...props}>
335
- {children}
336
- </h3>
337
- ),
338
- h4: ({ children, ...props }) => (
339
- <h4 className="text-base font-semibold mt-3 mb-1.5 text-foreground" {...props}>
340
- {children}
341
- </h4>
342
- ),
343
- p: ({ children, ...props }) => (
344
- <p className="mb-2 leading-relaxed text-foreground" {...props}>
345
- {injectCitations(children)}
346
- </p>
347
- ),
348
- ul: ({ children, ...props }) => (
349
- <ul className="list-disc list-outside pl-6 mb-4 space-y-1 text-foreground" {...props}>
350
- {children}
351
- </ul>
352
- ),
353
- ol: ({ children, ...props }) => (
354
- <ol className="list-decimal list-outside pl-6 mb-4 space-y-1 text-foreground" {...props}>
355
- {children}
356
- </ol>
357
- ),
358
- li: ({ children, ...props }) => (
359
- <li className="text-foreground" {...props}>
360
- {injectCitations(children)}
361
- </li>
362
- ),
363
- pre: ({ children }) => {
364
- const info = extractCodeInfo(children);
365
- if (info) {
366
- return <HighlightedCodeBlock language={info.language} code={info.code} />;
367
- }
368
- return (
369
- <pre className="rounded-lg bg-code-block p-4 overflow-x-auto text-sm font-mono text-code-block-foreground my-4">
370
- {children}
371
- </pre>
372
- );
373
- },
374
- code: ({ className, children, ...props }: React.HTMLAttributes<HTMLElement>) => {
375
- // Fenced code blocks are wrapped in <pre><code> — the pre handles styling.
376
- // Only inline code (no language class, not inside pre) gets the InlineCode style.
377
- if (className?.includes("language-")) {
378
- return <code {...props}>{children}</code>;
379
- }
380
- return <InlineCode {...props}>{children}</InlineCode>;
381
- },
382
- blockquote: ({ children, ...props }) => (
383
- <blockquote
384
- className="border-l-4 border-primary pl-4 py-2 my-4 italic text-muted-foreground"
385
- {...props}
386
- >
387
- {children}
388
- </blockquote>
389
- ),
390
- a: ({ children, href, ...props }) => (
391
- <HandledLink href={href || ""} {...props}>
392
- {children}
393
- </HandledLink>
394
- ),
395
- hr: (props) => <hr className="my-6 border-border" {...props} />,
396
- table: ({ children, ...props }) => (
397
- <div className="overflow-x-auto my-4">
398
- <table className="min-w-full divide-y divide-border" {...props}>
399
- {children}
400
- </table>
401
- </div>
402
- ),
403
- thead: ({ children, ...props }) => (
404
- <thead className="bg-muted" {...props}>
405
- {children}
406
- </thead>
407
- ),
408
- tbody: ({ children, ...props }) => (
409
- <tbody className="divide-y divide-border" {...props}>
410
- {children}
411
- </tbody>
412
- ),
413
- tr: ({ children, ...props }) => <tr {...props}>{children}</tr>,
414
- th: ({ children, ...props }) => (
415
- <th className="px-4 py-2 text-left text-sm font-semibold text-foreground" {...props}>
416
- {children}
417
- </th>
418
- ),
419
- td: ({ children, ...props }) => (
420
- <td className="px-4 py-2 text-sm text-foreground" {...props}>
421
- {children}
422
- </td>
423
- ),
424
- strong: ({ children, ...props }) => (
425
- <strong className="font-bold" {...props}>
426
- {children}
427
- </strong>
428
- ),
429
- em: ({ children, ...props }) => (
430
- <em className="italic" {...props}>
431
- {children}
432
- </em>
433
- ),
434
- };
435
-
436
- /* ---------- Inline [n] citations (React-level, no remark plugin) ---------- */
437
-
438
- const CITE_RE = /(\[\d+\])/;
439
-
440
- /** Open a citation via the registered handler (provided by feature modules). */
441
- function openCitation(result: CitationResult) {
442
- const handler = citationStore.getState().handler;
443
- if (handler) handler.openCitation(result);
444
- }
445
-
446
- /** Clickable superscript citation: [1] → opens via registered handler. */
447
- const CitationRef: FC<{ index: number }> = ({ index }) => {
448
- const result = useStore(citationStore, (s) => s.results[index - 1]);
449
- const hasHandler = useStore(citationStore, (s) => !!s.handler);
450
- if (!result) return <span className="text-muted-foreground">[{index}]</span>;
451
- if (!hasHandler) return <span className="text-muted-foreground">[{index}]</span>;
452
-
453
- return (
454
- <span
455
- role="button"
456
- tabIndex={0}
457
- onClick={() => openCitation(result)}
458
- onKeyDown={(e) => {
459
- if (e.key === "Enter") openCitation(result);
460
- }}
461
- title={`${result.entity_name}, p. ${result.page_number}`}
462
- className="inline-flex items-center text-primary font-semibold cursor-pointer hover:underline"
463
- >
464
- <sup className="text-[0.7em] ml-0.5">[{index}]</sup>
465
- </span>
466
- );
467
- };
468
-
469
- /**
470
- * Walk React children and replace [n] text fragments with CitationRef
471
- * components. Only processes string children — leaves elements untouched.
472
- */
473
- export function injectCitations(children: ReactNode): ReactNode {
474
- return Children.map(children, (child) => {
475
- if (typeof child === "string") {
476
- if (!CITE_RE.test(child)) return <AutoLinkedText text={child} />;
477
-
478
- const parts = child.split(CITE_RE);
479
- return parts.map((part, i) => {
480
- const m = /^\[(\d+)\]$/.exec(part);
481
- if (m) return <CitationRef key={i} index={parseInt(m[1], 10)} />;
482
- return <AutoLinkedText key={i} text={part} />;
483
- });
484
- }
485
-
486
- if (!isValidElement(child)) return child;
487
-
488
- const element = child as ReactElement<{ children?: ReactNode }>;
489
- const nested = element.props.children;
490
- if (nested == null) return child;
491
- return cloneElement(element, undefined, injectCitations(nested));
492
- });
493
- }
494
-
495
- /* ---------- MarkdownText (chat messages) ---------- */
496
-
497
- export const MarkdownText: FC = () => {
498
- return (
499
- <MarkdownTextPrimitive
500
- remarkPlugins={[remarkGfm]}
501
- components={{
502
- ...markdownComponents,
503
- SyntaxHighlighter: ShikiHighlighter,
504
- CodeHeader: CodeBlockHeader,
505
- }}
506
- />
507
- );
508
- };
@@ -1,87 +0,0 @@
1
- import { useEffect, useId, useState, type ReactNode } from "react";
2
-
3
- import { Button } from "@iloveagents/foundry-web-primitives";
4
- import { Input } from "@iloveagents/foundry-web-primitives";
5
- import { FormDialog } from "./form-dialog.tsx";
6
- import { Field } from "@iloveagents/foundry-web-primitives";
7
-
8
- export function NameDialog({
9
- open,
10
- onOpenChange,
11
- title,
12
- description,
13
- placeholder,
14
- label = "Name",
15
- submitLabel = "Create",
16
- initialValue = "",
17
- error,
18
- pending = false,
19
- extra,
20
- onSubmit,
21
- }: {
22
- open: boolean;
23
- onOpenChange: (open: boolean) => void;
24
- title: ReactNode;
25
- description?: ReactNode;
26
- placeholder: string;
27
- label?: ReactNode;
28
- submitLabel?: string;
29
- initialValue?: string;
30
- error?: ReactNode;
31
- pending?: boolean;
32
- extra?: ReactNode;
33
- onSubmit: (value: string) => void | Promise<void>;
34
- }) {
35
- const inputId = useId();
36
- const [value, setValue] = useState(initialValue);
37
-
38
- useEffect(() => {
39
- if (open) setValue(initialValue);
40
- }, [open, initialValue]);
41
-
42
- return (
43
- <FormDialog
44
- open={open}
45
- onOpenChange={onOpenChange}
46
- title={title}
47
- description={description}
48
- size="sm"
49
- footer={
50
- <>
51
- <Button type="button" variant="ghost" onClick={() => onOpenChange(false)} disabled={pending}>
52
- Cancel
53
- </Button>
54
- <Button
55
- type="submit"
56
- form={`${inputId}-form`}
57
- disabled={!value.trim() || pending}
58
- >
59
- {pending ? `${submitLabel}...` : submitLabel}
60
- </Button>
61
- </>
62
- }
63
- >
64
- <form
65
- id={`${inputId}-form`}
66
- className="space-y-4"
67
- onSubmit={(event) => {
68
- event.preventDefault();
69
- const trimmed = value.trim();
70
- if (!trimmed) return;
71
- void onSubmit(trimmed);
72
- }}
73
- >
74
- <Field label={label} htmlFor={inputId} error={error}>
75
- <Input
76
- id={inputId}
77
- value={value}
78
- onChange={(event) => setValue(event.target.value)}
79
- placeholder={placeholder}
80
- autoFocus
81
- />
82
- </Field>
83
- {extra}
84
- </form>
85
- </FormDialog>
86
- );
87
- }