diff --git a/genai-cookbook/README.md b/genai-cookbook/README.md index c922270..00241f6 100644 --- a/genai-cookbook/README.md +++ b/genai-cookbook/README.md @@ -79,16 +79,20 @@ The Agentic Cookbook is collection of recipes demonstrating how to build modern Build a streaming chat interface that maintains conversation context across multiple exchanges. This recipe demonstrates: - Real-time token streaming using the Vercel AI SDK -- Auto-scrolling message display using Mantine +- Markdown rendering with syntax-highlighted code blocks using Streamdown +- Auto-scrolling message display with smart scroll detection +- Seamless compatibility with Modular MAX and OpenAI-compatible endpoints ### 2. **Image Captioning** -Create an intelligent image captioning system that generates natural language descriptions for uploaded images. Features include: +Create an intelligent image captioning system that generates natural language descriptions for uploaded images with progressive streaming and performance tracking. Features include: +- **NDJSON streaming**: Custom useNDJSON hook for progressive results—captions appear as they're generated +- **Parallel processing**: Multiple images processed simultaneously for maximum speed +- **Performance metrics**: TTFT (time to first token) and duration tracking with human-readable formatting via pretty-ms - Drag-and-drop image upload with Mantine Dropzone -- Base64 image encoding for API transport - Customizable prompt for caption generation -- Gallery view with loading states and progress indicators +- Gallery view with loading states and real-time updates ## Architecture diff --git a/genai-cookbook/apps/cookbook/components/CodeToggle.tsx b/genai-cookbook/apps/cookbook/components/CodeToggle.tsx index 7fd567b..2097b46 100644 --- a/genai-cookbook/apps/cookbook/components/CodeToggle.tsx +++ b/genai-cookbook/apps/cookbook/components/CodeToggle.tsx @@ -24,7 +24,7 @@ export function CodeToggle() { icon?: React.ReactNode }) => { return ( - - @@ -214,19 +326,19 @@ function FormActions({ } // ============================================================================ -// Error reporting banner +// Error reporting // ============================================================================ import { Alert, Divider } from '@mantine/core' import { IconExclamationCircle } from '@tabler/icons-react' -/** Surfaces transport errors so users can adjust configuration or retry. */ -function ErrorAlert({ error }: { error: string | null }) { +/** Shows error banner when caption generation fails */ +function ErrorAlert({ error }: { error: Error | null }) { const errorIcon = if (error) { return ( - {error} + {error.message} ) } else { @@ -256,15 +368,12 @@ const centerStyle: React.CSSProperties = { overflow: 'hidden', } -/** - * Wraps Mantine's Dropzone so we can accept image uploads with minimal setup. - */ +/** Drag-and-drop file upload zone powered by Mantine's Dropzone */ function FileDrop({ onDrop, maxSizeMb, disabled }: FileDropProps) { const maxSizeBytes = maxSizeMb ? maxSizeMb * 1024 ** 2 : undefined return ( Upload Images - {/* Mantine Dropzone handles drag-and-drop + click-to-upload with built-in validation states. */} console.log('rejected files', files)} @@ -279,7 +388,6 @@ function FileDrop({ onDrop, maxSizeMb, disabled }: FileDropProps) { w="100%" bd="1px solid var(--mantine-color-default-border)" > - {/* Layout inside the dropzone uses Mantine Group/Box/Text for consistent spacing and color. */} { return ( - {/* Mantine Box + LoadingOverlay show spinner on top of the image while captioning. */} {image.caption ?? 'Image not yet captioned'} + + + TTFT:{' '} + {image.captionTTFT + ? prettyMilliseconds(image.captionTTFT, { unitCount: 4 }) + : '--'} + , Duration:{' '} + {image.captionDuration + ? '+' + + prettyMilliseconds(image.captionDuration, { unitCount: 4 }) + : '--'} + ) } @@ -362,64 +478,9 @@ function Gallery({ images }: { images: ImageData[] }) { } // ============================================================================ -// Request helpers +// File helper utilities // ============================================================================ -/** Parameters required to generate a caption for one image. */ -interface CaptionRequest { - image: ImageData - prompt: string - endpointId: string - modelName: string - api: string -} - -/** - * Sends the user-provided prompt and image to the recipe API. The payload matches - * the OpenAI-compatible schema, so Modular MAX or OpenAI can be swapped by - * changing the endpoint selection. - */ -async function generateCaption({ - image, - prompt, - endpointId, - modelName, - api, -}: CaptionRequest): Promise { - // Vercel AI SDK uses OpenAI-style message arrays - const systemMessage: SystemModelMessage = { role: 'system', content: prompt } - const userMessage: UserModelMessage = { - role: 'user', - content: [ - { - type: 'image', - image: image.imageData, - }, - ], - } - - // Bundle the prompt + image messages. - const messages = [systemMessage, userMessage] - - const response = await fetch(api, { - // Proxy to the Next.js route that pipes into the Vercel AI SDK transport. - method: 'POST', - body: JSON.stringify({ - endpointId, - modelName, - messages, - }), - }) - - if (!response.ok) { - throw new Error(await response.text()) - } - - // The API route relays Vercel AI SDK streaming back into JSON with the generated text. - const data = await response.json() - return data.text -} - /** Converts a File to a base64 data URL for transport to the API route. */ async function getDataFromFile(file: File): Promise { return await new Promise((resolve, reject) => { diff --git a/genai-cookbook/packages/recipes/src/multiturn-chat/api.ts b/genai-cookbook/packages/recipes/src/multiturn-chat/api.ts index 112176d..b31a965 100644 --- a/genai-cookbook/packages/recipes/src/multiturn-chat/api.ts +++ b/genai-cookbook/packages/recipes/src/multiturn-chat/api.ts @@ -3,19 +3,18 @@ import { RecipeContext } from '../types' import { createOpenAI } from '@ai-sdk/openai' /* - * This API route is the bridge between our chat surface and the provider that - * fulfills each request. Modular MAX exposes an OpenAI-compatible interface, so - * we can drive Modular MAX through existing Vercel AI SDK helpers. + * Multi-turn Chat API with Token Streaming * - * The client passes along the currently selected provider configuration - * (`baseURL` + `model`). This handler uses that data to stream the assistant - * response back to the browser in a format that `useChat` can render token by - * token. + * This API route streams chat completions from Modular MAX or any OpenAI-compatible + * endpoint. The Vercel AI SDK handles message conversion, streaming, and response + * formatting for seamless client-side consumption. + * + * Key features: + * - Token streaming: Response text streams progressively to the client + * - Message conversion: UIMessage format → model-compatible format + * - OpenAI-compatible: Works with Modular MAX, OpenAI, or other compatible servers + * - Stop sequences: Configurable end-of-turn markers for generation control */ - -// ============================================================================ -// POST /api route — streams chat completions -// ============================================================================ export default async function POST(req: Request, context: RecipeContext) { const { apiKey, baseUrl, modelName } = context const { messages } = await req.json() @@ -24,28 +23,26 @@ export default async function POST(req: Request, context: RecipeContext) { } try { - // createOpenAI returns an OpenAI-compatible client + // The Vercel AI SDK's createOpenAI works with any OpenAI-compatible endpoint const client = createOpenAI({ baseURL: baseUrl, apiKey }) - - // chat(modelName) works with LLM servers like MAX that - // implement the chat-completions format const model = client.chat(modelName) + // Stream chat completion with message format conversion const result = streamText({ model: model, - // Convert messages from the UIMessage format + // convertToModelMessages transforms UIMessage → model-compatible format messages: convertToModelMessages(messages), - // Respect the same stop sequence used by the model + // Stop sequences control when generation should end stopSequences: [''], }) - // Convert the streaming result into the structure the recipe UI consumes + // toUIMessageStreamResponse formats the stream for client-side useChat hook return result.toUIMessageStreamResponse({ originalMessages: messages, }) } catch (error) { const errorMessage = error instanceof Error ? `(${error.message})` : '' - return new Response(`Failed to generate caption ${errorMessage}`, { + return new Response(`Failed to stream chat completion ${errorMessage}`, { status: 424, }) } diff --git a/genai-cookbook/packages/recipes/src/multiturn-chat/ui.tsx b/genai-cookbook/packages/recipes/src/multiturn-chat/ui.tsx index cf27b3c..8801c8e 100644 --- a/genai-cookbook/packages/recipes/src/multiturn-chat/ui.tsx +++ b/genai-cookbook/packages/recipes/src/multiturn-chat/ui.tsx @@ -1,20 +1,23 @@ 'use client' /* - * This walkthrough shows how Modular MAX can share the same surface as OpenAI by - * leaning on the Vercel AI SDK. We configure a single transport that can talk to - * many models, stream responses token by token, and keep the UI responsive - * the entire time. + * Multi-turn Chat with Token Streaming * - * Mantine powers the visible shell because it ships polished primitives—such as - * ScrollArea—that would be tedious to build from scratch. Modular's internal - * Design Language System also layers on Mantine, so this mirrors our own - * production ergonomics. + * This recipe demonstrates how to build a chat interface that works with Modular MAX + * or any OpenAI-compatible endpoint using the Vercel AI SDK. Messages stream token-by-token + * for fluid, real-time responses. * - * Below you will find three sections: the top-level chat surface, the helpers - * that present streamed messages, and the composer form. Comments along the way - * trace how data and events move between the Vercel AI SDK, Modular MAX, and the - * surrounding React components. + * Key features: + * - Token streaming: Response text appears progressively as it's generated + * - Auto-scroll: Automatically follows new messages with smart manual scroll detection + * - Streamdown: Renders markdown with syntax-highlighted code blocks + * - Conversation history: Multi-turn context maintained across messages + * - Mantine UI: Polished components (ScrollArea, forms) for production-ready UX + * + * Architecture: + * - useChat hook (Vercel AI SDK): Manages streaming, message state, and transport + * - DefaultChatTransport: Routes requests to the selected OpenAI-compatible endpoint + * - Streamdown component: Renders streamed markdown with Shiki syntax highlighting */ import { useEffect, useRef, useState } from 'react' @@ -28,9 +31,8 @@ import { RecipeProps } from '../types' // ============================================================================ /* - * The chat surface brings everything together. It wires the Vercel AI SDK to a - * Modular MAX-compatible transport, tracks scroll position for auto-follow, and - * hands message data to the presentation components further down the file. + * Main chat component: wires the Vercel AI SDK to an OpenAI-compatible transport, + * tracks scroll position for auto-follow behavior, and manages message state. */ export default function Recipe({ endpoint, model, pathname }: RecipeProps) { // Controlled value for the chat composer input. @@ -132,10 +134,8 @@ export default function Recipe({ endpoint, model, pathname }: RecipeProps) { // ============================================================================ /* - * These helpers focus on rendering the chat history. They apply subtle - * animations, label each message by role, and prepare a scroll anchor so the - * surface can follow the newest streamed tokens coming from Modular MAX - * through the AI SDK transport. + * Renders chat history with Streamdown for markdown formatting and syntax highlighting. + * Provides a scroll anchor for auto-follow behavior as new tokens stream in. */ import type { UIMessage } from 'ai' import type { RefObject } from 'react' @@ -151,7 +151,8 @@ interface MessagesPanelProps { } /** - * Displays chat messages using Streamdown, a part of the Vercel AI SDK. + * Displays chat messages using Streamdown for markdown rendering with syntax highlighting. + * Streamdown is part of the Vercel AI SDK ecosystem for rendering streamed text. */ function MessagesPanel({ messages, bottomRef }: MessagesPanelProps) { return ( @@ -193,9 +194,8 @@ function MessagesPanel({ messages, bottomRef }: MessagesPanelProps) { // ============================================================================ /* - * The composer accepts user prompts and invokes the `useChat` helper. It keeps - * the input controlled, clears the text once a prompt is submitted, and - * delegates provider-agnostic networking to the transport configured above. + * Composer form: accepts user prompts and triggers message submission. + * Controlled input is cleared on send, with networking delegated to useChat transport. */ import { Button, Group, Input } from '@mantine/core' diff --git a/genai-cookbook/packages/recipes/src/registry/metadata.ts b/genai-cookbook/packages/recipes/src/registry/metadata.ts index 87d8842..ecc6621 100644 --- a/genai-cookbook/packages/recipes/src/registry/metadata.ts +++ b/genai-cookbook/packages/recipes/src/registry/metadata.ts @@ -5,12 +5,12 @@ export const recipeMetadata: Record = { slug: 'image-captioning', title: 'Image Captioning', description: - "This recipe walks through an end-to-end image captioning workflow that lets you upload pictures, tweak the guiding prompt, and generate natural-language captions through the OpenAI-compatible Vercel AI SDK transport capable of integrating with Modular MAX. The client component manages uploads, gallery state, and Mantine-based UI controls, then forwards the prompt and base64-encoded image to a Next.js API route. That route proxies the request to whichever OpenAI-compatible endpoint you select, using the SDK's chat abstraction to produce a caption and return it to the browser.", + "Generate captions for multiple images with progressive NDJSON streaming. Upload images, customize the prompt, and watch captions appear instantly as they're generated. Includes a custom useNDJSON hook for streaming, parallel processing for speed, and performance metrics (TTFT and duration) for each image. Works with Modular MAX or any OpenAI-compatible endpoint.", }, 'multiturn-chat': { slug: 'multiturn-chat', title: 'Multi-turn Chat', description: - "This recipe demonstrates a Mantine-powered chat surface that streams multi-turn conversations through the Vercel AI SDK, letting you toggle between Modular MAX and other OpenAI-compatible endpoints without rewriting UI logic. The page component keeps composer input, scroll-follow behavior, and the live message list in sync while forwarding each prompt to a Next.js API route. That route adapts the chat transcript into the SDK's message format, invokes the selected model via `openai.chat`, and streams tokens back to the browser so replies render fluidly as they arrive.", + 'Streaming chat interface with multi-turn conversation support. Messages stream token-by-token for fluid responses, with automatic scroll-follow. Uses Streamdown for markdown rendering with syntax highlighting. Seamlessly compatible with Modular MAX and other OpenAI-compatible endpoints.', }, } diff --git a/genai-cookbook/pnpm-lock.yaml b/genai-cookbook/pnpm-lock.yaml index 7e31c38..349c4e7 100644 --- a/genai-cookbook/pnpm-lock.yaml +++ b/genai-cookbook/pnpm-lock.yaml @@ -4,6 +4,54 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +catalogs: + default: + '@ai-sdk/openai': + specifier: ^2.0.23 + version: 2.0.44 + '@ai-sdk/react': + specifier: ^2.0.30 + version: 2.0.60 + '@mantine/core': + specifier: ^7.17.8 + version: 7.17.8 + '@mantine/dropzone': + specifier: ^7.17.8 + version: 7.17.8 + '@mantine/hooks': + specifier: ^7.17.8 + version: 7.17.8 + '@tabler/icons-react': + specifier: ^3.34.1 + version: 3.35.0 + '@types/node': + specifier: ^20 + version: 20.19.19 + '@types/react': + specifier: ^18 + version: 18.3.26 + '@types/react-dom': + specifier: ^18 + version: 18.3.7 + ai: + specifier: ^5.0.28 + version: 5.0.60 + nanoid: + specifier: ^5.1.5 + version: 5.1.6 + openai: + specifier: ^5.20.2 + version: 5.23.2 + pretty-ms: + specifier: ^9.3.0 + version: 9.3.0 + streamdown: + specifier: ^1.3.0 + version: 1.3.0 + typescript: + specifier: ^5 + version: 5.9.3 + importers: .: @@ -18,38 +66,41 @@ importers: apps/cookbook: dependencies: '@ai-sdk/openai': - specifier: ^2.0.23 - version: 2.0.44(zod@3.25.76) + specifier: 'catalog:' + version: 2.0.44(zod@4.1.12) '@ai-sdk/react': - specifier: ^2.0.30 - version: 2.0.60(react@18.3.1)(zod@3.25.76) + specifier: 'catalog:' + version: 2.0.60(react@18.3.1)(zod@4.1.12) '@mantine/core': - specifier: ^7.17.8 + specifier: 'catalog:' version: 7.17.8(@mantine/hooks@7.17.8(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mantine/dropzone': - specifier: ^7.17.8 + specifier: 'catalog:' version: 7.17.8(@mantine/core@7.17.8(@mantine/hooks@7.17.8(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.17.8(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mantine/hooks': - specifier: ^7.17.8 + specifier: 'catalog:' version: 7.17.8(react@18.3.1) '@modular/recipes': specifier: workspace:* version: link:../../packages/recipes '@tabler/icons-react': - specifier: ^3.34.1 + specifier: 'catalog:' version: 3.35.0(react@18.3.1) ai: - specifier: ^5.0.28 - version: 5.0.60(zod@3.25.76) + specifier: 'catalog:' + version: 5.0.60(zod@4.1.12) nanoid: - specifier: ^5.1.5 + specifier: 'catalog:' version: 5.1.6 next: specifier: ^14 version: 14.2.33(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.93.2) openai: - specifier: ^5.20.2 - version: 5.23.2(zod@3.25.76) + specifier: 'catalog:' + version: 5.23.2(zod@4.1.12) + pretty-ms: + specifier: 'catalog:' + version: 9.3.0 react: specifier: ^18 version: 18.3.1 @@ -63,17 +114,17 @@ importers: specifier: ^1.93.2 version: 1.93.2 streamdown: - specifier: ^1.3.0 + specifier: 'catalog:' version: 1.3.0(@types/react@18.3.26)(react@18.3.1) devDependencies: '@types/node': - specifier: ^20 + specifier: 'catalog:' version: 20.19.19 '@types/react': - specifier: ^18 + specifier: 'catalog:' version: 18.3.26 '@types/react-dom': - specifier: ^18 + specifier: 'catalog:' version: 18.3.7(@types/react@18.3.26) '@types/react-syntax-highlighter': specifier: ^15.5.13 @@ -91,38 +142,41 @@ importers: specifier: ^3.4.1 version: 3.4.18 typescript: - specifier: ^5 + specifier: 'catalog:' version: 5.9.3 packages/recipes: dependencies: '@ai-sdk/openai': - specifier: ^2.0.23 - version: 2.0.44(zod@3.25.76) + specifier: 'catalog:' + version: 2.0.44(zod@4.1.12) '@ai-sdk/react': - specifier: ^2.0.30 - version: 2.0.60(react@18.3.1)(zod@3.25.76) + specifier: 'catalog:' + version: 2.0.60(react@18.3.1)(zod@4.1.12) '@mantine/core': - specifier: ^7.17.8 + specifier: 'catalog:' version: 7.17.8(@mantine/hooks@7.17.8(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mantine/dropzone': - specifier: ^7.17.8 + specifier: 'catalog:' version: 7.17.8(@mantine/core@7.17.8(@mantine/hooks@7.17.8(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.17.8(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mantine/hooks': - specifier: ^7.17.8 + specifier: 'catalog:' version: 7.17.8(react@18.3.1) '@tabler/icons-react': - specifier: ^3.34.1 + specifier: 'catalog:' version: 3.35.0(react@18.3.1) ai: - specifier: ^5.0.28 - version: 5.0.60(zod@3.25.76) + specifier: 'catalog:' + version: 5.0.60(zod@4.1.12) nanoid: - specifier: ^5.1.5 + specifier: 'catalog:' version: 5.1.6 openai: - specifier: ^5.20.2 - version: 5.23.2(zod@3.25.76) + specifier: 'catalog:' + version: 5.23.2(zod@4.1.12) + pretty-ms: + specifier: 'catalog:' + version: 9.3.0 react: specifier: ^18 version: 18.3.1 @@ -130,20 +184,20 @@ importers: specifier: ^18 version: 18.3.1(react@18.3.1) streamdown: - specifier: ^1.3.0 + specifier: 'catalog:' version: 1.3.0(@types/react@18.3.26)(react@18.3.1) devDependencies: '@types/node': - specifier: ^20 + specifier: 'catalog:' version: 20.19.19 '@types/react': - specifier: ^18 + specifier: 'catalog:' version: 18.3.26 '@types/react-dom': - specifier: ^18 + specifier: 'catalog:' version: 18.3.7(@types/react@18.3.26) typescript: - specifier: ^5 + specifier: 'catalog:' version: 5.9.3 packages: @@ -2377,6 +2431,10 @@ packages: parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -2561,6 +2619,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + prismjs@1.27.0: resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} engines: {node: '>=6'} @@ -3214,47 +3276,47 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.1.12: + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} snapshots: - '@ai-sdk/gateway@1.0.33(zod@3.25.76)': + '@ai-sdk/gateway@1.0.33(zod@4.1.12)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.10(zod@3.25.76) + '@ai-sdk/provider-utils': 3.0.10(zod@4.1.12) '@vercel/oidc': 3.0.1 - zod: 3.25.76 + zod: 4.1.12 - '@ai-sdk/openai@2.0.44(zod@3.25.76)': + '@ai-sdk/openai@2.0.44(zod@4.1.12)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.10(zod@3.25.76) - zod: 3.25.76 + '@ai-sdk/provider-utils': 3.0.10(zod@4.1.12) + zod: 4.1.12 - '@ai-sdk/provider-utils@3.0.10(zod@3.25.76)': + '@ai-sdk/provider-utils@3.0.10(zod@4.1.12)': dependencies: '@ai-sdk/provider': 2.0.0 '@standard-schema/spec': 1.0.0 eventsource-parser: 3.0.6 - zod: 3.25.76 + zod: 4.1.12 '@ai-sdk/provider@2.0.0': dependencies: json-schema: 0.4.0 - '@ai-sdk/react@2.0.60(react@18.3.1)(zod@3.25.76)': + '@ai-sdk/react@2.0.60(react@18.3.1)(zod@4.1.12)': dependencies: - '@ai-sdk/provider-utils': 3.0.10(zod@3.25.76) - ai: 5.0.60(zod@3.25.76) + '@ai-sdk/provider-utils': 3.0.10(zod@4.1.12) + ai: 5.0.60(zod@4.1.12) react: 18.3.1 swr: 2.3.6(react@18.3.1) throttleit: 2.1.0 optionalDependencies: - zod: 3.25.76 + zod: 4.1.12 '@alloc/quick-lru@5.2.0': {} @@ -3943,13 +4005,13 @@ snapshots: acorn@8.15.0: {} - ai@5.0.60(zod@3.25.76): + ai@5.0.60(zod@4.1.12): dependencies: - '@ai-sdk/gateway': 1.0.33(zod@3.25.76) + '@ai-sdk/gateway': 1.0.33(zod@4.1.12) '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.10(zod@3.25.76) + '@ai-sdk/provider-utils': 3.0.10(zod@4.1.12) '@opentelemetry/api': 1.9.0 - zod: 3.25.76 + zod: 4.1.12 ajv@6.12.6: dependencies: @@ -4626,7 +4688,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -4648,7 +4710,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -5945,9 +6007,9 @@ snapshots: regex: 6.0.1 regex-recursion: 6.0.2 - openai@5.23.2(zod@3.25.76): + openai@5.23.2(zod@4.1.12): optionalDependencies: - zod: 3.25.76 + zod: 4.1.12 optionator@0.9.4: dependencies: @@ -5999,6 +6061,8 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse-ms@4.0.0: {} + parse5@7.3.0: dependencies: entities: 6.0.1 @@ -6102,6 +6166,10 @@ snapshots: prettier@3.6.2: {} + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + prismjs@1.27.0: {} prismjs@1.30.0: {} @@ -6955,6 +7023,6 @@ snapshots: yocto-queue@0.1.0: {} - zod@3.25.76: {} + zod@4.1.12: {} zwitch@2.0.4: {} diff --git a/genai-cookbook/pnpm-workspace.yaml b/genai-cookbook/pnpm-workspace.yaml index 3ff5faa..fb12e24 100644 --- a/genai-cookbook/pnpm-workspace.yaml +++ b/genai-cookbook/pnpm-workspace.yaml @@ -1,3 +1,20 @@ packages: - - "apps/*" - - "packages/*" + - apps/* + - packages/* + +catalog: + '@ai-sdk/openai': ^2.0.23 + '@ai-sdk/react': ^2.0.30 + '@mantine/core': ^7.17.8 + '@mantine/dropzone': ^7.17.8 + '@mantine/hooks': ^7.17.8 + '@tabler/icons-react': ^3.34.1 + '@types/node': ^20 + '@types/react': ^18 + '@types/react-dom': ^18 + ai: ^5.0.28 + nanoid: ^5.1.5 + pretty-ms: ^9.3.0 + openai: ^5.20.2 + streamdown: ^1.3.0 + typescript: ^5