Skip to content

Commit

Permalink
integrate update & delete conversation
Browse files Browse the repository at this point in the history
  • Loading branch information
adnanfajlur committed Oct 13, 2024
1 parent c07d8c5 commit 8a9c1f8
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 59 deletions.
13 changes: 9 additions & 4 deletions app/routes/api/completion.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
})
} else {
conversation = await prisma.conversation.findFirst({
where: { id: conversationId, userId: user.id },
where: { id: conversationId, userId: user.id, deletedAt: null },
include: conversationInclude,
})
}
Expand All @@ -60,7 +60,12 @@ export async function action({ request, params }: ActionFunctionArgs) {
enqueue('user-msg', { message: userMessage })

const messages: { role: string; content: string }[] = [
{ role: 'system', content: 'You are a helpful assistant. Always respond using Markdown formatting like chatgpt does. Use language from the conversation or source' },
{
role: 'system',
content: `You are a helpful assistant. Always respond using Markdown formatting like chatgpt does. Use language from the conversation or source. Current date: ${
new Date().toLocaleDateString()
}`,
},
...conversation.messages.map((msg) => ({ role: msg.sender, content: msg.content })),
{ role: 'user', content },
]
Expand Down Expand Up @@ -100,12 +105,12 @@ export async function action({ request, params }: ActionFunctionArgs) {
const title = generatedTitle.choices[0]?.message.content?.trim() || content

await prisma.conversation.update({
where: { id: conversation.id, userId: user.id },
where: { id: conversation.id, userId: user.id, deletedAt: null },
data: { title, updatedAt: new Date() },
})
} else {
await prisma.conversation.update({
where: { id: conversation.id, userId: user.id },
where: { id: conversation.id, userId: user.id, deletedAt: null },
data: { updatedAt: new Date() },
})
}
Expand Down
49 changes: 39 additions & 10 deletions app/routes/conversation-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
import { Box, BoxComponentProps, Loader, Menu } from '@mantine/core'
import { useSetState } from '@mantine/hooks'
import { Conversation } from '@prisma/client'
import { Link, useParams } from '@remix-run/react'
import { IconDots, IconEdit, IconPencil, IconShare, IconTrash, IconUpload } from '@tabler/icons-react'
import { IconDots, IconPencil, IconTrash } from '@tabler/icons-react'
import { ConversationType } from '~/types'
import { cn } from '~/utils/cn'

type ConversationMenuProps = Omit<BoxComponentProps, 'children'> & {
conversationId: string
title: string
conversation: ConversationType
onUpdate?: (conversation: ConversationType) => void
onDelete?: (conversation: ConversationType) => void
onClick?: () => void
}

export function ConversationMenu({ conversationId, title, className, ...props }: ConversationMenuProps) {
export function ConversationMenu({
conversation,
className,
onUpdate,
onDelete,
onClick,
...props
}: ConversationMenuProps) {
const params = useParams()

const activeConversationId = params['conversation-id']

const [state, setState] = useSetState({
isMenuOpened: false,
})

const isActive = conversationId === activeConversationId
const isActive = conversation.id === activeConversationId

return (
<Box
component={Link}
to={`/c/${conversationId}`}
to={`/c/${conversation.id}`}
{...props}
onClick={onClick}
className={cn(
'group relative rounded-lg hover:bg-dark-5 cursor-pointer text-sm text-white h-9',
{ 'bg-dark-5': state.isMenuOpened || isActive },
Expand All @@ -32,7 +45,7 @@ export function ConversationMenu({ conversationId, title, className, ...props }:
>
<div className="flex items-center gap-2 px-[11px] py-2">
<div className="relative grow overflow-hidden whitespace-nowrap flex items-center">
{title || <Loader size="xs" type="dots" color="white" className="ml-1" />}
{conversation.title || <Loader size="xs" type="dots" color="white" className="ml-1" />}
<div
className={cn('absolute bottom-0 top-0 right-0 to-transparent bg-gradient-to-l from-dark-8 from-0% w-8 group-hover:w-10 group-hover:from-60% group-hover:from-dark-5', {
'w-10 from-60% from-dark-5': state.isMenuOpened || isActive,
Expand All @@ -58,9 +71,25 @@ export function ConversationMenu({ conversationId, title, className, ...props }:
</div>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item leftSection={<IconUpload size={16} />}>Share</Menu.Item>
<Menu.Item leftSection={<IconPencil size={16} />}>Rename</Menu.Item>
<Menu.Item leftSection={<IconTrash size={16} />} color="red">Delete</Menu.Item>
<Menu.Item
leftSection={<IconPencil size={16} />}
onClick={(e) => {
e.preventDefault()
onUpdate && onUpdate(conversation)
}}
>
Rename
</Menu.Item>
<Menu.Item
leftSection={<IconTrash size={16} />}
color="red"
onClick={(e) => {
e.preventDefault()
onDelete && onDelete(conversation)
}}
>
Delete
</Menu.Item>
</Menu.Dropdown>
</Menu>
</Box>
Expand Down
66 changes: 42 additions & 24 deletions app/routes/conversation/conversation.route.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { ActionIcon, Card, Container, convertCssVariables, convertHsvaTo, Loader, Paper, ScrollArea, Text, Textarea } from '@mantine/core'
import { ActionIcon, Card, Container, Loader, ScrollArea, Text, Textarea } from '@mantine/core'
import { useMounted, useSetState } from '@mantine/hooks'
import { notifications } from '@mantine/notifications'
import { Conversation, Message } from '@prisma/client'
import { ActionFunctionArgs, json, LoaderFunctionArgs, type MetaFunction, redirect } from '@remix-run/node'
import { ActionFunctionArgs, json, LoaderFunctionArgs, type MetaFunction } from '@remix-run/node'
import { useFetcher, useLoaderData, useNavigate, useRevalidator } from '@remix-run/react'
import { IconArrowUp, IconBrandReact } from '@tabler/icons-react'
import { useEffect, useMemo } from 'react'
import { useEffect } from 'react'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import * as yup from 'yup'
import { getUserSession } from '~/handlers'
import { openai } from '~/libs/openai.server'
import { prisma } from '~/libs/prisma.server'
import { yupAction } from '~/utils/yup-action'
import { ConversationWithMessagesType, MessageType } from '~/types'
import classes from './conversation.route.module.css'

const CONVERSATION_SUGGESTIONS = [
Expand All @@ -22,16 +20,6 @@ const CONVERSATION_SUGGESTIONS = [
'Summarize a long document',
]

const createConversationSchema = yup.object({
content: yup.string().required(),
}).noUnknown()

type MessageType = Pick<Message, 'id' | 'content' | 'sender' | 'createdAt'>

type ConversationType = Conversation & {
messages: MessageType[]
}

export const meta: MetaFunction = () => {
return [
{ title: 'Chat - Remix workshop' },
Expand All @@ -41,13 +29,11 @@ export const meta: MetaFunction = () => {
export async function loader({ request, params }: LoaderFunctionArgs) {
const { user } = await getUserSession(request)

let conversation: ConversationType & {
isFresh?: boolean
} | null = null
let conversation: ConversationWithMessagesType | null = null

if (params['conversation-id']) {
conversation = await prisma.conversation.findFirst({
where: { id: params['conversation-id'], userId: user.id },
where: { id: params['conversation-id'], userId: user.id, deletedAt: null },
include: {
messages: {
orderBy: {
Expand All @@ -56,13 +42,45 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
},
},
})
}

return { conversation }
}

export async function action({ request, params }: ActionFunctionArgs) {
const { user } = await getUserSession(request)

const conversationId = params['conversation-id']

if (conversation) {
conversation.isFresh = conversation.messages.length === 1
if (conversationId) {
const formData = await request.formData()
const intent = formData.get('intent') as 'update' | 'delete'

if (intent === 'update') {
const title = formData.get('title') as string
if (!title) {
return json({ message: 'Title is required' }, { status: 400 })
}

await prisma.conversation.update({
where: { id: conversationId, userId: user.id, deletedAt: null },
data: { title },
})

return json({ success: true })
}

if (intent === 'delete') {
await prisma.conversation.update({
where: { id: conversationId, userId: user.id, deletedAt: null },
data: { deletedAt: new Date() },
})

return json({ success: true })
}
}

return { conversation }
return json({ message: 'Action not found' }, { status: 400 })
}

export default function ChatRoute() {
Expand Down Expand Up @@ -185,7 +203,7 @@ export default function ChatRoute() {
? (
<Container size="sm" className="w-full grow flex flex-col items-center justify-center">
<IconBrandReact size={52} stroke={1.4} />
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-10">
<div className="grid grid-cols-2 gap-4 mt-10">
{CONVERSATION_SUGGESTIONS.map((suggestion) => (
<Card
withBorder
Expand Down
Loading

0 comments on commit 8a9c1f8

Please sign in to comment.