Spaces:
Sleeping
Sleeping
feat: revalidate path / remove create chat api (use server action) / code block style (#53)
Browse files<img width="934" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/5669963/6fd046ea-e9b5-45e2-bbef-bff0f1e42ac3">
- app/api/chat/create/route.ts +0 -36
- app/chat/page.tsx +6 -11
- components/chat-sidebar/ChatCard.tsx +1 -0
- components/chat/ImageSelector.tsx +4 -9
- components/ui/CodeBlock.tsx +3 -4
- lib/db/functions.ts +12 -2
- lib/hooks/useChatWithMedia.ts +0 -87
- lib/types.ts +0 -31
- next.config.js +1 -0
- prisma/migrations/20240524012008_init/migration.sql +49 -0
- prisma/migrations/migration_lock.toml +3 -0
app/api/chat/create/route.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
| 1 |
-
import { dbPostCreateChat } from '@/lib/db/functions';
|
| 2 |
-
import { MessageRaw } from '@/lib/db/types';
|
| 3 |
-
import { withLogging } from '@/lib/logger';
|
| 4 |
-
import { revalidatePath } from 'next/cache';
|
| 5 |
-
|
| 6 |
-
/**
|
| 7 |
-
* @param req
|
| 8 |
-
* @returns
|
| 9 |
-
*/
|
| 10 |
-
export const POST = withLogging(
|
| 11 |
-
async (
|
| 12 |
-
_session,
|
| 13 |
-
json: {
|
| 14 |
-
id?: string;
|
| 15 |
-
url: string;
|
| 16 |
-
initMessages?: MessageRaw[];
|
| 17 |
-
},
|
| 18 |
-
): Promise<Response> => {
|
| 19 |
-
try {
|
| 20 |
-
const { url, id, initMessages } = json;
|
| 21 |
-
|
| 22 |
-
const response = await dbPostCreateChat({
|
| 23 |
-
id,
|
| 24 |
-
mediaUrl: url,
|
| 25 |
-
initMessages,
|
| 26 |
-
});
|
| 27 |
-
|
| 28 |
-
revalidatePath('/chat', 'layout');
|
| 29 |
-
return Response.json(response);
|
| 30 |
-
} catch (error) {
|
| 31 |
-
return new Response((error as Error).message, {
|
| 32 |
-
status: 400,
|
| 33 |
-
});
|
| 34 |
-
}
|
| 35 |
-
},
|
| 36 |
-
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/chat/page.tsx
CHANGED
|
@@ -14,7 +14,8 @@ import { IconDiscord, IconGitHub } from '@/components/ui/Icons';
|
|
| 14 |
import Link from 'next/link';
|
| 15 |
import { Button } from '@/components/ui/Button';
|
| 16 |
import Img from '@/components/ui/Img';
|
| 17 |
-
import {
|
|
|
|
| 18 |
|
| 19 |
// const EXAMPLE_URL = 'https://landing-lens-support.s3.us-east-2.amazonaws.com/vision-agent-examples/cereal-example.jpg';
|
| 20 |
const EXAMPLE_URL =
|
|
@@ -32,7 +33,7 @@ const exampleMessages = [
|
|
| 32 |
url: EXAMPLE_URL,
|
| 33 |
initMessages: [
|
| 34 |
{
|
| 35 |
-
role: 'user',
|
| 36 |
content:
|
| 37 |
EXAMPLE_PROMPT + '\n\n' + generateInputImageMarkdown(EXAMPLE_URL),
|
| 38 |
},
|
|
@@ -93,15 +94,9 @@ export default function Page() {
|
|
| 93 |
index > 1 && 'hidden md:block'
|
| 94 |
}`}
|
| 95 |
onClick={async () => {
|
| 96 |
-
const resp = await
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
'Content-Type': 'application/json',
|
| 100 |
-
},
|
| 101 |
-
body: JSON.stringify({
|
| 102 |
-
url: example.url,
|
| 103 |
-
initMessages: example.initMessages,
|
| 104 |
-
}),
|
| 105 |
});
|
| 106 |
if (resp) {
|
| 107 |
router.push(`/chat/${resp.id}`);
|
|
|
|
| 14 |
import Link from 'next/link';
|
| 15 |
import { Button } from '@/components/ui/Button';
|
| 16 |
import Img from '@/components/ui/Img';
|
| 17 |
+
import { MessageRaw } from '@/lib/db/types';
|
| 18 |
+
import { dbPostCreateChat } from '@/lib/db/functions';
|
| 19 |
|
| 20 |
// const EXAMPLE_URL = 'https://landing-lens-support.s3.us-east-2.amazonaws.com/vision-agent-examples/cereal-example.jpg';
|
| 21 |
const EXAMPLE_URL =
|
|
|
|
| 33 |
url: EXAMPLE_URL,
|
| 34 |
initMessages: [
|
| 35 |
{
|
| 36 |
+
role: 'user' as MessageRaw['role'],
|
| 37 |
content:
|
| 38 |
EXAMPLE_PROMPT + '\n\n' + generateInputImageMarkdown(EXAMPLE_URL),
|
| 39 |
},
|
|
|
|
| 94 |
index > 1 && 'hidden md:block'
|
| 95 |
}`}
|
| 96 |
onClick={async () => {
|
| 97 |
+
const resp = await dbPostCreateChat({
|
| 98 |
+
mediaUrl: example.url,
|
| 99 |
+
initMessages: example.initMessages,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
});
|
| 101 |
if (resp) {
|
| 102 |
router.push(`/chat/${resp.id}`);
|
components/chat-sidebar/ChatCard.tsx
CHANGED
|
@@ -36,6 +36,7 @@ export const ChatCardLayout: React.FC<
|
|
| 36 |
|
| 37 |
const ChatCard: React.FC<ChatCardProps> = ({ chat, isAdminView }) => {
|
| 38 |
const { id: chatIdFromParam } = useParams();
|
|
|
|
| 39 |
const { id, mediaUrl, messages, userId, updatedAt } = chat;
|
| 40 |
if (!id) {
|
| 41 |
return null;
|
|
|
|
| 36 |
|
| 37 |
const ChatCard: React.FC<ChatCardProps> = ({ chat, isAdminView }) => {
|
| 38 |
const { id: chatIdFromParam } = useParams();
|
| 39 |
+
const router = useRouter();
|
| 40 |
const { id, mediaUrl, messages, userId, updatedAt } = chat;
|
| 41 |
if (!id) {
|
| 42 |
return null;
|
components/chat/ImageSelector.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
| 12 |
getVideoCover,
|
| 13 |
} from '@rajesh896/video-thumbnails-generator';
|
| 14 |
import { ChatWithMessages } from '@/lib/db/types';
|
|
|
|
| 15 |
|
| 16 |
export interface ImageSelectorProps {}
|
| 17 |
|
|
@@ -87,15 +88,9 @@ const ImageSelector: React.FC<ImageSelectorProps> = () => {
|
|
| 87 |
return upload(thumbnailFile, resp.id);
|
| 88 |
});
|
| 89 |
}
|
| 90 |
-
await
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
'Content-Type': 'application/json',
|
| 94 |
-
},
|
| 95 |
-
body: JSON.stringify({
|
| 96 |
-
id: resp.id,
|
| 97 |
-
url: resp.publicUrl,
|
| 98 |
-
}),
|
| 99 |
});
|
| 100 |
setUploading(false);
|
| 101 |
router.push(`/chat/${resp.id}`);
|
|
|
|
| 12 |
getVideoCover,
|
| 13 |
} from '@rajesh896/video-thumbnails-generator';
|
| 14 |
import { ChatWithMessages } from '@/lib/db/types';
|
| 15 |
+
import { dbPostCreateChat } from '@/lib/db/functions';
|
| 16 |
|
| 17 |
export interface ImageSelectorProps {}
|
| 18 |
|
|
|
|
| 88 |
return upload(thumbnailFile, resp.id);
|
| 89 |
});
|
| 90 |
}
|
| 91 |
+
await dbPostCreateChat({
|
| 92 |
+
id: resp.id,
|
| 93 |
+
mediaUrl: resp.publicUrl,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
});
|
| 95 |
setUploading(false);
|
| 96 |
router.push(`/chat/${resp.id}`);
|
components/ui/CodeBlock.tsx
CHANGED
|
@@ -91,10 +91,9 @@ const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
|
| 91 |
if (isCopied) return;
|
| 92 |
copyToClipboard(value);
|
| 93 |
};
|
| 94 |
-
|
| 95 |
return (
|
| 96 |
-
<div className="relative w-full font-sans codeblock bg-zinc-950">
|
| 97 |
-
<div className="flex items-center justify-between w-full
|
| 98 |
<span className="text-xs lowercase">{language}</span>
|
| 99 |
<div className="flex items-center space-x-1">
|
| 100 |
<Button
|
|
@@ -126,7 +125,7 @@ const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
|
| 126 |
margin: 0,
|
| 127 |
width: '100%',
|
| 128 |
background: 'transparent',
|
| 129 |
-
padding: '1.5rem 1rem',
|
| 130 |
}}
|
| 131 |
lineNumberStyle={{
|
| 132 |
userSelect: 'none',
|
|
|
|
| 91 |
if (isCopied) return;
|
| 92 |
copyToClipboard(value);
|
| 93 |
};
|
|
|
|
| 94 |
return (
|
| 95 |
+
<div className="relative w-full font-sans codeblock bg-zinc-950 rounded-lg overflow-hidden">
|
| 96 |
+
<div className="flex items-center justify-between w-full pl-8 pr-4 pt-2 text-zinc-100">
|
| 97 |
<span className="text-xs lowercase">{language}</span>
|
| 98 |
<div className="flex items-center space-x-1">
|
| 99 |
<Button
|
|
|
|
| 125 |
margin: 0,
|
| 126 |
width: '100%',
|
| 127 |
background: 'transparent',
|
| 128 |
+
padding: '0.5rem 1rem 1.5rem 1rem',
|
| 129 |
}}
|
| 130 |
lineNumberStyle={{
|
| 131 |
userSelect: 'none',
|
lib/db/functions.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
| 3 |
import { sessionUser } from '@/auth';
|
| 4 |
import prisma from './prisma';
|
| 5 |
import { ChatWithMessages, MessageRaw } from './types';
|
|
|
|
|
|
|
| 6 |
|
| 7 |
/**
|
| 8 |
* Finds or creates a user in the database based on the provided email and name.
|
|
@@ -45,6 +47,7 @@ export async function dbGetAllChat(): Promise<ChatWithMessages[]> {
|
|
| 45 |
where: { userId },
|
| 46 |
include: {
|
| 47 |
messages: true,
|
|
|
|
| 48 |
},
|
| 49 |
});
|
| 50 |
}
|
|
@@ -89,7 +92,7 @@ export async function dbPostCreateChat({
|
|
| 89 |
}
|
| 90 |
: {};
|
| 91 |
try {
|
| 92 |
-
|
| 93 |
data: {
|
| 94 |
id,
|
| 95 |
mediaUrl: mediaUrl,
|
|
@@ -105,6 +108,9 @@ export async function dbPostCreateChat({
|
|
| 105 |
messages: true,
|
| 106 |
},
|
| 107 |
});
|
|
|
|
|
|
|
|
|
|
| 108 |
} catch (error) {
|
| 109 |
console.error(error);
|
| 110 |
}
|
|
@@ -139,7 +145,11 @@ export async function dbPostCreateMessage(chatId: string, message: MessageRaw) {
|
|
| 139 |
}
|
| 140 |
|
| 141 |
export async function dbDeleteChat(chatId: string) {
|
| 142 |
-
|
| 143 |
where: { id: chatId },
|
| 144 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
}
|
|
|
|
| 3 |
import { sessionUser } from '@/auth';
|
| 4 |
import prisma from './prisma';
|
| 5 |
import { ChatWithMessages, MessageRaw } from './types';
|
| 6 |
+
import { revalidatePath } from 'next/cache';
|
| 7 |
+
import { redirect } from 'next/navigation';
|
| 8 |
|
| 9 |
/**
|
| 10 |
* Finds or creates a user in the database based on the provided email and name.
|
|
|
|
| 47 |
where: { userId },
|
| 48 |
include: {
|
| 49 |
messages: true,
|
| 50 |
+
user: true,
|
| 51 |
},
|
| 52 |
});
|
| 53 |
}
|
|
|
|
| 92 |
}
|
| 93 |
: {};
|
| 94 |
try {
|
| 95 |
+
const response = await prisma.chat.create({
|
| 96 |
data: {
|
| 97 |
id,
|
| 98 |
mediaUrl: mediaUrl,
|
|
|
|
| 108 |
messages: true,
|
| 109 |
},
|
| 110 |
});
|
| 111 |
+
|
| 112 |
+
revalidatePath('/chat', 'layout');
|
| 113 |
+
return response;
|
| 114 |
} catch (error) {
|
| 115 |
console.error(error);
|
| 116 |
}
|
|
|
|
| 145 |
}
|
| 146 |
|
| 147 |
export async function dbDeleteChat(chatId: string) {
|
| 148 |
+
await prisma.chat.delete({
|
| 149 |
where: { id: chatId },
|
| 150 |
});
|
| 151 |
+
|
| 152 |
+
revalidatePath('/chat', 'layout');
|
| 153 |
+
|
| 154 |
+
return;
|
| 155 |
}
|
lib/hooks/useChatWithMedia.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
| 1 |
-
import { useChat, type Message } from 'ai/react';
|
| 2 |
-
import { toast } from 'react-hot-toast';
|
| 3 |
-
import { useEffect, useState } from 'react';
|
| 4 |
-
import { MediaDetails } from '../fetch';
|
| 5 |
-
import { MessageWithSelectedDataset } from '../types';
|
| 6 |
-
|
| 7 |
-
const useChatWithMedia = (mediaList: MediaDetails[]) => {
|
| 8 |
-
const {
|
| 9 |
-
messages,
|
| 10 |
-
append,
|
| 11 |
-
reload,
|
| 12 |
-
stop,
|
| 13 |
-
isLoading,
|
| 14 |
-
input,
|
| 15 |
-
setInput,
|
| 16 |
-
setMessages,
|
| 17 |
-
} = useChat({
|
| 18 |
-
sendExtraMessageFields: true,
|
| 19 |
-
onResponse(response) {
|
| 20 |
-
if (response.status !== 200) {
|
| 21 |
-
toast.error(response.statusText);
|
| 22 |
-
}
|
| 23 |
-
},
|
| 24 |
-
initialMessages: [
|
| 25 |
-
{
|
| 26 |
-
id: 'system',
|
| 27 |
-
content: `For the full conversation, user have provided the following images: ${mediaList.map(media => media.name)}. Please help reply to user regarding these images`,
|
| 28 |
-
dataset: mediaList,
|
| 29 |
-
role: 'system',
|
| 30 |
-
},
|
| 31 |
-
] as MessageWithSelectedDataset[],
|
| 32 |
-
});
|
| 33 |
-
|
| 34 |
-
const [loadingDots, setLoadingDots] = useState('');
|
| 35 |
-
|
| 36 |
-
useEffect(() => {
|
| 37 |
-
let loadingInterval: NodeJS.Timeout;
|
| 38 |
-
|
| 39 |
-
if (isLoading) {
|
| 40 |
-
loadingInterval = setInterval(() => {
|
| 41 |
-
setLoadingDots(prevMessage => {
|
| 42 |
-
switch (prevMessage) {
|
| 43 |
-
case '':
|
| 44 |
-
return '.';
|
| 45 |
-
case '.':
|
| 46 |
-
return '..';
|
| 47 |
-
case '..':
|
| 48 |
-
return '...';
|
| 49 |
-
case '...':
|
| 50 |
-
return '';
|
| 51 |
-
default:
|
| 52 |
-
return '';
|
| 53 |
-
}
|
| 54 |
-
});
|
| 55 |
-
}, 500);
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
return () => {
|
| 59 |
-
clearInterval(loadingInterval);
|
| 60 |
-
};
|
| 61 |
-
}, [isLoading]);
|
| 62 |
-
|
| 63 |
-
const assistantLoadingMessage = {
|
| 64 |
-
id: 'loading',
|
| 65 |
-
content: loadingDots,
|
| 66 |
-
role: 'assistant',
|
| 67 |
-
};
|
| 68 |
-
|
| 69 |
-
const messageWithLoading =
|
| 70 |
-
isLoading &&
|
| 71 |
-
messages.length &&
|
| 72 |
-
messages[messages.length - 1].role !== 'assistant'
|
| 73 |
-
? [...messages, assistantLoadingMessage]
|
| 74 |
-
: messages;
|
| 75 |
-
|
| 76 |
-
return {
|
| 77 |
-
messages: messageWithLoading as MessageWithSelectedDataset[],
|
| 78 |
-
append,
|
| 79 |
-
reload,
|
| 80 |
-
stop,
|
| 81 |
-
isLoading,
|
| 82 |
-
input,
|
| 83 |
-
setInput,
|
| 84 |
-
};
|
| 85 |
-
};
|
| 86 |
-
|
| 87 |
-
export default useChatWithMedia;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lib/types.ts
CHANGED
|
@@ -1,42 +1,11 @@
|
|
| 1 |
import { type Message } from 'ai';
|
| 2 |
|
| 3 |
-
export type ServerActionResult<Result> = Promise<
|
| 4 |
-
| Result
|
| 5 |
-
| {
|
| 6 |
-
error: string;
|
| 7 |
-
}
|
| 8 |
-
>;
|
| 9 |
-
|
| 10 |
-
/**
|
| 11 |
-
* @deprecated
|
| 12 |
-
*/
|
| 13 |
-
export type DatasetImageEntity = {
|
| 14 |
-
url: string;
|
| 15 |
-
selected?: boolean;
|
| 16 |
-
name: string;
|
| 17 |
-
};
|
| 18 |
-
|
| 19 |
-
/**
|
| 20 |
-
* @deprecated
|
| 21 |
-
*/
|
| 22 |
-
export type MessageWithSelectedDataset = Message & {
|
| 23 |
-
dataset: DatasetImageEntity[];
|
| 24 |
-
};
|
| 25 |
-
|
| 26 |
export type MessageBase = {
|
| 27 |
role: Message['role'];
|
| 28 |
content: string;
|
| 29 |
id: string;
|
| 30 |
};
|
| 31 |
|
| 32 |
-
export type ChatEntity = {
|
| 33 |
-
url: string;
|
| 34 |
-
id?: string; // a chat without id is not to be saved
|
| 35 |
-
user: string; // email
|
| 36 |
-
messages: MessageBase[];
|
| 37 |
-
updatedAt: number;
|
| 38 |
-
};
|
| 39 |
-
|
| 40 |
export interface SignedPayload {
|
| 41 |
id: string;
|
| 42 |
publicUrl: string;
|
|
|
|
| 1 |
import { type Message } from 'ai';
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
export type MessageBase = {
|
| 4 |
role: Message['role'];
|
| 5 |
content: string;
|
| 6 |
id: string;
|
| 7 |
};
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
export interface SignedPayload {
|
| 10 |
id: string;
|
| 11 |
publicUrl: string;
|
next.config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
/** @type {import('next').NextConfig} */
|
| 2 |
|
| 3 |
module.exports = {
|
|
|
|
| 4 |
images: {
|
| 5 |
remotePatterns: [
|
| 6 |
{
|
|
|
|
| 1 |
/** @type {import('next').NextConfig} */
|
| 2 |
|
| 3 |
module.exports = {
|
| 4 |
+
// reactStrictMode: false,
|
| 5 |
images: {
|
| 6 |
remotePatterns: [
|
| 7 |
{
|
prisma/migrations/20240524012008_init/migration.sql
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- CreateEnum
|
| 2 |
+
CREATE TYPE "MessageRole" AS ENUM ('system', 'user', 'assistant');
|
| 3 |
+
|
| 4 |
+
-- CreateTable
|
| 5 |
+
CREATE TABLE "user" (
|
| 6 |
+
"id" TEXT NOT NULL,
|
| 7 |
+
"name" TEXT NOT NULL,
|
| 8 |
+
"email" TEXT NOT NULL,
|
| 9 |
+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 10 |
+
"updated_at" TIMESTAMP(3) NOT NULL,
|
| 11 |
+
|
| 12 |
+
CONSTRAINT "user_pkey" PRIMARY KEY ("id")
|
| 13 |
+
);
|
| 14 |
+
|
| 15 |
+
-- CreateTable
|
| 16 |
+
CREATE TABLE "chat" (
|
| 17 |
+
"id" TEXT NOT NULL,
|
| 18 |
+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 19 |
+
"updated_at" TIMESTAMP(3) NOT NULL,
|
| 20 |
+
"userId" TEXT,
|
| 21 |
+
"mediaUrl" TEXT NOT NULL,
|
| 22 |
+
|
| 23 |
+
CONSTRAINT "chat_pkey" PRIMARY KEY ("id")
|
| 24 |
+
);
|
| 25 |
+
|
| 26 |
+
-- CreateTable
|
| 27 |
+
CREATE TABLE "message" (
|
| 28 |
+
"id" TEXT NOT NULL,
|
| 29 |
+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 30 |
+
"updated_at" TIMESTAMP(3) NOT NULL,
|
| 31 |
+
"userId" TEXT,
|
| 32 |
+
"chatId" TEXT NOT NULL,
|
| 33 |
+
"content" TEXT NOT NULL,
|
| 34 |
+
"role" "MessageRole" NOT NULL,
|
| 35 |
+
|
| 36 |
+
CONSTRAINT "message_pkey" PRIMARY KEY ("id")
|
| 37 |
+
);
|
| 38 |
+
|
| 39 |
+
-- CreateIndex
|
| 40 |
+
CREATE UNIQUE INDEX "user_email_key" ON "user"("email");
|
| 41 |
+
|
| 42 |
+
-- AddForeignKey
|
| 43 |
+
ALTER TABLE "chat" ADD CONSTRAINT "chat_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
| 44 |
+
|
| 45 |
+
-- AddForeignKey
|
| 46 |
+
ALTER TABLE "message" ADD CONSTRAINT "message_chatId_fkey" FOREIGN KEY ("chatId") REFERENCES "chat"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
| 47 |
+
|
| 48 |
+
-- AddForeignKey
|
| 49 |
+
ALTER TABLE "message" ADD CONSTRAINT "message_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
prisma/migrations/migration_lock.toml
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Please do not edit this file manually
|
| 2 |
+
# It should be added in your version-control system (i.e. Git)
|
| 3 |
+
provider = "postgresql"
|