From aa386f67656933db6e9237b5e166b7137dfbfbb7 Mon Sep 17 00:00:00 2001 From: Innei Date: Wed, 6 Aug 2025 01:15:56 +0800 Subject: [PATCH] feat(ai-chat): integrate react flow to replace mermaid - Added @xyflow/react as a dependency and integrated its flow chart functionality into the AI chat interface. - Updated AIDisplay components to improve rendering and state management, ensuring a more responsive user experience. - Refactored several display components for better readability and maintainability, including AIDisplayAnalyticsPart, AIDisplayEntriesPart, AIDisplayFeedsPart, and AIDisplaySubscriptionsPart. - Introduced AIDisplayFlowPart for visualizing flow charts, enhancing the overall functionality of the chat interface. These changes significantly improve the user experience and maintainability of the AI chat application. Signed-off-by: Innei --- apps/desktop/layer/renderer/package.json | 3 +- .../displays/AIDisplayAnalyticsPart.tsx | 86 +++++++------- .../displays/AIDisplayEntriesPart.tsx | 104 ++++++++-------- .../displays/AIDisplayFeedsPart.tsx | 85 +++++++------- .../components/displays/AIDisplayFlowPart.tsx | 41 +++++++ .../displays/AIDisplaySubscriptionsPart.tsx | 111 +++++++++--------- .../ai-chat/components/displays/share.tsx | 4 +- .../components/layouts/ChatInterface.tsx | 2 +- .../components/message/AIMessageParts.tsx | 31 +++++ .../src/modules/ai-chat/store/types.ts | 1 + pnpm-lock.yaml | 73 +++++++++++- 11 files changed, 339 insertions(+), 202 deletions(-) create mode 100644 apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayFlowPart.tsx diff --git a/apps/desktop/layer/renderer/package.json b/apps/desktop/layer/renderer/package.json index 860361e18..0f9de71c3 100644 --- a/apps/desktop/layer/renderer/package.json +++ b/apps/desktop/layer/renderer/package.json @@ -49,6 +49,7 @@ "@tanstack/react-virtual": "3.13.12", "@use-gesture/react": "10.3.1", "@welldone-software/why-did-you-render": "10.0.1", + "@xyflow/react": "12.8.2", "@yornaath/batshit": "0.10.1", "ai": "5.0.2", "camelcase-keys": "9.1.3", @@ -118,7 +119,7 @@ "@follow/models": "workspace:*", "@follow/types": "workspace:*", "@follow/utils": "workspace:*", - "@folo-services/ai-tools": "0.2.16", + "@folo-services/ai-tools": "0.2.17", "@types/node": "24.0.10", "@vite-pwa/assets-generator": "1.0.0", "fake-indexeddb": "6.0.1", diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayAnalyticsPart.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayAnalyticsPart.tsx index cdf6df72f..5271a5f12 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayAnalyticsPart.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayAnalyticsPart.tsx @@ -14,7 +14,6 @@ import { TableRow, } from "@follow/components/ui/table/index.js" import dayjs from "dayjs" -import { memo } from "react" import type { AIDisplayAnalyticsTool } from "../../store/types" import { withDisplayStateHandler } from "./share" @@ -228,52 +227,53 @@ const OverviewAnalytics = ({ data }: { data: AnalyticsData["overviewStats"] }) = ) } -const AIDisplayAnalyticsPartBase = memo( - ({ output }: { output: NonNullable }) => { - const { analyticsData, analyticsType, timeRange, displayType, title } = output +const AIDisplayAnalyticsPartBase = ({ + output, +}: { + output: NonNullable +}) => { + const { analyticsData, analyticsType, timeRange, displayType, title } = output - const renderAnalytics = () => { - switch (analyticsType) { - case "feed": { - return - } - case "subscription": { - return - } - case "reading": { - return - } - case "trending": { - return - } - case "overview": { - return - } - default: { - return
No analytics data available
- } + const renderAnalytics = () => { + switch (analyticsType) { + case "feed": { + return + } + case "subscription": { + return + } + case "reading": { + return + } + case "trending": { + return + } + case "overview": { + return + } + default: { + return
No analytics data available
} } + } - return ( - - - - 📊 - - {title || - `${analyticsType.charAt(0).toUpperCase() + analyticsType.slice(1)} Analytics`} - - - - {formatTimeRange(timeRange)} • Display type: {displayType} - - - {renderAnalytics()} - - ) - }, -) + return ( + + + + 📊 + + {title || `${analyticsType.charAt(0).toUpperCase() + analyticsType.slice(1)} Analytics`} + + + + {formatTimeRange(timeRange)} • Display type: {displayType} + + + {renderAnalytics()} + + ) +} export const AIDisplayAnalyticsPart = withDisplayStateHandler({ title: "Analytics", diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayEntriesPart.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayEntriesPart.tsx index 4b258967f..5c50a429e 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayEntriesPart.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayEntriesPart.tsx @@ -262,62 +262,64 @@ const GroupedEntries = ({ ) } -const AIDisplayEntriesPartBase = memo( - ({ output }: { output: NonNullable }) => { - const { - entries, - displayType = "grid", - showSummary = true, - showMetadata = true, - title, - groupBy = "none", - } = output +const AIDisplayEntriesPartBase = ({ + output, +}: { + output: NonNullable +}) => { + const { + entries, + displayType = "grid", + showSummary = true, + showMetadata = true, + title, + groupBy = "none", + } = output - // Calculate statistics - const totalEntries = entries.length - const feedsCount = new Set(entries.map((e) => e.entry.feedId)).size - const authorsCount = new Set(entries.map((e) => e.entry.author).filter(Boolean)).size - const categoriesCount = new Set(entries.flatMap((e) => e.entry.categories || [])).size + // Calculate statistics + const totalEntries = entries.length + const feedsCount = new Set(entries.map((e) => e.entry.feedId)).size + const authorsCount = new Set(entries.map((e) => e.entry.author).filter(Boolean)).size + const categoriesCount = new Set(entries.flatMap((e) => e.entry.categories || [])).size - const renderEntries = () => { - if (groupBy !== "none") { - return ( - - ) - } - - if (displayType === "timeline") { - return - } - return + const renderEntries = () => { + if (groupBy !== "none") { + return ( + + ) } - return ( - - {/* Statistics Overview */} -
- - - - -
+ if (displayType === "timeline") { + return + } + return + } - {/* Entries Display */} - {renderEntries()} -
- ) - }, -) + return ( + + {/* Statistics Overview */} +
+ + + + +
+ + {/* Entries Display */} + {renderEntries()} +
+ ) +} export const AIDisplayEntriesPart = withDisplayStateHandler({ title: "Entries", diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayFeedsPart.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayFeedsPart.tsx index e73453f89..60cb7c1ca 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayFeedsPart.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayFeedsPart.tsx @@ -6,7 +6,6 @@ import { CardTitle, } from "@follow/components/ui/card/index.js" import dayjs from "dayjs" -import { memo } from "react" import { FeedIcon } from "~/modules/feed/feed-icon" @@ -77,52 +76,50 @@ const FeedsGrid = ({ data, showAnalytics }: { data: FeedData; showAnalytics: boo ) } -const AIDisplayFeedsPartBase = memo( - ({ output }: { output: NonNullable }) => { - const { feeds, showAnalytics = true, title } = output +const AIDisplayFeedsPartBase = ({ + output, +}: { + output: NonNullable +}) => { + const { feeds, showAnalytics = true, title } = output - // Calculate statistics - const totalFeeds = feeds.length - const activeFeeds = feeds.filter((f) => !f.feed.errorMessage).length - const errorFeeds = feeds.filter((f) => f.feed.errorMessage).length - const totalSubscriptions = feeds.reduce( - (acc, f) => acc + (f.analytics?.subscriptionCount || 0), - 0, - ) - const totalViews = feeds.reduce((acc, f) => acc + (f.analytics?.view || 0), 0) + // Calculate statistics + const totalFeeds = feeds.length + const activeFeeds = feeds.filter((f) => !f.feed.errorMessage).length + const errorFeeds = feeds.filter((f) => f.feed.errorMessage).length + const totalSubscriptions = feeds.reduce( + (acc, f) => acc + (f.analytics?.subscriptionCount || 0), + 0, + ) + const totalViews = feeds.reduce((acc, f) => acc + (f.analytics?.view || 0), 0) - return ( - - {/* Statistics Overview */} -
- - - {showAnalytics && ( - <> - - - - )} -
+ return ( + + {/* Statistics Overview */} +
+ + + {showAnalytics && ( + <> + + + + )} +
- -
- ) - }, -) + +
+ ) +} export const AIDisplayFeedsPart = withDisplayStateHandler({ title: "Feeds", diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayFlowPart.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayFlowPart.tsx new file mode 100644 index 000000000..b731e0f69 --- /dev/null +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayFlowPart.tsx @@ -0,0 +1,41 @@ +import "@xyflow/react/dist/style.css" + +import { useIsDark } from "@follow/hooks" +import { Background, ReactFlow } from "@xyflow/react" + +import type { AIDisplayFlowTool } from "../../store/types" +import { toolMemo } from "./share" + +export const AIDisplayFlowPart = toolMemo( + ({ part, loadingElement }: { part: AIDisplayFlowTool; loadingElement: React.ReactNode }) => { + if (!part.input) return loadingElement + if (part.state === "input-streaming") { + return loadingElement + } + + const { nodes, edges } = part.input.flowChart as any + const colorMode = useIsDark() ? "dark" : "light" + return ( +
+ + + +
+ ) + }, +) diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplaySubscriptionsPart.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplaySubscriptionsPart.tsx index 12de893ae..ee81e660e 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplaySubscriptionsPart.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplaySubscriptionsPart.tsx @@ -6,7 +6,6 @@ import { CardTitle, } from "@follow/components/ui/card/index.js" import dayjs from "dayjs" -import { memo } from "react" import { FeedIcon } from "~/modules/feed/feed-icon" @@ -167,41 +166,35 @@ const GroupedSubscriptions = ({ ) } -const AIDisplaySubscriptionsPartBase = memo( - ({ output }: { output: NonNullable }) => { - const { - subscriptions, - displayType = "list", - showAnalytics = true, - showCategories = true, - title, - groupBy = "none", - filterBy = "all", - } = output +const AIDisplaySubscriptionsPartBase = ({ + output, +}: { + output: NonNullable +}) => { + const { + subscriptions, + displayType = "list", + showAnalytics = true, + showCategories = true, + title, + groupBy = "none", + filterBy = "all", + } = output - // Calculate statistics - const totalSubscriptions = subscriptions.length - const categoriesCount = new Set( - subscriptions.map((s) => s.subscription?.category).filter(Boolean), - ).size - const activeSubscriptions = subscriptions.filter((s) => !s.feed?.errorMessage).length - const totalViews = subscriptions.reduce((acc, s) => acc + (s.subscription?.view || 0), 0) - - const renderSubscriptions = () => { - if (groupBy !== "none") { - return ( - - ) - } + // Calculate statistics + const totalSubscriptions = subscriptions.length + const categoriesCount = new Set( + subscriptions.map((s) => s.subscription?.category).filter(Boolean), + ).size + const activeSubscriptions = subscriptions.filter((s) => !s.feed?.errorMessage).length + const totalViews = subscriptions.reduce((acc, s) => acc + (s.subscription?.view || 0), 0) + const renderSubscriptions = () => { + if (groupBy !== "none") { return ( - @@ -209,30 +202,38 @@ const AIDisplaySubscriptionsPartBase = memo( } return ( - - {/* Statistics Overview */} -
- - - {showCategories && } - -
- - {/* Subscriptions Display */} - {renderSubscriptions()} -
+ ) - }, -) + } + + return ( + + {/* Statistics Overview */} +
+ + + {showCategories && } + +
+ + {/* Subscriptions Display */} + {renderSubscriptions()} +
+ ) +} export const AIDisplaySubscriptionsPart = withDisplayStateHandler< AIDisplaySubscriptionsTool["output"] diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/share.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/share.tsx index 79c9fb0b2..0a581bd9e 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/share.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/share.tsx @@ -33,7 +33,7 @@ export function withDisplayStateHandler(config: { return function

( WrappedComponent: ComponentType

}>, ): ComponentType

{ - const WithDisplayStateHandler = (props: P) => { + const WithDisplayStateHandler = toolMemo((props: P) => { const { part } = props // Handle error state @@ -60,7 +60,7 @@ export function withDisplayStateHandler(config: { // Render the wrapped component with the validated output return } /> - } + }) WithDisplayStateHandler.displayName = `withDisplayStateHandler(${WrappedComponent.displayName || WrappedComponent.name})` diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatInterface.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatInterface.tsx index 15af6406f..d08809fb9 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatInterface.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatInterface.tsx @@ -193,7 +193,7 @@ const ChatInterfaceContent = () => { className={clsx( "absolute mx-auto duration-200 ease-in-out", hasMessages && "inset-x-0 bottom-0 max-w-4xl px-6 pb-6", - !hasMessages && "inset-x-0 bottom-1/2 max-w-3xl translate-y-full px-6 pb-6", + !hasMessages && "inset-x-0 bottom-1/2 max-w-3xl translate-y-[calc(100%+2rem)] px-6 pb-6", )} > {error && } diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/AIMessageParts.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/AIMessageParts.tsx index 7f597c7f0..8e8f851ca 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/AIMessageParts.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/AIMessageParts.tsx @@ -1,3 +1,5 @@ +import "@xyflow/react/dist/style.css" + import type { ToolUIPart } from "ai" import type { SerializedEditorState } from "lexical" import { m } from "motion/react" @@ -8,6 +10,7 @@ import type { AIDisplayAnalyticsTool, AIDisplayEntriesTool, AIDisplayFeedsTool, + AIDisplayFlowTool, AIDisplaySubscriptionsTool, BizUIMessage, } from "~/modules/ai-chat/store/types" @@ -18,11 +21,16 @@ import { AIDisplayFeedsPart, AIDisplaySubscriptionsPart, } from "../displays" +// import { AIDisplayFlowPart } from "../displays/AIDisplayFlowPart" import { AIDataBlockPart } from "./AIDataBlockPart" import { AIMarkdownMessage, AIMarkdownStreamingMessage } from "./AIMarkdownMessage" import { AIRichTextMessage } from "./AIRichTextMessage" import { ToolInvocationComponent } from "./ToolInvocationComponent" +const LazyAIDisplayFlowPart = React.lazy(() => + import("../displays/AIDisplayFlowPart").then((mod) => ({ default: mod.AIDisplayFlowPart })), +) + interface MessagePartsProps { message: BizUIMessage } @@ -98,6 +106,29 @@ export const AIMessageParts: React.FC = React.memo(({ message return } + case "tool-displayFlowChart": { + const loadingElement = ( +

+
+
+ + + Generating Flow Chart... + +
+
+
+ ) + return ( + + + + ) + } + default: { if (part.type.startsWith("tool-")) { if (part.type.startsWith("tool-chunkBreak")) { diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/store/types.ts b/apps/desktop/layer/renderer/src/modules/ai-chat/store/types.ts index bbc1f4829..b2d9a5adc 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/store/types.ts +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/store/types.ts @@ -18,5 +18,6 @@ export type AIDisplayAnalyticsTool = ToolWithState export type AIDisplayEntriesTool = ToolWithState export type AIDisplaySubscriptionsTool = ToolWithState +export type AIDisplayFlowTool = ToolWithState export { type BizUIMessage, type BizUIMetadata, type BizUITools } from "@folo-services/ai-tools" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b31da6ff5..ccf5d98b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -559,6 +559,9 @@ importers: '@welldone-software/why-did-you-render': specifier: 10.0.1 version: 10.0.1(react@19.0.0) + '@xyflow/react': + specifier: 12.8.2 + version: 12.8.2(@types/react@19.1.8)(immer@10.1.1(patch_hash=594c60b929bc0a3b56576f1a1787da1aec2a1fba51e3c21ec09c0ed38280af6c))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@yornaath/batshit': specifier: 0.10.1 version: 0.10.1 @@ -762,8 +765,8 @@ importers: specifier: workspace:* version: link:../../../../packages/internal/utils '@folo-services/ai-tools': - specifier: 0.2.16 - version: 0.2.16(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3) + specifier: 0.2.17 + version: 0.2.17(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3) '@types/node': specifier: 24.0.10 version: 24.0.10 @@ -4063,8 +4066,8 @@ packages: '@follow-app/client-sdk@0.3.26': resolution: {integrity: sha512-rIQq8fgxEsqu0o5DjY0Q8tTpK75ZLqfo1laW5KQVPL3ME8omljFTLXyX6khe7NXCx+1yHJGzJQ5S89qZGeb20g==} - '@folo-services/ai-tools@0.2.16': - resolution: {integrity: sha512-ZMV8dMxGGkbm5P3X4+0mguC1o0s+UbLyczsbHUawM0cR7+TWsnC7JYeZ7mifxZKz3nucsfg7oV89A++6p/Qeiw==} + '@folo-services/ai-tools@0.2.17': + resolution: {integrity: sha512-TQDWY4PpLtXqw+YkvmEcCg6JnbHDolbh2EJt6YzRXbfSm3qs+bonY9sPkqjG8PN2hqO7kYEmy8JBXGJdpGGwEw==} '@folo-services/constants@0.1.16': resolution: {integrity: sha512-zTpp0izvwK+t1uZurU0gh1yt5M253tfHdFOzxPi7mdUDdsI/cfXtwrBAbRYz1ne27RjRTP04KpzW2U5QOaDzhA==} @@ -7010,6 +7013,15 @@ packages: resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} engines: {node: '>=10.0.0'} + '@xyflow/react@12.8.2': + resolution: {integrity: sha512-VifLpxOy74ck283NQOtBn1e8igmB7xo7ADDKxyBHkKd8IKpyr16TgaYOhzqVwNMdB4NT+m++zfkic530L+gEXw==} + peerDependencies: + react: 19.0.0 + react-dom: 19.0.0 + + '@xyflow/system@0.0.66': + resolution: {integrity: sha512-TTxESDwPsATnuDMUeYYtKe4wt9v8bRO29dgYBhR8HyhSCzipnAdIL/1CDfFd+WqS1srVreo24u6zZeVIDk4r3Q==} + '@yornaath/batshit-devtools@1.7.1': resolution: {integrity: sha512-AyttV1Njj5ug+XqEWY1smV45dTWMlWKtj1B8jcFYgBKUFyUlF/qEhD+iP1E5UaRYW6hQRYD9T2WNDwFTrOMWzQ==} @@ -7858,6 +7870,9 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + clean-css@5.3.3: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} @@ -16082,6 +16097,21 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: 19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + zustand@5.0.6: resolution: {integrity: sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==} engines: {node: '>=12.20.0'} @@ -19255,7 +19285,7 @@ snapshots: - sql.js - sqlite3 - '@folo-services/ai-tools@0.2.16(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)': + '@folo-services/ai-tools@0.2.17(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)': dependencies: '@ai-sdk/openai': 2.0.2(zod@3.25.76) '@folo-services/drizzle': 0.1.11(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2) @@ -22913,6 +22943,29 @@ snapshots: '@xmldom/xmldom@0.8.10': {} + '@xyflow/react@12.8.2(@types/react@19.1.8)(immer@10.1.1(patch_hash=594c60b929bc0a3b56576f1a1787da1aec2a1fba51e3c21ec09c0ed38280af6c))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@xyflow/system': 0.0.66 + classcat: 5.0.5 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + zustand: 4.5.7(@types/react@19.1.8)(immer@10.1.1(patch_hash=594c60b929bc0a3b56576f1a1787da1aec2a1fba51e3c21ec09c0ed38280af6c))(react@19.0.0) + transitivePeerDependencies: + - '@types/react' + - immer + + '@xyflow/system@0.0.66': + dependencies: + '@types/d3-drag': 3.0.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + '@yornaath/batshit-devtools@1.7.1': {} '@yornaath/batshit@0.10.1': @@ -23986,6 +24039,8 @@ snapshots: dependencies: clsx: 2.1.1 + classcat@5.0.5: {} + clean-css@5.3.3: dependencies: source-map: 0.6.1 @@ -33543,6 +33598,14 @@ snapshots: zod@3.25.76: {} + zustand@4.5.7(@types/react@19.1.8)(immer@10.1.1(patch_hash=594c60b929bc0a3b56576f1a1787da1aec2a1fba51e3c21ec09c0ed38280af6c))(react@19.0.0): + dependencies: + use-sync-external-store: 1.5.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.1.8 + immer: 10.1.1(patch_hash=594c60b929bc0a3b56576f1a1787da1aec2a1fba51e3c21ec09c0ed38280af6c) + react: 19.0.0 + zustand@5.0.6(@types/react@19.1.8)(immer@10.1.1(patch_hash=594c60b929bc0a3b56576f1a1787da1aec2a1fba51e3c21ec09c0ed38280af6c))(react@19.0.0)(use-sync-external-store@1.5.0(react@19.0.0)): optionalDependencies: '@types/react': 19.1.8