Skip to content

"Add chat functionality for students" #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
61124e5
chore: Update npm dependency for @radix-ui/react-hover-card to versio…
guillermoscript Jun 23, 2024
a3f1fbb
feat: Add chat functionality for students
guillermoscript Jun 23, 2024
16dfe69
refactor: Add HoverCard component for UI hover functionality
guillermoscript Jun 23, 2024
d963f1d
feat: Add StudentChatSidebar component for student chat functionality
guillermoscript Jun 23, 2024
d184d46
feat: Add FreeChat component for student chat functionality
guillermoscript Jun 23, 2024
eaec949
feat: Add SuggestionsContainer component for chat suggestions
guillermoscript Jun 23, 2024
8b75029
feat: Add Error component for displaying chat loading errors
guillermoscript Jun 23, 2024
8012af3
refactor: Improve error handling and course authorization in CoursesL…
guillermoscript Jun 23, 2024
195d713
feat: Add FreeChatPage component for student chat functionality
guillermoscript Jun 23, 2024
cba3e7a
feat: Add ChatPage component for student chat page
guillermoscript Jun 23, 2024
227bf72
feat: Add ChatPage component for student chat page
guillermoscript Jun 23, 2024
e745249
feat: Add background color to BigSidebar component
guillermoscript Jun 23, 2024
67e2f5e
refactor: Remove unused imports and optimize code in Chat component
guillermoscript Jun 23, 2024
c3be826
feat: Add supabase integration for chat message handling
guillermoscript Jun 23, 2024
524801d
feat: Add support for chat type and title in chat messages
guillermoscript Jun 23, 2024
ee3de3f
build fix
guillermoscript Jun 23, 2024
4cdf084
removed files
guillermoscript Jun 23, 2024
9d4f12d
feat: Add CoursesLayout component for chat page
guillermoscript Jun 23, 2024
a1a4dd0
refactor: Remove console.log statement in FreeChat component
guillermoscript Jun 23, 2024
41cd1ee
refactor: Update ChatInput component styles
guillermoscript Jun 23, 2024
f653658
refactor: Remove h1 tag from FreeChatPage component
guillermoscript Jun 23, 2024
0441898
refactor: now its a more freindly compone
guillermoscript Jun 23, 2024
6332771
refactor: Update CreateCourse component validation for product_id
guillermoscript Jun 23, 2024
4a5696c
refactor: Update StudentChatSidebar component styles
guillermoscript Jun 23, 2024
2bff92c
refactor: Update CreateCoursePage to order courses by created_at
guillermoscript Jun 23, 2024
47d1ffd
wip
guillermoscript Jun 23, 2024
bd3cc10
partial fix for build, this need to be checked in the future @skapxd
guillermoscript Jun 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions actions/dashboard/chatActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use server'

import { createResponse } from '@/utils/functions'
import { createClient } from '@/utils/supabase/server'
import { Tables } from '@/utils/supabase/supabase'

export async function studentCreateNewChat (state: {
chatType: Tables<'chats'>['chat_type']
title: string
}) {
const supabase = createClient()
const userData = await supabase.auth.getUser()

if (userData.error) {
return createResponse('error', 'Error no user found', null, 'Error no user found')
}

const studentId = userData.data.user?.id

const chatInsert = await supabase.from('chats').insert({
user_id: studentId,
chat_type: state.chatType,
created_at: new Date().toISOString(),
title: state.title
}).select('chat_id').single()

if (chatInsert.error) {
return createResponse('error', 'Error creating chat', null, 'Error creating chat')
}

// return chat id
// revalidatePath('/dashboard/student/chat/', 'layout')
return createResponse('success', 'Chat created successfully', chatInsert.data, null)
}

