chore: update to ai sdk v5 beta (#1074)

This commit is contained in:
Jeremy 2025-07-03 02:26:34 -07:00 committed by GitHub
parent 7d8e71383f
commit 4c281fe09d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 1372 additions and 1060 deletions

View file

@ -1,6 +1,4 @@
'use client';
import type { UIMessage } from 'ai';
import cx from 'classnames';
import { AnimatePresence, motion } from 'framer-motion';
import { memo, useState } from 'react';
@ -19,6 +17,11 @@ import { MessageEditor } from './message-editor';
import { DocumentPreview } from './document-preview';
import { MessageReasoning } from './message-reasoning';
import type { UseChatHelpers } from '@ai-sdk/react';
import type { ChatMessage } from '@/lib/types';
import { useDataStream } from './data-stream-provider';
// Type narrowing is handled by TypeScript's control flow analysis
// The AI SDK provides proper discriminated unions for tool calls
const PurePreviewMessage = ({
chatId,
@ -26,21 +29,27 @@ const PurePreviewMessage = ({
vote,
isLoading,
setMessages,
reload,
regenerate,
isReadonly,
requiresScrollPadding,
}: {
chatId: string;
message: UIMessage;
message: ChatMessage;
vote: Vote | undefined;
isLoading: boolean;
setMessages: UseChatHelpers['setMessages'];
reload: UseChatHelpers['reload'];
setMessages: UseChatHelpers<ChatMessage>['setMessages'];
regenerate: UseChatHelpers<ChatMessage>['regenerate'];
isReadonly: boolean;
requiresScrollPadding: boolean;
}) => {
const [mode, setMode] = useState<'view' | 'edit'>('view');
const attachmentsFromMessage = message.parts.filter(
(part) => part.type === 'file',
);
useDataStream();
return (
<AnimatePresence>
<motion.div
@ -72,20 +81,23 @@ const PurePreviewMessage = ({
'min-h-96': message.role === 'assistant' && requiresScrollPadding,
})}
>
{message.experimental_attachments &&
message.experimental_attachments.length > 0 && (
<div
data-testid={`message-attachments`}
className="flex flex-row justify-end gap-2"
>
{message.experimental_attachments.map((attachment) => (
<PreviewAttachment
key={attachment.url}
attachment={attachment}
/>
))}
</div>
)}
{attachmentsFromMessage.length > 0 && (
<div
data-testid={`message-attachments`}
className="flex flex-row justify-end gap-2"
>
{attachmentsFromMessage.map((attachment) => (
<PreviewAttachment
key={attachment.url}
attachment={{
name: attachment.filename ?? 'file',
contentType: attachment.mediaType,
url: attachment.url,
}}
/>
))}
</div>
)}
{message.parts?.map((part, index) => {
const { type } = part;
@ -96,7 +108,7 @@ const PurePreviewMessage = ({
<MessageReasoning
key={key}
isLoading={isLoading}
reasoning={part.reasoning}
reasoning={part.text}
/>
);
}
@ -146,75 +158,151 @@ const PurePreviewMessage = ({
message={message}
setMode={setMode}
setMessages={setMessages}
reload={reload}
regenerate={regenerate}
/>
</div>
);
}
}
if (type === 'tool-invocation') {
const { toolInvocation } = part;
const { toolName, toolCallId, state } = toolInvocation;
if (state === 'call') {
const { args } = toolInvocation;
if (type === 'tool-getWeather') {
const { toolCallId, state } = part;
if (state === 'input-available') {
return (
<div
key={toolCallId}
className={cx({
skeleton: ['getWeather'].includes(toolName),
})}
>
{toolName === 'getWeather' ? (
<Weather />
) : toolName === 'createDocument' ? (
<DocumentPreview isReadonly={isReadonly} args={args} />
) : toolName === 'updateDocument' ? (
<DocumentToolCall
type="update"
args={args}
isReadonly={isReadonly}
/>
) : toolName === 'requestSuggestions' ? (
<DocumentToolCall
type="request-suggestions"
args={args}
isReadonly={isReadonly}
/>
) : null}
<div key={toolCallId} className="skeleton">
<Weather />
</div>
);
}
if (state === 'result') {
const { result } = toolInvocation;
if (state === 'output-available') {
const { output } = part;
return (
<div key={toolCallId}>
<Weather weatherAtLocation={output} />
</div>
);
}
}
if (type === 'tool-createDocument') {
const { toolCallId, state } = part;
if (state === 'input-available') {
const { input } = part;
return (
<div key={toolCallId}>
<DocumentPreview isReadonly={isReadonly} args={input} />
</div>
);
}
if (state === 'output-available') {
const { output } = part;
if ('error' in output) {
return (
<div
key={toolCallId}
className="text-red-500 p-2 border rounded"
>
Error: {String(output.error)}
</div>
);
}
return (
<div key={toolCallId}>
{toolName === 'getWeather' ? (
<Weather weatherAtLocation={result} />
) : toolName === 'createDocument' ? (
<DocumentPreview
isReadonly={isReadonly}
result={result}
/>
) : toolName === 'updateDocument' ? (
<DocumentToolResult
type="update"
result={result}
isReadonly={isReadonly}
/>
) : toolName === 'requestSuggestions' ? (
<DocumentToolResult
type="request-suggestions"
result={result}
isReadonly={isReadonly}
/>
) : (
<pre>{JSON.stringify(result, null, 2)}</pre>
)}
<DocumentPreview
isReadonly={isReadonly}
result={output}
/>
</div>
);
}
}
if (type === 'tool-updateDocument') {
const { toolCallId, state } = part;
if (state === 'input-available') {
const { input } = part;
return (
<div key={toolCallId}>
<DocumentToolCall
type="update"
args={input}
isReadonly={isReadonly}
/>
</div>
);
}
if (state === 'output-available') {
const { output } = part;
if ('error' in output) {
return (
<div
key={toolCallId}
className="text-red-500 p-2 border rounded"
>
Error: {String(output.error)}
</div>
);
}
return (
<div key={toolCallId}>
<DocumentToolResult
type="update"
result={output}
isReadonly={isReadonly}
/>
</div>
);
}
}
if (type === 'tool-requestSuggestions') {
const { toolCallId, state } = part;
if (state === 'input-available') {
const { input } = part;
return (
<div key={toolCallId}>
<DocumentToolCall
type="request-suggestions"
args={input}
isReadonly={isReadonly}
/>
</div>
);
}
if (state === 'output-available') {
const { output } = part;
if ('error' in output) {
return (
<div
key={toolCallId}
className="text-red-500 p-2 border rounded"
>
Error: {String(output.error)}
</div>
);
}
return (
<div key={toolCallId}>
<DocumentToolResult
type="request-suggestions"
result={output}
isReadonly={isReadonly}
/>
</div>
);
}
@ -247,7 +335,7 @@ export const PreviewMessage = memo(
if (!equal(prevProps.message.parts, nextProps.message.parts)) return false;
if (!equal(prevProps.vote, nextProps.vote)) return false;
return true;
return false;
},
);