feat: add image block type (#709)

This commit is contained in:
Jeremy 2025-01-15 19:59:29 +05:30 committed by GitHub
parent 6c23a8a604
commit df449a408a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 229 additions and 92 deletions

View file

@ -2,13 +2,14 @@ import {
type Message, type Message,
convertToCoreMessages, convertToCoreMessages,
createDataStreamResponse, createDataStreamResponse,
experimental_generateImage,
streamObject, streamObject,
streamText, streamText,
} from 'ai'; } from 'ai';
import { z } from 'zod'; import { z } from 'zod';
import { auth } from '@/app/(auth)/auth'; import { auth } from '@/app/(auth)/auth';
import { customModel } from '@/lib/ai'; import { customModel, imageGenerationModel } from '@/lib/ai';
import { models } from '@/lib/ai/models'; import { models } from '@/lib/ai/models';
import { import {
codePrompt, codePrompt,
@ -124,10 +125,10 @@ export async function POST(request: Request) {
}, },
createDocument: { createDocument: {
description: description:
'Create a document for a writing activity. This tool will call other functions that will generate the contents of the document based on the title and kind.', 'Create a document for a writing or content creation activities like image generation. This tool will call other functions that will generate the contents of the document based on the title and kind.',
parameters: z.object({ parameters: z.object({
title: z.string(), title: z.string(),
kind: z.enum(['text', 'code']), kind: z.enum(['text', 'code', 'image']),
}), }),
execute: async ({ title, kind }) => { execute: async ({ title, kind }) => {
const id = generateUUID(); const id = generateUUID();
@ -204,6 +205,21 @@ export async function POST(request: Request) {
} }
} }
dataStream.writeData({ type: 'finish', content: '' });
} else if (kind === 'image') {
const { image } = await experimental_generateImage({
model: imageGenerationModel,
prompt: title,
n: 1,
});
draftText = image.base64;
dataStream.writeData({
type: 'image-delta',
content: image.base64,
});
dataStream.writeData({ type: 'finish', content: '' }); dataStream.writeData({ type: 'finish', content: '' });
} }
@ -309,6 +325,21 @@ export async function POST(request: Request) {
} }
} }
dataStream.writeData({ type: 'finish', content: '' });
} else if (document.kind === 'image') {
const { image } = await experimental_generateImage({
model: imageGenerationModel,
prompt: description,
n: 1,
});
draftText = image.base64;
dataStream.writeData({
type: 'image-delta',
content: image.base64,
});
dataStream.writeData({ type: 'finish', content: '' }); dataStream.writeData({ type: 'finish', content: '' });
} }

View file