export async function studentSubmitMessage (state: {
chatId: number
message: string
}) {
const supabase = createClient()
const userData = await supabase.auth.getUser()

if (userData.error) {
return createResponse('error', 'Error no user found', null, 'Error no user found')
}

const studentId = userData.data.user?.id

const messageInsert = await supabase.from('messages').insert({
user_id: studentId,
chat_id: state.chatId,
content: state.message,
created_at: new Date().toISOString()
})

if (messageInsert.error) {
return createResponse('error', 'Error creating message', null, 'Error creating message')
}

return createResponse('success', 'Message sent successfully', null, null)
}
6 changes: 3 additions & 3 deletions actions/dashboard/linkProductToCourse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { createResponse } from '@/utils/functions'
export async function linkProductAction (data: courseSchemaType) {
console.log(data)

if (!data.price || !data.status || !data.course_id) {
return createResponse('error', 'Please fill in all fields', null, null)
}
// if (!data.price || !data.status || !data.course_id) {
// return createResponse('error', 'Please fill in all fields', null, null)
// }

return createResponse('success', 'Product linked successfully', null, null)
}
49 changes: 47 additions & 2 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,58 @@
import { google } from '@ai-sdk/google'
import { StreamingTextResponse, streamText } from 'ai'

import { createClient } from '@/utils/supabase/server'
// Allow streaming responses up to 30 seconds
export const dynamic = 'force-dynamic'
export const maxDuration = 60

export async function POST (req: Request) {
const { messages } = await req.json()
const { messages, chatId } = await req.json()
const supabase = createClient()

const userData = await supabase.auth.getUser()

if (userData.error) {
return Response.redirect('/login')
}

const result = await streamText({
model: google('models/gemini-1.5-pro-latest'),
messages,
temperature: 0
temperature: 0,
async onFinish (event) {
const lastUserMessage = messages[messages.length - 1]

console.log(messages)

console.log(lastUserMessage)

const chat_id = chatId ?? (await supabase.from('chats').select('chat_id').eq('title', lastUserMessage.content).single()).data.chat_id

console.log(chat_id)

const messageInsert = await supabase.from('messages').insert([
{
chat_id,
message: lastUserMessage.content,
created_at: new Date().toISOString(),
sender: 'user'
},
{
chat_id,
message: event.text,
created_at: new Date().toISOString(),
sender: 'assistant'
}

])

if (messageInsert.error) {
console.log('Error creating message', messageInsert.error)
}

console.log('Message sent successfully')
}
})

return new StreamingTextResponse(result.toAIStream())
Expand Down
26 changes: 26 additions & 0 deletions app/dashboard/student/chat/[chatId]/free-chat/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client' // Error components must be Client Components

import { useEffect } from 'react'

import GenericError from '@/components/GenericError'

export default function Error ({
error,
reset
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])

return (
<GenericError
retry={reset}
title="Oh no! An error occurred loading the chat."
description={error.message}
/>
)
}
45 changes: 45 additions & 0 deletions app/dashboard/student/chat/[chatId]/free-chat/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import FreeChat from '@/components/dashboards/student/chat/FreeChat'
import { createClient } from '@/utils/supabase/server'

export const dynamic = 'force-dynamic'
export const maxDuration = 30

export default async function FreeChatPage ({
params
}: {
params: {
chatId: string
}
}) {
const supabase = createClient()

const messagesData = await supabase
.from('messages')
.select('*')
.eq('chat_id', Number(params.chatId))
.order('created_at', { ascending: true })

if (messagesData.error) {
console.log(messagesData.error)
throw new Error('Error fetching messages')
}

return (

<>
<div className='flex flex-col gap-4 overflow-y-auto h-[calc(100vh-4rem)]'>

<FreeChat
chatId={Number(params.chatId)}
initialMessages={
messagesData.data.map(message => ({
id: message.id.toString(),
content: message.message,
role: message.sender
}))
}
/>
</div>
</>
)
}
33 changes: 33 additions & 0 deletions app/dashboard/student/chat/[chatId]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createClient } from '@/utils/supabase/server'

