refactor: use generateId() for id creation (#736)
This commit is contained in:
parent
085f4a8ac4
commit
2d47ffbd77
8 changed files with 20 additions and 75 deletions
|
|
@ -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'),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import useSWR from 'swr';
|
|
||||||
|
|
||||||
export function useUserMessageId() {
|
|
||||||
const { data: userMessageIdFromServer, mutate: setUserMessageIdFromServer } =
|
|
||||||
useSWR('userMessageIdFromServer', null);
|
|
||||||
|
|
||||||
return { userMessageIdFromServer, setUserMessageIdFromServer };
|
|
||||||
}
|
|
||||||
19
lib/utils.ts
19
lib/utils.ts
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue