perf(ai): improve ai message streaming performance

- Updated ai package version from 5.0.0-beta.34 to 5.0.0.
- Added react-fast-compare package for improved memoization in chat components.
- Refactored AIDisplayAnalyticsPart and AIDisplaySubscriptionsPart to utilize toolMemo for performance optimization.
- Introduced a new utility function for throttled markdown parsing in AIMarkdownMessage component.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-08-01 17:37:54 +08:00
parent 29b96118dd
commit 6ca2ab1e2d
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
7 changed files with 311 additions and 253 deletions

View File

@ -51,7 +51,7 @@
"@use-gesture/react": "10.3.1",
"@welldone-software/why-did-you-render": "10.0.1",
"@yornaath/batshit": "0.10.1",
"ai": "5.0.0-beta.34",
"ai": "5.0.0",
"camelcase-keys": "9.1.3",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
@ -85,6 +85,7 @@
"plain-shiki": "0.3.0",
"re-resizable": "6.11.2",
"react-blurhash": "0.3.0",
"react-fast-compare": "3.2.2",
"react-fast-marquee": "1.6.5",
"react-hook-form": "7.60.0",
"react-hotkeys-hook": "5.1.0",

View File

@ -14,10 +14,10 @@ import {
TableRow,
} from "@follow/components/ui/table/index.js"
import dayjs from "dayjs"
import { memo } from "react"
import type { AIDisplayAnalyticsTool } from "../../__internal__/types"
import { ErrorState, LoadingState } from "../common-states"
import { toolMemo } from "./share"
import { ChartPlaceholder, StatCard } from "./shared"
type AnalyticsData = AIDisplayAnalyticsTool["output"]["analyticsData"]
@ -230,7 +230,7 @@ const OverviewAnalytics = ({ data }: { data: AnalyticsData["overviewStats"] }) =
)
}
export const AIDisplayAnalyticsPart = memo(({ part }: { part: AIDisplayAnalyticsTool }) => {
export const AIDisplayAnalyticsPart = toolMemo(({ part }: { part: AIDisplayAnalyticsTool }) => {
// Handle error state
if (part.state === "output-error") {
return (

View File

@ -6,12 +6,12 @@ import {
CardTitle,
} from "@follow/components/ui/card/index.js"
import dayjs from "dayjs"
import { memo } from "react"
import { FeedIcon } from "~/modules/feed/feed-icon"
import type { AIDisplaySubscriptionsTool } from "../../__internal__/types"
import { ErrorState, LoadingState } from "../common-states"
import { toolMemo } from "./share"
import { AnalyticsMetrics, CategoryTag, EmptyState, StatCard } from "./shared"
type SubscriptionData = AIDisplaySubscriptionsTool["output"]["subscriptions"]
@ -172,105 +172,107 @@ const GroupedSubscriptions = ({
)
}
export const AIDisplaySubscriptionsPart = memo(({ part }: { part: AIDisplaySubscriptionsTool }) => {
// Handle error state
if (part.state === "output-error") {
return (
<ErrorState
title="Subscriptions Error"
error="An error occurred while loading subscriptions"
maxWidth="max-w-6xl"
/>
)
}
// Handle loading state
if (part.state !== "output-available" || !part.output) {
return (
<LoadingState
title="Loading Subscriptions..."
description="Fetching subscription data..."
maxWidth="max-w-6xl"
/>
)
}
// Extract output with proper typing
const output = part.output as NonNullable<AIDisplaySubscriptionsTool["output"]>
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") {
export const AIDisplaySubscriptionsPart = toolMemo(
({ part }: { part: AIDisplaySubscriptionsTool }) => {
// Handle error state
if (part.state === "output-error") {
return (
<GroupedSubscriptions
data={subscriptions}
groupBy={groupBy}
displayType={displayType}
showAnalytics={showAnalytics}
showCategories={showCategories}
<ErrorState
title="Subscriptions Error"
error="An error occurred while loading subscriptions"
maxWidth="max-w-6xl"
/>
)
}
switch (displayType) {
default: {
// Handle loading state
if (part.state !== "output-available" || !part.output) {
return (
<LoadingState
title="Loading Subscriptions..."
description="Fetching subscription data..."
maxWidth="max-w-6xl"
/>
)
}
// Extract output with proper typing
const output = part.output as NonNullable<AIDisplaySubscriptionsTool["output"]>
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 (
<SubscriptionsGrid
<GroupedSubscriptions
data={subscriptions}
groupBy={groupBy}
displayType={displayType}
showAnalytics={showAnalytics}
showCategories={showCategories}
/>
)
}
switch (displayType) {
default: {
return (
<SubscriptionsGrid
data={subscriptions}
showAnalytics={showAnalytics}
showCategories={showCategories}
/>
)
}
}
}
}
return (
<Card className="mb-2 w-full min-w-0">
<div className="w-[9999px] max-w-[calc(var(--ai-chat-layout-width,65ch)_-120px)]" />
<CardHeader>
<CardTitle className="text-text flex items-center gap-2 text-xl font-semibold">
<span className="text-lg">📋</span>
<span>{title || "My Subscriptions"}</span>
</CardTitle>
<CardDescription>
{formatDisplayType(displayType)} {formatFilterBy(filterBy)} {formatGroupBy(groupBy)}
</CardDescription>
</CardHeader>
<CardContent className="@container space-y-6">
{/* Statistics Overview */}
<div className="@[700px]:grid-cols-4 grid grid-cols-2 gap-4">
<StatCard title="Total Subscriptions" value={totalSubscriptions} emoji="📊" />
<StatCard
title="Active Feeds"
value={activeSubscriptions}
description={`${totalSubscriptions - activeSubscriptions} inactive`}
emoji="🟢"
/>
{showCategories && <StatCard title="Categories" value={categoriesCount} emoji="🏷️" />}
<StatCard title="Total Views" value={totalViews.toLocaleString()} emoji="👀" />
</div>
return (
<Card className="mb-2 w-full min-w-0">
<div className="w-[9999px] max-w-[calc(var(--ai-chat-layout-width,65ch)_-120px)]" />
<CardHeader>
<CardTitle className="text-text flex items-center gap-2 text-xl font-semibold">
<span className="text-lg">📋</span>
<span>{title || "My Subscriptions"}</span>
</CardTitle>
<CardDescription>
{formatDisplayType(displayType)} {formatFilterBy(filterBy)} {formatGroupBy(groupBy)}
</CardDescription>
</CardHeader>
<CardContent className="@container space-y-6">
{/* Statistics Overview */}
<div className="@[700px]:grid-cols-4 grid grid-cols-2 gap-4">
<StatCard title="Total Subscriptions" value={totalSubscriptions} emoji="📊" />
<StatCard
title="Active Feeds"
value={activeSubscriptions}
description={`${totalSubscriptions - activeSubscriptions} inactive`}
emoji="🟢"
/>
{showCategories && <StatCard title="Categories" value={categoriesCount} emoji="🏷️" />}
<StatCard title="Total Views" value={totalViews.toLocaleString()} emoji="👀" />
</div>
{/* Subscriptions Display */}
{renderSubscriptions()}
</CardContent>
</Card>
)
})
{/* Subscriptions Display */}
{renderSubscriptions()}
</CardContent>
</Card>
)
},
)

View File

@ -0,0 +1,15 @@
import type { ComponentType } from "react"
import { memo } from "react"
import isEqual from "react-fast-compare"
interface PartWithState {
part: {
state: string
}
}
export const toolMemo = <P extends PartWithState>(FC: ComponentType<P>): ComponentType<P> =>
memo(FC, (prev, next) => {
if (prev.part.state === "output-available") return true
return isEqual(prev, next)
}) as ComponentType<P>

View File

@ -1,13 +1,138 @@
import type { LinkProps } from "@follow/components/ui/link/LinkWithTooltip.js"
import { parseMarkdown } from "@follow/components/utils/parse-markdown.js"
import { cn, isBizId } from "@follow/utils"
import { createElement, isValidElement, memo, useMemo } from "react"
import {
createElement,
isValidElement,
memo,
useCallback,
useDeferredValue,
useMemo,
useRef,
} from "react"
import { ShikiHighLighter } from "~/components/ui/code-highlighter"
import { MermaidDiagram } from "~/components/ui/diagrams"
import { MarkdownLink } from "~/components/ui/markdown/renderers/MarkdownLink"
import { usePeekModal } from "~/hooks/biz/usePeekModal"
// Custom hook for throttled markdown parsing during streaming
const useThrottledMarkdownParsing = (text: string, isProcessing: boolean) => {
const lastParsedTextRef = useRef<string>("")
const cachedResultRef = useRef<any>(null)
const lastParseTimeRef = useRef<number>(0)
const parseWithCache = useCallback((content: string, shouldProcess: boolean) => {
const now = Date.now()
// During streaming, throttle parsing
if (shouldProcess && cachedResultRef.current) {
const timeSinceLastParse = now - lastParseTimeRef.current
if (timeSinceLastParse < 16) {
// Return cached result if within throttle window
return cachedResultRef.current
}
}
// If content hasn't changed, return cached result
if (content === lastParsedTextRef.current && cachedResultRef.current) {
return cachedResultRef.current
}
// Parse and cache the result
const result = parseMarkdown(content, {
components: {
pre: ({ children }) => {
const props = isValidElement(children) && "props" in children && children.props
if (props) {
const { className, children } = props as any
if (className && className.includes("language-") && typeof children === "string") {
const language = className.replace("language-", "")
const code = children
// Render Mermaid diagrams - skip during processing for performance
if (language === "mermaid") {
return <MermaidDiagram code={code} shouldRender={!shouldProcess} />
}
return <ShikiHighLighter code={code} language={language} showCopy />
}
}
return <pre className="text-text-secondary">{children}</pre>
},
a: ({ node, ...props }) => {
return createElement(RelatedEntryLink, { ...props } as any)
},
table: ({ children, ref, node, ...props }) => {
return (
<div className="border-border bg-material-thin overflow-x-auto rounded-lg border">
<table {...props} className="divide-border my-0 min-w-full divide-y text-sm">
{children}
</table>
</div>
)
},
thead: ({ children, ref, node, ...props }) => {
return (
<thead {...props} className="bg-fill-tertiary">
{children}
</thead>
)
},
th: ({ children, ref, node, ...props }) => {
return (
<th
{...props}
className="text-text-secondary whitespace-nowrap px-4 py-3 text-left text-xs font-medium uppercase tracking-wider"
>
{children}
</th>
)
},
tbody: ({ children, ref, node, ...props }) => {
return (
<tbody {...props} className="bg-material-ultra-thin divide-border divide-y">
{children}
</tbody>
)
},
tr: ({ children, ref, node, ...props }) => {
return (
<tr {...props} className="hover:bg-material-thin transition-colors duration-150">
{children}
</tr>
)
},
td: ({ children, ref, node, ...props }) => {
return (
<td {...props} className="text-text whitespace-nowrap px-4 py-3 text-sm">
{children}
</td>
)
},
},
}).content
lastParsedTextRef.current = content
cachedResultRef.current = result
lastParseTimeRef.current = now
return result
}, [])
return useMemo(() => {
// For non-processing state, always parse immediately
if (!isProcessing) {
return parseWithCache(text, false)
}
// During processing, apply throttling logic
return parseWithCache(text, true)
}, [text, isProcessing, parseWithCache])
}
export const AIMarkdownMessage = memo(
({
text,
@ -18,102 +143,45 @@ export const AIMarkdownMessage = memo(
className?: string
isProcessing?: boolean
}) => {
const className = tw`prose dark:prose-invert text-sm
const className = `prose dark:prose-invert text-sm
prose-h1:text-2xl prose-h2:text-xl prose-h3:text-lg prose-h4:text-base prose-h5:text-base prose-h6:text-sm
prose-li:list-disc prose-li:marker:text-accent prose-hr:border-border prose-hr:mx-8
`
prose-li:list-disc prose-li:marker:text-accent prose-hr:border-border prose-hr:mx-8`
return (
<div className={cn(className, classNameProp)}>
{useMemo(
() =>
parseMarkdown(text, {
components: {
pre: ({ children }) => {
// props
const props = isValidElement(children) && "props" in children && children.props
// Use deferred value for lower priority rendering during streaming
const deferredText = useDeferredValue(text)
if (props) {
const { className, children } = props as any
if (
className &&
className.includes("language-") &&
typeof children === "string"
) {
const language = className.replace("language-", "")
const code = children
// Render Mermaid diagrams
if (language === "mermaid") {
return <MermaidDiagram code={code} shouldRender={!isProcessing} />
}
return <ShikiHighLighter code={code} language={language} showCopy />
}
}
return <pre className="text-text-secondary">{children}</pre>
},
a: ({ node, ...props }) => {
return createElement(RelatedEntryLink, { ...props } as any)
},
table: ({ children, ref, node, ...props }) => {
return (
<div className="border-border bg-material-thin overflow-x-auto rounded-lg border">
<table {...props} className="divide-border my-0 min-w-full divide-y text-sm">
{children}
</table>
</div>
)
},
thead: ({ children, ref, node, ...props }) => {
return (
<thead {...props} className="bg-fill-tertiary">
{children}
</thead>
)
},
th: ({ children, ref, node, ...props }) => {
return (
<th
{...props}
className="text-text-secondary whitespace-nowrap px-4 py-3 text-left text-xs font-medium uppercase tracking-wider"
>
{children}
</th>
)
},
tbody: ({ children, ref, node, ...props }) => {
return (
<tbody {...props} className="bg-material-ultra-thin divide-border divide-y">
{children}
</tbody>
)
},
tr: ({ children, ref, node, ...props }) => {
return (
<tr
{...props}
className="hover:bg-material-thin transition-colors duration-150"
>
{children}
</tr>
)
},
td: ({ children, ref, node, ...props }) => {
return (
<td {...props} className="text-text whitespace-nowrap px-4 py-3 text-sm">
{children}
</td>
)
},
},
}).content,
[isProcessing, text],
)}
</div>
// Use our optimized parsing hook
const parsedContent = useThrottledMarkdownParsing(
// During streaming, use deferred text for non-urgent updates
isProcessing ? deferredText : text,
isProcessing ?? false,
)
return <div className={cn(className, classNameProp)}>{parsedContent}</div>
},
// Enhanced memo comparison for better performance
(prevProps, nextProps) => {
// If not processing, do normal comparison
if (!nextProps.isProcessing && !prevProps.isProcessing) {
return (
prevProps.text === nextProps.text &&
prevProps.className === nextProps.className &&
prevProps.isProcessing === nextProps.isProcessing
)
}
// During processing, be more lenient with text changes to reduce re-renders
if (nextProps.isProcessing) {
// Only re-render if there's a significant change or processing state changes
return (
prevProps.text === nextProps.text &&
prevProps.className === nextProps.className &&
prevProps.isProcessing === nextProps.isProcessing
)
}
// Default comparison
return false
},
)

View File

@ -29,28 +29,6 @@ export function convertLexicalToMarkdown(editor: LexicalEditor): string {
return markdown
}
/**
* Create MessageContent for user's rich text input
*/
export function createRichTextMessage(editor: LexicalEditor): MessageContent {
const schema = editor.getEditorState().toJSON()
return {
format: "richtext",
content: schema,
}
}
/**
* Create MessageContent for plaintext (AI responses, existing messages)
*/
export function createPlaintextMessage(text: string): MessageContent {
return {
format: "plaintext",
content: text,
}
}
/**
* Get markdown string for AI communication from any MessageContent
*/
@ -70,17 +48,3 @@ export function getMarkdownForAI(message: MessageContent, editor?: LexicalEditor
// Fallback
return ""
}
/**
* Check if message is rich text format
*/
export function isRichTextMessage(message: MessageContent): boolean {
return message.format === "richtext"
}
/**
* Check if message is plaintext format
*/
export function isPlaintextMessage(message: MessageContent): boolean {
return message.format === "plaintext"
}

View File

@ -562,8 +562,8 @@ importers:
specifier: 0.10.1
version: 0.10.1
ai:
specifier: 5.0.0-beta.34
version: 5.0.0-beta.34(zod@3.25.75)
specifier: 5.0.0
version: 5.0.0(zod@3.25.75)
camelcase-keys:
specifier: 9.1.3
version: 9.1.3
@ -663,6 +663,9 @@ importers:
react-blurhash:
specifier: 0.3.0
version: 0.3.0(blurhash@2.0.5)(react@19.0.0)
react-fast-compare:
specifier: 3.2.2
version: 3.2.2
react-fast-marquee:
specifier: 1.6.5
version: 1.6.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@ -1957,8 +1960,8 @@ packages:
graphql:
optional: true
'@ai-sdk/gateway@1.0.0-beta.19':
resolution: {integrity: sha512-felWPMuECZRGx8xnmvH5dW3jywKTkGnw/tXN8szphGzEDr/BfxywuXijfPBG2WBUS6frPXsvSLDRdCm5W38PXA==}
'@ai-sdk/gateway@1.0.0':
resolution: {integrity: sha512-VEm87DyRx1yIPywbTy8ntoyh4jEDv1rJ88m+2I7zOm08jJI5BhFtAWh0OF6YzZu1Vu4NxhOWO4ssGdsqydDQ3A==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4
@ -1975,8 +1978,8 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4
'@ai-sdk/provider-utils@3.0.0-beta.10':
resolution: {integrity: sha512-e6WSsgM01au04/1L/v5daXHn00eKjPBQXl3jq3BfvQbQ1jo8Rls2pvrdkyVc25jBW4TV4Zm+tw+v6NAh5NPXMA==}
'@ai-sdk/provider-utils@3.0.0':
resolution: {integrity: sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4
@ -1993,12 +1996,12 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4
'@ai-sdk/provider@2.0.0-beta.1':
resolution: {integrity: sha512-Z8SPncMtS3RsoXITmT7NVwrAq6M44dmw0DoUOYJqNNtCu8iMWuxB8Nxsoqpa0uEEy9R1V1ZThJAXTYgjTUxl3w==}
'@ai-sdk/provider@2.0.0':
resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==}
engines: {node: '>=18'}
'@ai-sdk/provider@2.0.0-beta.2':
resolution: {integrity: sha512-vqhtZA7R24q1XnmfmIb1fZSmHMIaJH1BVQ+0kFnNJgqWsc+V8i+yfetZ37gUc4fXATFmBuS/6O7+RPoHsZ2Fqg==}
'@ai-sdk/provider@2.0.0-beta.1':
resolution: {integrity: sha512-Z8SPncMtS3RsoXITmT7NVwrAq6M44dmw0DoUOYJqNNtCu8iMWuxB8Nxsoqpa0uEEy9R1V1ZThJAXTYgjTUxl3w==}
engines: {node: '>=18'}
'@alloc/quick-lru@5.2.0':
@ -7116,8 +7119,8 @@ packages:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
ai@5.0.0-beta.34:
resolution: {integrity: sha512-AFJ4p35AxA+1KFtnoouePLaAUpoj0IxIAoq/xgIv88qzYajTg4Sac5KaV4CDHFRLoF0L2cwhlFXt/Ss/zyBKkA==}
ai@5.0.0:
resolution: {integrity: sha512-F4jOhOSeiZD8lXpF4l1hRqyM1jbqoLKGVZNxAP467wmQCsWUtElMa3Ki5PrDMq6qvUNC3deUKfERDAsfj7IDlg==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4
@ -13548,6 +13551,9 @@ packages:
peerDependencies:
react: 19.0.0
react-fast-compare@3.2.2:
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
react-fast-marquee@1.6.5:
resolution: {integrity: sha512-swDnPqrT2XISAih0o74zQVE2wQJFMvkx+9VZXYYNSLb/CUcAzU9pNj637Ar2+hyRw6b4tP6xh4GQZip2ZCpQpg==}
peerDependencies:
@ -16173,16 +16179,16 @@ snapshots:
optionalDependencies:
graphql: 16.8.1
'@ai-sdk/gateway@1.0.0-beta.19(zod@3.25.75)':
'@ai-sdk/gateway@1.0.0(zod@3.25.75)':
dependencies:
'@ai-sdk/provider': 2.0.0-beta.2
'@ai-sdk/provider-utils': 3.0.0-beta.10(zod@3.25.75)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.0(zod@3.25.75)
zod: 3.25.75
'@ai-sdk/gateway@1.0.0-beta.19(zod@3.25.76)':
'@ai-sdk/gateway@1.0.0(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 2.0.0-beta.2
'@ai-sdk/provider-utils': 3.0.0-beta.10(zod@3.25.76)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.0(zod@3.25.76)
zod: 3.25.76
'@ai-sdk/gateway@1.0.0-beta.3(zod@3.25.75)':
@ -16203,17 +16209,17 @@ snapshots:
'@ai-sdk/provider-utils': 3.0.0-beta.5(zod@3.25.76)
zod: 3.25.76
'@ai-sdk/provider-utils@3.0.0-beta.10(zod@3.25.75)':
'@ai-sdk/provider-utils@3.0.0(zod@3.25.75)':
dependencies:
'@ai-sdk/provider': 2.0.0-beta.2
'@ai-sdk/provider': 2.0.0
'@standard-schema/spec': 1.0.0
eventsource-parser: 3.0.3
zod: 3.25.75
zod-to-json-schema: 3.24.5(zod@3.25.75)
'@ai-sdk/provider-utils@3.0.0-beta.10(zod@3.25.76)':
'@ai-sdk/provider-utils@3.0.0(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 2.0.0-beta.2
'@ai-sdk/provider': 2.0.0
'@standard-schema/spec': 1.0.0
eventsource-parser: 3.0.3
zod: 3.25.76
@ -16243,11 +16249,11 @@ snapshots:
zod: 3.25.76
zod-to-json-schema: 3.24.5(zod@3.25.76)
'@ai-sdk/provider@2.0.0-beta.1':
'@ai-sdk/provider@2.0.0':
dependencies:
json-schema: 0.4.0
'@ai-sdk/provider@2.0.0-beta.2':
'@ai-sdk/provider@2.0.0-beta.1':
dependencies:
json-schema: 0.4.0
@ -19381,7 +19387,7 @@ snapshots:
dependencies:
'@ai-sdk/openai': 2.0.0-beta.11(zod@3.25.76)
'@folo-services/drizzle': 0.1.9(@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.0-beta.34(zod@3.25.76)
ai: 5.0.0(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
transitivePeerDependencies:
@ -23138,19 +23144,19 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
ai@5.0.0-beta.34(zod@3.25.75):
ai@5.0.0(zod@3.25.75):
dependencies:
'@ai-sdk/gateway': 1.0.0-beta.19(zod@3.25.75)
'@ai-sdk/provider': 2.0.0-beta.2
'@ai-sdk/provider-utils': 3.0.0-beta.10(zod@3.25.75)
'@ai-sdk/gateway': 1.0.0(zod@3.25.75)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.0(zod@3.25.75)
'@opentelemetry/api': 1.9.0
zod: 3.25.75
ai@5.0.0-beta.34(zod@3.25.76):
ai@5.0.0(zod@3.25.76):
dependencies:
'@ai-sdk/gateway': 1.0.0-beta.19(zod@3.25.76)
'@ai-sdk/provider': 2.0.0-beta.2
'@ai-sdk/provider-utils': 3.0.0-beta.10(zod@3.25.76)
'@ai-sdk/gateway': 1.0.0(zod@3.25.76)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.0(zod@3.25.76)
'@opentelemetry/api': 1.9.0
zod: 3.25.76
@ -30761,6 +30767,8 @@ snapshots:
'@babel/runtime': 7.27.6
react: 19.0.0
react-fast-compare@3.2.2: {}
react-fast-marquee@1.6.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
react: 19.0.0