From fad0b3f08e52dd997d077db2ab65637878107752 Mon Sep 17 00:00:00 2001 From: Jeremy Vieira <98359007+JeremyVieira@users.noreply.github.com> Date: Fri, 1 Nov 2024 20:44:19 -0400 Subject: [PATCH 01/55] Chatbot and Review Added chatbot at /chatbot and review at /review extensions --- prisma/schema.prisma | 19 ++ src/lib/components/ChatBox.svelte | 66 ++++++ src/routes/api/chatbot/+server.ts | 46 ++++ src/routes/api/review/+server.ts | 113 +++++++++ src/routes/auth/login/+page.server.ts | 2 + src/routes/chatbot/+page.svelte | 9 + src/routes/chatbot/ChatBox.css | 61 +++++ src/routes/review/+page.svelte | 326 ++++++++++++++++++++++++++ src/routes/styles/review-form.css | 293 +++++++++++++++++++++++ 9 files changed, 935 insertions(+) create mode 100644 src/lib/components/ChatBox.svelte create mode 100644 src/routes/api/chatbot/+server.ts create mode 100644 src/routes/api/review/+server.ts create mode 100644 src/routes/chatbot/+page.svelte create mode 100644 src/routes/chatbot/ChatBox.css create mode 100644 src/routes/review/+page.svelte create mode 100644 src/routes/styles/review-form.css diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b3708e6..249bd31 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -25,6 +25,25 @@ model User { updatedAt DateTime @updatedAt } +model Review { + id Int @id @default(autoincrement()) + rating Int + deliveryRating Int + wasDeliveryOnTime Boolean + comment String? + userId Int + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + images Image[] +} + +model Image { + id Int @id @default(autoincrement()) + url String + reviewId Int + review Review @relation(fields: [reviewId], references: [id]) +} + enum UserRole { ADMIN CUSTOMER diff --git a/src/lib/components/ChatBox.svelte b/src/lib/components/ChatBox.svelte new file mode 100644 index 0000000..e88e467 --- /dev/null +++ b/src/lib/components/ChatBox.svelte @@ -0,0 +1,66 @@ + + +
+
+ {#each messages as message} +
+ {message.sender === "user" ? "You" : "Bot"}: {message.text} +
+ {/each} +
+ + + +
diff --git a/src/routes/api/chatbot/+server.ts b/src/routes/api/chatbot/+server.ts new file mode 100644 index 0000000..6e32931 --- /dev/null +++ b/src/routes/api/chatbot/+server.ts @@ -0,0 +1,46 @@ +import OpenAIApi from 'openai'; +import Configuration from 'openai'; +import type { RequestHandler } from '@sveltejs/kit'; + +const configuration = new Configuration({ + apiKey: process.env.OPENAI_API_KEY || 'api-key here' // Use environment variable for security +}); +//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 +//@ts-ignore +const openai = new OpenAIApi(configuration); + +export const POST: RequestHandler = async ({ request }) => { + try { + const { message } = await request.json(); + console.log(message); + const response = await openai.chat.completions.create({ + model: 'gpt-3.5-turbo', + messages: [{ role: 'user', content: message }], + max_tokens: 2048, + n: 1, + temperature: 0.7 + }); + + const botResponse = response.choices[0]?.message?.content?.trim(); + return new Response( + JSON.stringify({ response: botResponse || "No response available" }), + { + headers: { 'Content-Type': 'application/json' }, + status: 200 + } + ); + + } catch (error) { + console.error('Error in OpenAI API request:', error); + return new Response( + JSON.stringify({ + response: 'Sorry, there was an error processing your request.', + error: error.message || 'Unknown error' + }), + { + headers: { 'Content-Type': 'application/json' }, + status: 500 + } + ); + } +}; diff --git a/src/routes/api/review/+server.ts b/src/routes/api/review/+server.ts new file mode 100644 index 0000000..93f227b --- /dev/null +++ b/src/routes/api/review/+server.ts @@ -0,0 +1,113 @@ +import { json, type RequestHandler } from "@sveltejs/kit"; +import { PrismaClient } from "@prisma/client"; +import { parse } from "cookie"; + +const prisma = new PrismaClient(); + +export const POST: RequestHandler = async ({ request }) => { + try { + const formData = await request.formData(); + + // Required fields validation with proper typing + const rating = Number(formData.get("rating")); + const deliveryRating = Number(formData.get("deliveryRating")); + const wasDeliveryOnTime = formData.get("wasDeliveryOnTime") === "true"; + + // Fetch user ID from cookies + const cookies = request.headers.get("cookie"); + const parsedCookies = parse(cookies || ""); + const userIdStr = parsedCookies["userId"]; + // Convert userId to number + const userId = Number(userIdStr); + console.log("userId", userId); + + // Validate required fields + if (isNaN(rating) || rating < 1 || rating > 5) { + return json( + { error: "Invalid rating: must be between 1 and 5" }, + { status: 400 }, + ); + } + + if (isNaN(deliveryRating) || deliveryRating < 1 || deliveryRating > 5) { + return json( + { error: "Invalid delivery rating: must be between 1 and 5" }, + { status: 400 }, + ); + } + + if (isNaN(userId)) { + return json({ error: "Invalid user ID" }, { status: 400 }); + } + + // Optional comment field + const comment = formData.get("comment")?.toString() || ""; + + // Create the review with the schema-matching fields + const review = await prisma.review.create({ + data: { + rating, + deliveryRating, + wasDeliveryOnTime, + comment, + userId, + }, + }); + + // Handle optional image uploads + const imagePromises: Promise[] = []; + let imageIndex = 0; + + for (const [key, value] of formData.entries()) { + if (key.startsWith("image") && value instanceof File) { + const file = value; + + // Validate file type + if (!file.type.startsWith("image/")) { + continue; + } + + // Generate unique filename + const filename = `${Date.now()}-${imageIndex}-${file.name}`; + const filepath = join(process.cwd(), "static", "uploads", filename); + + // Convert file to buffer and save + const arrayBuffer = await file.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + writeFileSync(filepath, buffer); + + // Create image record + imagePromises.push( + prisma.image.create({ + data: { + url: `/uploads/${filename}`, + reviewId: review.id, + }, + }), + ); + imageIndex++; + } + } + + // Process images if any exist + if (imagePromises.length > 0) { + await Promise.all(imagePromises); + } + + // Fetch complete review with images + const completeReview = await prisma.review.findUnique({ + where: { id: review.id }, + include: { images: true }, + }); + + return json(completeReview); + } catch (error) { + console.error("Error creating review:", error); + + if (error instanceof Error) { + return json({ error: error.message }, { status: 500 }); + } + + return json({ error: "Failed to create review" }, { status: 500 }); + } +}; diff --git a/src/routes/auth/login/+page.server.ts b/src/routes/auth/login/+page.server.ts index 51aac76..a0d7318 100644 --- a/src/routes/auth/login/+page.server.ts +++ b/src/routes/auth/login/+page.server.ts @@ -69,6 +69,8 @@ export const actions = { }), { path: "/" }, ); + cookies.set("userId", String(user.id), { path: "/" }); + //make it easier to access userID without decode // Prevent open redirect, only allow destinations starting with / if ((url.searchParams.get("destination") || "").indexOf("/") == 0) { diff --git a/src/routes/chatbot/+page.svelte b/src/routes/chatbot/+page.svelte new file mode 100644 index 0000000..c69abd0 --- /dev/null +++ b/src/routes/chatbot/+page.svelte @@ -0,0 +1,9 @@ + + +
+

My SvelteKit App

+ +
diff --git a/src/routes/chatbot/ChatBox.css b/src/routes/chatbot/ChatBox.css new file mode 100644 index 0000000..837ad66 --- /dev/null +++ b/src/routes/chatbot/ChatBox.css @@ -0,0 +1,61 @@ +body { + font-family: Arial, sans-serif; + background-color: #f7f7f7; + text-align: center; + margin: 0; +} + +.chat-container { + margin-top: 50px; +} + +.chat-box { + height: 400px; + width: 300px; + border: 1px solid #ccc; + margin: 0 auto; + padding: 10px; + background-color: #fff; + overflow-y: scroll; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +#user-input { + width: 200px; + padding: 10px; + margin-top: 10px; + background-color: white; + color: #333; + border: 1px solid #ccc; + border-radius: 4px; +} + +button { + padding: 10px; + margin-left: 5px; + background-color: #007bff; + color: white; + border: none; + cursor: pointer; +} + +button:hover { + background-color: #0056b3; +} + +.message { + text-align: left; + margin: 5px 0; + padding: 5px; + border-radius: 4px; +} + +.message.user { + background-color: #e6f2ff; + color: #333; +} + +.message.bot { + background-color: #f0f0f0; + color: #333; +} diff --git a/src/routes/review/+page.svelte b/src/routes/review/+page.svelte new file mode 100644 index 0000000..bcbaebc --- /dev/null +++ b/src/routes/review/+page.svelte @@ -0,0 +1,326 @@ + + +
+

Rate Your Delivery Experience

+ + {#if feedback.message} + + {/if} + +
+ +
+

Overall Rating

+
+ {#each [1, 2, 3, 4, 5] as star} + + {/each} +
+
+ + +
+

Delivery Service

+
+ {#each [1, 2, 3, 4, 5] as star} + + {/each} +
+
+ + +
+

Was your delivery on time?

+
+ + +
+
+ + +
+

Additional Comments

+