export default async function CoursesLayout ({
children,
params
}: {
children: React.ReactNode
params: {
chatId: string
}
}) {
const supabase = createClient()

const chatData = await supabase
.from('chats')
.select('*')
.eq('chat_id', Number(params.chatId))
.single()

if (chatData.error) {
console.log(chatData.error)
throw new Error('Error fetching chat')
}

return (
<div className='flex flex-col gap-4 p-4'>
<h1 className="text-2xl font-semibold text-gray-800">
{chatData.data.title}
</h1>
{children}
</div>
)
}
34 changes: 34 additions & 0 deletions app/dashboard/student/chat/[chatId]/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* v0 by Vercel.
* @see https://v0.dev/t/vkBhDMemo6s
* Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app
*/
import { Skeleton } from '@/components/ui/skeleton'

export default function Component () {
return (
<div className="grid min-h-screen w-full">

<div className="hidden border-r bg-gray-100/40 lg:block dark:bg-gray-800/40">
<div className="flex h-full flex-col gap-2 max-h-[calc(100vh-6rem)]">
<div className="flex h-[60px] items-center border-b px-6">
<Skeleton className="h-6 w-6 rounded-full" />
<Skeleton className="h-5 w-24 ml-2" />
</div>
<div className="flex-1 overflow-auto py-2">
<nav className="grid gap-2 px-4">
<Skeleton className="h-8 w-full rounded-md" />
<Skeleton className="h-8 w-full rounded-md" />
<Skeleton className="h-8 w-full rounded-md" />
<Skeleton className="h-8 w-full rounded-md" />
<Skeleton className="h-8 w-full rounded-md" />
</nav>
</div>
<div className="mt-auto p-4">
<Skeleton className="h-[125px] w-full rounded-lg" />
</div>
</div>
</div>
</div>
)
}
47 changes: 47 additions & 0 deletions app/dashboard/student/chat/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import StudentChatSidebar from '@/components/dashboards/student/chat/StudentChatSidebar'
import { createClient } from '@/utils/supabase/server'

export default async function CoursesLayout ({
children
}: {
children: React.ReactNode
}) {
const supabase = createClient()
const user = await supabase.auth.getUser()

if (user.error != null) {
throw new Error(user.error.message)
}

const userCourses = await supabase
.from('enrollments')
.select('enrollment_id')
.eq('user_id', user.data.user.id)

const userSubscriptions = await supabase
.from('subscriptions')
.select('subscription_id')
.eq('user_id', user.data.user.id)
.eq('subscription_status', 'active')

if (userSubscriptions.error != null && userCourses.error != null) {
throw new Error(
'Something went wrong while fetching your courses and subscriptions.'
)
}

if (userSubscriptions.data.length === 0 && userCourses.data.length === 0) {
throw new Error('You are not authorized to view this page.')
}

return (
<div className="grid min-h-screen w-full lg:grid-cols-[280px_1fr] gap-6">
<StudentChatSidebar
userRole='student'
/>
<div className="flex flex-col">
{children}
</div>
</div>
)
}
34 changes: 34 additions & 0 deletions app/dashboard/student/chat/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* v0 by Vercel.
* @see https://v0.dev/t/vkBhDMemo6s
* Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app
*/
import { Skeleton } from '@/components/ui/skeleton'

export default function Component () {
return (
<div className="grid min-h-screen w-full">

<div className="hidden border-r bg-gray-100/40 lg:block dark:bg-gray-800/40">
<div className="flex h-full flex-col gap-2 max-h-[calc(100vh-6rem)]">
<div className="flex h-[60px] items-center border-b px-6">
<Skeleton className="h-6 w-6 rounded-full" />
<Skeleton className="h-5 w-24 ml-2" />
</div>
<div className="flex-1 overflow-auto py-2">
<nav className="grid gap-2 px-4">
<Skeleton className="h-8 w-full rounded-md" />
<Skeleton className="h-8 w-full rounded-md" />
<Skeleton className="h-8 w-full rounded-md" />
<Skeleton className="h-8 w-full rounded-md" />
<Skeleton className="h-8 w-full rounded-md" />
</nav>
</div>
<div className="mt-auto p-4">
<Skeleton className="h-[125px] w-full rounded-lg" />
</div>
</div>
</div>
</div>
)
}
Loading
Loading