@iloveagents/foundry-web-ui 0.6.0 → 0.7.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/dist/components/app-brand.d.ts +23 -0
- package/dist/components/app-brand.js +36 -6
- package/dist/components/tool-panel.js +54 -2
- package/dist/lib/theme-runtime.d.ts +13 -0
- package/dist/lib/theme-runtime.js +6 -7
- package/dist/lib/tool-panel-store.d.ts +6 -0
- package/dist/lib/tool-panel-store.js +42 -0
- package/package.json +3 -3
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
import type { ThemeBranding } from "../lib/theme-runtime.js";
|
|
2
|
+
export interface BrandLockup {
|
|
3
|
+
/** Image to render, or undefined to fall back to the generic glyph. */
|
|
4
|
+
markUrl?: string;
|
|
5
|
+
/** True when `markUrl` is a compact icon rather than a wordmark. */
|
|
6
|
+
isIcon: boolean;
|
|
7
|
+
name: string;
|
|
8
|
+
showName: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Decide what the brand lockup renders. Exported (underscore-prefixed) so the
|
|
12
|
+
* branching is unit-testable without a DOM renderer — same approach as
|
|
13
|
+
* ``tool-fallback.tsx``.
|
|
14
|
+
*
|
|
15
|
+
* The rule worth stating: whether the name is printed depends on the *shape*
|
|
16
|
+
* of the brand image. A wordmark already spells the brand out, so pairing it
|
|
17
|
+
* with `appName` stutters ("ANDRITZ ANDRITZ"). An icon says nothing on its
|
|
18
|
+
* own, so it needs the name to complete the lockup. Hence an icon wins over a
|
|
19
|
+
* wordmark when both are set — supplying an icon is an explicit request for
|
|
20
|
+
* "<mark> Product Name", which is what a tenant whose product name differs
|
|
21
|
+
* from their company name is asking for.
|
|
22
|
+
*/
|
|
23
|
+
export declare function _resolveBrandLockup(branding: ThemeBranding, mode: "light" | "dark", showName: boolean): BrandLockup;
|
|
1
24
|
export declare function AppBrand({ showName, className, iconClassName, labelClassName, }: {
|
|
2
25
|
showName?: boolean;
|
|
3
26
|
className?: string;
|
|
@@ -2,12 +2,42 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Layers3 } from "lucide-react";
|
|
3
3
|
import { cn } from "@iloveagents/foundry-web-primitives";
|
|
4
4
|
import { useThemeRuntime } from "./theme-runtime-provider.js";
|
|
5
|
+
/**
|
|
6
|
+
* Decide what the brand lockup renders. Exported (underscore-prefixed) so the
|
|
7
|
+
* branching is unit-testable without a DOM renderer — same approach as
|
|
8
|
+
* ``tool-fallback.tsx``.
|
|
9
|
+
*
|
|
10
|
+
* The rule worth stating: whether the name is printed depends on the *shape*
|
|
11
|
+
* of the brand image. A wordmark already spells the brand out, so pairing it
|
|
12
|
+
* with `appName` stutters ("ANDRITZ ANDRITZ"). An icon says nothing on its
|
|
13
|
+
* own, so it needs the name to complete the lockup. Hence an icon wins over a
|
|
14
|
+
* wordmark when both are set — supplying an icon is an explicit request for
|
|
15
|
+
* "<mark> Product Name", which is what a tenant whose product name differs
|
|
16
|
+
* from their company name is asking for.
|
|
17
|
+
*/
|
|
18
|
+
export function _resolveBrandLockup(branding, mode, showName) {
|
|
19
|
+
const isDark = mode === "dark";
|
|
20
|
+
const pick = (dark, light) => (isDark ? dark || light : light || dark);
|
|
21
|
+
const iconUrl = pick(branding.darkIconUrl, branding.iconUrl);
|
|
22
|
+
const logoUrl = pick(branding.darkLogoUrl, branding.logoUrl);
|
|
23
|
+
const markUrl = iconUrl || logoUrl;
|
|
24
|
+
return {
|
|
25
|
+
markUrl,
|
|
26
|
+
isIcon: Boolean(iconUrl),
|
|
27
|
+
name: branding.appName || "Foundry UI",
|
|
28
|
+
showName: showName && (Boolean(iconUrl) || !markUrl),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
5
31
|
export function AppBrand({ showName = true, className, iconClassName, labelClassName, }) {
|
|
6
32
|
const runtime = useThemeRuntime();
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
33
|
+
const lockup = _resolveBrandLockup(runtime.branding, runtime.mode, showName);
|
|
34
|
+
return (_jsxs("span", { className: cn("flex items-center gap-2", className), children: [lockup.markUrl ? (_jsx("img", { src: lockup.markUrl, alt: lockup.name, className: cn("shrink-0 object-contain",
|
|
35
|
+
// Both lock HEIGHT and let width follow the aspect ratio. Sizing an
|
|
36
|
+
// icon into a square box (`size-5`) silently shrinks any mark that
|
|
37
|
+
// is not 1:1 — `object-contain` letterboxes a 4:3 glyph to 20x15,
|
|
38
|
+
// so it lands visibly smaller and thinner than the wordmark it
|
|
39
|
+
// replaced. Marks are rarely square, so height is the only
|
|
40
|
+
// dimension worth fixing. The max-width is just a runaway guard:
|
|
41
|
+
// tighter for an icon, which has to leave room for the name.
|
|
42
|
+
lockup.isIcon ? "h-5 w-auto max-w-8" : "h-5 w-auto max-w-[7rem]", iconClassName) })) : (_jsx(Layers3, { className: cn("size-5 shrink-0 text-primary", iconClassName) })), lockup.showName && _jsx("span", { className: cn("truncate", labelClassName), children: lockup.name })] }));
|
|
13
43
|
}
|
|
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState, Suspense } from "react";
|
|
|
3
3
|
import { X, Maximize2, Minimize2, Copy, Check, Pin, PinOff, FolderOpen } from "lucide-react";
|
|
4
4
|
import { useNavigate } from "react-router";
|
|
5
5
|
import { cn } from "@iloveagents/foundry-web-primitives";
|
|
6
|
-
import { useToolPanelStore } from "../lib/tool-panel-store.js";
|
|
6
|
+
import { clampPanelWidth, MIN_PANEL_WIDTH, useToolPanelStore, } from "../lib/tool-panel-store.js";
|
|
7
7
|
import { useAppStore } from "../lib/app-store.js";
|
|
8
8
|
import ReactMarkdown from "react-markdown";
|
|
9
9
|
import remarkGfm from "remark-gfm";
|
|
@@ -26,6 +26,40 @@ export function ToolPanel() {
|
|
|
26
26
|
const content = useToolPanelStore((state) => state.content);
|
|
27
27
|
const closePanel = useToolPanelStore((state) => state.closePanel);
|
|
28
28
|
const panelWidth = useToolPanelStore((state) => state.panelWidth);
|
|
29
|
+
const setPanelWidth = useToolPanelStore((state) => state.setPanelWidth);
|
|
30
|
+
const useOverlay = useToolPanelStore((state) => state.useOverlay);
|
|
31
|
+
/** Pointer id of an in-flight resize drag (null when idle). */
|
|
32
|
+
const dragPointerId = useRef(null);
|
|
33
|
+
const maxPanelWidth = typeof window !== "undefined"
|
|
34
|
+
? clampPanelWidth(Number.POSITIVE_INFINITY, window.innerWidth)
|
|
35
|
+
: MIN_PANEL_WIDTH;
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const onPointerMove = (event) => {
|
|
38
|
+
if (dragPointerId.current === null)
|
|
39
|
+
return;
|
|
40
|
+
setPanelWidth(window.innerWidth - event.clientX);
|
|
41
|
+
};
|
|
42
|
+
// pointercancel matters: a cancelled drag must not leave the document
|
|
43
|
+
// unselectable with a col-resize cursor (review catch).
|
|
44
|
+
const onPointerEnd = (event) => {
|
|
45
|
+
if (dragPointerId.current !== null && event.pointerId !== dragPointerId.current)
|
|
46
|
+
return;
|
|
47
|
+
dragPointerId.current = null;
|
|
48
|
+
document.body.style.cursor = "";
|
|
49
|
+
document.body.style.userSelect = "";
|
|
50
|
+
};
|
|
51
|
+
window.addEventListener("pointermove", onPointerMove);
|
|
52
|
+
window.addEventListener("pointerup", onPointerEnd);
|
|
53
|
+
window.addEventListener("pointercancel", onPointerEnd);
|
|
54
|
+
return () => {
|
|
55
|
+
window.removeEventListener("pointermove", onPointerMove);
|
|
56
|
+
window.removeEventListener("pointerup", onPointerEnd);
|
|
57
|
+
window.removeEventListener("pointercancel", onPointerEnd);
|
|
58
|
+
// Unmounting mid-drag must not strand the styles either.
|
|
59
|
+
document.body.style.cursor = "";
|
|
60
|
+
document.body.style.userSelect = "";
|
|
61
|
+
};
|
|
62
|
+
}, [setPanelWidth]);
|
|
29
63
|
const isFullscreen = useToolPanelStore((state) => state.isFullscreen);
|
|
30
64
|
const toggleFullscreen = useToolPanelStore((state) => state.toggleFullscreen);
|
|
31
65
|
const navigate = useNavigate();
|
|
@@ -85,5 +119,23 @@ export function ToolPanel() {
|
|
|
85
119
|
}, [isOpen, closePanel]);
|
|
86
120
|
if (!content)
|
|
87
121
|
return null;
|
|
88
|
-
return (_jsxs("div", { "data-tool-panel": true, className: cn("fixed top-0 right-0 z-40", "h-dvh", "bg-background border-l border-border shadow-2xl", "transition-transform duration-300 ease-in-out", "flex flex-col", isOpen ? "translate-x-0" : "translate-x-full"), style: { width: panelWidth }, children: [
|
|
122
|
+
return (_jsxs("div", { "data-tool-panel": true, className: cn("fixed top-0 right-0 z-40", "h-dvh", "bg-background border-l border-border shadow-2xl", "transition-transform duration-300 ease-in-out", "flex flex-col", isOpen ? "translate-x-0" : "translate-x-full"), style: { width: panelWidth }, children: [isOpen && !useOverlay && !isFullscreen ? (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": "Resize panel", "aria-valuemin": MIN_PANEL_WIDTH, "aria-valuemax": maxPanelWidth, "aria-valuenow": Math.round(panelWidth), tabIndex: 0, onPointerDown: (event) => {
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
dragPointerId.current = event.pointerId;
|
|
125
|
+
document.body.style.cursor = "col-resize";
|
|
126
|
+
document.body.style.userSelect = "none";
|
|
127
|
+
}, onKeyDown: (event) => {
|
|
128
|
+
const step = event.shiftKey ? 80 : 24;
|
|
129
|
+
if (event.key === "ArrowLeft")
|
|
130
|
+
setPanelWidth(panelWidth + step);
|
|
131
|
+
else if (event.key === "ArrowRight")
|
|
132
|
+
setPanelWidth(panelWidth - step);
|
|
133
|
+
else if (event.key === "Home")
|
|
134
|
+
setPanelWidth(maxPanelWidth);
|
|
135
|
+
else if (event.key === "End")
|
|
136
|
+
setPanelWidth(MIN_PANEL_WIDTH);
|
|
137
|
+
else
|
|
138
|
+
return;
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
}, className: cn("absolute left-0 top-0 z-10 h-full w-2 -translate-x-1/2", "cursor-col-resize touch-none", "before:absolute before:inset-y-0 before:left-1/2 before:w-px before:-translate-x-1/2", "before:bg-transparent hover:before:bg-primary/40 focus-visible:before:bg-primary", "focus:outline-none") })) : null, _jsx("div", { className: "flex-shrink-0 border-b border-border px-6 py-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h2", { className: "text-lg font-semibold text-foreground truncate pr-4", children: content.title }), _jsxs("div", { className: "flex items-center gap-1", children: [entityId && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: handleToggleEntityPin, className: cn("flex items-center justify-center", "size-8 rounded-md shrink-0", "hover:bg-muted transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", isEntityPinned && "text-primary"), "aria-label": isEntityPinned ? "Unpin document" : "Pin document", title: isEntityPinned ? "Unpin document" : "Pin document", children: isEntityPinned ? (_jsx(PinOff, { className: "size-4" })) : (_jsx(Pin, { className: "size-4 text-muted-foreground" })) }), _jsx("button", { type: "button", onClick: handleNavigateToEntity, className: cn("flex items-center justify-center", "size-8 rounded-md shrink-0", "hover:bg-muted transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"), "aria-label": "Open in navigation", title: "Open in navigation", children: _jsx(FolderOpen, { className: "size-4 text-muted-foreground" }) })] })), _jsx("button", { type: "button", onClick: handleCopyAll, className: cn("flex items-center justify-center", "size-8 rounded-md flex-shrink-0", "hover:bg-muted transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"), "aria-label": "Copy content", children: copied ? (_jsx(Check, { className: "size-4 text-success" })) : (_jsx(Copy, { className: "size-4 text-muted-foreground" })) }), _jsx("button", { type: "button", onClick: toggleFullscreen, className: cn("flex items-center justify-center", "size-8 rounded-md flex-shrink-0", "hover:bg-muted transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"), "aria-label": isFullscreen ? "Exit fullscreen" : "Fullscreen", children: isFullscreen ? (_jsx(Minimize2, { className: "size-4 text-muted-foreground" })) : (_jsx(Maximize2, { className: "size-4 text-muted-foreground" })) }), _jsx("button", { type: "button", onClick: closePanel, className: cn("flex items-center justify-center", "size-8 rounded-md flex-shrink-0", "hover:bg-muted transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"), "aria-label": "Close panel", children: _jsx(X, { className: "size-5 text-muted-foreground" }) })] })] }) }), _jsxs("div", { className: "relative flex-1 overflow-y-auto", ref: contentRef, "data-testid": "tool-panel-content", children: [_jsx(PanelContentRenderer, { content: content }), _jsx(SelectionPopover, { containerRef: contentRef })] })] }));
|
|
89
141
|
}
|
|
@@ -84,8 +84,21 @@ export interface ThemeAppSlots {
|
|
|
84
84
|
export interface ThemeBranding {
|
|
85
85
|
appName?: string;
|
|
86
86
|
appTitle?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Full wordmark — an image that already spells the brand out. Rendered on
|
|
89
|
+
* its own, because setting it beside `appName` prints the brand twice.
|
|
90
|
+
*/
|
|
87
91
|
logoUrl?: string;
|
|
88
92
|
darkLogoUrl?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Compact mark (square-ish glyph, no words). Rendered *with* `appName`
|
|
95
|
+
* beside it, so a tenant can show "<mark> Product Name" — the usual answer
|
|
96
|
+
* when the product name differs from the company name. Takes precedence
|
|
97
|
+
* over `logoUrl` when both are set, since supplying an icon is an explicit
|
|
98
|
+
* request for the lockup.
|
|
99
|
+
*/
|
|
100
|
+
iconUrl?: string;
|
|
101
|
+
darkIconUrl?: string;
|
|
89
102
|
}
|
|
90
103
|
export interface ThemeDefinition {
|
|
91
104
|
light?: ThemeColorSlots;
|
|
@@ -82,6 +82,8 @@ const BRANDING_KEYS = [
|
|
|
82
82
|
"appTitle",
|
|
83
83
|
"logoUrl",
|
|
84
84
|
"darkLogoUrl",
|
|
85
|
+
"iconUrl",
|
|
86
|
+
"darkIconUrl",
|
|
85
87
|
];
|
|
86
88
|
function cleanObject(value, keys) {
|
|
87
89
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
@@ -412,9 +414,9 @@ export const FOUNDRY_VIOLET_THEME = {
|
|
|
412
414
|
ring: "oklch(0.72 0.18 286)",
|
|
413
415
|
},
|
|
414
416
|
typography: {
|
|
415
|
-
bodyFont: "
|
|
416
|
-
headingFont: "
|
|
417
|
-
monoFont: "
|
|
417
|
+
bodyFont: '"Segoe UI", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
|
|
418
|
+
headingFont: '"Segoe UI", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
|
|
419
|
+
monoFont: '"SFMono-Regular", "Cascadia Code", "JetBrains Mono", ui-monospace, monospace',
|
|
418
420
|
},
|
|
419
421
|
radius: {
|
|
420
422
|
radius: "0.875rem",
|
|
@@ -433,10 +435,7 @@ export function resolveThemeRuntime(layers, mode) {
|
|
|
433
435
|
const effectiveMode = getEffectiveThemeMode(mode);
|
|
434
436
|
const basePreset = layers.find((layer) => layer.basePreset && layer.basePreset.trim())?.basePreset ??
|
|
435
437
|
"foundry-violet";
|
|
436
|
-
const definitions = [
|
|
437
|
-
getThemePreset(basePreset),
|
|
438
|
-
...layers.map((layer) => layer.definition),
|
|
439
|
-
];
|
|
438
|
+
const definitions = [getThemePreset(basePreset), ...layers.map((layer) => layer.definition)];
|
|
440
439
|
const definition = mergeThemeDefinitions(...definitions);
|
|
441
440
|
const colors = {
|
|
442
441
|
...(effectiveMode === "dark" ? definition.dark : definition.light),
|
|
@@ -29,6 +29,10 @@ export interface PanelRendererEntry {
|
|
|
29
29
|
content: ToolPanelContent;
|
|
30
30
|
}>>;
|
|
31
31
|
}
|
|
32
|
+
/** Smallest useful panel, and the space the chat/content beside it must keep. */
|
|
33
|
+
export declare const MIN_PANEL_WIDTH = 360;
|
|
34
|
+
/** Clamp a desired width so neither side can be squeezed out of usefulness. */
|
|
35
|
+
export declare const clampPanelWidth: (desired: number, viewport: number) => number;
|
|
32
36
|
interface ToolPanelState {
|
|
33
37
|
isOpen: boolean;
|
|
34
38
|
content: ToolPanelContent | null;
|
|
@@ -41,6 +45,8 @@ interface ToolPanelState {
|
|
|
41
45
|
setContent: (content: ToolPanelContent) => void;
|
|
42
46
|
updateLayout: () => void;
|
|
43
47
|
toggleFullscreen: () => void;
|
|
48
|
+
/** Drag-to-resize the split; clamped and persisted per user. */
|
|
49
|
+
setPanelWidth: (width: number) => void;
|
|
44
50
|
/** Register a custom content renderer (e.g., PDF viewer, entity pages). */
|
|
45
51
|
registerRenderer: (entry: PanelRendererEntry) => void;
|
|
46
52
|
}
|
|
@@ -6,12 +6,40 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { create } from "zustand";
|
|
8
8
|
const SIDE_BY_SIDE_BREAKPOINT = 1280;
|
|
9
|
+
/** Smallest useful panel, and the space the chat/content beside it must keep. */
|
|
10
|
+
export const MIN_PANEL_WIDTH = 360;
|
|
11
|
+
const MIN_SIBLING_WIDTH = 420;
|
|
12
|
+
const PANEL_WIDTH_STORAGE_KEY = "foundry.toolPanel.width";
|
|
13
|
+
/** Clamp a desired width so neither side can be squeezed out of usefulness. */
|
|
14
|
+
export const clampPanelWidth = (desired, viewport) => {
|
|
15
|
+
if (viewport < SIDE_BY_SIDE_BREAKPOINT)
|
|
16
|
+
return viewport;
|
|
17
|
+
const max = Math.max(MIN_PANEL_WIDTH, viewport - MIN_SIBLING_WIDTH);
|
|
18
|
+
return Math.round(Math.min(max, Math.max(MIN_PANEL_WIDTH, desired)));
|
|
19
|
+
};
|
|
20
|
+
const readStoredPanelWidth = () => {
|
|
21
|
+
if (typeof window === "undefined")
|
|
22
|
+
return null;
|
|
23
|
+
try {
|
|
24
|
+
const raw = window.localStorage.getItem(PANEL_WIDTH_STORAGE_KEY);
|
|
25
|
+
const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
|
26
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null; // private mode / storage disabled — fall back to the default
|
|
30
|
+
}
|
|
31
|
+
};
|
|
9
32
|
const getPanelWidth = () => {
|
|
10
33
|
if (typeof window === "undefined")
|
|
11
34
|
return 800;
|
|
12
35
|
const screenWidth = window.innerWidth;
|
|
13
36
|
if (screenWidth < SIDE_BY_SIDE_BREAKPOINT)
|
|
14
37
|
return screenWidth;
|
|
38
|
+
// A width the user dragged to wins over the computed default, re-clamped
|
|
39
|
+
// to the current viewport so a narrow window can't strand the content.
|
|
40
|
+
const stored = readStoredPanelWidth();
|
|
41
|
+
if (stored !== null)
|
|
42
|
+
return clampPanelWidth(stored, screenWidth);
|
|
15
43
|
return Math.min(1000, Math.max(600, screenWidth * 0.5));
|
|
16
44
|
};
|
|
17
45
|
const shouldUseOverlay = () => {
|
|
@@ -30,6 +58,20 @@ export const useToolPanelStore = create((set, get) => ({
|
|
|
30
58
|
registerRenderer: (entry) => set((state) => ({ renderers: [...state.renderers, entry] })),
|
|
31
59
|
closePanel: () => set({ isOpen: false, isFullscreen: false }),
|
|
32
60
|
setContent: (content) => set({ content }),
|
|
61
|
+
setPanelWidth: (width) => {
|
|
62
|
+
if (typeof window === "undefined")
|
|
63
|
+
return;
|
|
64
|
+
if (get().isFullscreen)
|
|
65
|
+
return;
|
|
66
|
+
const next = clampPanelWidth(width, window.innerWidth);
|
|
67
|
+
try {
|
|
68
|
+
window.localStorage.setItem(PANEL_WIDTH_STORAGE_KEY, String(next));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Persisting is best-effort; the drag still applies for this session.
|
|
72
|
+
}
|
|
73
|
+
set({ panelWidth: next });
|
|
74
|
+
},
|
|
33
75
|
updateLayout: () => {
|
|
34
76
|
const { isFullscreen } = get();
|
|
35
77
|
if (!isFullscreen) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iloveagents/foundry-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"tailwind-merge": "^3.5.0",
|
|
54
54
|
"react-markdown": "^10.0.0",
|
|
55
55
|
"remark-gfm": "^4.0.0",
|
|
56
|
-
"@iloveagents/foundry-agent": "^0.
|
|
57
|
-
"@iloveagents/foundry-web-primitives": "^0.
|
|
56
|
+
"@iloveagents/foundry-agent": "^0.7.0",
|
|
57
|
+
"@iloveagents/foundry-web-primitives": "^0.7.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@ag-ui/client": "^0.0.52",
|