Skip to content

Commit 858b418

Browse files
Merge pull request #71 from guillermoscript/65-exam-prep-integrate-practice-quizzes-flashcards-and-study-guides
Examn Generation with LLM for students to practices
2 parents c108ff6 + c0071c2 commit 858b418

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+3158
-824
lines changed

actions/dashboard/ExamPreparationActions.tsx

Lines changed: 673 additions & 0 deletions
Large diffs are not rendered by default.

actions/dashboard/chatActions.ts

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use server'
22

3+
import { revalidatePath } from 'next/cache'
4+
35
import { createResponse } from '@/utils/functions'
46
import { createClient } from '@/utils/supabase/server'
57
import { Tables } from '@/utils/supabase/supabase'
@@ -29,11 +31,11 @@ export async function studentCreateNewChat (state: {
2931
}
3032

3133
// return chat id
32-
// revalidatePath('/dashboard/student/chat/', 'layout')
34+
revalidatePath('/dashboard/student/chat/', 'layout')
3335
return createResponse('success', 'Chat created successfully', chatInsert.data, null)
3436
}
3537

36-
export async function studentSubmitMessage (state: {
38+
export async function studentInsertChatMessage (state: {
3739
chatId: number
3840
message: string
3941
}) {
@@ -44,18 +46,134 @@ export async function studentSubmitMessage (state: {
4446
return createResponse('error', 'Error no user found', null, 'Error no user found')
4547
}
4648

47-
const studentId = userData.data.user?.id
49+
const messageInsert = await supabase.from('messages').insert({
50+
chat_id: state.chatId,
51+
message: state.message,
52+
sender: 'user',
53+
created_at: new Date().toISOString()
54+
})
55+
56+
if (messageInsert.error) {
57+
return createResponse('error', 'Error creating message', null, 'Error creating message')
58+
}
59+
60+
return createResponse('success', 'Message sent successfully', null, null)
61+
}
62+
63+
export async function studentUpdateChatTitle (state: {
64+
chatId: number
65+
title: string
66+
}) {
67+
const supabase = createClient()
68+
const userData = await supabase.auth.getUser()
69+
70+
if (userData.error) {
71+
return createResponse('error', 'Error no user found', null, 'Error no user found')
72+
}
73+
74+
const chatUpdate = await supabase.from('chats').update({
75+
title: state.title
76+
}).eq('chat_id', state.chatId)
77+
78+
if (chatUpdate.error) {
79+
return createResponse('error', 'Error updating chat', null, 'Error updating chat')
80+
}
81+
82+
revalidatePath('/dashboard/student/chat/', 'layout')
83+
return createResponse('success', 'Chat updated successfully', null, null)
84+
}
85+
86+
export async function insertChatMessage (state: {
87+
chatId: number
88+
message: string
89+
sender: Tables<'messages'>['sender']
90+
}) {
91+
const supabase = createClient()
92+
const userData = await supabase.auth.getUser()
93+
94+
if (userData.error) {
95+
return createResponse('error', 'Error no user found', null, 'Error no user found')
96+
}
97+
98+
const messageInsert = await supabase.from('messages').insert({
99+
chat_id: state.chatId,
100+
message: state.message,
101+
sender: state.sender,
102+
created_at: new Date().toISOString()
103+
})
104+
105+
if (messageInsert.error) {
106+
return createResponse('error', 'Error creating message', null, 'Error creating message')
107+
}
108+
109+
return createResponse('success', 'Message sent successfully', null, null)
110+
}
111+
112+
export async function studentUpdateChat (state: {
113+
chatId: number
114+
title: string
115+
}) {
116+
const supabase = createClient()
117+
const userData = await supabase.auth.getUser()
118+
119+
if (userData.error) {
120+
return createResponse('error', 'Error no user found', null, 'Error no user found')
121+
}
122+
123+
const chatUpdate = await supabase.from('chats').update({
124+
title: state.title
125+
}).eq('chat_id', state.chatId)
126+
127+
if (chatUpdate.error) {
128+
return createResponse('error', 'Error updating chat', null, 'Error updating chat')
129+
}
130+
131+
// revalidatePath('/dashboard/student/chat/', 'layout')
132+
return createResponse('success', 'Chat updated successfully', null, null)
133+
}
134+
135+
export async function studentSubmitMessage (state: {
136+
chatId: number
137+
message: string
138+
}) {
139+
const supabase = createClient()
140+
const userData = await supabase.auth.getUser()
141+
142+
if (userData.error) {
143+
return createResponse('error', 'Error no user found', null, 'Error no user found')
144+
}
48145

49146
const messageInsert = await supabase.from('messages').insert({
50-
user_id: studentId,
51147
chat_id: state.chatId,
52-
content: state.message,
148+
message: state.message,
149+
sender: 'user',
53150
created_at: new Date().toISOString()
54151
})
55152

56153
if (messageInsert.error) {
154+
console.log(messageInsert.error)
57155
return createResponse('error', 'Error creating message', null, 'Error creating message')
58156
}
59157

60158
return createResponse('success', 'Message sent successfully', null, null)
61159
}
160+
161+
export async function deleteChat (state: {
162+
chatId: number
163+
}) {
164+
const supabase = createClient()
165+
const userData = await supabase.auth.getUser()
166+
167+
if (userData.error) {
168+
return createResponse('error', 'Error no user found', null, 'Error no user found')
169+
}
170+
171+
const chatDelete = await supabase.from('chats').delete().eq('chat_id', state.chatId)
172+
173+
if (chatDelete.error) {
174+
return createResponse('error', 'Error deleting chat', null, 'Error deleting chat')
175+
}
176+
177+
revalidatePath('/dashboard/student/chat/', 'layout')
178+
return createResponse('success', 'Chat deleted successfully', null, null)
179+
}

app/api/chat/route.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ export async function POST (req: Request) {
2323
async onFinish (event) {
2424
const lastUserMessage = messages[messages.length - 1]
2525

26-
console.log(messages)
27-
28-
console.log(lastUserMessage)
29-
3026
const chat_id = chatId ?? (await supabase.from('chats').select('chat_id').eq('title', lastUserMessage.content).single()).data.chat_id
3127

3228
console.log(chat_id)
@@ -50,8 +46,6 @@ export async function POST (req: Request) {
5046
if (messageInsert.error) {
5147
console.log('Error creating message', messageInsert.error)
5248
}
53-
54-
console.log('Message sent successfully')
5549
}
5650
})
5751

app/dashboard/loading.tsx

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,14 @@
1-
/**
2-
* v0 by Vercel.
3-
* @see https://v0.dev/t/vkBhDMemo6s
4-
* Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app
5-
*/
61
import { Skeleton } from '@/components/ui/skeleton'
72

83
export default function Component () {
94
return (
10-
<div className="grid min-h-screen w-full lg:grid-cols-[280px_1fr]">
11-
<div className="hidden border-r bg-gray-100/40 lg:block dark:bg-gray-800/40">
12-
<div className="flex h-full max-h-screen flex-col gap-2">
13-
<div className="flex h-[60px] items-center border-b px-6">
14-
<Skeleton className="h-6 w-6 rounded-full" />
15-
<Skeleton className="h-5 w-24 ml-2" />
16-
</div>
17-
<div className="flex-1 overflow-auto py-2">
18-
<nav className="grid gap-2 px-4">
19-
<Skeleton className="h-8 w-full rounded-md" />
20-
<Skeleton className="h-8 w-full rounded-md" />
21-
<Skeleton className="h-8 w-full rounded-md" />
22-
<Skeleton className="h-8 w-full rounded-md" />
23-
<Skeleton className="h-8 w-full rounded-md" />
24-
</nav>
25-
</div>
26-
<div className="mt-auto p-4">
27-
<Skeleton className="h-[125px] w-full rounded-lg" />
28-
</div>
29-
</div>
30-
</div>
5+
<div className="grid min-h-screen w-full lg:grid-cols-[1fr]">
316
<div className="flex flex-col">
327
<main className="flex-1 p-4 md:p-6">
8+
<div className="flex items-center">
9+
<Skeleton className="h-6 w-40 rounded-md" />
10+
<Skeleton className="h-8 w-20 ml-auto rounded-md" />
11+
</div>
3312
<div className="mt-4 border rounded-lg shadow-sm">
3413
<Skeleton className="h-[200px] w-full rounded-t-lg" />
3514
<div className="p-4 md:p-6">
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use client' // Error components must be Client Components
2+
3+
import { useEffect } from 'react'
4+
5+
import GenericError from '@/components/GenericError'
6+
7+
export default function Error ({
8+
error,
9+
reset
10+
}: {
11+
error: Error & { digest?: string }
12+
reset: () => void
13+
}) {
14+
useEffect(() => {
15+
// Log the error to an error reporting service
16+
console.error(error)
17+
}, [error])
18+
19+
return (
20+
<GenericError
21+
retry={reset}
22+
title="Oh no! An error occurred loading the chat."
23+
description={error.message}
24+
/>
25+
)
26+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { AI, getUIStateFromAIState } from '@/actions/dashboard/ExamPreparationActions'
2+
import { createClient } from '@/utils/supabase/server'
3+
4+
export default async function ExamnChatIdPageLayout ({
5+
params,
6+
children
7+
}: {
8+
params: {
9+
chatId: string
10+
}
11+
children: React.ReactNode
12+
}) {
13+
const supabase = createClient()
14+
15+
const messagesData = await supabase
16+
.from('chats')
17+
.select('*, messages(*)')
18+
.eq('chat_id', Number(params.chatId))
19+
.order('created_at', { foreignTable: 'messages', ascending: true })
20+
.single()
21+
22+
if (messagesData.error) {
23+
console.log(messagesData.error)
24+
throw new Error('Error fetching messages')
25+
}
26+
27+
// WTF!!!! why this need an await?????
28+
const uiState = await getUIStateFromAIState({
29+
id: messagesData.data.chat_id.toString(),
30+
createdAt: new Date(messagesData.data.created_at),
31+
messages: messagesData.data.messages.map((message) => {
32+
if (!message) return null
33+
34+
if (message.sender === 'tool') {
35+
return ({
36+
id: message.id,
37+
role: message.sender as any,
38+
content: JSON.parse(message.message)
39+
})
40+
}
41+
42+
if (message.sender === 'assistant') {
43+
// if the content is a json stringified object then parse it
44+
if (message.message.startsWith('{') || message.message.startsWith('[')) {
45+
return ({
46+
id: message.id,
47+
role: message.sender as any,
48+
content: JSON.parse(message.message)
49+
})
50+
}
51+
return ({
52+
id: message.id,
53+
role: message.sender as any,
54+
content: message.message
55+
})
56+
}
57+
58+
return ({
59+
id: message.id,
60+
role: message.sender as any,
61+
content: message.message
62+
})
63+
}) as any
64+
})
65+
66+
const messsages = messagesData.data.messages.map((message) => {
67+
if (message.sender === 'tool') {
68+
return ({
69+
id: message.id.toString(),
70+
role: message.sender as any,
71+
content: JSON.parse(message.message)
72+
})
73+
}
74+
75+
if (message.sender === 'assistant') {
76+
// if the content is a json stringified object then parse it
77+
if (message.message.startsWith('{') || message.message.startsWith('[')) {
78+
return ({
79+
id: message.id.toString(),
80+
role: message.sender as any,
81+
content: JSON.parse(message.message)
82+
})
83+
}
84+
85+
return ({
86+
id: message.id.toString(),
87+
role: message.sender as any,
88+
content: message.message
89+
})
90+
}
91+
92+
return ({
93+
id: message.id.toString(),
94+
role: message.sender as any,
95+
content: message.message
96+
})
97+
})
98+
99+
return (
100+
<AI
101+
initialUIState={uiState}
102+
initialAIState={{ chatId: (params.chatId), messages: messsages }}
103+
>
104+
{children}
105+
</AI>
106+
)
107+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import ChatLoadingSkeleton from '@/components/dashboards/student/chat/ChatLoadingSkeleton'
2+
3+
export default function Loading () {
4+
return (
5+
<ChatLoadingSkeleton />
6+
)
7+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import ExamPrepChat from '@/components/dashboards/student/chat/ExamPrepChat'
2+
3+
export const dynamic = 'force-dynamic'
4+
export const maxDuration = 30
5+
6+
export default async function ExamnChatIdPage ({
7+
params
8+
}: {
9+
params: {
10+
chatId: string
11+
}
12+
}) {
13+
return (
14+
<ExamPrepChat
15+
chatId={Number(params.chatId)}
16+
/>
17+
)
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import ChatLoadingSkeleton from '@/components/dashboards/student/chat/ChatLoadingSkeleton'
2+
3+
export default function Loading () {
4+
return (
5+
<ChatLoadingSkeleton />
6+
)
7+
}

app/dashboard/student/chat/[chatId]/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default async function CoursesLayout ({
2424

2525
return (
2626
<div className='flex flex-col gap-4 p-4'>
27-
<h1 className="text-2xl font-semibold text-gray-800">
27+
<h1 className="text-2xl font-semibold">
2828
{chatData.data.title}
2929
</h1>
3030
{children}

0 commit comments

Comments
 (0)