diff --git a/app/(chat)/adapter.ts b/app/(chat)/adapter.ts index cc1e7f3..06fe0df 100644 --- a/app/(chat)/adapter.ts +++ b/app/(chat)/adapter.ts @@ -1,5 +1,6 @@ import { Ok, Err, type Result } from 'ts-results-es'; +import type { ApiGetConversationMessagesResponse } from '@/app/(chat)/types'; import { extractErrorMessageOrDefault } from '@/lib/utils'; import type { @@ -55,6 +56,44 @@ export const getConversation = async ( } }; +/** + * Get messages of a conversation + * @param accessToken + * @param projectId + * @param conversationId + * @returns result containing the conversation messages + */ +export const getConversationMessages = async ( + accessToken: string, + projectId: string, + conversationId: string, +): Promise> => { + try { + const conversationResponse = await fetch( + `${patternCoreEndpoint}/playground/conversation/${projectId}/${conversationId}`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }, + ); + + if (conversationResponse.ok) { + const conversationMessages: ApiGetConversationMessagesResponse = ( + await conversationResponse.json() + ).metadata.history; + + return Ok(conversationMessages); + } + return Err( + `Fetching conversation messages failed with error code ${conversationResponse.status}`, + ); + } catch (error) { + return Err(extractErrorMessageOrDefault(error)); + } +}; + /** * Create a conversation * @param accessToken @@ -65,6 +104,7 @@ export const getConversation = async ( export const createConversation = async ( accessToken: string, projectId: string, + conversationId: string, conversationName: string, ): Promise> => { try { @@ -79,6 +119,7 @@ export const createConversation = async ( body: JSON.stringify({ name: conversationName, project_id: projectId, + conversation_id: conversationId, }), }, ); diff --git a/app/(chat)/chat/[id]/page.tsx b/app/(chat)/chat/[id]/page.tsx index c7000d3..5b6215c 100644 --- a/app/(chat)/chat/[id]/page.tsx +++ b/app/(chat)/chat/[id]/page.tsx @@ -1,64 +1,60 @@ -import { cookies } from 'next/headers'; import { notFound } from 'next/navigation'; import { auth } from '@/app/(auth)/auth'; import { Chat } from '@/components/chat'; -import { getChatById, getMessagesByChatId } from '@/lib/db/queries'; -import { convertToUIMessages } from '@/lib/utils'; import { DataStreamHandler } from '@/components/data-stream-handler'; -import { DEFAULT_CHAT_MODEL } from '@/lib/ai/models'; +import { convertToUIMessages } from '@/lib/utils'; + +import { getConversation, getConversationMessages } from '../../service'; export default async function Page(props: { params: Promise<{ id: string }> }) { const params = await props.params; - const { id } = params; - const chat = await getChatById({ id }); + const session = await auth(); - if (!chat) { - notFound(); + if ( + !session || + !session.chainId || + !session.address || + !session.accessToken + ) { + return notFound(); } - const session = await auth(); + const { id } = params; + const chatResult = await getConversation( + session.accessToken, + session.projectId, + id, + ); - if (chat.visibility === 'private') { - if (!session || !session.user) { - return notFound(); - } + if (chatResult.isErr()) { + return chatResult.unwrap(); + } - if (session.user.id !== chat.userId) { - return notFound(); - } + const chat = chatResult.value; + if (!chat) { + return notFound(); } - const messagesFromDb = await getMessagesByChatId({ + const messagesResult = await getConversationMessages( + session.accessToken, + session.projectId, id, - }); - - const cookieStore = await cookies(); - const chatModelFromCookie = cookieStore.get('chat-model'); + ); - if (!chatModelFromCookie) { - return ( - <> - - - - ); + if (messagesResult.isErr()) { + return messagesResult.unwrap(); } + const messages = messagesResult.value; + return ( <> diff --git a/app/(chat)/page.tsx b/app/(chat)/page.tsx index 9fbc743..8af779a 100644 --- a/app/(chat)/page.tsx +++ b/app/(chat)/page.tsx @@ -1,39 +1,16 @@ -import { cookies } from 'next/headers'; - import { Chat } from '@/components/chat'; -import { DEFAULT_CHAT_MODEL } from '@/lib/ai/models'; -import { generateUUID } from '@/lib/utils'; import { DataStreamHandler } from '@/components/data-stream-handler'; +import { generateUUID } from '@/lib/utils'; export default async function Page() { const id = generateUUID(); - const cookieStore = await cookies(); - const modelIdFromCookie = cookieStore.get('chat-model'); - - if (!modelIdFromCookie) { - return ( - <> - - - - ); - } - return ( <> diff --git a/app/(chat)/service.ts b/app/(chat)/service.ts index 391fd7a..a2af95b 100644 --- a/app/(chat)/service.ts +++ b/app/(chat)/service.ts @@ -28,6 +28,7 @@ export const getOrCreateConversation = async ( const createConversationResult = await createConversation( accessToken, projectId, + conversationId, 'Default Title', ); if (createConversationResult.isErr()) { @@ -40,4 +41,9 @@ export const getOrCreateConversation = async ( return Ok(conversation); }; -export { sendMessage, sendMessageStreamed } from './adapter'; +export { + sendMessage, + sendMessageStreamed, + getConversation, + getConversationMessages, +} from './adapter'; diff --git a/app/(chat)/types.ts b/app/(chat)/types.ts index e8aed20..c48a13f 100644 --- a/app/(chat)/types.ts +++ b/app/(chat)/types.ts @@ -4,7 +4,13 @@ export interface Conversation { project_id: string; } +export interface Message { + role: 'human' | 'ai'; + content: string; +} + export type ApiGetConversationResponse = Conversation | null; +export type ApiGetConversationMessagesResponse = Message[]; export type ApiCreateConversationResponse = Conversation; export type ApiSendMessageResponse = string; export type ApiSendMessageStreamedResponse = ReadableStream; diff --git a/components/chat.tsx b/components/chat.tsx index 8d04c46..95017ea 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -1,30 +1,28 @@ -"use client"; +'use client'; -import type { Attachment, Message } from "ai"; -import { useChat } from "ai/react"; -import { useState } from "react"; -import useSWR, { useSWRConfig } from "swr"; +import type { Attachment, Message } from 'ai'; +import { useChat } from 'ai/react'; +import { useState } from 'react'; +import { toast } from 'sonner'; +import useSWR, { useSWRConfig } from 'swr'; -import { ChatHeader } from "@/components/chat-header"; -import type { Vote } from "@/lib/db/schema"; -import { fetcher, generateUUID } from "@/lib/utils"; +import { ChatHeader } from '@/components/chat-header'; +import { useArtifactSelector } from '@/hooks/use-artifact'; +import type { Vote } from '@/lib/db/schema'; +import { fetcher, generateUUID } from '@/lib/utils'; -import { Artifact } from "./artifact"; -import { MultimodalInput } from "./multimodal-input"; -import { Messages } from "./messages"; -import { VisibilityType } from "./visibility-selector"; -import { useArtifactSelector } from "@/hooks/use-artifact"; -import { toast } from "sonner"; +import { Artifact } from './artifact'; +import { Messages } from './messages'; +import { MultimodalInput } from './multimodal-input'; +import type { VisibilityType } from './visibility-selector'; export function Chat({ id, initialMessages, - selectedChatModel, isReadonly, }: { id: string; initialMessages: Array; - selectedChatModel: string; selectedVisibilityType: VisibilityType; isReadonly: boolean; }) { @@ -42,22 +40,22 @@ export function Chat({ reload, } = useChat({ id, - body: { id, selectedChatModel: selectedChatModel }, + body: { id }, initialMessages, experimental_throttle: 100, sendExtraMessageFields: true, generateId: generateUUID, onFinish: () => { - mutate("/api/history"); + mutate('/api/history'); }, onError: (error) => { - toast.error("An error occured, please try again!"); + toast.error('An error occured, please try again!'); }, }); const { data: votes } = useSWR>( `/api/vote?chatId=${id}`, - fetcher + fetcher, ); const [attachments, setAttachments] = useState>([]); diff --git a/lib/utils.ts b/lib/utils.ts index d0cff3a..06b2d3b 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -2,12 +2,12 @@ import type { CoreAssistantMessage, CoreToolMessage, Message, - ToolInvocation, } from 'ai'; import { type ClassValue, clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; -import type { Message as DBMessage, Document } from '@/lib/db/schema'; +import type { Message as CoreMessage } from '@/app/(chat)/types'; +import type { Document } from '@/lib/db/schema'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); @@ -83,50 +83,21 @@ function addToolMessageToChat({ }); } -export function convertToUIMessages( - messages: Array, -): Array { - return messages.reduce((chatMessages: Array, message) => { - if (message.role === 'tool') { - return addToolMessageToChat({ - toolMessage: message as CoreToolMessage, - messages: chatMessages, - }); - } - - let textContent = ''; - let reasoning: string | undefined = undefined; - const toolInvocations: Array = []; - - if (typeof message.content === 'string') { - textContent = message.content; - } else if (Array.isArray(message.content)) { - for (const content of message.content) { - if (content.type === 'text') { - textContent += content.text; - } else if (content.type === 'tool-call') { - toolInvocations.push({ - state: 'call', - toolCallId: content.toolCallId, - toolName: content.toolName, - args: content.args, - }); - } else if (content.type === 'reasoning') { - reasoning = content.reasoning; - } - } - } - - chatMessages.push({ - id: message.id, - role: message.role as Message['role'], - content: textContent, - reasoning, - toolInvocations, - }); - - return chatMessages; - }, []); +export function convertToUIMessages(messages: CoreMessage[]): Array { + return messages.reduce( + (chatMessages: Array, message, index) => [ + // biome-ignore lint:‌ premature optimization + ...chatMessages, + { + id: `${index}`, + role: message.role === 'ai' ? 'assistant' : 'user', + content: message.content, + reasoning: '', + toolInvocations: [], + }, + ], + [], + ); } type ResponseMessageWithoutId = CoreToolMessage | CoreAssistantMessage;