@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,361 +0,0 @@
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
-
10
- import { create } from "zustand";
11
-
12
- export type ContextPersistence = "persistent" | "ephemeral";
13
-
14
- // --- Domain-agnostic context payloads ---
15
-
16
- export interface TextPayload {
17
- kind: "text";
18
- text: string;
19
- }
20
-
21
- export interface PagePayload {
22
- kind: "page";
23
- path: string;
24
- label: string;
25
- meta?: Record<string, unknown>;
26
- }
27
-
28
- export interface ReferencePayload {
29
- kind: "ref";
30
- /** Domain decides: "block", "entity", "row", "cell", etc. */
31
- refType: string;
32
- /** Domain-specific ID: "entityId:blockId", "table:row", etc. */
33
- refId: string;
34
- /** Optional display text (~100 chars) */
35
- preview?: string;
36
- /**
37
- * Optional exact JSON target inside the referenced domain object.
38
- *
39
- * This keeps the existing refType/refId contract stable for deep links
40
- * while allowing feature modules to target one schema-declared field,
41
- * table cell, JSON atom, or app-defined custom value precisely.
42
- */
43
- target?: {
44
- kind?: string;
45
- path?: string;
46
- label?: string;
47
- scope?: string;
48
- containerId?: string;
49
- containerLabel?: string;
50
- currentValue?: unknown;
51
- proposedValue?: unknown;
52
- changeIds?: string[];
53
- meta?: Record<string, unknown>;
54
- };
55
- /** Domain-specific data the agent can use */
56
- meta?: Record<string, unknown>;
57
- }
58
-
59
- export type ContextPayload = TextPayload | PagePayload | ReferencePayload;
60
-
61
- export type ContextItemType = "selection" | "page" | "ref" | "note";
62
-
63
- export interface ContextItem {
64
- id: string;
65
- type: ContextItemType;
66
- label: string;
67
- payload: ContextPayload;
68
- sourcePage: string;
69
- persistence: ContextPersistence;
70
- createdAt: number;
71
- }
72
-
73
- export interface NavContext {
74
- group: string | null;
75
- groupDescription: string | null;
76
- groupMeta: Record<string, unknown>;
77
- label: string;
78
- description: string | null;
79
- meta: Record<string, unknown>;
80
- /** Merged instructions from group + page (assembled by app-layout from hierarchy) */
81
- contextInstructions: string | null;
82
- }
83
-
84
- export interface AgentStateProjection {
85
- navigation: {
86
- group: string | null;
87
- groupDescription: string | null;
88
- groupMeta: Record<string, unknown>;
89
- page: string;
90
- label: string;
91
- description: string | null;
92
- meta: Record<string, unknown>;
93
- contextInstructions: string | null;
94
- };
95
- selectedItemId: string | null;
96
- content: Record<string, unknown>;
97
- highlights: string[];
98
- /** Compact context items — text truncated, pages as paths, refs as IDs */
99
- contextItems: Array<Record<string, unknown>>;
100
- }
101
-
102
- const MAX_ITEMS = 10;
103
- const MAX_LABEL_LENGTH = 500;
104
- const MAX_CONTEXT_VALUE_LENGTH = 2_000;
105
- const TRUNCATION_SUFFIX = "...";
106
-
107
- const DEFAULT_NAV_CONTEXT: NavContext = {
108
- group: null,
109
- groupDescription: null,
110
- groupMeta: {},
111
- label: "Chat",
112
- description: null,
113
- meta: {},
114
- contextInstructions: null,
115
- };
116
-
117
- function refDedupKey(payload: ReferencePayload): string {
118
- const selectionScope =
119
- typeof payload.meta?.selectionScope === "string" ? payload.meta.selectionScope : "block";
120
- const targetPath =
121
- typeof payload.target?.path === "string"
122
- ? payload.target.path
123
- : typeof payload.meta?.targetPath === "string"
124
- ? payload.meta.targetPath
125
- : undefined;
126
-
127
- if (selectionScope === "atom" && targetPath) {
128
- return `${payload.refType}:${payload.refId}:atom:${targetPath}`;
129
- }
130
- if (selectionScope === "text") {
131
- return `${payload.refType}:${payload.refId}:text:${payload.preview ?? ""}`;
132
- }
133
- return `${payload.refType}:${payload.refId}:block`;
134
- }
135
-
136
- function compactContextValue(value: unknown): unknown {
137
- if (typeof value === "string" && value.length > MAX_CONTEXT_VALUE_LENGTH) {
138
- return `${value.slice(0, MAX_CONTEXT_VALUE_LENGTH - TRUNCATION_SUFFIX.length)}${TRUNCATION_SUFFIX}`;
139
- }
140
- return value;
141
- }
142
-
143
- function compactReferenceTarget(
144
- target: ReferencePayload["target"],
145
- ): ReferencePayload["target"] {
146
- if (!target) return undefined;
147
- return {
148
- ...target,
149
- currentValue: compactContextValue(target.currentValue),
150
- proposedValue: compactContextValue(target.proposedValue),
151
- };
152
- }
153
-
154
- interface AppState {
155
- // Thread state
156
- threadActive: boolean;
157
- markThreadActive: () => void;
158
-
159
- // Current page (set by router, used for ephemeral context clearing)
160
- currentPage: string;
161
- setCurrentPage: (page: string) => void;
162
-
163
- // Navigation context (synced from nav store by app-layout)
164
- navContext: NavContext;
165
- setNavContext: (ctx: NavContext) => void;
166
-
167
- // Shared UI state
168
- selectedItemId: string | null;
169
- setSelectedItemId: (id: string | null) => void;
170
- content: Record<string, unknown>;
171
- updateContentField: (key: string, value: unknown) => void;
172
- clearContent: () => void;
173
- highlights: string[];
174
- addHighlight: (id: string) => void;
175
- removeHighlight: (id: string) => void;
176
- clearHighlights: () => void;
177
-
178
- // Context items
179
- contextItems: ContextItem[];
180
- sentContext: Record<string, ContextItem[]>;
181
- addContextItem: (item: Omit<ContextItem, "id" | "createdAt">) => void;
182
- removeContextItem: (id: string) => void;
183
- promoteContextItem: (id: string) => void;
184
- clearAllContext: () => void;
185
- consumeContextForMessage: (messageId: string) => ContextItem[];
186
-
187
- // AG-UI state projection
188
- getAgentState: () => AgentStateProjection;
189
-
190
- // Full reset (new conversation)
191
- resetAll: () => void;
192
- }
193
-
194
- export const useAppStore = create<AppState>((set, get) => ({
195
- // Thread state
196
- threadActive: false,
197
- markThreadActive: () => set({ threadActive: true }),
198
-
199
- // Current page
200
- currentPage: "/chat",
201
- setCurrentPage: (page) => {
202
- const prev = get().currentPage;
203
- if (prev === page) return;
204
-
205
- // Clear ephemeral context on navigation
206
- set({
207
- currentPage: page,
208
- contextItems: get().contextItems.filter((i) => i.persistence !== "ephemeral"),
209
- });
210
- },
211
-
212
- // Navigation context
213
- navContext: DEFAULT_NAV_CONTEXT,
214
- setNavContext: (ctx) => set({ navContext: ctx }),
215
-
216
- // Shared UI state
217
- selectedItemId: null,
218
- setSelectedItemId: (id) => set({ selectedItemId: id }),
219
- content: {},
220
- updateContentField: (key, value) => set({ content: { ...get().content, [key]: value } }),
221
- clearContent: () => set({ content: {} }),
222
- highlights: [],
223
- addHighlight: (id) => {
224
- const { highlights } = get();
225
- if (!highlights.includes(id)) {
226
- set({ highlights: [...highlights, id] });
227
- }
228
- },
229
- removeHighlight: (id) => set({ highlights: get().highlights.filter((h) => h !== id) }),
230
- clearHighlights: () => set({ highlights: [] }),
231
-
232
- // Context items
233
- contextItems: [],
234
- sentContext: {},
235
-
236
- addContextItem: (item) => {
237
- const { contextItems } = get();
238
-
239
- // Deduplicate by type-specific key
240
- const isDuplicate = contextItems.some((existing) => {
241
- if (item.type === "page" && existing.type === "page") {
242
- const a = item.payload as PagePayload;
243
- const b = existing.payload as PagePayload;
244
- return a.path === b.path;
245
- }
246
- if (item.type === "ref" && existing.type === "ref") {
247
- const a = item.payload as ReferencePayload;
248
- const b = existing.payload as ReferencePayload;
249
- return refDedupKey(a) === refDedupKey(b);
250
- }
251
- // selection / note: dedup by label + sourcePage
252
- return existing.label === item.label && existing.sourcePage === item.sourcePage;
253
- });
254
- if (isDuplicate) return;
255
-
256
- if (contextItems.length >= MAX_ITEMS) return;
257
-
258
- const label =
259
- item.label.length > MAX_LABEL_LENGTH
260
- ? item.label.slice(0, MAX_LABEL_LENGTH) + "..."
261
- : item.label;
262
-
263
- set({
264
- contextItems: [
265
- ...contextItems,
266
- { ...item, label, id: crypto.randomUUID(), createdAt: Date.now() },
267
- ],
268
- });
269
- },
270
-
271
- removeContextItem: (id) => set({ contextItems: get().contextItems.filter((i) => i.id !== id) }),
272
-
273
- promoteContextItem: (id) =>
274
- set({
275
- contextItems: get().contextItems.map((i) =>
276
- i.id === id ? { ...i, persistence: "persistent" as const } : i,
277
- ),
278
- }),
279
-
280
- clearAllContext: () => set({ contextItems: [], sentContext: {} }),
281
-
282
- consumeContextForMessage: (messageId) => {
283
- const { contextItems, sentContext } = get();
284
- if (contextItems.length === 0) return [];
285
-
286
- const snapshot = [...contextItems];
287
-
288
- // Keep persistent, remove ephemeral
289
- set({
290
- contextItems: contextItems.filter((i) => i.persistence === "persistent"),
291
- sentContext: { ...sentContext, [messageId]: snapshot },
292
- });
293
-
294
- return snapshot;
295
- },
296
-
297
- // AG-UI state projection
298
- getAgentState: () => {
299
- const s = get();
300
- return {
301
- navigation: {
302
- group: s.navContext.group,
303
- groupDescription: s.navContext.groupDescription,
304
- groupMeta: s.navContext.groupMeta,
305
- page: s.currentPage,
306
- label: s.navContext.label,
307
- description: s.navContext.description,
308
- meta: s.navContext.meta,
309
- contextInstructions: s.navContext.contextInstructions,
310
- },
311
- selectedItemId: s.selectedItemId,
312
- content: s.content,
313
- highlights: s.highlights,
314
- contextItems: s.contextItems.map(({ type, label, payload, sourcePage }) => {
315
- switch (payload.kind) {
316
- case "text":
317
- return {
318
- type,
319
- label: label.slice(0, 200),
320
- text: payload.text.slice(0, 500),
321
- sourcePage,
322
- };
323
- case "page":
324
- return { type, label, path: payload.path, sourcePage };
325
- case "ref":
326
- return {
327
- type,
328
- label: label.slice(0, 200),
329
- refType: payload.refType,
330
- refId: payload.refId,
331
- preview: payload.preview?.slice(0, 100),
332
- target: compactReferenceTarget(payload.target),
333
- meta: payload.meta,
334
- targetPath:
335
- typeof payload.target?.path === "string"
336
- ? payload.target.path
337
- : typeof payload.meta?.targetPath === "string"
338
- ? payload.meta.targetPath
339
- : undefined,
340
- sourcePage,
341
- };
342
- default:
343
- return { type, label, sourcePage };
344
- }
345
- }),
346
- };
347
- },
348
-
349
- // Full reset
350
- resetAll: () =>
351
- set({
352
- threadActive: false,
353
- currentPage: "/chat",
354
- navContext: DEFAULT_NAV_CONTEXT,
355
- selectedItemId: null,
356
- content: {},
357
- highlights: [],
358
- contextItems: [],
359
- sentContext: {},
360
- }),
361
- }));
@@ -1,92 +0,0 @@
1
- /**
2
- * File attachment adapter for assistant-ui.
3
- *
4
- * Reads files client-side as base64 and produces FileMessagePart content.
5
- * The AG-UI adapter converts these to BinaryInputContent for the protocol,
6
- * and the agent framework passes them to the Responses API as input_file
7
- * or input_image -- no separate upload endpoint needed.
8
- *
9
- * Supported: images (PNG, JPEG, WEBP, GIF), PDFs, DOCX
10
- */
11
-
12
- import type {
13
- AttachmentAdapter,
14
- PendingAttachment,
15
- CompleteAttachment,
16
- Attachment,
17
- } from "@assistant-ui/react";
18
-
19
- const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20 MB
20
-
21
- /** Infer MIME type from extension when browser reports empty file.type (common for .docx) */
22
- const EXTENSION_MIME: Record<string, string> = {
23
- ".pdf": "application/pdf",
24
- ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
25
- ".png": "image/png",
26
- ".jpg": "image/jpeg",
27
- ".jpeg": "image/jpeg",
28
- ".gif": "image/gif",
29
- ".webp": "image/webp",
30
- };
31
-
32
- function resolveMimeType(file: File): string {
33
- if (file.type) return file.type;
34
- const ext = file.name.slice(file.name.lastIndexOf(".")).toLowerCase();
35
- return EXTENSION_MIME[ext] ?? "application/octet-stream";
36
- }
37
-
38
- function fileToBase64(file: File): Promise<string> {
39
- return new Promise((resolve, reject) => {
40
- const reader = new FileReader();
41
- reader.onload = () => {
42
- const result = reader.result as string;
43
- // Strip the data URI prefix (e.g. "data:image/png;base64,"), keep raw base64
44
- resolve(result.split(",")[1]);
45
- };
46
- reader.onerror = reject;
47
- reader.readAsDataURL(file);
48
- });
49
- }
50
-
51
- export class FileAttachmentAdapter implements AttachmentAdapter {
52
- accept =
53
- "image/*,application/pdf,.pdf,.docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document";
54
-
55
- async add({ file }: { file: File }): Promise<PendingAttachment> {
56
- if (file.size > MAX_FILE_SIZE) {
57
- throw new Error(`File too large (${Math.round(file.size / 1024 / 1024)} MB). Max 20 MB.`);
58
- }
59
-
60
- const mimeType = resolveMimeType(file);
61
-
62
- return {
63
- id: crypto.randomUUID(),
64
- type: mimeType.startsWith("image/") ? "image" : "document",
65
- name: file.name,
66
- contentType: mimeType,
67
- file,
68
- status: { type: "requires-action", reason: "composer-send" },
69
- };
70
- }
71
-
72
- async send(attachment: PendingAttachment): Promise<CompleteAttachment> {
73
- const base64 = await fileToBase64(attachment.file);
74
-
75
- return {
76
- ...attachment,
77
- status: { type: "complete" },
78
- content: [
79
- {
80
- type: "file" as const,
81
- data: base64,
82
- mimeType: attachment.contentType ?? "application/octet-stream",
83
- filename: attachment.name,
84
- },
85
- ],
86
- };
87
- }
88
-
89
- async remove(_attachment: Attachment): Promise<void> {
90
- // No-op -- files are read client-side, nothing to clean up
91
- }
92
- }
@@ -1,144 +0,0 @@
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
-
11
- import {
12
- type FC,
13
- type PropsWithChildren,
14
- type ReactNode,
15
- useEffect,
16
- useState,
17
- } from "react";
18
- import {
19
- authStore,
20
- initializeMsal,
21
- getMsalInstance,
22
- type MsalAuthConfig,
23
- type MsalClientApplication,
24
- } from "@iloveagents/foundry-agent/msal";
25
-
26
- interface AuthProviderProps {
27
- config: MsalAuthConfig;
28
- }
29
-
30
- type MsalProviderComponent = FC<{
31
- instance: MsalClientApplication;
32
- children: ReactNode;
33
- }>;
34
-
35
- // Dynamic imports for MSAL React (stays optional)
36
- type MsalHook = () => {
37
- instance: MsalClientApplication;
38
- accounts: { name?: string; username: string; localAccountId: string }[];
39
- inProgress: string;
40
- };
41
-
42
- export const AuthProvider: FC<PropsWithChildren<AuthProviderProps>> = ({
43
- config,
44
- children,
45
- }) => {
46
- const [ready, setReady] = useState(false);
47
- const [initError, setInitError] = useState<string | null>(null);
48
- const [MsalProvider, setMsalProvider] = useState<MsalProviderComponent | null>(null);
49
- const [useMsalHook, setUseMsalHook] = useState<MsalHook | null>(null);
50
-
51
- useEffect(() => {
52
- let cancelled = false;
53
-
54
- (async () => {
55
- try {
56
- await initializeMsal(config);
57
- const msalReact = await import("@azure/msal-react");
58
-
59
- if (cancelled) return;
60
-
61
- setMsalProvider(() => msalReact.MsalProvider as unknown as MsalProviderComponent);
62
- setUseMsalHook(() => msalReact.useMsal as unknown as MsalHook);
63
- setReady(true);
64
- } catch (error) {
65
- console.error("[auth] Failed to initialize MSAL", error);
66
- if (cancelled) return;
67
- setInitError(
68
- error instanceof Error ? error.message : "Failed to initialize authentication.",
69
- );
70
- setReady(true);
71
- }
72
- })();
73
-
74
- return () => { cancelled = true; };
75
- }, [config]);
76
-
77
- if (!ready) return null;
78
-
79
- if (initError) {
80
- return (
81
- <div className="flex min-h-dvh items-center justify-center bg-background px-6 text-center">
82
- <div className="max-w-md rounded-2xl border border-border bg-card px-6 py-5 shadow-sm">
83
- <h1 className="text-lg font-semibold text-foreground">Authentication unavailable</h1>
84
- <p className="mt-2 text-sm text-muted-foreground">{initError}</p>
85
- </div>
86
- </div>
87
- );
88
- }
89
-
90
- if (!MsalProvider || !useMsalHook) {
91
- return null;
92
- }
93
-
94
- const instance = getMsalInstance()!;
95
-
96
- return (
97
- <MsalProvider instance={instance}>
98
- <AuthGuard useMsalHook={useMsalHook}>{children}</AuthGuard>
99
- </MsalProvider>
100
- );
101
- };
102
-
103
- /**
104
- * AuthGuard — waits for MsalProvider to finish processing redirects,
105
- * then either syncs the account or triggers loginRedirect.
106
- *
107
- * Uses inProgress === "none" to avoid redirect loops (best practice from
108
- * Microsoft docs — never call loginRedirect while another interaction is active).
109
- */
110
- const AuthGuard: FC<PropsWithChildren<{ useMsalHook: MsalHook }>> = ({
111
- useMsalHook,
112
- children,
113
- }) => {
114
- const { instance, accounts, inProgress } = useMsalHook();
115
- const [authenticated, setAuthenticated] = useState(false);
116
-
117
- useEffect(() => {
118
- // Wait until MsalProvider finishes processing any redirect or startup
119
- if (inProgress !== "none") return;
120
-
121
- if (accounts.length > 0) {
122
- const account = accounts[0];
123
- authStore.getState().signIn({
124
- name: account.name ?? account.username,
125
- email: account.username,
126
- oid: account.localAccountId,
127
- });
128
- instance.setActiveAccount(account);
129
- setAuthenticated(true);
130
- } else {
131
- // No account and no interaction in progress — redirect to login.
132
- // Only request OIDC scopes here. API/SPACES tokens are acquired
133
- // silently later via acquireTokenSilent (consent is per-resource
134
- // in Entra ID — including them here would show N consent screens).
135
- instance.loginRedirect({
136
- scopes: ["openid", "profile", "email"],
137
- });
138
- }
139
- }, [instance, accounts, inProgress]);
140
-
141
- if (!authenticated) return null;
142
-
143
- return <>{children}</>;
144
- };
@@ -1,46 +0,0 @@
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
-
13
- import { create } from "zustand";
14
-
15
- interface ChatBubbleState {
16
- /** Bubble floating panel is visible */
17
- isOpen: boolean;
18
- /** Chat is expanded into the main layout area */
19
- isExpanded: boolean;
20
- /** Page panel visible in expanded mode (false = FULL mode) */
21
- showPagePanel: boolean;
22
-
23
- open: () => void;
24
- close: () => void;
25
- toggle: () => void;
26
- /** Expand: hide bubble, show chat in main layout */
27
- expand: () => void;
28
- /** Collapse: hide expanded chat, show bubble */
29
- collapse: () => void;
30
- /** Toggle page panel in expanded mode (EXPANDED ↔ FULL) */
31
- togglePagePanel: () => void;
32
- }
33
-
34
- export const useChatBubbleStore = create<ChatBubbleState>((set) => ({
35
- isOpen: false,
36
- isExpanded: false,
37
- showPagePanel: true,
38
-
39
- open: () => set({ isOpen: true }),
40
- close: () => set({ isOpen: false, isExpanded: false }),
41
- toggle: () => set((s) => ({ isOpen: !s.isOpen })),
42
-
43
- expand: () => set({ isOpen: false, isExpanded: true, showPagePanel: true }),
44
- collapse: () => set({ isOpen: true, isExpanded: false }),
45
- togglePagePanel: () => set((s) => ({ showPagePanel: !s.showPagePanel })),
46
- }));