@ -2,11 +2,11 @@ import { cn } from '@/lib/utils';
import { ClockRewind, CopyIcon, RedoIcon, UndoIcon } from './icons'; import { ClockRewind, CopyIcon, RedoIcon, UndoIcon } from './icons';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
import { useCopyToClipboard } from 'usehooks-ts';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { ConsoleOutput, UIBlock } from './block'; import { ConsoleOutput, UIBlock } from './block';
import { Dispatch, memo, SetStateAction } from 'react'; import { Dispatch, memo, SetStateAction } from 'react';
import { RunCodeButton } from './run-code-button'; import { RunCodeButton } from './run-code-button';
import { useMultimodalCopyToClipboard } from '@/hooks/use-multimodal-copy-to-clipboard';
interface BlockActionsProps { interface BlockActionsProps {
block: UIBlock; block: UIBlock;
@ -25,7 +25,8 @@ function PureBlockActions({
mode, mode,
setConsoleOutputs, setConsoleOutputs,
}: BlockActionsProps) { }: BlockActionsProps) {
const [_, copyToClipboard] = useCopyToClipboard(); const { copyTextToClipboard, copyImageToClipboard } =
useMultimodalCopyToClipboard();
return ( return (
<div className="flex flex-row gap-1"> <div className="flex flex-row gap-1">
@ -96,7 +97,12 @@ function PureBlockActions({
variant="outline" variant="outline"
className="p-2 h-fit dark:hover:bg-zinc-700" className="p-2 h-fit dark:hover:bg-zinc-700"
onClick={() => { onClick={() => {
copyToClipboard(block.content); if (block.kind === 'image') {
copyImageToClipboard(block.content);
} else {
copyTextToClipboard(block.content);
}
toast.success('Copied to clipboard!'); toast.success('Copied to clipboard!');
}} }}
disabled={block.status === 'streaming'} disabled={block.status === 'streaming'}

View file

@ -34,8 +34,9 @@ import { Console } from './console';
import { useSidebar } from './ui/sidebar'; import { useSidebar } from './ui/sidebar';
import { useBlock } from '@/hooks/use-block'; import { useBlock } from '@/hooks/use-block';
import equal from 'fast-deep-equal'; import equal from 'fast-deep-equal';
import { ImageEditor } from './image-editor';
export type BlockKind = 'text' | 'code'; export type BlockKind = 'text' | 'code' | 'image';
export interface UIBlock { export interface UIBlock {
title: string; title: string;
@ -476,7 +477,7 @@ function PureBlock({
})} })}
> >
{isDocumentsFetching && !block.content ? ( {isDocumentsFetching && !block.content ? (
<DocumentSkeleton /> <DocumentSkeleton blockKind={block.kind} />
) : block.kind === 'code' ? ( ) : block.kind === 'code' ? (
<CodeEditor <CodeEditor
content={ content={
@ -512,9 +513,22 @@ function PureBlock({
newContent={getDocumentContentById(currentVersionIndex)} newContent={getDocumentContentById(currentVersionIndex)}
/> />
) )
) : block.kind === 'image' ? (
<ImageEditor
title={block.title}
content={
isCurrentVersion
? block.content
: getDocumentContentById(currentVersionIndex)
}
isCurrentVersion={isCurrentVersion}
currentVersionIndex={currentVersionIndex}
status={block.status}
isInline={false}
/>
) : null} ) : null}
{suggestions ? ( {suggestions && suggestions.length > 0 ? (
<div className="md:hidden h-dvh w-12 shrink-0" /> <div className="md:hidden h-dvh w-12 shrink-0" />
) : null} ) : null}

View file

@ -6,13 +6,13 @@ import { BlockKind } from './block';
import { Suggestion } from '@/lib/db/schema'; import { Suggestion } from '@/lib/db/schema';
import { initialBlockData, useBlock } from '@/hooks/use-block'; import { initialBlockData, useBlock } from '@/hooks/use-block';
import { useUserMessageId } from '@/hooks/use-user-message-id'; import { useUserMessageId } from '@/hooks/use-user-message-id';
import { cx } from 'class-variance-authority';
import { useSWRConfig } from 'swr'; import { useSWRConfig } from 'swr';
type DataStreamDelta = { type DataStreamDelta = {
type: type:
| 'text-delta' | 'text-delta'
| 'code-delta' | 'code-delta'
| 'image-delta'
| 'title' | 'title'
| 'id' | 'id'
| 'suggestion' | 'suggestion'
@ -107,6 +107,14 @@ export function DataStreamHandler({ id }: { id: string }) {
status: 'streaming', status: 'streaming',
}; };
case 'image-delta':
return {
...draftBlock,
content: delta.content as string,
isVisible: true,
status: 'streaming',
};
case 'suggestion': case 'suggestion':
setTimeout(() => { setTimeout(() => {
setOptimisticSuggestions((currentSuggestions) => [ setOptimisticSuggestions((currentSuggestions) => [

View file

@ -8,8 +8,8 @@ import {
useMemo, useMemo,
useRef, useRef,
} from 'react'; } from 'react';
import { UIBlock } from './block'; import { BlockKind, UIBlock } from './block';
import { FileIcon, FullscreenIcon, LoaderIcon } from './icons'; import { FileIcon, FullscreenIcon, ImageIcon, LoaderIcon } from './icons';
import { cn, fetcher } from '@/lib/utils'; import { cn, fetcher } from '@/lib/utils';
import { Document } from '@/lib/db/schema'; import { Document } from '@/lib/db/schema';
import { InlineDocumentSkeleton } from './document-skeleton'; import { InlineDocumentSkeleton } from './document-skeleton';
@ -19,6 +19,7 @@ import { DocumentToolCall, DocumentToolResult } from './document';
import { CodeEditor } from './code-editor'; import { CodeEditor } from './code-editor';
import { useBlock } from '@/hooks/use-block'; import { useBlock } from '@/hooks/use-block';
import equal from 'fast-deep-equal'; import equal from 'fast-deep-equal';
import { ImageEditor } from './image-editor';
interface DocumentPreviewProps { interface DocumentPreviewProps {
isReadonly: boolean; isReadonly: boolean;
@ -78,7 +79,7 @@ export function DocumentPreview({
} }
if (isDocumentsFetching) { if (isDocumentsFetching) {
return <LoadingSkeleton />; return <LoadingSkeleton blockKind={result.kind ?? args.kind} />;
} }
const document: Document | null = previewDocument const document: Document | null = previewDocument
@ -94,13 +95,14 @@ export function DocumentPreview({
} }
: null; : null;
if (!document) return <LoadingSkeleton />; if (!document) return <LoadingSkeleton blockKind={block.kind} />;
return ( return (
<div className="relative w-full cursor-pointer"> <div className="relative w-full cursor-pointer">
<HitboxLayer hitboxRef={hitboxRef} result={result} setBlock={setBlock} /> <HitboxLayer hitboxRef={hitboxRef} result={result} setBlock={setBlock} />
<DocumentHeader <DocumentHeader
title={document.title} title={document.title}
kind={document.kind}
isStreaming={block.status === 'streaming'} isStreaming={block.status === 'streaming'}
/> />
<DocumentContent document={document} /> <DocumentContent document={document} />
@ -108,7 +110,7 @@ export function DocumentPreview({
); );
} }
const LoadingSkeleton = () => ( const LoadingSkeleton = ({ blockKind }: { blockKind: BlockKind }) => (
<div className="w-full"> <div className="w-full">
<div className="p-4 border rounded-t-2xl flex flex-row gap-2 items-center justify-between dark:bg-muted h-[57px] dark:border-zinc-700 border-b-0"> <div className="p-4 border rounded-t-2xl flex flex-row gap-2 items-center justify-between dark:bg-muted h-[57px] dark:border-zinc-700 border-b-0">
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
@ -121,9 +123,15 @@ const LoadingSkeleton = () => (
<FullscreenIcon /> <FullscreenIcon />
</div> </div>
</div> </div>
<div className="overflow-y-scroll border rounded-b-2xl p-8 pt-4 bg-muted border-t-0 dark:border-zinc-700"> {blockKind === 'image' ? (
<InlineDocumentSkeleton /> <div className="overflow-y-scroll border rounded-b-2xl bg-muted border-t-0 dark:border-zinc-700">
</div> <div className="animate-pulse h-[257px] bg-muted-foreground/20 w-full" />
</div>
) : (
<div className="overflow-y-scroll border rounded-b-2xl p-8 pt-4 bg-muted border-t-0 dark:border-zinc-700">
<InlineDocumentSkeleton />
</div>
)}
</div> </div>
); );
@ -185,9 +193,11 @@ const HitboxLayer = memo(PureHitboxLayer, (prevProps, nextProps) => {
const PureDocumentHeader = ({ const PureDocumentHeader = ({
title, title,
kind,
isStreaming, isStreaming,
}: { }: {
title: string; title: string;
kind: BlockKind;
isStreaming: boolean; isStreaming: boolean;
}) => ( }) => (
<div className="p-4 border rounded-t-2xl flex flex-row gap-2 items-start sm:items-center justify-between dark:bg-muted border-b-0 dark:border-zinc-700"> <div className="p-4 border rounded-t-2xl flex flex-row gap-2 items-start sm:items-center justify-between dark:bg-muted border-b-0 dark:border-zinc-700">
@ -197,6 +207,8 @@ const PureDocumentHeader = ({
<div className="animate-spin"> <div className="animate-spin">
<LoaderIcon /> <LoaderIcon />
</div> </div>
) : kind === 'image' ? (
<ImageIcon />
) : ( ) : (
<FileIcon /> <FileIcon />
)} )}
@ -244,6 +256,15 @@ const DocumentContent = ({ document }: { document: Document }) => {
<CodeEditor {...commonProps} /> <CodeEditor {...commonProps} />
</div> </div>
</div> </div>
) : document.kind === 'image' ? (
<ImageEditor
title={document.title}
content={document.content ?? ''}
isCurrentVersion={true}
currentVersionIndex={0}
status={block.status}
isInline={true}
/>
) : null} ) : null}
</div> </div>
); );

View file

@ -1,7 +1,13 @@
'use client'; 'use client';
export const DocumentSkeleton = () => { import { BlockKind } from './block';
return (
export const DocumentSkeleton = ({ blockKind }: { blockKind: BlockKind }) => {
return blockKind === 'image' ? (
<div className="flex flex-col gap-4 w-full justify-center items-center h-[calc(100dvh-60px)]">
<div className="animate-pulse rounded-lg bg-muted-foreground/20 size-96" />
</div>
) : (
<div className="flex flex-col gap-4 w-full"> <div className="flex flex-col gap-4 w-full">
<div className="animate-pulse rounded-lg h-12 bg-muted-foreground/20 w-1/2" /> <div className="animate-pulse rounded-lg h-12 bg-muted-foreground/20 w-1/2" />
<div className="animate-pulse rounded-lg h-5 bg-muted-foreground/20 w-full" /> <div className="animate-pulse rounded-lg h-5 bg-muted-foreground/20 w-full" />

View file

@ -0,0 +1,50 @@
import { LoaderIcon } from './icons';
import cn from 'classnames';
interface ImageEditorProps {
title: string;
content: string;
isCurrentVersion: boolean;
currentVersionIndex: number;
status: string;
isInline: boolean;
}
export function ImageEditor({
title,
content,
isCurrentVersion,
currentVersionIndex,
status,
isInline,
}: ImageEditorProps) {
return (
<div
className={cn('flex flex-row items-center justify-center w-full', {
'h-[calc(100dvh-60px)]': !isInline,
'h-[200px]': isInline,
})}
>
{status === 'streaming' ? (
<div className="flex flex-row gap-4 items-center">
{!isInline && (
<div className="animate-spin">
<LoaderIcon />
</div>
)}
<div>Generating Image...</div>
</div>
) : (
<picture>
<img
className={cn('w-full h-fit max-w-[800px]', {
'p-0 md:p-20': !isInline,
})}
src={`data:image/png;base64,${content}`}
alt={title}
/>
</picture>
)}
</div>
);
}

View file

@ -29,13 +29,12 @@ import { sanitizeUIMessages } from '@/lib/utils';
import { import {
ArrowUpIcon, ArrowUpIcon,
CodeIcon, CodeIcon,
FileIcon,
LogsIcon, LogsIcon,
MessageIcon, MessageIcon,
PenIcon, PenIcon,
SparklesIcon,
StopIcon, StopIcon,
SummarizeIcon, SummarizeIcon,
TerminalIcon,
} from './icons'; } from './icons';
import { BlockKind } from './block'; import { BlockKind } from './block';
@ -327,6 +326,7 @@ const toolsByBlockKind: Record<
icon: <LogsIcon />, icon: <LogsIcon />,
}, },
], ],
image: [],
}; };
export const Tools = ({ export const Tools = ({
@ -347,7 +347,7 @@ export const Tools = ({
) => Promise<string | null | undefined>; ) => Promise<string | null | undefined>;
isAnimating: boolean; isAnimating: boolean;
setIsToolbarVisible: Dispatch<SetStateAction<boolean>>; setIsToolbarVisible: Dispatch<SetStateAction<boolean>>;
blockKind: 'text' | 'code'; blockKind: BlockKind;
}) => { }) => {
const [primaryTool, ...secondaryTools] = toolsByBlockKind[blockKind]; const [primaryTool, ...secondaryTools] = toolsByBlockKind[blockKind];
@ -407,7 +407,7 @@ const PureToolbar = ({
) => Promise<string | null | undefined>; ) => Promise<string | null | undefined>;
stop: () => void; stop: () => void;
setMessages: Dispatch<SetStateAction<Message[]>>; setMessages: Dispatch<SetStateAction<Message[]>>;
blockKind: 'text' | 'code'; blockKind: BlockKind;
}) => { }) => {
const toolbarRef = useRef<HTMLDivElement>(null); const toolbarRef = useRef<HTMLDivElement>(null);
const timeoutRef = useRef<ReturnType<typeof setTimeout>>(); const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
@ -451,6 +451,10 @@ const PureToolbar = ({
} }
}, [isLoading, setIsToolbarVisible]); }, [isLoading, setIsToolbarVisible]);
if (toolsByBlockKind[blockKind].length === 0) {
return null;
}
return ( return (
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<motion.div <motion.div

View file

@ -0,0 +1,22 @@
import { useCopyToClipboard } from 'usehooks-ts';
async function copyImageToClipboard(base64String: string) {
try {
const blob = await fetch(`data:image/png;base64,${base64String}`).then(
(res) => res.blob(),
);
const item = new ClipboardItem({
'image/png': blob,
});
await navigator.clipboard.write([item]);
} catch (error) {
console.error('Failed to copy image to clipboard:', error);
}
}
export function useMultimodalCopyToClipboard() {
const [_, copyTextToClipboard] = useCopyToClipboard();
return { copyTextToClipboard, copyImageToClipboard };
}

View file

@ -9,3 +9,5 @@ export const customModel = (apiIdentifier: string) => {
middleware: customMiddleware, middleware: customMiddleware,
}); });
}; };
export const imageGenerationModel = openai.image('dall-e-3');

View file

@ -72,7 +72,7 @@ export const document = pgTable(
createdAt: timestamp('createdAt').notNull(), createdAt: timestamp('createdAt').notNull(),
title: text('title').notNull(), title: text('title').notNull(),
content: text('content'), content: text('content'),
kind: varchar('text', { enum: ['text', 'code'] }) kind: varchar('text', { enum: ['text', 'code', 'image'] })
.notNull() .notNull()
.default('text'), .default('text'),
userId: uuid('userId') userId: uuid('userId')

View file

@ -18,7 +18,7 @@
"db:up": "drizzle-kit up" "db:up": "drizzle-kit up"
}, },
"dependencies": { "dependencies": {
"@ai-sdk/openai": "1.0.6", "@ai-sdk/openai": "1.0.19",
"@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.6", "@codemirror/lang-python": "^6.1.6",
"@codemirror/state": "^6.5.0", "@codemirror/state": "^6.5.0",
@ -37,7 +37,7 @@
"@vercel/analytics": "^1.3.1", "@vercel/analytics": "^1.3.1",
"@vercel/blob": "^0.24.1", "@vercel/blob": "^0.24.1",
"@vercel/postgres": "^0.10.0", "@vercel/postgres": "^0.10.0",
"ai": "4.0.20", "ai": "4.0.36",
"bcrypt-ts": "^5.0.2", "bcrypt-ts": "^5.0.2",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",

103
pnpm-lock.yaml generated
View file

@ -9,8 +9,8 @@ importers:
.: .:
dependencies: dependencies:
'@ai-sdk/openai': '@ai-sdk/openai':
specifier: 1.0.6 specifier: 1.0.19
version: 1.0.6(zod@3.23.8) version: 1.0.19(zod@3.23.8)
'@codemirror/lang-javascript': '@codemirror/lang-javascript':
specifier: ^6.2.2 specifier: ^6.2.2
version: 6.2.2 version: 6.2.2
@ -66,8 +66,8 @@ importers:
specifier: ^0.10.0 specifier: ^0.10.0
version: 0.10.0 version: 0.10.0
ai: ai:
specifier: 4.0.20 specifier: 4.0.36
version: 4.0.20(react@19.0.0-rc-45804af1-20241021)(zod@3.23.8) version: 4.0.36(react@19.0.0-rc-45804af1-20241021)(zod@3.23.8)
bcrypt-ts: bcrypt-ts:
specifier: ^5.0.2 specifier: ^5.0.2
version: 5.0.2 version: 5.0.2
@ -240,14 +240,14 @@ importers:
packages: packages:
'@ai-sdk/openai@1.0.6': '@ai-sdk/openai@1.0.19':
resolution: {integrity: sha512-AhNILXn/hVD91mrokg9Wph78BiWNP/N0tJiua+UCS5TYXCbSeeqKqiodRiNxw6tDA5sGnrC6yCv8TjgY1CwzWg==} resolution: {integrity: sha512-7qmLgppWpGUhSgrH0a6CtgD9hZeRh2hARppl1B7fNhVbekYftSMucsdCiVlKbQzSKPxox0vkNMmwjKa/7xf8bQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
zod: ^3.0.0 zod: ^3.0.0
'@ai-sdk/provider-utils@2.0.2': '@ai-sdk/provider-utils@2.0.7':
resolution: {integrity: sha512-IAvhKhdlXqiSmvx/D4uNlFYCl8dWT+M9K+IuEcSgnE2Aj27GWu8sDIpAf4r4Voc+wOUkOECVKQhFo8g9pozdjA==} resolution: {integrity: sha512-4sfPlKEALHPXLmMFcPlYksst3sWBJXmCDZpIBJisRrmwGG6Nn3mq0N1Zu/nZaGcrWZoOY+HT2Wbxla1oTElYHQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
zod: ^3.0.0 zod: ^3.0.0
@ -255,25 +255,12 @@ packages:
zod: zod:
optional: true optional: true
'@ai-sdk/provider-utils@2.0.4': '@ai-sdk/provider@1.0.4':
resolution: {integrity: sha512-GMhcQCZbwM6RoZCri0MWeEWXRt/T+uCxsmHEsTwNvEH3GDjNzchfX25C8ftry2MeEOOn6KfqCLSKomcgK6RoOg==} resolution: {integrity: sha512-lJi5zwDosvvZER3e/pB8lj1MN3o3S7zJliQq56BRr4e9V3fcRyFtwP0JRxaRS5vHYX3OJ154VezVoQNrk0eaKw==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
peerDependenciesMeta:
zod:
optional: true
'@ai-sdk/provider@1.0.1':
resolution: {integrity: sha512-mV+3iNDkzUsZ0pR2jG0sVzU6xtQY5DtSCBy3JFycLp6PwjyLw/iodfL3MwdmMCRJWgs3dadcHejRnMvF9nGTBg==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@ai-sdk/provider@1.0.2': '@ai-sdk/react@1.0.11':
resolution: {integrity: sha512-YYtP6xWQyaAf5LiWLJ+ycGTOeBLWrED7LUrvc+SQIWhGaneylqbaGsyQL7VouQUeQ4JZ1qKYZuhmi3W56HADPA==} resolution: {integrity: sha512-ndBPA7dx2DqUr7s4zO1cRAPkFGS+wWvSri6OWfCuhfyTAADQ4vdd56vFP9zdTZl4cyL27Vh0hKLfFJMGx83MUQ==}
engines: {node: '>=18'}
'@ai-sdk/react@1.0.6':
resolution: {integrity: sha512-8Hkserq0Ge6AEi7N4hlv2FkfglAGbkoAXEZ8YSp255c3PbnZz6+/5fppw+aROmZMOfNwallSRuy1i/iPa2rBpQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc react: ^18 || ^19 || ^19.0.0-rc
@ -284,8 +271,8 @@ packages:
zod: zod:
optional: true optional: true
'@ai-sdk/ui-utils@1.0.5': '@ai-sdk/ui-utils@1.0.10':
resolution: {integrity: sha512-DGJSbDf+vJyWmFNexSPUsS1AAy7gtsmFmoSyNbNbJjwl9hRIf2dknfA1V0ahx6pg3NNklNYFm53L8Nphjovfvg==} resolution: {integrity: sha512-wZfZNH2IloTx5b1O8CU7/R/icm8EsmURElPckYwNYj2YZrKk9X5XeYSDBF/1/J83obzsn0i7VKkIf40qhRzVVA==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
zod: ^3.0.0 zod: ^3.0.0
@ -1626,8 +1613,8 @@ packages:
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
hasBin: true hasBin: true
ai@4.0.20: ai@4.0.36:
resolution: {integrity: sha512-dYevYKtREcjSVopBDFWVNca7WJEI1p9Vr9eo7V7fZHzi2vXGDyEa2WYatjFbpR6z6gpVAxKHsof8EoN+B1IAsA==} resolution: {integrity: sha512-KeXQe+eKQK57WYzN+K6VUBZqj/n70V+cCwrXAhXa7enbZCAsErvQKqrnxkF1qm9UfPuNKNEFV5BRoMKJ5gRfAw==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc react: ^18 || ^19 || ^19.0.0-rc
@ -3738,10 +3725,10 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
zod-to-json-schema@3.23.5: zod-to-json-schema@3.24.1:
resolution: {integrity: sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==} resolution: {integrity: sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==}
peerDependencies: peerDependencies:
zod: ^3.23.3 zod: ^3.24.1
zod@3.23.8: zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
@ -3751,53 +3738,40 @@ packages:
snapshots: snapshots:
'@ai-sdk/openai@1.0.6(zod@3.23.8)': '@ai-sdk/openai@1.0.19(zod@3.23.8)':
dependencies: dependencies:
'@ai-sdk/provider': 1.0.1 '@ai-sdk/provider': 1.0.4
'@ai-sdk/provider-utils': 2.0.2(zod@3.23.8) '@ai-sdk/provider-utils': 2.0.7(zod@3.23.8)
zod: 3.23.8 zod: 3.23.8
'@ai-sdk/provider-utils@2.0.2(zod@3.23.8)': '@ai-sdk/provider-utils@2.0.7(zod@3.23.8)':
dependencies: dependencies:
'@ai-sdk/provider': 1.0.1 '@ai-sdk/provider': 1.0.4
eventsource-parser: 3.0.0
nanoid: 3.3.7
secure-json-parse: 2.7.0
optionalDependencies:
zod: 3.23.8
'@ai-sdk/provider-utils@2.0.4(zod@3.23.8)':
dependencies:
'@ai-sdk/provider': 1.0.2
eventsource-parser: 3.0.0 eventsource-parser: 3.0.0
nanoid: 3.3.8 nanoid: 3.3.8
secure-json-parse: 2.7.0 secure-json-parse: 2.7.0
optionalDependencies: optionalDependencies:
zod: 3.23.8 zod: 3.23.8
'@ai-sdk/provider@1.0.1': '@ai-sdk/provider@1.0.4':
dependencies: dependencies:
json-schema: 0.4.0 json-schema: 0.4.0
'@ai-sdk/provider@1.0.2': '@ai-sdk/react@1.0.11(react@19.0.0-rc-45804af1-20241021)(zod@3.23.8)':
dependencies: dependencies:
json-schema: 0.4.0 '@ai-sdk/provider-utils': 2.0.7(zod@3.23.8)
'@ai-sdk/ui-utils': 1.0.10(zod@3.23.8)
'@ai-sdk/react@1.0.6(react@19.0.0-rc-45804af1-20241021)(zod@3.23.8)':
dependencies:
'@ai-sdk/provider-utils': 2.0.4(zod@3.23.8)
'@ai-sdk/ui-utils': 1.0.5(zod@3.23.8)
swr: 2.2.5(react@19.0.0-rc-45804af1-20241021) swr: 2.2.5(react@19.0.0-rc-45804af1-20241021)
throttleit: 2.1.0 throttleit: 2.1.0
optionalDependencies: optionalDependencies:
react: 19.0.0-rc-45804af1-20241021 react: 19.0.0-rc-45804af1-20241021
zod: 3.23.8 zod: 3.23.8
'@ai-sdk/ui-utils@1.0.5(zod@3.23.8)': '@ai-sdk/ui-utils@1.0.10(zod@3.23.8)':
dependencies: dependencies:
'@ai-sdk/provider': 1.0.2 '@ai-sdk/provider': 1.0.4
'@ai-sdk/provider-utils': 2.0.4(zod@3.23.8) '@ai-sdk/provider-utils': 2.0.7(zod@3.23.8)
zod-to-json-schema: 3.23.5(zod@3.23.8) zod-to-json-schema: 3.24.1(zod@3.23.8)
optionalDependencies: optionalDependencies:
zod: 3.23.8 zod: 3.23.8
@ -4896,15 +4870,14 @@ snapshots:
acorn@8.14.0: {} acorn@8.14.0: {}
ai@4.0.20(react@19.0.0-rc-45804af1-20241021)(zod@3.23.8): ai@4.0.36(react@19.0.0-rc-45804af1-20241021)(zod@3.23.8):
dependencies: dependencies:
'@ai-sdk/provider': 1.0.2 '@ai-sdk/provider': 1.0.4
'@ai-sdk/provider-utils': 2.0.4(zod@3.23.8) '@ai-sdk/provider-utils': 2.0.7(zod@3.23.8)
'@ai-sdk/react': 1.0.6(react@19.0.0-rc-45804af1-20241021)(zod@3.23.8) '@ai-sdk/react': 1.0.11(react@19.0.0-rc-45804af1-20241021)(zod@3.23.8)
'@ai-sdk/ui-utils': 1.0.5(zod@3.23.8) '@ai-sdk/ui-utils': 1.0.10(zod@3.23.8)
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
jsondiffpatch: 0.6.0 jsondiffpatch: 0.6.0
zod-to-json-schema: 3.23.5(zod@3.23.8)
optionalDependencies: optionalDependencies:
react: 19.0.0-rc-45804af1-20241021 react: 19.0.0-rc-45804af1-20241021
zod: 3.23.8 zod: 3.23.8
@ -7499,7 +7472,7 @@ snapshots:
yocto-queue@0.1.0: {} yocto-queue@0.1.0: {}
zod-to-json-schema@3.23.5(zod@3.23.8): zod-to-json-schema@3.24.1(zod@3.23.8):
dependencies: dependencies:
zod: 3.23.8 zod: 3.23.8