@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
@@ -1,373 +0,0 @@
1
- import { useRef, useState } from "react";
2
- import {
3
- ThreadPrimitive,
4
- ComposerPrimitive,
5
- MessagePrimitive,
6
- ActionBarPrimitive,
7
- useAuiState,
8
- useThreadViewport,
9
- } from "@assistant-ui/react";
10
- import {
11
- MessageSquare,
12
- X,
13
- Maximize2,
14
- SquarePen,
15
- ArrowUp,
16
- Square,
17
- ChevronDown,
18
- Copy,
19
- Check,
20
- Paperclip,
21
- } from "lucide-react";
22
- import { cn } from "@iloveagents/foundry-web-primitives";
23
- import { Button } from "@iloveagents/foundry-web-primitives";
24
- import { TooltipIconButton } from "./tooltip-icon-button.tsx";
25
- import { ContextPins } from "./context-bar.tsx";
26
- import { ComposerContextBadges, SentContextBadges } from "./context-badges.tsx";
27
- import { ComposerSubmitBridge } from "./composer-submit-bridge.tsx";
28
- import { MarkdownText } from "./markdown-text.tsx";
29
- import { LoadingIndicator } from "./loading-indicator.tsx";
30
- import { useAppStore } from "../lib/app-store.ts";
31
- import { useChatBubbleStore } from "../lib/chat-bubble-store.ts";
32
- import { useNewConversation } from "../lib/use-new-conversation.ts";
33
- import { RAIL_WIDTH, useSidebarStore } from "../lib/sidebar-store.ts";
34
-
35
- const CHAT_BUBBLE_INSET = 8;
36
-
37
- function BubbleAssistantMessage() {
38
- return (
39
- <MessagePrimitive.Root className="w-full mb-2 group">
40
- <div className="text-foreground text-sm leading-relaxed flex flex-col gap-2">
41
- <MessagePrimitive.Parts
42
- components={{
43
- Text: MarkdownText,
44
- }}
45
- />
46
- </div>
47
- <div className="flex h-7 w-8 items-center mt-1">
48
- <ActionBarPrimitive.Root
49
- hideWhenRunning
50
- className={cn(
51
- "pointer-events-none flex items-center gap-1 opacity-0 transition-opacity",
52
- "group-hover:pointer-events-auto group-hover:opacity-100",
53
- "group-focus-within:pointer-events-auto group-focus-within:opacity-100",
54
- )}
55
- >
56
- <ActionBarPrimitive.Copy asChild>
57
- <TooltipIconButton tooltip="Copy" size="icon" className="size-7">
58
- <MessagePrimitive.If copied={false}>
59
- <Copy className="size-3" />
60
- </MessagePrimitive.If>
61
- <MessagePrimitive.If copied>
62
- <Check className="size-3 text-primary" />
63
- </MessagePrimitive.If>
64
- </TooltipIconButton>
65
- </ActionBarPrimitive.Copy>
66
- </ActionBarPrimitive.Root>
67
- </div>
68
- </MessagePrimitive.Root>
69
- );
70
- }
71
-
72
- function BubbleUserMessage() {
73
- const messageId = useAuiState((s) => s.message.id);
74
-
75
- return (
76
- <MessagePrimitive.Root className="w-full mb-2">
77
- <div className="flex flex-col items-end gap-1">
78
- <SentContextBadges messageId={messageId} />
79
- <div className="max-w-[85%] bg-muted rounded-2xl px-3 py-2 text-sm text-foreground">
80
- <MessagePrimitive.Content />
81
- </div>
82
- </div>
83
- </MessagePrimitive.Root>
84
- );
85
- }
86
-
87
- function BubbleScrollToBottomButton() {
88
- const isAtBottom = useThreadViewport((s) => s.isAtBottom);
89
- const scrollToBottom = useThreadViewport((s) => s.scrollToBottom);
90
-
91
- if (isAtBottom) return null;
92
-
93
- return (
94
- <Button
95
- variant="outline"
96
- size="icon"
97
- className="absolute bottom-2 left-1/2 -translate-x-1/2 z-10 rounded-full shadow-md size-7"
98
- onClick={() => scrollToBottom({ behavior: "smooth" })}
99
- >
100
- <ChevronDown className="size-3" />
101
- </Button>
102
- );
103
- }
104
-
105
- export function ChatBubble() {
106
- const isOpen = useChatBubbleStore((s) => s.isOpen);
107
- const isExpanded = useChatBubbleStore((s) => s.isExpanded);
108
- const openBubble = useChatBubbleStore((s) => s.open);
109
- const closeBubble = useChatBubbleStore((s) => s.close);
110
- const expandChat = useChatBubbleStore((s) => s.expand);
111
- const startNewThread = useNewConversation({ navigateToChat: false, preserveNavigation: true });
112
- const isSidebarOpen = useSidebarStore((s) => s.isOpen);
113
- const sidebarWidth = useSidebarStore((s) => s.width);
114
- const pageLabel = useAppStore((s) => s.navContext.label);
115
- const currentPage = useAppStore((s) => s.currentPage);
116
- const threadActive = useAppStore((s) => s.threadActive);
117
-
118
- // Draggable position: null = use default CSS anchored with equal left/bottom inset.
119
- const [dragPos, setDragPos] = useState<{ x: number; y: number } | null>(null);
120
- const dragStart = useRef<{ mx: number; my: number; px: number; py: number } | null>(null);
121
- const panelRef = useRef<HTMLDivElement>(null);
122
-
123
- // Close and reset drag position so it reopens at the default spot
124
- const handleClose = () => {
125
- setDragPos(null);
126
- closeBubble();
127
- };
128
-
129
- const onHeaderPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
130
- // Don't start drag when clicking header buttons
131
- if ((e.target as HTMLElement).closest("button")) return;
132
- const panel = panelRef.current;
133
- if (!panel) return;
134
- e.preventDefault();
135
- const rect = panel.getBoundingClientRect();
136
- dragStart.current = { mx: e.clientX, my: e.clientY, px: rect.left, py: rect.top };
137
- // Capture the initial position so first pointer-move works correctly
138
- setDragPos({ x: rect.left, y: rect.top });
139
- e.currentTarget.setPointerCapture(e.pointerId);
140
- };
141
-
142
- const onHeaderPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
143
- if (!dragStart.current) return;
144
- const panel = panelRef.current;
145
- const panelW = panel?.offsetWidth ?? 380;
146
- const panelH = panel?.offsetHeight ?? 600;
147
- const dx = e.clientX - dragStart.current.mx;
148
- const dy = e.clientY - dragStart.current.my;
149
- // Clamp within viewport so the panel can't be dragged fully off-screen
150
- const x = Math.max(0, Math.min(window.innerWidth - panelW, dragStart.current.px + dx));
151
- const y = Math.max(0, Math.min(window.innerHeight - panelH, dragStart.current.py + dy));
152
- setDragPos({ x, y });
153
- };
154
-
155
- const onHeaderPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
156
- if (!dragStart.current) return;
157
- dragStart.current = null;
158
- e.currentTarget.releasePointerCapture(e.pointerId);
159
- };
160
-
161
- const onHeaderLostPointerCapture = () => {
162
- dragStart.current = null;
163
- };
164
-
165
- // Hide bubble on chat page (already full-screen) and in expanded mode
166
- if (currentPage === "/" || isExpanded) return null;
167
-
168
- return (
169
- <>
170
- {!isOpen && (
171
- <div
172
- aria-hidden="true"
173
- className={cn(
174
- "pointer-events-none fixed bottom-0 left-0 z-40 hidden md:block",
175
- "border-r border-sidebar-border",
176
- "bg-gradient-to-b from-sidebar/0 via-sidebar/88 to-sidebar/96",
177
- "supports-[backdrop-filter]:backdrop-blur",
178
- )}
179
- style={{
180
- width: isSidebarOpen ? sidebarWidth : RAIL_WIDTH,
181
- height: 64,
182
- }}
183
- />
184
- )}
185
-
186
- {/* Trigger button — bottom-left corner */}
187
- {!isOpen && (
188
- <button
189
- type="button"
190
- onClick={openBubble}
191
- className={cn(
192
- "fixed bottom-4 z-50",
193
- "size-10 rounded-full",
194
- "bg-primary text-primary-foreground shadow-lg",
195
- "flex items-center justify-center",
196
- "hover:bg-primary/90 transition-all duration-200",
197
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
198
- )}
199
- style={{ left: CHAT_BUBBLE_INSET, bottom: CHAT_BUBBLE_INSET }}
200
- aria-label="Open chat"
201
- >
202
- <MessageSquare className="size-4" />
203
- </button>
204
- )}
205
-
206
- {/* Chat panel — draggable, anchored bottom-left by default */}
207
- {isOpen && (
208
- <div
209
- ref={panelRef}
210
- className={cn(
211
- "fixed z-50",
212
- "rounded-2xl border border-border",
213
- "bg-background shadow-2xl",
214
- "flex flex-col overflow-hidden",
215
- "w-95 h-150",
216
- )}
217
- style={
218
- dragPos
219
- ? { left: dragPos.x, top: dragPos.y }
220
- : { left: CHAT_BUBBLE_INSET, bottom: CHAT_BUBBLE_INSET }
221
- }
222
- >
223
- {/* Header — drag handle */}
224
- <div
225
- className={cn(
226
- "flex items-center justify-between px-4 py-2.5 border-b border-border shrink-0",
227
- "cursor-grab active:cursor-grabbing select-none",
228
- )}
229
- style={{ touchAction: "none" }}
230
- onPointerDown={onHeaderPointerDown}
231
- onPointerMove={onHeaderPointerMove}
232
- onPointerUp={onHeaderPointerUp}
233
- onLostPointerCapture={onHeaderLostPointerCapture}
234
- >
235
- <div className="flex flex-col min-w-0">
236
- <div className="flex items-center gap-1.5 text-sm">
237
- <MessageSquare className="size-3.5 text-primary shrink-0" />
238
- <span className="font-semibold text-foreground truncate">{pageLabel}</span>
239
- </div>
240
- <span className="text-xs text-muted-foreground">
241
- {threadActive ? "Active thread" : "New conversation"}
242
- </span>
243
- </div>
244
- <div className="flex items-center gap-0.5">
245
- <TooltipIconButton
246
- tooltip="New Thread"
247
- size="icon"
248
- className="size-7"
249
- onClick={startNewThread}
250
- >
251
- <SquarePen className="size-3.5" />
252
- </TooltipIconButton>
253
- <TooltipIconButton
254
- tooltip="Expand chat"
255
- size="icon"
256
- className="size-7"
257
- onClick={expandChat}
258
- >
259
- <Maximize2 className="size-3.5" />
260
- </TooltipIconButton>
261
- <TooltipIconButton
262
- tooltip="Close"
263
- size="icon"
264
- className="size-7"
265
- onClick={handleClose}
266
- >
267
- <X className="size-4" />
268
- </TooltipIconButton>
269
- </div>
270
- </div>
271
-
272
- {/* Thread */}
273
- <ThreadPrimitive.Root className="flex flex-col flex-1 overflow-hidden">
274
- <div className="relative flex-1 overflow-hidden">
275
- <ThreadPrimitive.Viewport className="absolute inset-0 overflow-y-auto">
276
- <ThreadPrimitive.Empty>
277
- <div className="flex h-full items-center justify-center p-4">
278
- <p className="text-sm text-muted-foreground text-center">
279
- Ask the agent about this page
280
- </p>
281
- </div>
282
- </ThreadPrimitive.Empty>
283
-
284
- <ThreadPrimitive.If empty={false}>
285
- <div className="px-4 pt-4">
286
- <ThreadPrimitive.Messages
287
- components={{
288
- UserMessage: BubbleUserMessage,
289
- AssistantMessage: BubbleAssistantMessage,
290
- }}
291
- />
292
- <ThreadPrimitive.If running>
293
- <LoadingIndicator />
294
- </ThreadPrimitive.If>
295
- </div>
296
- </ThreadPrimitive.If>
297
- </ThreadPrimitive.Viewport>
298
- <ThreadPrimitive.If empty={false}>
299
- <BubbleScrollToBottomButton />
300
- </ThreadPrimitive.If>
301
- </div>
302
-
303
- {/* Composer — same pattern as full chat: clip + pin + input + send */}
304
- <div className="shrink-0 border-t border-border px-3 py-3">
305
- <ComposerPrimitive.Root
306
- className={cn(
307
- "flex flex-col gap-1",
308
- "rounded-2xl border border-input",
309
- "bg-background shadow-sm",
310
- "px-3 py-2",
311
- "focus-within:border-primary/50",
312
- "transition-all duration-200",
313
- )}
314
- >
315
- <ComposerSubmitBridge />
316
- <ComposerContextBadges />
317
- <div className="flex items-center gap-2">
318
- <div className="flex items-center -space-x-1 shrink-0">
319
- <ComposerPrimitive.AddAttachment asChild>
320
- <TooltipIconButton
321
- tooltip="Attach file"
322
- size="icon"
323
- className="size-7 shrink-0"
324
- >
325
- <Paperclip className="size-3.5" />
326
- </TooltipIconButton>
327
- </ComposerPrimitive.AddAttachment>
328
- <ContextPins compact showPinButton />
329
- </div>
330
- <ComposerPrimitive.Input
331
- autoFocus
332
- placeholder="Message..."
333
- rows={1}
334
- className={cn(
335
- "flex-1 bg-transparent",
336
- "py-1 text-sm",
337
- "text-foreground outline-none",
338
- "placeholder:text-muted-foreground",
339
- "resize-none max-h-20",
340
- )}
341
- />
342
- <ThreadPrimitive.If running>
343
- <ComposerPrimitive.Cancel asChild>
344
- <Button
345
- variant="secondary"
346
- size="icon"
347
- className="rounded-full size-8 shrink-0"
348
- aria-label="Stop"
349
- >
350
- <Square className="size-3" />
351
- </Button>
352
- </ComposerPrimitive.Cancel>
353
- </ThreadPrimitive.If>
354
- <ThreadPrimitive.If running={false}>
355
- <ComposerPrimitive.Send asChild>
356
- <Button
357
- size="icon"
358
- className="rounded-full size-8 shrink-0"
359
- aria-label="Send"
360
- >
361
- <ArrowUp className="size-4" />
362
- </Button>
363
- </ComposerPrimitive.Send>
364
- </ThreadPrimitive.If>
365
- </div>
366
- </ComposerPrimitive.Root>
367
- </div>
368
- </ThreadPrimitive.Root>
369
- </div>
370
- )}
371
- </>
372
- );
373
- }
@@ -1,44 +0,0 @@
1
- import { type FC } from "react";
2
- import { Link } from "react-router";
3
- import { SquarePen, Menu } from "lucide-react";
4
- import { cn } from "@iloveagents/foundry-web-primitives";
5
- import { AppBrand } from "./app-brand.tsx";
6
- import { TooltipIconButton } from "./tooltip-icon-button.tsx";
7
- import { ThemeToggle } from "./theme-toggle.tsx";
8
- import { UserMenu } from "./user-menu.tsx";
9
- import { useSidebarStore } from "../lib/sidebar-store.ts";
10
- import { useNewConversation } from "../lib/use-new-conversation.ts";
11
-
12
- export const ChatHeader: FC = () => {
13
- const handleNewConversation = useNewConversation();
14
- const toggleMobile = useSidebarStore((s) => s.toggleMobile);
15
- const isSidebarOpen = useSidebarStore((s) => s.isOpen);
16
-
17
- return (
18
- <div className={cn("shrink-0", "bg-background border-b border-border")}>
19
- <div className="px-4 py-3 flex items-center justify-between">
20
- <div className="flex items-center gap-2">
21
- <TooltipIconButton tooltip="Open sidebar" onClick={toggleMobile} className="md:hidden">
22
- <Menu className="size-5" />
23
- </TooltipIconButton>
24
- <Link
25
- to="/"
26
- className={cn(
27
- "flex items-center gap-2.5 hover:opacity-80 transition-opacity",
28
- isSidebarOpen && "md:hidden",
29
- )}
30
- >
31
- <AppBrand labelClassName="text-sm font-semibold text-foreground" />
32
- </Link>
33
- </div>
34
- <div className="flex items-center gap-1">
35
- <ThemeToggle />
36
- <TooltipIconButton tooltip="New Thread" size="icon" onClick={handleNewConversation}>
37
- <SquarePen className="size-4 text-primary" />
38
- </TooltipIconButton>
39
- <UserMenu />
40
- </div>
41
- </div>
42
- </div>
43
- );
44
- };
@@ -1,230 +0,0 @@
1
- /**
2
- * Client Tool Executor — registers custom UIs for client-side tools.
3
- *
4
- * Tool execution happens in the AGUIRunner (client-side tools intercepted at
5
- * `tool-call-end`). This component only registers the UI rendering and
6
- * injects the navigate function (which requires React Router context).
7
- *
8
- * Modern assistant-ui v0.12 pattern: each tool UI is a top-level
9
- * `makeAssistantToolUI(...)` component. We mount them as children of
10
- * `<ClientToolExecutor>` inside the AssistantRuntimeProvider tree.
11
- */
12
-
13
- import { makeAssistantToolUI } from "@assistant-ui/react";
14
- import { useNavigate } from "react-router";
15
- import { useEffect } from "react";
16
- import { setNavigateFunction } from "../lib/client-tools.ts";
17
- import { useToolPanelStore } from "../lib/tool-panel-store.ts";
18
- import { ConfirmationCard } from "./confirmation-card.tsx";
19
- import { ToolCallCard } from "./tool-call-card.tsx";
20
- import {
21
- Navigation,
22
- Palette,
23
- PanelRight,
24
- MousePointerClick,
25
- PenLine,
26
- Highlighter,
27
- ShieldCheck,
28
- Info,
29
- } from "lucide-react";
30
-
31
- const NavigateUI = makeAssistantToolUI<{ path: string }, { status: string }>({
32
- toolName: "ui_navigate",
33
- render: ({ args, result, status }) => {
34
- const navigate = useNavigate();
35
- return (
36
- <ToolCallCard
37
- icon={<Navigation className="size-3.5" />}
38
- title="Navigate"
39
- description={
40
- status.type === "running"
41
- ? "Navigating..."
42
- : result?.status === "success"
43
- ? `Navigated to ${args?.path}`
44
- : "Navigation failed"
45
- }
46
- status={status}
47
- action={
48
- status.type === "complete" && args?.path
49
- ? { label: "Go to page", onClick: () => navigate(args.path) }
50
- : undefined
51
- }
52
- className="w-full"
53
- />
54
- );
55
- },
56
- });
57
-
58
- const SetThemeUI = makeAssistantToolUI<{ mode: string }, { status: string }>({
59
- toolName: "ui_set_theme",
60
- render: ({ args, result, status }) => (
61
- <ToolCallCard
62
- icon={<Palette className="size-3.5" />}
63
- title="Set Theme"
64
- description={
65
- status.type === "running"
66
- ? "Changing theme..."
67
- : result?.status === "success"
68
- ? `Theme set to ${args?.mode}`
69
- : "Theme change failed"
70
- }
71
- status={status}
72
- className="w-full"
73
- />
74
- ),
75
- });
76
-
77
- const OpenPanelUI = makeAssistantToolUI<
78
- { title: string; path?: string; content?: string; type?: string },
79
- { status: string }
80
- >({
81
- toolName: "ui_open_panel",
82
- render: ({ args, result, status }) => {
83
- const handleReopen = () => {
84
- if (args?.path) {
85
- useToolPanelStore.getState().openPanel({
86
- title: args.title,
87
- content: args.path,
88
- type: "page",
89
- });
90
- } else if (args?.content) {
91
- useToolPanelStore.getState().openPanel({
92
- title: args.title,
93
- content: args.content,
94
- type: (args.type as "markdown" | "text" | "page" | "pdf") || "markdown",
95
- });
96
- }
97
- };
98
-
99
- return (
100
- <ToolCallCard
101
- icon={<PanelRight className="size-3.5" />}
102
- title="Open Panel"
103
- description={
104
- status.type === "running"
105
- ? "Opening panel..."
106
- : result?.status === "success"
107
- ? `Panel: ${args?.title}`
108
- : "Panel failed"
109
- }
110
- status={status}
111
- action={
112
- status.type === "complete" && result?.status === "success"
113
- ? { label: "View", onClick: handleReopen }
114
- : undefined
115
- }
116
- className="w-full"
117
- />
118
- );
119
- },
120
- });
121
-
122
- const SelectItemUI = makeAssistantToolUI<{ itemId?: string }, unknown>({
123
- toolName: "ui_select_item",
124
- render: ({ args, status }) => (
125
- <ToolCallCard
126
- icon={<MousePointerClick className="size-3.5" />}
127
- title="Select Item"
128
- description={
129
- status.type === "running"
130
- ? "Selecting..."
131
- : args?.itemId
132
- ? `Selected: ${args.itemId}`
133
- : "Selection cleared"
134
- }
135
- status={status}
136
- className="w-full"
137
- />
138
- ),
139
- });
140
-
141
- const UpdateContentUI = makeAssistantToolUI<{ fields?: Record<string, unknown> }, unknown>({
142
- toolName: "ui_update_content",
143
- render: ({ args, status }) => {
144
- const fieldNames = args?.fields ? Object.keys(args.fields).join(", ") : "";
145
- return (
146
- <ToolCallCard
147
- icon={<PenLine className="size-3.5" />}
148
- title="Update Content"
149
- description={status.type === "running" ? "Updating..." : `Updated: ${fieldNames}`}
150
- status={status}
151
- className="w-full"
152
- />
153
- );
154
- },
155
- });
156
-
157
- const HighlightUI = makeAssistantToolUI<{ entityId?: string }, unknown>({
158
- toolName: "ui_highlight",
159
- render: ({ args, status }) => (
160
- <ToolCallCard
161
- icon={<Highlighter className="size-3.5" />}
162
- title="Highlight"
163
- description={
164
- status.type === "running" ? "Highlighting..." : `Highlighted: ${args?.entityId}`
165
- }
166
- status={status}
167
- className="w-full"
168
- />
169
- ),
170
- });
171
-
172
- const ConfirmUI = makeAssistantToolUI<
173
- { action?: string; details?: string },
174
- { status?: string }
175
- >({
176
- toolName: "ui_confirm",
177
- render: ({ args, result, status }) => {
178
- if (result?.status === "pending") {
179
- return (
180
- <ConfirmationCard
181
- action={args?.action ?? "Unknown action"}
182
- details={args?.details}
183
- status={status}
184
- />
185
- );
186
- }
187
- return (
188
- <ToolCallCard
189
- icon={<ShieldCheck className="size-3.5" />}
190
- title="Confirmation Required"
191
- description={args?.action ?? "Confirmation requested"}
192
- status={status}
193
- className="w-full"
194
- />
195
- );
196
- },
197
- });
198
-
199
- const GetContextUI = makeAssistantToolUI<unknown, unknown>({
200
- toolName: "ui_get_context",
201
- render: ({ status }) => (
202
- <ToolCallCard
203
- icon={<Info className="size-3.5" />}
204
- title="Get Context"
205
- description={status.type === "running" ? "Reading context..." : "Context loaded"}
206
- status={status}
207
- className="w-full"
208
- />
209
- ),
210
- });
211
-
212
- export function ClientToolExecutor() {
213
- const navigate = useNavigate();
214
- useEffect(() => {
215
- setNavigateFunction(navigate);
216
- }, [navigate]);
217
-
218
- return (
219
- <>
220
- <NavigateUI />
221
- <SetThemeUI />
222
- <OpenPanelUI />
223
- <SelectItemUI />
224
- <UpdateContentUI />
225
- <HighlightUI />
226
- <ConfirmUI />
227
- <GetContextUI />
228
- </>
229
- );
230
- }
@@ -1,37 +0,0 @@
1
- import type { ReactNode } from "react";
2
- import type { LucideIcon } from "lucide-react";
3
- import { SearchX } from "lucide-react";
4
-
5
- import { cn } from "@iloveagents/foundry-web-primitives";
6
-
7
- export function CollectionEmptyState({
8
- icon: Icon = SearchX,
9
- title,
10
- description,
11
- action,
12
- className,
13
- }: {
14
- icon?: LucideIcon | null;
15
- title: string;
16
- description?: string;
17
- action?: ReactNode;
18
- className?: string;
19
- }) {
20
- return (
21
- <div
22
- className={cn(
23
- "flex min-h-[18rem] flex-col items-center justify-center rounded-[1.75rem] bg-muted/18 px-6 py-10 text-center ring-1 ring-border/35",
24
- className,
25
- )}
26
- >
27
- {Icon ? (
28
- <div className="mb-4 flex size-14 items-center justify-center rounded-2xl bg-background/85 text-primary shadow-[0_8px_24px_-18px_rgb(15_23_42/0.35)]">
29
- <Icon className="size-6" />
30
- </div>
31
- ) : null}
32
- <div className="text-base font-semibold text-foreground">{title}</div>
33
- {description ? <p className="mt-2 max-w-xl text-sm leading-6 text-muted-foreground">{description}</p> : null}
34
- {action ? <div className="mt-5">{action}</div> : null}
35
- </div>
36
- );
37
- }