Skip to content

Commit ff6bd73

Browse files
authored
Merge pull request #9 from pattern-tech/feat/conversation-id
fix: Conversation id
2 parents 266c204 + 1051edc commit ff6bd73

File tree

7 files changed

+124
-129
lines changed

7 files changed

+124
-129
lines changed

app/(chat)/adapter.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Ok, Err, type Result } from 'ts-results-es';
22

3+
import type { ApiGetConversationMessagesResponse } from '@/app/(chat)/types';
34
import { extractErrorMessageOrDefault } from '@/lib/utils';
45

56
import type {
@@ -55,6 +56,44 @@ export const getConversation = async (
5556
}
5657
};
5758

59+
/**
60+
* Get messages of a conversation
61+
* @param accessToken
62+
* @param projectId
63+
* @param conversationId
64+
* @returns result containing the conversation messages
65+
*/
66+
export const getConversationMessages = async (
67+
accessToken: string,
68+
projectId: string,
69+
conversationId: string,
70+
): Promise<Result<ApiGetConversationMessagesResponse, string>> => {
71+
try {
72+
const conversationResponse = await fetch(
73+
`${patternCoreEndpoint}/playground/conversation/${projectId}/${conversationId}`,
74+
{
75+
headers: {
76+
Authorization: `Bearer ${accessToken}`,
77+
'Content-Type': 'application/json',
78+
},
79+
},
80+
);
81+
82+
if (conversationResponse.ok) {
83+
const conversationMessages: ApiGetConversationMessagesResponse = (
84+
await conversationResponse.json()
85+
).metadata.history;
86+
87+
return Ok(conversationMessages);
88+
}
89+
return Err(
90+
`Fetching conversation messages failed with error code ${conversationResponse.status}`,
91+
);
92+
} catch (error) {
93+
return Err(extractErrorMessageOrDefault(error));
94+
}
95+
};
96+
5897
/**
5998
* Create a conversation
6099
* @param accessToken
@@ -65,6 +104,7 @@ export const getConversation = async (
65104
export const createConversation = async (
66105
accessToken: string,
67106
projectId: string,
107+
conversationId: string,
68108
conversationName: string,
69109
): Promise<Result<ApiCreateConversationResponse, string>> => {
70110
try {
@@ -79,6 +119,7 @@ export const createConversation = async (
79119
body: JSON.stringify({
80120
name: conversationName,
81121
project_id: projectId,
122+
conversation_id: conversationId,
82123
}),
83124
},
84125
);

app/(chat)/chat/[id]/page.tsx

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,60 @@
1-
import { cookies } from 'next/headers';
21
import { notFound } from 'next/navigation';
32

43
import { auth } from '@/app/(auth)/auth';
54
import { Chat } from '@/components/chat';
6-
import { getChatById, getMessagesByChatId } from '@/lib/db/queries';
7-
import { convertToUIMessages } from '@/lib/utils';
85
import { DataStreamHandler } from '@/components/data-stream-handler';
9-
import { DEFAULT_CHAT_MODEL } from '@/lib/ai/models';
6+
import { convertToUIMessages } from '@/lib/utils';
7+
8+
import { getConversation, getConversationMessages } from '../../service';
109

1110
export default async function Page(props: { params: Promise<{ id: string }> }) {
1211
const params = await props.params;
13-
const { id } = params;
14-
const chat = await getChatById({ id });
12+
const session = await auth();
1513

16-
if (!chat) {
17-
notFound();
14+
if (
15+
!session ||
16+
!session.chainId ||
17+
!session.address ||
18+
!session.accessToken
19+
) {
20+
return notFound();
1821
}
1922

20-
const session = await auth();
23+
const { id } = params;
24+
const chatResult = await getConversation(
25+
session.accessToken,
26+
session.projectId,
27+
id,
28+
);
2129

22-
if (chat.visibility === 'private') {
23-
if (!session || !session.user) {
24-
return notFound();
25-
}
30+
if (chatResult.isErr()) {
31+
return chatResult.unwrap();
32+
}
2633

27-
if (session.user.id !== chat.userId) {
28-
return notFound();
29-
}
34+
const chat = chatResult.value;
35+
if (!chat) {
36+
return notFound();
3037
}
3138

32-
const messagesFromDb = await getMessagesByChatId({
39+
const messagesResult = await getConversationMessages(
40+
session.accessToken,
41+
session.projectId,
3342
id,
34-
});
35-
36-
const cookieStore = await cookies();
37-
const chatModelFromCookie = cookieStore.get('chat-model');
43+
);
3844

39-
if (!chatModelFromCookie) {
40-
return (
41-
<>
42-
<Chat
43-
id={chat.id}
44-
initialMessages={convertToUIMessages(messagesFromDb)}
45-
selectedChatModel={DEFAULT_CHAT_MODEL}
46-
selectedVisibilityType={chat.visibility}
47-
isReadonly={session?.user?.id !== chat.userId}
48-
/>
49-
<DataStreamHandler id={id} />
50-
</>
51-
);
45+
if (messagesResult.isErr()) {
46+
return messagesResult.unwrap();
5247
}
5348

49+
const messages = messagesResult.value;
50+
5451
return (
5552
<>
5653
<Chat
5754
id={chat.id}
58-
initialMessages={convertToUIMessages(messagesFromDb)}
59-
selectedChatModel={chatModelFromCookie.value}
60-
selectedVisibilityType={chat.visibility}
61-
isReadonly={session?.user?.id !== chat.userId}
55+
initialMessages={convertToUIMessages(messages)}
56+
selectedVisibilityType="private"
57+
isReadonly={false}
6258
/>
6359
<DataStreamHandler id={id} />
6460
</>

app/(chat)/page.tsx

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,16 @@
1-
import { cookies } from 'next/headers';
2-
31
import { Chat } from '@/components/chat';
4-
import { DEFAULT_CHAT_MODEL } from '@/lib/ai/models';
5-
import { generateUUID } from '@/lib/utils';
62
import { DataStreamHandler } from '@/components/data-stream-handler';
3+
import { generateUUID } from '@/lib/utils';
74

85
export default async function Page() {
96
const id = generateUUID();
107

11-
const cookieStore = await cookies();
12-
const modelIdFromCookie = cookieStore.get('chat-model');
13-
14-
if (!modelIdFromCookie) {
15-
return (
16-
<>
17-
<Chat
18-
key={id}
19-
id={id}
20-
initialMessages={[]}
21-
selectedChatModel={DEFAULT_CHAT_MODEL}
22-
selectedVisibilityType="private"
23-
isReadonly={false}
24-
/>
25-
<DataStreamHandler id={id} />
26-
</>
27-
);
28-
}
29-
308
return (
319
<>
3210
<Chat
3311
key={id}
3412
id={id}
3513
initialMessages={[]}
36-
selectedChatModel={modelIdFromCookie.value}
3714
selectedVisibilityType="private"
3815
isReadonly={false}
3916
/>

app/(chat)/service.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const getOrCreateConversation = async (
2828
const createConversationResult = await createConversation(
2929
accessToken,
3030
projectId,
31+
conversationId,
3132
'Default Title',
3233
);
3334
if (createConversationResult.isErr()) {
@@ -40,4 +41,9 @@ export const getOrCreateConversation = async (
4041
return Ok(conversation);
4142
};
4243

43-
export { sendMessage, sendMessageStreamed } from './adapter';
44+
export {
45+
sendMessage,
46+
sendMessageStreamed,
47+
getConversation,
48+
getConversationMessages,
49+
} from './adapter';

app/(chat)/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ export interface Conversation {
44
project_id: string;
55
}
66

7+
export interface Message {
8+
role: 'human' | 'ai';
9+
content: string;
10+
}
11+
712
export type ApiGetConversationResponse = Conversation | null;
13+
export type ApiGetConversationMessagesResponse = Message[];
814
export type ApiCreateConversationResponse = Conversation;
915
export type ApiSendMessageResponse = string;
1016
export type ApiSendMessageStreamedResponse = ReadableStream;

components/chat.tsx

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
1-
"use client";
1+
'use client';
22

3-
import type { Attachment, Message } from "ai";
4-
import { useChat } from "ai/react";
5-
import { useState } from "react";
6-
import useSWR, { useSWRConfig } from "swr";
3+
import type { Attachment, Message } from 'ai';
4+
import { useChat } from 'ai/react';
5+
import { useState } from 'react';
6+
import { toast } from 'sonner';
7+
import useSWR, { useSWRConfig } from 'swr';
78

8-
import { ChatHeader } from "@/components/chat-header";
9-
import type { Vote } from "@/lib/db/schema";
10-
import { fetcher, generateUUID } from "@/lib/utils";
9+
import { ChatHeader } from '@/components/chat-header';
10+
import { useArtifactSelector } from '@/hooks/use-artifact';
11+
import type { Vote } from '@/lib/db/schema';
12+
import { fetcher, generateUUID } from '@/lib/utils';
1113

12-
import { Artifact } from "./artifact";
13-
import { MultimodalInput } from "./multimodal-input";
14-
import { Messages } from "./messages";
15-
import { VisibilityType } from "./visibility-selector";
16-
import { useArtifactSelector } from "@/hooks/use-artifact";
17-
import { toast } from "sonner";
14+
import { Artifact } from './artifact';
15+
import { Messages } from './messages';
16+
import { MultimodalInput } from './multimodal-input';
17+
import type { VisibilityType } from './visibility-selector';
1818

1919
export function Chat({
2020
id,
2121
initialMessages,
22-
selectedChatModel,
2322
isReadonly,
2423
}: {
2524
id: string;
2625
initialMessages: Array<Message>;
27-
selectedChatModel: string;
2826
selectedVisibilityType: VisibilityType;
2927
isReadonly: boolean;
3028
}) {
@@ -42,22 +40,22 @@ export function Chat({
4240
reload,
4341
} = useChat({
4442
id,
45-
body: { id, selectedChatModel: selectedChatModel },
43+
body: { id },
4644
initialMessages,
4745
experimental_throttle: 100,
4846
sendExtraMessageFields: true,
4947
generateId: generateUUID,
5048
onFinish: () => {
51-
mutate("/api/history");
49+
mutate('/api/history');
5250
},
5351
onError: (error) => {
54-
toast.error("An error occured, please try again!");
52+
toast.error('An error occured, please try again!');
5553
},
5654
});
5755

5856
const { data: votes } = useSWR<Array<Vote>>(
5957
`/api/vote?chatId=${id}`,
60-
fetcher
58+
fetcher,
6159
);
6260

6361
const [attachments, setAttachments] = useState<Array<Attachment>>([]);

lib/utils.ts

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import type {
22
CoreAssistantMessage,
33
CoreToolMessage,
44
Message,
5-
ToolInvocation,
65
} from 'ai';
76
import { type ClassValue, clsx } from 'clsx';
87
import { twMerge } from 'tailwind-merge';
98

10-
import type { Message as DBMessage, Document } from '@/lib/db/schema';
9+
import type { Message as CoreMessage } from '@/app/(chat)/types';
10+
import type { Document } from '@/lib/db/schema';
1111

1212
export function cn(...inputs: ClassValue[]) {
1313
return twMerge(clsx(inputs));
@@ -83,50 +83,21 @@ function addToolMessageToChat({
8383
});
8484
}
8585

86-
export function convertToUIMessages(
87-
messages: Array<DBMessage>,
88-
): Array<Message> {
89-
return messages.reduce((chatMessages: Array<Message>, message) => {
90-
if (message.role === 'tool') {
91-
return addToolMessageToChat({
92-
toolMessage: message as CoreToolMessage,
93-
messages: chatMessages,
94-
});
95-
}
96-
97-
let textContent = '';
98-
let reasoning: string | undefined = undefined;
99-
const toolInvocations: Array<ToolInvocation> = [];
100-
101-
if (typeof message.content === 'string') {
102-
textContent = message.content;
103-
} else if (Array.isArray(message.content)) {
104-
for (const content of message.content) {
105-
if (content.type === 'text') {
106-
textContent += content.text;
107-
} else if (content.type === 'tool-call') {
108-
toolInvocations.push({
109-
state: 'call',
110-
toolCallId: content.toolCallId,
111-
toolName: content.toolName,
112-
args: content.args,
113-
});
114-
} else if (content.type === 'reasoning') {
115-
reasoning = content.reasoning;
116-
}
117-
}
118-
}
119-
120-
chatMessages.push({
121-
id: message.id,
122-
role: message.role as Message['role'],
123-
content: textContent,
124-
reasoning,
125-
toolInvocations,
126-
});
127-
128-
return chatMessages;
129-
}, []);
86+
export function convertToUIMessages(messages: CoreMessage[]): Array<Message> {
87+
return messages.reduce(
88+
(chatMessages: Array<Message>, message, index) => [
89+
// biome-ignore lint:‌ premature optimization
90+
...chatMessages,
91+
{
92+
id: `${index}`,
93+
role: message.role === 'ai' ? 'assistant' : 'user',
94+
content: message.content,
95+
reasoning: '',
96+
toolInvocations: [],
97+
},
98+
],
99+
[],
100+
);
130101
}
131102

132103
type ResponseMessageWithoutId = CoreToolMessage | CoreAssistantMessage;

0 commit comments

Comments
 (0)