@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.
- package/README.md +13 -4
- package/dist/components/ag-ui-runtime-provider.d.ts +64 -0
- package/dist/components/ag-ui-runtime-provider.js +71 -0
- package/dist/components/app-brand.d.ts +6 -0
- package/dist/components/app-brand.js +13 -0
- package/dist/components/assistant-chat.d.ts +26 -0
- package/dist/components/assistant-chat.js +93 -0
- package/dist/components/chat-attachments.d.ts +25 -0
- package/dist/components/chat-attachments.js +20 -0
- package/dist/components/chat-bubble.d.ts +1 -0
- package/dist/components/chat-bubble.js +103 -0
- package/dist/components/chat-header.d.ts +2 -0
- package/dist/components/chat-header.js +16 -0
- package/dist/components/client-tool-executor.d.ts +12 -0
- package/dist/components/client-tool-executor.js +108 -0
- package/dist/components/collection-empty-state.d.ts +9 -0
- package/dist/components/collection-empty-state.js +6 -0
- package/dist/components/collection-filter-bar.d.ts +19 -0
- package/dist/components/collection-filter-bar.js +28 -0
- package/dist/components/collection-search-input.d.ts +6 -0
- package/dist/components/collection-search-input.js +8 -0
- package/dist/components/collection-skeleton.d.ts +6 -0
- package/dist/components/collection-skeleton.js +10 -0
- package/dist/components/collection-sort-menu.d.ts +10 -0
- package/dist/components/collection-sort-menu.js +9 -0
- package/dist/components/collection-surface.d.ts +8 -0
- package/dist/components/collection-surface.js +5 -0
- package/dist/components/collection-toolbar.d.ts +9 -0
- package/dist/components/collection-toolbar.js +5 -0
- package/dist/components/collection-view-toggle.d.ts +7 -0
- package/dist/components/collection-view-toggle.js +17 -0
- package/dist/components/composer-submit-bridge.d.ts +1 -0
- package/dist/components/composer-submit-bridge.js +15 -0
- package/dist/components/confirm-dialog.d.ts +11 -0
- package/dist/components/confirm-dialog.js +6 -0
- package/dist/components/confirmation-card.d.ts +14 -0
- package/dist/components/confirmation-card.js +12 -0
- package/dist/components/context-badges.d.ts +16 -0
- package/dist/components/context-badges.js +56 -0
- package/dist/components/context-bar.d.ts +41 -0
- package/dist/components/context-bar.js +123 -0
- package/dist/components/form-dialog.d.ts +12 -0
- package/dist/components/form-dialog.js +13 -0
- package/dist/components/global-selection-popover.d.ts +7 -0
- package/dist/components/global-selection-popover.js +167 -0
- package/dist/components/infinite-scroll-sentinel.d.ts +6 -0
- package/dist/components/infinite-scroll-sentinel.js +25 -0
- package/dist/components/json-viewer.d.ts +10 -0
- package/dist/components/json-viewer.js +80 -0
- package/dist/components/loading-indicator.d.ts +4 -0
- package/dist/components/loading-indicator.js +22 -0
- package/dist/components/markdown-text.d.ts +13 -0
- package/dist/components/markdown-text.js +221 -0
- package/dist/components/name-dialog.d.ts +16 -0
- package/dist/components/name-dialog.js +23 -0
- package/dist/components/selection-popover.d.ts +12 -0
- package/dist/components/selection-popover.js +111 -0
- package/dist/components/show-document-tool-ui.d.ts +1 -0
- package/dist/components/show-document-tool-ui.js +53 -0
- package/dist/components/sidebar.d.ts +4 -0
- package/dist/components/sidebar.js +904 -0
- package/dist/components/surface-card.d.ts +9 -0
- package/dist/components/surface-card.js +33 -0
- package/dist/components/theme-runtime-provider.d.ts +15 -0
- package/dist/components/theme-runtime-provider.js +48 -0
- package/dist/components/theme-toggle.d.ts +2 -0
- package/dist/components/theme-toggle.js +19 -0
- package/dist/components/tool-call-card.d.ts +27 -0
- package/dist/components/tool-call-card.js +13 -0
- package/dist/components/tool-fallback.d.ts +26 -0
- package/dist/components/tool-fallback.js +129 -0
- package/dist/components/tool-panel-layout.d.ts +10 -0
- package/dist/components/tool-panel-layout.js +12 -0
- package/dist/components/tool-panel.d.ts +1 -0
- package/dist/components/tool-panel.js +89 -0
- package/dist/components/tooltip-icon-button.d.ts +7 -0
- package/dist/components/tooltip-icon-button.js +9 -0
- package/dist/components/user-menu.d.ts +2 -0
- package/dist/components/user-menu.js +14 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +83 -0
- package/dist/lib/ag-ui-adapter.d.ts +29 -0
- package/dist/lib/ag-ui-adapter.js +373 -0
- package/dist/lib/app-store.d.ts +115 -0
- package/dist/lib/app-store.js +199 -0
- package/dist/lib/attachment-adapter.d.ts +20 -0
- package/dist/lib/attachment-adapter.js +99 -0
- package/dist/lib/auth-provider.d.ts +16 -0
- package/dist/lib/auth-provider.js +88 -0
- package/dist/lib/chat-bubble-store.d.ts +30 -0
- package/dist/lib/chat-bubble-store.js +23 -0
- package/dist/lib/chat-lifecycle-store.d.ts +10 -0
- package/{src/lib/chat-lifecycle-store.ts → dist/lib/chat-lifecycle-store.js} +3 -13
- package/dist/lib/client-tools.d.ts +11 -0
- package/dist/lib/client-tools.js +274 -0
- package/dist/lib/composer-submit-store.d.ts +12 -0
- package/dist/lib/composer-submit-store.js +17 -0
- package/dist/lib/dev-store.d.ts +38 -0
- package/dist/lib/dev-store.js +32 -0
- package/dist/lib/nav-config.d.ts +159 -0
- package/dist/lib/nav-config.js +162 -0
- package/dist/lib/nav-dnd.d.ts +8 -0
- package/dist/lib/nav-dnd.js +9 -0
- package/dist/lib/nav-store.d.ts +45 -0
- package/dist/lib/nav-store.js +49 -0
- package/dist/lib/selection-context.d.ts +21 -0
- package/dist/lib/selection-context.js +34 -0
- package/dist/lib/sidebar-store.d.ts +23 -0
- package/dist/lib/sidebar-store.js +98 -0
- package/dist/lib/theme-runtime.d.ts +130 -0
- package/dist/lib/theme-runtime.js +479 -0
- package/dist/lib/theme-store.d.ts +15 -0
- package/dist/lib/theme-store.js +53 -0
- package/dist/lib/tool-panel-store.d.ts +48 -0
- package/dist/lib/tool-panel-store.js +69 -0
- package/dist/lib/use-new-conversation.d.ts +8 -0
- package/dist/lib/use-new-conversation.js +62 -0
- package/dist/lib/use-page-tools.d.ts +27 -0
- package/{src/lib/use-page-tools.ts → dist/lib/use-page-tools.js} +11 -13
- package/dist/styles.css +142 -0
- package/dist/ui/collapsible.d.ts +5 -0
- package/{src/ui/collapsible.tsx → dist/ui/collapsible.js} +0 -2
- package/dist/ui/dialog.d.ts +15 -0
- package/dist/ui/dialog.js +25 -0
- package/dist/ui/dropdown-menu.d.ts +7 -0
- package/dist/ui/dropdown-menu.js +19 -0
- package/dist/ui/popover.d.ts +7 -0
- package/dist/ui/popover.js +10 -0
- package/dist/ui/select.d.ts +4 -0
- package/dist/ui/select.js +9 -0
- package/dist/ui/tooltip.d.ts +7 -0
- package/dist/ui/tooltip.js +10 -0
- package/package.json +31 -12
- package/AGENTS.md +0 -59
- package/CHANGELOG.md +0 -211
- package/CLAUDE.md +0 -1
- package/src/__tests__/no-spaces-imports.test.ts +0 -59
- package/src/__tests__/open-core-guards.test.ts +0 -307
- package/src/__tests__/theme-runtime.test.ts +0 -81
- package/src/__tests__/tool-fallback.test.tsx +0 -109
- package/src/components/__tests__/context-bar.test.ts +0 -112
- package/src/components/ag-ui-runtime-provider.tsx +0 -170
- package/src/components/app-brand.tsx +0 -38
- package/src/components/assistant-chat.tsx +0 -461
- package/src/components/chat-attachments.tsx +0 -68
- package/src/components/chat-bubble.tsx +0 -373
- package/src/components/chat-header.tsx +0 -44
- package/src/components/client-tool-executor.tsx +0 -230
- package/src/components/collection-empty-state.tsx +0 -37
- package/src/components/collection-filter-bar.tsx +0 -168
- package/src/components/collection-search-input.tsx +0 -41
- package/src/components/collection-skeleton.tsx +0 -55
- package/src/components/collection-sort-menu.tsx +0 -62
- package/src/components/collection-surface.tsx +0 -46
- package/src/components/collection-toolbar.tsx +0 -33
- package/src/components/collection-view-toggle.tsx +0 -61
- package/src/components/composer-submit-bridge.tsx +0 -19
- package/src/components/confirm-dialog.tsx +0 -49
- package/src/components/confirmation-card.tsx +0 -32
- package/src/components/context-badges.tsx +0 -136
- package/src/components/context-bar.tsx +0 -238
- package/src/components/form-dialog.tsx +0 -56
- package/src/components/global-selection-popover.tsx +0 -266
- package/src/components/infinite-scroll-sentinel.tsx +0 -48
- package/src/components/json-viewer.tsx +0 -232
- package/src/components/loading-indicator.tsx +0 -40
- package/src/components/markdown-text-citations.test.tsx +0 -108
- package/src/components/markdown-text.tsx +0 -508
- package/src/components/name-dialog.tsx +0 -87
- package/src/components/selection-popover.tsx +0 -156
- package/src/components/show-document-tool-ui.tsx +0 -90
- package/src/components/sidebar.test.tsx +0 -611
- package/src/components/sidebar.tsx +0 -1689
- package/src/components/surface-card.tsx +0 -45
- package/src/components/theme-runtime-provider.tsx +0 -93
- package/src/components/theme-toggle.tsx +0 -27
- package/src/components/tool-call-card.tsx +0 -126
- package/src/components/tool-fallback.tsx +0 -169
- package/src/components/tool-panel-layout.tsx +0 -36
- package/src/components/tool-panel.tsx +0 -233
- package/src/components/tooltip-icon-button.tsx +0 -28
- package/src/components/user-menu.tsx +0 -60
- package/src/index.ts +0 -185
- package/src/lib/__tests__/ag-ui-adapter.test.ts +0 -279
- package/src/lib/__tests__/app-store.test.ts +0 -405
- package/src/lib/__tests__/attachment-adapter.test.ts +0 -48
- package/src/lib/__tests__/chat-bubble-store.test.ts +0 -67
- package/src/lib/__tests__/client-tools.test.ts +0 -124
- package/src/lib/__tests__/dev-store.test.ts +0 -78
- package/src/lib/__tests__/nav-config.test.ts +0 -135
- package/src/lib/__tests__/nav-dnd.test.ts +0 -24
- package/src/lib/__tests__/nav-store.test.ts +0 -59
- package/src/lib/__tests__/selection-context.test.ts +0 -114
- package/src/lib/__tests__/sidebar-store.test.ts +0 -144
- package/src/lib/__tests__/theme-store.test.ts +0 -83
- package/src/lib/__tests__/tool-panel-store.test.ts +0 -61
- package/src/lib/__tests__/use-page-context.test.ts +0 -58
- package/src/lib/ag-ui-adapter.ts +0 -426
- package/src/lib/app-store.ts +0 -361
- package/src/lib/attachment-adapter.ts +0 -92
- package/src/lib/auth-provider.tsx +0 -144
- package/src/lib/chat-bubble-store.ts +0 -46
- package/src/lib/client-tools.ts +0 -293
- package/src/lib/composer-submit-store.ts +0 -32
- package/src/lib/dev-store.ts +0 -69
- package/src/lib/nav-config.ts +0 -329
- package/src/lib/nav-dnd.ts +0 -14
- package/src/lib/nav-store.ts +0 -89
- package/src/lib/selection-context.ts +0 -65
- package/src/lib/sidebar-store.ts +0 -113
- package/src/lib/theme-runtime.ts +0 -660
- package/src/lib/theme-store.ts +0 -67
- package/src/lib/tool-panel-store.ts +0 -121
- package/src/lib/use-new-conversation.test.tsx +0 -168
- package/src/lib/use-new-conversation.ts +0 -64
- package/src/ui/dialog.tsx +0 -109
- package/src/ui/dropdown-menu.tsx +0 -75
- package/src/ui/popover.tsx +0 -34
- package/src/ui/select.tsx +0 -15
- package/src/ui/tooltip.tsx +0 -30
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -16
- package/vitest.config.ts +0 -8
package/src/lib/app-store.ts
DELETED
|
@@ -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
|
-
}));
|