diff --git a/apps/desktop/layer/renderer/package.json b/apps/desktop/layer/renderer/package.json index c9431ab8e..7cbffd976 100644 --- a/apps/desktop/layer/renderer/package.json +++ b/apps/desktop/layer/renderer/package.json @@ -121,7 +121,7 @@ "@follow/models": "workspace:*", "@follow/types": "workspace:*", "@follow/utils": "workspace:*", - "@folo-services/ai-tools": "0.2.31", + "@folo-services/ai-tools": "0.2.33", "@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/AIDisplayFlowPart.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/AIDisplayFlowPart.tsx index 332278ce3..1cdcbb4bb 100644 --- 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 @@ -1,13 +1,14 @@ import "@xyflow/react/dist/style.css" import { useIsDark } from "@follow/hooks" +import { thenable } from "@follow/utils" import { cn } from "@follow/utils/utils" +import type { ToolWithState } from "@folo-services/ai-tools" import { Background, Controls, ReactFlow } from "@xyflow/react" import { useCallback } from "react" import { useModalStack } from "~/components/ui/modal/stacked/hooks" -import type { AIDisplayFlowTool } from "../../store/types" import { toolMemo } from "./share" const FlowPreviewModal = ({ @@ -41,64 +42,63 @@ const FlowPreviewModal = ({ ) } -export const AIDisplayFlowPart = toolMemo( - ({ part, loadingElement }: { part: AIDisplayFlowTool; loadingElement: React.ReactNode }) => { - if (!part.input) return loadingElement - if (part.state === "input-streaming") { - return loadingElement - } +export const AIDisplayFlowPart = toolMemo(({ part }: { part: ToolWithState }) => { + if (!part.input) throw thenable + if (part.state === "input-streaming") { + throw thenable + } - const { nodes, edges } = part.input.flowChart as any - const colorMode = useIsDark() ? "dark" : "light" - const { present } = useModalStack() + // `part.input.flowChart` to make compatible with old data + const { nodes, edges } = part.input.flowChart || (part.input.schema.flowChart as any) + const colorMode = useIsDark() ? "dark" : "light" + const { present } = useModalStack() - const handleOpenModal = useCallback(() => { - present({ - title: "Flow Chart Preview", - content: () => , - max: true, - canClose: true, - clickOutsideToDismiss: false, - modalContentClassName: "p-0 h-full", - modalClassName: "h-[90vh] w-[90vw]", - }) - }, [nodes, edges, colorMode, present]) + const handleOpenModal = useCallback(() => { + present({ + title: "Flow Chart Preview", + content: () => , + max: true, + canClose: true, + clickOutsideToDismiss: false, + modalContentClassName: "p-0 h-full", + modalClassName: "h-[90vh] w-[90vw]", + }) + }, [nodes, edges, colorMode, present]) - return ( -
- - - - + return ( +
+ + + + - {/* Expand/Preview button */} - -
- ) - }, -) + {/* Expand/Preview button */} + +
+ ) +}) diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIChatContextBar.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIChatContextBar.tsx index cbb5c1c06..5e5ad0d56 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIChatContextBar.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIChatContextBar.tsx @@ -1,17 +1,12 @@ -import { ActionButton } from "@follow/components/ui/button/index.js" import { cn } from "@follow/utils/utils" import { memo, useCallback, useMemo, useRef } from "react" -import { toast } from "sonner" import { useAISettingValue } from "~/atoms/settings/ai" import { DropdownMenu, DropdownMenuTrigger } from "~/components/ui/dropdown-menu/dropdown-menu" -import { useModalStack } from "~/components/ui/modal/stacked/hooks" import { useFileUploadWithDefaults } from "~/modules/ai-chat/hooks/useFileUpload" import { useAIChatStore } from "~/modules/ai-chat/store/AIChatContext" import { SUPPORTED_MIME_ACCEPT } from "~/modules/ai-chat/utils/file-validation" -import { useCanCreateNewAITask } from "~/modules/ai-task/query" -import { AITaskModal } from "../../../ai-task/components/ai-task-modal" import { ContextBlock } from "../context-bar/blocks" import { ContextMenuContent, ShortcutsMenuContent } from "../context-bar/menus" @@ -21,8 +16,6 @@ export const AIChatContextBar: Component<{ onSendShortcut?: (prompt: string) => const { shortcuts } = useAISettingValue() const fileInputRef = useRef(null) const { handleFileInputChange } = useFileUploadWithDefaults() - const { present } = useModalStack() - const canCreateNewTask = useCanCreateNewAITask() // Filter enabled shortcuts const enabledShortcuts = useMemo( @@ -34,18 +27,6 @@ export const AIChatContextBar: Component<{ onSendShortcut?: (prompt: string) => fileInputRef.current?.click() }, []) - const handleScheduleActionClick = () => { - if (!canCreateNewTask) { - toast.error("Please remove an existing task before creating a new one.") - return - } - present({ - title: "New AI Task", - canClose: true, - content: () => , - }) - } - return (
{/* Add Context Button */} @@ -97,14 +78,6 @@ export const AIChatContextBar: Component<{ onSendShortcut?: (prompt: string) => )} - - - - {/* Context Blocks */} {blocks.map((block) => ( diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatHeader.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatHeader.tsx index f771db299..62cb4cf2c 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatHeader.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatHeader.tsx @@ -41,7 +41,7 @@ const ChatHeaderLayout = ({ [chatActions], ) - const maskImage = `linear-gradient(to bottom, black 0%, black 75%, transparent 100%)` + const maskImage = `linear-gradient(to bottom, black 0%, black 90%, transparent 100%)` return (
{ return (
- {messages.map((message) => ( - - {message.role === "user" ? ( - - ) : ( - - )} - - ))} + {messages.map((message, index) => { + const isLastMessage = index === messages.length - 1 + return ( + + {message.role === "user" ? ( + + ) : ( + + )} + + ) + })}
) } diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/CollapsibleError.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/CollapsibleError.tsx index 21b5f2d81..a1a2f1620 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/CollapsibleError.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/CollapsibleError.tsx @@ -1,7 +1,6 @@ -import { Spring } from "@follow/components/constants/spring.js" +import { ScrollArea } from "@follow/components/ui/scroll-area/ScrollArea.js" import { cn } from "@follow/utils" import { ExceptionCodeMap } from "@follow-app/client-sdk" -import { m } from "motion/react" import * as React from "react" import { useI18n } from "~/hooks/common/useI18n" @@ -12,6 +11,7 @@ interface CollapsibleErrorProps { className?: string collapsedHeight?: string icon?: string + expandedMaxHeight?: string } interface ErrorData { @@ -27,6 +27,7 @@ export const CollapsibleError: React.FC = ({ className, collapsedHeight = "48px", icon = "i-mgc-alert-cute-fi", + expandedMaxHeight = "240px", }) => { const [isExpanded, setIsExpanded] = React.useState(false) const t = useI18n() @@ -159,56 +160,42 @@ export const CollapsibleError: React.FC = ({ onMouseEnter={() => setIsExpanded(true)} onMouseLeave={() => setIsExpanded(false)} > - + {/* Collapsed Content */}
- {/* Collapsed Content */} -
- - - -
-
{getErrorTitle()}
-
- - hover to expand - +
+
- - {/* Expanded Content */} - +
{getErrorTitle()}
+
+ + hover to expand + +
+ + {/* Expanded Content */} +
+
-
+
{displayMessage as string}
{contextualInfo && ( @@ -217,9 +204,9 @@ export const CollapsibleError: React.FC = ({
)}
- +
-
+
) } diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/TaskReportDropdown.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/TaskReportDropdown.tsx index 3793fc343..ddbe821ec 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/TaskReportDropdown.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/TaskReportDropdown.tsx @@ -10,15 +10,17 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, } from "~/components/ui/dropdown-menu/dropdown-menu" -import { useDialog } from "~/components/ui/modal/stacked/hooks" +import { useDialog, useModalStack } from "~/components/ui/modal/stacked/hooks" import { useChatActions, useCurrentChatId } from "~/modules/ai-chat/store/hooks" import { AIChatSessionService } from "~/modules/ai-chat-session" import { useAIChatSessionListQuery, useDeleteAIChatSessionMutation, } from "~/modules/ai-chat-session/query" +import { AITaskModal, useCanCreateNewAITask } from "~/modules/ai-task" import { AIPersistService } from "../../services" @@ -77,7 +79,7 @@ const SessionItem = ({ session, onClick, onDelete, isLoading }: SessionItemProps const EmptyState = () => { return (
- +

All task reports read

) @@ -100,6 +102,20 @@ export const TaskReportDropdown = ({ triggerElement, asChild = true }: TaskRepor const hasUnreadSessions = unreadSessions.length > 0 + const { present } = useModalStack() + const canCreateNewTask = useCanCreateNewAITask() + const handleScheduleActionClick = () => { + if (!canCreateNewTask) { + toast.error("Please remove an existing task before creating a new one.") + return + } + present({ + title: "New AI Task", + canClose: true, + content: () => , + }) + } + const handleSessionSelect = useCallback( async (session: AIChatSession) => { if (session.chatId === currentChatId) return @@ -153,7 +169,7 @@ export const TaskReportDropdown = ({ triggerElement, asChild = true }: TaskRepor const defaultTrigger = ( - + {hasUnreadSessions && ( )} + + {canCreateNewTask && ( + <> + + + + New Task + + + )} ) diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/AIChatMessage.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/AIChatMessage.tsx index bbc974402..8b46ba5b4 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/AIChatMessage.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/AIChatMessage.tsx @@ -23,6 +23,7 @@ export interface ChatMessage { interface AIChatMessageProps { message: BizUIMessage + isLastMessage: boolean } // Utility function for converting message to markdown @@ -58,61 +59,64 @@ const useMessageMarkdownFormat = (message: BizUIMessage) => { } // AI message component -export const AIChatMessage: React.FC = React.memo(({ message }) => { - if (message.parts.length === 0) { - throw thenable - } - - const getMessageMarkdownFormat = useMessageMarkdownFormat(message) - - const handleCopy = React.useCallback(async () => { - const messageContent = getMessageMarkdownFormat() - try { - await copyToClipboard(messageContent) - toast.success("Message copied to clipboard") - } catch { - toast.error("Failed to copy message") +export const AIChatMessage: React.FC = React.memo( + ({ message, isLastMessage }) => { + if (message.parts.length === 0) { + throw thenable } - }, [getMessageMarkdownFormat]) - return ( -
-
- {/* Normal message display */} -
-
- + const getMessageMarkdownFormat = useMessageMarkdownFormat(message) + + const handleCopy = React.useCallback(async () => { + const messageContent = getMessageMarkdownFormat() + try { + await copyToClipboard(messageContent) + toast.success("Message copied to clipboard") + } catch { + toast.error("Failed to copy message") + } + }, [getMessageMarkdownFormat]) + + return ( +
+
+ {/* Normal message display */} +
+
+ +
-
- {/* Action buttons */} -
- - {message.metadata && ( - - - - )} -
+ {/* Action buttons */} +
+ -
+ {message.metadata && ( + + + + )} +
+ +
+
-
- ) -}) + ) + }, +) export const AIChatWaitingIndicator: React.FC = () => { return ( 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 90a2ab79a..8ac85fce9 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,5 +1,7 @@ import "@xyflow/react/dist/style.css" +import { alwaysFalse } from "@follow/utils" +import { ErrorBoundary } from "@sentry/react" import type { ToolUIPart } from "ai" import * as React from "react" @@ -29,98 +31,100 @@ const LazyAIDisplayFlowPart = React.lazy(() => interface AIMessagePartsProps { message: BizUIMessage + isLastMessage: boolean } -export const AIMessageParts: React.FC = React.memo(({ message }) => { - const [shouldStreamingAnimation, setShouldStreamingAnimation] = React.useState(false) - const chatStatus = useChatStatus() +export const AIMessageParts: React.FC = React.memo( + ({ message, isLastMessage }) => { + const [shouldStreamingAnimation, setShouldStreamingAnimation] = React.useState(false) + const chatStatus = useChatStatus() - React.useEffect(() => { - // Delay 2s to set shouldStreamingAnimation - const timerId = setTimeout(() => { - setShouldStreamingAnimation(true) - }, 2000) - return () => clearTimeout(timerId) - }, []) + React.useEffect(() => { + // Delay 2s to set shouldStreamingAnimation + const timerId = setTimeout(() => { + setShouldStreamingAnimation(true) + }, 2000) + return () => clearTimeout(timerId) + }, []) - const shouldMessageAnimation = React.useMemo(() => { - return chatStatus === "streaming" && shouldStreamingAnimation - }, [chatStatus, shouldStreamingAnimation]) + const shouldMessageAnimation = React.useMemo(() => { + return chatStatus === "streaming" && shouldStreamingAnimation && isLastMessage + }, [chatStatus, isLastMessage, shouldStreamingAnimation]) - return message.parts.map((part, index) => { - const partKey = `${message.id}-${index}` + return message.parts.map((part, index) => { + const partKey = `${message.id}-${index}` - switch (part.type) { - case "text": { - return ( - - ) - } + switch (part.type) { + case "text": { + return ( + + ) + } - case "reasoning": { - return ( - - ) - } + case "reasoning": { + return ( + + ) + } - case "tool-displayAnalytics": { - return - } - case "tool-displayEntries": { - return - } - case "tool-displaySubscriptions": { - return ( - - ) - } - case "tool-displayFeeds": { - return - } + case "tool-displayAnalytics": { + return + } + case "tool-displayEntries": { + return + } + case "tool-displaySubscriptions": { + return ( + + ) + } + case "tool-displayFeeds": { + return + } - case "tool-displayFlowChart": { - const loadingElement = ( -
-
-
- - - Generating Flow Chart... - + case "tool-displayFlowChart": { + const loadingElement = ( +
+
+
+ + + Generating Flow Chart... + +
-
- ) - return ( - - - - ) - } - - default: { - if (part.type.startsWith("tool-")) { - if (part.type.startsWith("tool-chunkBreak")) { - return null - } - return + ) + return ( + + + + + + ) + } + + default: { + if (part.type.startsWith("tool-")) { + if (part.type.startsWith("tool-chunkBreak")) { + return null + } + return + } + return null } - return null } - } - }) -}) + }) + }, +) AIMessageParts.displayName = "AIMessageParts" diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/animated/constants.ts b/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/animated/constants.ts index e4c3bcf16..b8b187e30 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/animated/constants.ts +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/message/animated/constants.ts @@ -1,4 +1,4 @@ -export const DEFAULT_ANIMATION = "ft-typeIn 0.5s ease-in-out" +export const DEFAULT_ANIMATION = "mask-left-to-right 0.5s ease-in-out" export const ANIMATION_STYLE = { animation: DEFAULT_ANIMATION, } diff --git a/packages/internal/utils/src/noop.ts b/packages/internal/utils/src/noop.ts index 2b49e954f..3c4cbef24 100644 --- a/packages/internal/utils/src/noop.ts +++ b/packages/internal/utils/src/noop.ts @@ -2,3 +2,5 @@ export const noop = () => {} // eslint-disable-next-line unicorn/no-thenable export const thenable: any = { then: noop } export const emptyObject = {} + +export const alwaysFalse = () => false diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c35d47bf..2c22fd641 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -774,8 +774,8 @@ importers: specifier: workspace:* version: link:../../../../packages/internal/utils '@folo-services/ai-tools': - specifier: 0.2.31 - version: 0.2.31(@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.33 + version: 0.2.33(@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 @@ -4060,18 +4060,18 @@ packages: '@follow-app/client-sdk@0.3.50': resolution: {integrity: sha512-qc8gI5li/v62h1oR8p0bkLh+tlNaEP8jogppoP92v10XktERtd0rrx5grvSQ1pw0mM3Fws4CknEJZeGH9leDaw==} - '@folo-services/ai-tools@0.2.31': - resolution: {integrity: sha512-oqTVMBc6UCLOt/3M9GX4I+DJ8WT2U8Hlok1MN/Sruw8sQu0ZvtoDvkuiJEtssOQAGDifzHm7z8AVmOgNoGrKUg==} + '@folo-services/ai-tools@0.2.33': + resolution: {integrity: sha512-5Q35xDIT5dt/l1Dn0ApadD8l7NHK8rVJYqemHuENYvaUDPNJ8f7SXfXY5HwaMv/LpV20ln5DwMEHXwjtMdP75g==} '@folo-services/constants@0.1.29': resolution: {integrity: sha512-7YH5UQyUoT0STsoAKXTJgdAlvW88RKe5L/yqq4nbovSf2UJm1yEfl+opOYMeYEKBzX3OnixIipPL/1oKQPeh0w==} - '@folo-services/drizzle@0.1.22': - resolution: {integrity: sha512-n4L0bBA5AxqJn5K3k+BTzmtuZh90LAO7G6zALCo3H+BOsnMsQ1OCsQH/xFg7m6ICQ80LJVbHdUN6b4ZKUoQJ/g==} - '@folo-services/drizzle@0.1.23': resolution: {integrity: sha512-Y4oGzfcYtsh9jdaDZvMh0CMO2tD1c2VSt1gtaZbq9uF17sv45empMI72OdGfWEJBJse7h/P+YGydX7iK63aoIQ==} + '@folo-services/drizzle@0.1.24': + resolution: {integrity: sha512-C2B32AuAIoPihwkowEzQVSCq9jz5gUJOXWSTsh46ICCXIQ+FeDLclGuOS13+3FQ79mGBslUqrRu5chpoeDLE/g==} + '@folo-services/exceptions@0.1.16': resolution: {integrity: sha512-ThmLLe1Dv2CFsDIgcuY6kJhYJnjiQ7UEsewaqc25MkuImDabhzWnnad+3zRnJGLpveaMeEZ69uqUsTbKC+fR7g==} @@ -19018,10 +19018,10 @@ snapshots: - sql.js - sqlite3 - '@folo-services/ai-tools@0.2.31(@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.33(@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.22(zod@3.25.76) - '@folo-services/drizzle': 0.1.22(@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) + '@folo-services/drizzle': 0.1.24(@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) ai: 5.0.26(zod@3.25.76) drizzle-orm: 0.44.3(@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) zod: 3.25.76 @@ -19061,7 +19061,7 @@ snapshots: dependencies: zod: 3.25.76 - '@folo-services/drizzle@0.1.22(@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)': + '@folo-services/drizzle@0.1.23(@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)': dependencies: '@folo-services/exceptions': 0.1.16 drizzle-orm: 0.44.3(@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) @@ -19100,7 +19100,7 @@ snapshots: - sql.js - sqlite3 - '@folo-services/drizzle@0.1.23(@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)': + '@folo-services/drizzle@0.1.24(@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)': dependencies: '@folo-services/exceptions': 0.1.16 drizzle-orm: 0.44.3(@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)