refactor: use generateId() for id creation (#736)

This commit is contained in:
Jeremy 2025-01-28 18:51:04 +05:30 committed by GitHub
parent 085f4a8ac4
commit 2d47ffbd77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 20 additions and 75 deletions

View file

@ -1,6 +1,6 @@
'use server'; 'use server';
import { type CoreUserMessage, generateText } from 'ai'; import { type CoreUserMessage, generateText, Message } from 'ai';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
import { customModel } from '@/lib/ai'; import { customModel } from '@/lib/ai';
@ -19,7 +19,7 @@ export async function saveModelId(model: string) {
export async function generateTitleFromUserMessage({ export async function generateTitleFromUserMessage({
message, message,
}: { }: {
message: CoreUserMessage; message: Message;
}) { }) {
const { text: title } = await generateText({ const { text: title } = await generateText({
model: customModel('gpt-4o-mini'), model: customModel('gpt-4o-mini'),

View file

@ -1,6 +1,5 @@
import { import {
type Message, type Message,
convertToCoreMessages,
createDataStreamResponse, createDataStreamResponse,
smoothStream, smoothStream,
streamText, streamText,
@ -65,8 +64,7 @@ export async function POST(request: Request) {
return new Response('Model not found', { status: 404 }); return new Response('Model not found', { status: 404 });
} }
const coreMessages = convertToCoreMessages(messages); const userMessage = getMostRecentUserMessage(messages);
const userMessage = getMostRecentUserMessage(coreMessages);
if (!userMessage) { if (!userMessage) {
return new Response('No user message found', { status: 400 }); return new Response('No user message found', { status: 400 });
@ -79,28 +77,20 @@ export async function POST(request: Request) {
await saveChat({ id, userId: session.user.id, title }); await saveChat({ id, userId: session.user.id, title });
} }
const userMessageId = generateUUID();
await saveMessages({ await saveMessages({
messages: [ messages: [{ ...userMessage, createdAt: new Date(), chatId: id }],
{ ...userMessage, id: userMessageId, createdAt: new Date(), chatId: id },
],
}); });
return createDataStreamResponse({ return createDataStreamResponse({
execute: (dataStream) => { execute: (dataStream) => {
dataStream.writeData({
type: 'user-message-id',
content: userMessageId,
});
const result = streamText({ const result = streamText({
model: customModel(model.apiIdentifier), model: customModel(model.apiIdentifier),
system: systemPrompt, system: systemPrompt,
messages: coreMessages, messages,
maxSteps: 5, maxSteps: 5,
experimental_activeTools: allTools, experimental_activeTools: allTools,
experimental_transform: smoothStream({ chunking: 'word' }), experimental_transform: smoothStream({ chunking: 'word' }),
experimental_generateMessageId: generateUUID,
tools: { tools: {
getWeather, getWeather,
createDocument: createDocument({ session, dataStream, model }), createDocument: createDocument({ session, dataStream, model }),
@ -120,16 +110,8 @@ export async function POST(request: Request) {
await saveMessages({ await saveMessages({
messages: responseMessagesWithoutIncompleteToolCalls.map( messages: responseMessagesWithoutIncompleteToolCalls.map(
(message) => { (message) => {
const messageId = generateUUID();
if (message.role === 'assistant') {
dataStream.writeMessageAnnotation({
messageIdFromServer: messageId,
});
}
return { return {
id: messageId, id: message.id,
chatId: id, chatId: id,
role: message.role, role: message.role,
content: message.content, content: message.content,

View file

@ -7,7 +7,7 @@ import useSWR, { useSWRConfig } from 'swr';
import { ChatHeader } from '@/components/chat-header'; import { ChatHeader } from '@/components/chat-header';
import type { Vote } from '@/lib/db/schema'; import type { Vote } from '@/lib/db/schema';
import { fetcher } from '@/lib/utils'; import { fetcher, generateUUID } from '@/lib/utils';
import { Block } from './block'; import { Block } from './block';
import { MultimodalInput } from './multimodal-input'; import { MultimodalInput } from './multimodal-input';
@ -45,6 +45,8 @@ export function Chat({
body: { id, modelId: selectedModelId }, body: { id, modelId: selectedModelId },
initialMessages, initialMessages,
experimental_throttle: 100, experimental_throttle: 100,
sendExtraMessageFields: true,
generateId: generateUUID,
onFinish: () => { onFinish: () => {
mutate('/api/history'); mutate('/api/history');
}, },

View file

@ -5,7 +5,6 @@ import { useEffect, useRef } from 'react';
import { blockDefinitions, BlockKind } from './block'; import { blockDefinitions, 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';
export type DataStreamDelta = { export type DataStreamDelta = {
type: type:
@ -17,14 +16,12 @@ export type DataStreamDelta = {
| 'suggestion' | 'suggestion'
| 'clear' | 'clear'
| 'finish' | 'finish'
| 'user-message-id'
| 'kind'; | 'kind';
content: string | Suggestion; content: string | Suggestion;
}; };
export function DataStreamHandler({ id }: { id: string }) { export function DataStreamHandler({ id }: { id: string }) {
const { data: dataStream } = useChat({ id }); const { data: dataStream } = useChat({ id });
const { setUserMessageIdFromServer } = useUserMessageId();
const { block, setBlock, setMetadata } = useBlock(); const { block, setBlock, setMetadata } = useBlock();
const lastProcessedIndex = useRef(-1); const lastProcessedIndex = useRef(-1);
@ -35,11 +32,6 @@ export function DataStreamHandler({ id }: { id: string }) {
lastProcessedIndex.current = dataStream.length - 1; lastProcessedIndex.current = dataStream.length - 1;
(newDeltas as DataStreamDelta[]).forEach((delta: DataStreamDelta) => { (newDeltas as DataStreamDelta[]).forEach((delta: DataStreamDelta) => {
if (delta.type === 'user-message-id') {
setUserMessageIdFromServer(delta.content as string);
return;
}
const blockDefinition = blockDefinitions.find( const blockDefinition = blockDefinitions.find(
(blockDefinition) => blockDefinition.kind === block.kind, (blockDefinition) => blockDefinition.kind === block.kind,
); );
@ -97,7 +89,7 @@ export function DataStreamHandler({ id }: { id: string }) {
} }
}); });
}); });
}, [dataStream, setBlock, setUserMessageIdFromServer, setMetadata, block]); }, [dataStream, setBlock, setMetadata, block]);
return null; return null;
} }

View file

@ -4,7 +4,6 @@ import { useSWRConfig } from 'swr';
import { useCopyToClipboard } from 'usehooks-ts'; import { useCopyToClipboard } from 'usehooks-ts';
import type { Vote } from '@/lib/db/schema'; import type { Vote } from '@/lib/db/schema';
import { getMessageIdFromAnnotations } from '@/lib/utils';
import { CopyIcon, ThumbDownIcon, ThumbUpIcon } from './icons'; import { CopyIcon, ThumbDownIcon, ThumbUpIcon } from './icons';
import { Button } from './ui/button'; import { Button } from './ui/button';
@ -62,13 +61,11 @@ export function PureMessageActions({
disabled={vote?.isUpvoted} disabled={vote?.isUpvoted}
variant="outline" variant="outline"
onClick={async () => { onClick={async () => {
const messageId = getMessageIdFromAnnotations(message);
const upvote = fetch('/api/vote', { const upvote = fetch('/api/vote', {
method: 'PATCH', method: 'PATCH',
body: JSON.stringify({ body: JSON.stringify({
chatId, chatId,
messageId, messageId: message.id,
type: 'up', type: 'up',
}), }),
}); });
@ -116,13 +113,11 @@ export function PureMessageActions({
variant="outline" variant="outline"
disabled={vote && !vote.isUpvoted} disabled={vote && !vote.isUpvoted}
onClick={async () => { onClick={async () => {
const messageId = getMessageIdFromAnnotations(message);
const downvote = fetch('/api/vote', { const downvote = fetch('/api/vote', {
method: 'PATCH', method: 'PATCH',
body: JSON.stringify({ body: JSON.stringify({
chatId, chatId,
messageId, messageId: message.id,
type: 'down', type: 'down',
}), }),
}); });

View file

@ -6,7 +6,6 @@ import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';
import { Textarea } from './ui/textarea'; import { Textarea } from './ui/textarea';
import { deleteTrailingMessages } from '@/app/(chat)/actions'; import { deleteTrailingMessages } from '@/app/(chat)/actions';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { useUserMessageId } from '@/hooks/use-user-message-id';
export type MessageEditorProps = { export type MessageEditorProps = {
message: Message; message: Message;
@ -25,7 +24,6 @@ export function MessageEditor({
setMessages, setMessages,
reload, reload,
}: MessageEditorProps) { }: MessageEditorProps) {
const { userMessageIdFromServer } = useUserMessageId();
const [isSubmitting, setIsSubmitting] = useState<boolean>(false); const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const [draftContent, setDraftContent] = useState<string>(message.content); const [draftContent, setDraftContent] = useState<string>(message.content);
@ -74,16 +72,9 @@ export function MessageEditor({
disabled={isSubmitting} disabled={isSubmitting}
onClick={async () => { onClick={async () => {
setIsSubmitting(true); setIsSubmitting(true);
const messageId = userMessageIdFromServer ?? message.id;
if (!messageId) {
toast.error('Something went wrong, please try again!');
setIsSubmitting(false);
return;
}
await deleteTrailingMessages({ await deleteTrailingMessages({
id: messageId, id: message.id,
}); });
setMessages((messages) => { setMessages((messages) => {

View file

@ -1,10 +0,0 @@
'use client';
import useSWR from 'swr';
export function useUserMessageId() {
const { data: userMessageIdFromServer, mutate: setUserMessageIdFromServer } =
useSWR('userMessageIdFromServer', null);
return { userMessageIdFromServer, setUserMessageIdFromServer };
}

View file

@ -126,9 +126,12 @@ export function convertToUIMessages(
}, []); }, []);
} }
type ResponseMessageWithoutId = CoreToolMessage | CoreAssistantMessage;
type ResponseMessage = ResponseMessageWithoutId & { id: string };
export function sanitizeResponseMessages( export function sanitizeResponseMessages(
messages: Array<CoreToolMessage | CoreAssistantMessage>, messages: Array<ResponseMessage>,
): Array<CoreToolMessage | CoreAssistantMessage> { ): Array<ResponseMessage> {
const toolResultIds: Array<string> = []; const toolResultIds: Array<string> = [];
for (const message of messages) { for (const message of messages) {
@ -198,7 +201,7 @@ export function sanitizeUIMessages(messages: Array<Message>): Array<Message> {
); );
} }
export function getMostRecentUserMessage(messages: Array<CoreMessage>) { export function getMostRecentUserMessage(messages: Array<Message>) {
const userMessages = messages.filter((message) => message.role === 'user'); const userMessages = messages.filter((message) => message.role === 'user');
return userMessages.at(-1); return userMessages.at(-1);
} }
@ -212,13 +215,3 @@ export function getDocumentTimestampByIndex(
return documents[index].createdAt; return documents[index].createdAt;
} }
export function getMessageIdFromAnnotations(message: Message) {
if (!message.annotations) return message.id;
const [annotation] = message.annotations;
if (!annotation) return message.id;
// @ts-expect-error messageIdFromServer is not defined in MessageAnnotation
return annotation.messageIdFromServer;
}