@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
@@ -0,0 +1,199 @@
1
+ /**
2
+ * App Store — unified shared state for the AG-UI multi-page application.
3
+ *
4
+ * - Navigation context (synced from nav store via app-layout)
5
+ * - Persistent vs ephemeral context lifecycle
6
+ * - Shared UI state (selectedItemId, content, highlights)
7
+ * - Strict AG-UI state projection sent to the agent on every request
8
+ */
9
+ import { create } from "zustand";
10
+ const MAX_ITEMS = 10;
11
+ const MAX_LABEL_LENGTH = 500;
12
+ const MAX_CONTEXT_VALUE_LENGTH = 2000;
13
+ const TRUNCATION_SUFFIX = "...";
14
+ const DEFAULT_NAV_CONTEXT = {
15
+ group: null,
16
+ groupDescription: null,
17
+ groupMeta: {},
18
+ label: "Chat",
19
+ description: null,
20
+ meta: {},
21
+ contextInstructions: null,
22
+ };
23
+ function refDedupKey(payload) {
24
+ const selectionScope = typeof payload.meta?.selectionScope === "string" ? payload.meta.selectionScope : "block";
25
+ const targetPath = typeof payload.target?.path === "string"
26
+ ? payload.target.path
27
+ : typeof payload.meta?.targetPath === "string"
28
+ ? payload.meta.targetPath
29
+ : undefined;
30
+ if (selectionScope === "atom" && targetPath) {
31
+ return `${payload.refType}:${payload.refId}:atom:${targetPath}`;
32
+ }
33
+ if (selectionScope === "text") {
34
+ return `${payload.refType}:${payload.refId}:text:${payload.preview ?? ""}`;
35
+ }
36
+ return `${payload.refType}:${payload.refId}:block`;
37
+ }
38
+ function compactContextValue(value) {
39
+ if (typeof value === "string" && value.length > MAX_CONTEXT_VALUE_LENGTH) {
40
+ return `${value.slice(0, MAX_CONTEXT_VALUE_LENGTH - TRUNCATION_SUFFIX.length)}${TRUNCATION_SUFFIX}`;
41
+ }
42
+ return value;
43
+ }
44
+ function compactReferenceTarget(target) {
45
+ if (!target)
46
+ return undefined;
47
+ return {
48
+ ...target,
49
+ currentValue: compactContextValue(target.currentValue),
50
+ proposedValue: compactContextValue(target.proposedValue),
51
+ };
52
+ }
53
+ export const useAppStore = create((set, get) => ({
54
+ // Thread state
55
+ threadActive: false,
56
+ markThreadActive: () => set({ threadActive: true }),
57
+ // Current page
58
+ currentPage: "/chat",
59
+ setCurrentPage: (page) => {
60
+ const prev = get().currentPage;
61
+ if (prev === page)
62
+ return;
63
+ // Clear ephemeral context on navigation
64
+ set({
65
+ currentPage: page,
66
+ contextItems: get().contextItems.filter((i) => i.persistence !== "ephemeral"),
67
+ });
68
+ },
69
+ // Navigation context
70
+ navContext: DEFAULT_NAV_CONTEXT,
71
+ setNavContext: (ctx) => set({ navContext: ctx }),
72
+ // Shared UI state
73
+ selectedItemId: null,
74
+ setSelectedItemId: (id) => set({ selectedItemId: id }),
75
+ content: {},
76
+ updateContentField: (key, value) => set({ content: { ...get().content, [key]: value } }),
77
+ clearContent: () => set({ content: {} }),
78
+ highlights: [],
79
+ addHighlight: (id) => {
80
+ const { highlights } = get();
81
+ if (!highlights.includes(id)) {
82
+ set({ highlights: [...highlights, id] });
83
+ }
84
+ },
85
+ removeHighlight: (id) => set({ highlights: get().highlights.filter((h) => h !== id) }),
86
+ clearHighlights: () => set({ highlights: [] }),
87
+ // Context items
88
+ contextItems: [],
89
+ sentContext: {},
90
+ addContextItem: (item) => {
91
+ const { contextItems } = get();
92
+ // Deduplicate by type-specific key
93
+ const isDuplicate = contextItems.some((existing) => {
94
+ if (item.type === "page" && existing.type === "page") {
95
+ const a = item.payload;
96
+ const b = existing.payload;
97
+ return a.path === b.path;
98
+ }
99
+ if (item.type === "ref" && existing.type === "ref") {
100
+ const a = item.payload;
101
+ const b = existing.payload;
102
+ return refDedupKey(a) === refDedupKey(b);
103
+ }
104
+ // selection / note: dedup by label + sourcePage
105
+ return existing.label === item.label && existing.sourcePage === item.sourcePage;
106
+ });
107
+ if (isDuplicate)
108
+ return;
109
+ if (contextItems.length >= MAX_ITEMS)
110
+ return;
111
+ const label = item.label.length > MAX_LABEL_LENGTH
112
+ ? item.label.slice(0, MAX_LABEL_LENGTH) + "..."
113
+ : item.label;
114
+ set({
115
+ contextItems: [
116
+ ...contextItems,
117
+ { ...item, label, id: crypto.randomUUID(), createdAt: Date.now() },
118
+ ],
119
+ });
120
+ },
121
+ removeContextItem: (id) => set({ contextItems: get().contextItems.filter((i) => i.id !== id) }),
122
+ promoteContextItem: (id) => set({
123
+ contextItems: get().contextItems.map((i) => i.id === id ? { ...i, persistence: "persistent" } : i),
124
+ }),
125
+ clearAllContext: () => set({ contextItems: [], sentContext: {} }),
126
+ consumeContextForMessage: (messageId) => {
127
+ const { contextItems, sentContext } = get();
128
+ if (contextItems.length === 0)
129
+ return [];
130
+ const snapshot = [...contextItems];
131
+ // Keep persistent, remove ephemeral
132
+ set({
133
+ contextItems: contextItems.filter((i) => i.persistence === "persistent"),
134
+ sentContext: { ...sentContext, [messageId]: snapshot },
135
+ });
136
+ return snapshot;
137
+ },
138
+ // AG-UI state projection
139
+ getAgentState: () => {
140
+ const s = get();
141
+ return {
142
+ navigation: {
143
+ group: s.navContext.group,
144
+ groupDescription: s.navContext.groupDescription,
145
+ groupMeta: s.navContext.groupMeta,
146
+ page: s.currentPage,
147
+ label: s.navContext.label,
148
+ description: s.navContext.description,
149
+ meta: s.navContext.meta,
150
+ contextInstructions: s.navContext.contextInstructions,
151
+ },
152
+ selectedItemId: s.selectedItemId,
153
+ content: s.content,
154
+ highlights: s.highlights,
155
+ contextItems: s.contextItems.map(({ type, label, payload, sourcePage }) => {
156
+ switch (payload.kind) {
157
+ case "text":
158
+ return {
159
+ type,
160
+ label: label.slice(0, 200),
161
+ text: payload.text.slice(0, 500),
162
+ sourcePage,
163
+ };
164
+ case "page":
165
+ return { type, label, path: payload.path, sourcePage };
166
+ case "ref":
167
+ return {
168
+ type,
169
+ label: label.slice(0, 200),
170
+ refType: payload.refType,
171
+ refId: payload.refId,
172
+ preview: payload.preview?.slice(0, 100),
173
+ target: compactReferenceTarget(payload.target),
174
+ meta: payload.meta,
175
+ targetPath: typeof payload.target?.path === "string"
176
+ ? payload.target.path
177
+ : typeof payload.meta?.targetPath === "string"
178
+ ? payload.meta.targetPath
179
+ : undefined,
180
+ sourcePage,
181
+ };
182
+ default:
183
+ return { type, label, sourcePage };
184
+ }
185
+ }),
186
+ };
187
+ },
188
+ // Full reset
189
+ resetAll: () => set({
190
+ threadActive: false,
191
+ currentPage: "/chat",
192
+ navContext: DEFAULT_NAV_CONTEXT,
193
+ selectedItemId: null,
194
+ content: {},
195
+ highlights: [],
196
+ contextItems: [],
197
+ sentContext: {},
198
+ }),
199
+ }));
@@ -0,0 +1,20 @@
1
+ /**
2
+ * File attachment adapter for assistant-ui.
3
+ *
4
+ * Reads image files client-side as base64 and produces FileMessagePart content.
5
+ * Non-image documents are represented as text metadata so starter apps don't
6
+ * send malformed multimodal payloads to providers that require a separate file
7
+ * upload or document extraction pipeline.
8
+ *
9
+ * Supported: images (PNG, JPEG, WEBP, GIF). PDFs/DOCX can be attached, but the
10
+ * default starter only sends their metadata to the agent.
11
+ */
12
+ import type { AttachmentAdapter, PendingAttachment, CompleteAttachment, Attachment } from "@assistant-ui/react";
13
+ export declare class FileAttachmentAdapter implements AttachmentAdapter {
14
+ accept: string;
15
+ add({ file }: {
16
+ file: File;
17
+ }): Promise<PendingAttachment>;
18
+ send(attachment: PendingAttachment): Promise<CompleteAttachment>;
19
+ remove(_attachment: Attachment): Promise<void>;
20
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * File attachment adapter for assistant-ui.
3
+ *
4
+ * Reads image files client-side as base64 and produces FileMessagePart content.
5
+ * Non-image documents are represented as text metadata so starter apps don't
6
+ * send malformed multimodal payloads to providers that require a separate file
7
+ * upload or document extraction pipeline.
8
+ *
9
+ * Supported: images (PNG, JPEG, WEBP, GIF). PDFs/DOCX can be attached, but the
10
+ * default starter only sends their metadata to the agent.
11
+ */
12
+ const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20 MB
13
+ /** Infer MIME type from extension when browser reports empty file.type (common for .docx) */
14
+ const EXTENSION_MIME = {
15
+ ".pdf": "application/pdf",
16
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
17
+ ".png": "image/png",
18
+ ".jpg": "image/jpeg",
19
+ ".jpeg": "image/jpeg",
20
+ ".gif": "image/gif",
21
+ ".webp": "image/webp",
22
+ };
23
+ function resolveMimeType(file) {
24
+ if (file.type)
25
+ return file.type;
26
+ const ext = file.name.slice(file.name.lastIndexOf(".")).toLowerCase();
27
+ return EXTENSION_MIME[ext] ?? "application/octet-stream";
28
+ }
29
+ function fileToBase64(file) {
30
+ return new Promise((resolve, reject) => {
31
+ const reader = new FileReader();
32
+ reader.onload = () => {
33
+ const result = reader.result;
34
+ // Strip the data URI prefix (e.g. "data:image/png;base64,"), keep raw base64
35
+ resolve(result.split(",")[1]);
36
+ };
37
+ reader.onerror = reject;
38
+ reader.readAsDataURL(file);
39
+ });
40
+ }
41
+ export class FileAttachmentAdapter {
42
+ constructor() {
43
+ this.accept = "image/*,application/pdf,.pdf,.docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document";
44
+ }
45
+ async add({ file }) {
46
+ if (file.size > MAX_FILE_SIZE) {
47
+ throw new Error(`File too large (${Math.round(file.size / 1024 / 1024)} MB). Max 20 MB.`);
48
+ }
49
+ const mimeType = resolveMimeType(file);
50
+ return {
51
+ id: crypto.randomUUID(),
52
+ type: mimeType.startsWith("image/") ? "image" : "document",
53
+ name: file.name,
54
+ contentType: mimeType,
55
+ file,
56
+ status: { type: "requires-action", reason: "composer-send" },
57
+ };
58
+ }
59
+ async send(attachment) {
60
+ const mimeType = attachment.contentType ?? "application/octet-stream";
61
+ if (!mimeType.startsWith("image/")) {
62
+ return {
63
+ ...attachment,
64
+ status: { type: "complete" },
65
+ content: [
66
+ {
67
+ type: "text",
68
+ text: `[Attached file: ${attachment.name}` +
69
+ `; type: ${mimeType}` +
70
+ `; size: ${formatFileSize(attachment.file.size)}]`,
71
+ },
72
+ ],
73
+ };
74
+ }
75
+ const base64 = await fileToBase64(attachment.file);
76
+ return {
77
+ ...attachment,
78
+ status: { type: "complete" },
79
+ content: [
80
+ {
81
+ type: "file",
82
+ data: base64,
83
+ mimeType,
84
+ filename: attachment.name,
85
+ },
86
+ ],
87
+ };
88
+ }
89
+ async remove(_attachment) {
90
+ // No-op -- files are read client-side, nothing to clean up
91
+ }
92
+ }
93
+ function formatFileSize(bytes) {
94
+ if (bytes < 1024)
95
+ return `${bytes} B`;
96
+ if (bytes < 1024 * 1024)
97
+ return `${Math.round(bytes / 1024)} KB`;
98
+ return `${Math.round((bytes / 1024 / 1024) * 10) / 10} MB`;
99
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * AuthProvider — wraps the app with MSAL authentication.
3
+ *
4
+ * Uses MsalProvider's built-in redirect handling (no manual handleRedirectPromise).
5
+ * AuthGuard uses useMsal() + inProgress to avoid redirect loops.
6
+ *
7
+ * The provider expects a fully resolved MSAL configuration.
8
+ * Missing configuration is treated as an application bootstrap error.
9
+ */
10
+ import { type FC, type PropsWithChildren } from "react";
11
+ import { type MsalAuthConfig } from "@iloveagents/foundry-agent/msal";
12
+ interface AuthProviderProps {
13
+ config: MsalAuthConfig;
14
+ }
15
+ export declare const AuthProvider: FC<PropsWithChildren<AuthProviderProps>>;
16
+ export {};
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * AuthProvider — wraps the app with MSAL authentication.
4
+ *
5
+ * Uses MsalProvider's built-in redirect handling (no manual handleRedirectPromise).
6
+ * AuthGuard uses useMsal() + inProgress to avoid redirect loops.
7
+ *
8
+ * The provider expects a fully resolved MSAL configuration.
9
+ * Missing configuration is treated as an application bootstrap error.
10
+ */
11
+ import { useEffect, useState, } from "react";
12
+ import { authStore, initializeMsal, getMsalInstance, } from "@iloveagents/foundry-agent/msal";
13
+ export const AuthProvider = ({ config, children, }) => {
14
+ const [ready, setReady] = useState(false);
15
+ const [initError, setInitError] = useState(null);
16
+ const [MsalProvider, setMsalProvider] = useState(null);
17
+ const [useMsalHook, setUseMsalHook] = useState(null);
18
+ useEffect(() => {
19
+ let cancelled = false;
20
+ (async () => {
21
+ try {
22
+ await initializeMsal(config);
23
+ const msalReact = await import("@azure/msal-react");
24
+ if (cancelled)
25
+ return;
26
+ setMsalProvider(() => msalReact.MsalProvider);
27
+ setUseMsalHook(() => msalReact.useMsal);
28
+ setReady(true);
29
+ }
30
+ catch (error) {
31
+ console.error("[auth] Failed to initialize MSAL", error);
32
+ if (cancelled)
33
+ return;
34
+ setInitError(error instanceof Error ? error.message : "Failed to initialize authentication.");
35
+ setReady(true);
36
+ }
37
+ })();
38
+ return () => { cancelled = true; };
39
+ }, [config]);
40
+ if (!ready)
41
+ return null;
42
+ if (initError) {
43
+ return (_jsx("div", { className: "flex min-h-dvh items-center justify-center bg-background px-6 text-center", children: _jsxs("div", { className: "max-w-md rounded-2xl border border-border bg-card px-6 py-5 shadow-sm", children: [_jsx("h1", { className: "text-lg font-semibold text-foreground", children: "Authentication unavailable" }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: initError })] }) }));
44
+ }
45
+ if (!MsalProvider || !useMsalHook) {
46
+ return null;
47
+ }
48
+ const instance = getMsalInstance();
49
+ return (_jsx(MsalProvider, { instance: instance, children: _jsx(AuthGuard, { useMsalHook: useMsalHook, children: children }) }));
50
+ };
51
+ /**
52
+ * AuthGuard — waits for MsalProvider to finish processing redirects,
53
+ * then either syncs the account or triggers loginRedirect.
54
+ *
55
+ * Uses inProgress === "none" to avoid redirect loops (best practice from
56
+ * Microsoft docs — never call loginRedirect while another interaction is active).
57
+ */
58
+ const AuthGuard = ({ useMsalHook, children, }) => {
59
+ const { instance, accounts, inProgress } = useMsalHook();
60
+ const [authenticated, setAuthenticated] = useState(false);
61
+ useEffect(() => {
62
+ // Wait until MsalProvider finishes processing any redirect or startup
63
+ if (inProgress !== "none")
64
+ return;
65
+ if (accounts.length > 0) {
66
+ const account = accounts[0];
67
+ authStore.getState().signIn({
68
+ name: account.name ?? account.username,
69
+ email: account.username,
70
+ oid: account.localAccountId,
71
+ });
72
+ instance.setActiveAccount(account);
73
+ setAuthenticated(true);
74
+ }
75
+ else {
76
+ // No account and no interaction in progress — redirect to login.
77
+ // Only request OIDC scopes here. API/SPACES tokens are acquired
78
+ // silently later via acquireTokenSilent (consent is per-resource
79
+ // in Entra ID — including them here would show N consent screens).
80
+ instance.loginRedirect({
81
+ scopes: ["openid", "profile", "email"],
82
+ });
83
+ }
84
+ }, [instance, accounts, inProgress]);
85
+ if (!authenticated)
86
+ return null;
87
+ return _jsx(_Fragment, { children: children });
88
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Chat Bubble Store — manages chat display mode as a layout state.
3
+ *
4
+ * Four modes: CLOSED → BUBBLE → EXPANDED → FULL
5
+ * - CLOSED: only trigger button visible
6
+ * - BUBBLE (isOpen): floating 380x600 chat panel
7
+ * - EXPANDED (isExpanded): chat fills main area, page goes to panel
8
+ * - FULL: expanded with page panel closed
9
+ *
10
+ * No route changes. The URL stays the same in all modes.
11
+ */
12
+ interface ChatBubbleState {
13
+ /** Bubble floating panel is visible */
14
+ isOpen: boolean;
15
+ /** Chat is expanded into the main layout area */
16
+ isExpanded: boolean;
17
+ /** Page panel visible in expanded mode (false = FULL mode) */
18
+ showPagePanel: boolean;
19
+ open: () => void;
20
+ close: () => void;
21
+ toggle: () => void;
22
+ /** Expand: hide bubble, show chat in main layout */
23
+ expand: () => void;
24
+ /** Collapse: hide expanded chat, show bubble */
25
+ collapse: () => void;
26
+ /** Toggle page panel in expanded mode (EXPANDED ↔ FULL) */
27
+ togglePagePanel: () => void;
28
+ }
29
+ export declare const useChatBubbleStore: import("zustand").UseBoundStore<import("zustand").StoreApi<ChatBubbleState>>;
30
+ export {};
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Chat Bubble Store — manages chat display mode as a layout state.
3
+ *
4
+ * Four modes: CLOSED → BUBBLE → EXPANDED → FULL
5
+ * - CLOSED: only trigger button visible
6
+ * - BUBBLE (isOpen): floating 380x600 chat panel
7
+ * - EXPANDED (isExpanded): chat fills main area, page goes to panel
8
+ * - FULL: expanded with page panel closed
9
+ *
10
+ * No route changes. The URL stays the same in all modes.
11
+ */
12
+ import { create } from "zustand";
13
+ export const useChatBubbleStore = create((set) => ({
14
+ isOpen: false,
15
+ isExpanded: false,
16
+ showPagePanel: true,
17
+ open: () => set({ isOpen: true }),
18
+ close: () => set({ isOpen: false, isExpanded: false }),
19
+ toggle: () => set((s) => ({ isOpen: !s.isOpen })),
20
+ expand: () => set({ isOpen: false, isExpanded: true, showPagePanel: true }),
21
+ collapse: () => set({ isOpen: true, isExpanded: false }),
22
+ togglePagePanel: () => set((s) => ({ showPagePanel: !s.showPagePanel })),
23
+ }));
@@ -0,0 +1,10 @@
1
+ interface ChatLifecycleStore {
2
+ /**
3
+ * Called from :func:`useNewConversation` when the user starts a
4
+ * new thread. Spaces wires this to clear the active-chat sticky id.
5
+ */
6
+ onNewConversation: (() => void) | null;
7
+ setOnNewConversation: (fn: (() => void) | null) => void;
8
+ }
9
+ export declare const useChatLifecycleStore: import("zustand").UseBoundStore<import("zustand").StoreApi<ChatLifecycleStore>>;
10
+ export {};
@@ -21,17 +21,7 @@
21
21
  * ``onNewConversation`` null — the call site no-ops gracefully.
22
22
  */
23
23
  import { create } from "zustand";
24
-
25
- interface ChatLifecycleStore {
26
- /**
27
- * Called from :func:`useNewConversation` when the user starts a
28
- * new thread. Spaces wires this to clear the active-chat sticky id.
29
- */
30
- onNewConversation: (() => void) | null;
31
- setOnNewConversation: (fn: (() => void) | null) => void;
32
- }
33
-
34
- export const useChatLifecycleStore = create<ChatLifecycleStore>((set) => ({
35
- onNewConversation: null,
36
- setOnNewConversation: (fn) => set({ onNewConversation: fn }),
24
+ export const useChatLifecycleStore = create((set) => ({
25
+ onNewConversation: null,
26
+ setOnNewConversation: (fn) => set({ onNewConversation: fn }),
37
27
  }));
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Global Client Tools — registered into the dynamic tool registry at app init.
3
+ *
4
+ * Naming convention:
5
+ * ui_* — tools that affect the UI (navigate, theme, panel, content)
6
+ *
7
+ * Page-specific tools use their own prefix (e.g., page_search_documents).
8
+ * The adapter reads from the registry (not this file) on every request.
9
+ */
10
+ export declare function setNavigateFunction(fn: (path: string) => void): void;
11
+ export declare function registerGlobalTools(): void;