Skip to content
This repository has been archived by the owner on Jan 18, 2025. It is now read-only.

Commit

Permalink
Merge pull request #35 from SvelteShipSolutions/fix/floating-chatbot
Browse files Browse the repository at this point in the history
fix: Floating Chatbot
  • Loading branch information
Niravanaa authored Nov 28, 2024
2 parents a086dba + 6864f1b commit 5fa8df7
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 90 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"leaflet": "^1.9.4",
"leaflet-routing-machine": "^3.2.12",
"nodemailer": "^6.9.12",
"openai": "^4.72.0",
"openai": "^4.73.0",
"stripe": "^17.3.1",
"svelte-chartjs": "^3.1.5",
"svelteship-soen-343": "file:",
Expand Down
2 changes: 1 addition & 1 deletion src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { redirect } from "@sveltejs/kit";
import { validateAndRefreshSession } from "$lib/server/session";
import type { Handle } from "@sveltejs/kit";

const openRoutes = /^(\/|\/auth\/(login|logout|register)|\/api\/stripe\/webhook|\/track(\/.*)?|\/quotation|\/rates-calculator|\/about)$/;
const openRoutes = /^(\/|\/auth\/(login|logout|register)|\/api\/stripe\/webhook|\/track(\/.*)?|\/api\/chatbot|\/quotation|\/rates-calculator\/|\/about|)$/;

export const handle: Handle = async ({ event, resolve }) => {
const session = String(event.cookies.get("SvelteShip-Session") || "");
Expand Down
117 changes: 52 additions & 65 deletions src/lib/components/ChatBox.svelte
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
<script lang="ts">
import { onMount } from "svelte";
import { fade } from "svelte/transition";
// State variables
let userInput = "";
let messages: { sender: "user" | "bot"; text: string }[] = [];
let messages: { sender: "user" | "bot"; text: string }[] = [
{
sender: "bot",
text: "👋 Hello! I'm your friendly chatbot assistant. How can I help you today?",
},
];
let chatBox: HTMLDivElement;
// Determine theme based on system preference
onMount(() => {
const isDarkMode = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
if (isDarkMode) {
document.documentElement.classList.add("dark");
}
});
// Function to send a message
async function sendMessage() {
if (!userInput.trim()) return;
Expand Down Expand Up @@ -62,78 +56,71 @@
</script>

<div
class=" flex min-h-min items-center justify-center py-20 transition-colors duration-300">
class="flex w-full max-w-md flex-col overflow-hidden rounded-lg bg-white shadow-lg dark:bg-surface-800">
<!-- Header -->
<div
class="flex w-full max-w-md flex-col overflow-hidden rounded-lg bg-white shadow-lg dark:bg-gray-800">
<!-- Header without Theme Toggle -->
<div
class="flex items-center justify-between bg-orange-500 px-4 py-2 dark:bg-orange-600">
<h2 class="text-lg font-semibold text-white">ChatBot</h2>
</div>
class="flex items-center justify-between bg-primary-500 px-4 py-2 dark:bg-primary-500">
<h2 class="text-lg font-semibold text-white">ChatBot</h2>
</div>

<!-- Chat Messages -->
<div
bind:this={chatBox}
class="chat-box flex-1 overflow-y-auto bg-gray-50 p-4 dark:bg-gray-700">
{#each messages as message, index}
<!-- Chat Messages -->
<div
bind:this={chatBox}
class="chat-box max-h-[400px] flex-1 overflow-y-auto bg-surface-50 p-4 dark:bg-surface-700">
{#each messages as message, index}
<div
class="mb-3 flex {message.sender === 'user'
? 'justify-end'
: 'justify-start'}"
transition:fade={{ duration: 300, delay: index * 50 }}>
<div
class="mb-3 flex {message.sender === 'user'
? 'justify-end'
: 'justify-start'}"
transition:fade={{ duration: 300, delay: index * 50 }}>
<div
class={`max-w-xs rounded-lg px-4 py-2 shadow ${
message.sender === "user"
? "bg-orange-500 text-white"
: message.text.toLowerCase().includes("error")
? "bg-red-500 text-white"
: "bg-gray-200 text-gray-800 dark:bg-gray-600 dark:text-gray-100"
}`}>
<span class="font-semibold">
{message.sender === "user" ? "You" : "Bot"}:
</span>
<span>{message.text}</span>
</div>
class={`max-w-xs rounded-lg px-4 py-2 shadow ${
message.sender === "user"
? "bg-primary-500 text-white"
: message.text.toLowerCase().includes("error")
? "bg-error-500 text-white"
: "bg-surface-200 text-gray-800 dark:bg-surface-600 dark:text-gray-100"
}`}>
<span class="font-semibold">
{message.sender === "user" ? "You" : "Bot"}:
</span>
<span>{message.text}</span>
</div>
{/each}
</div>
</div>
{/each}
</div>

<!-- Input Area -->
<div class="flex items-center bg-gray-100 px-4 py-3 dark:bg-gray-800">
<input
type="text"
bind:value={userInput}
placeholder="Type your message..."
class="flex-1 rounded-lg border border-gray-300 px-3 py-2 transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-orange-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
on:keydown={(e) => e.key === "Enter" && sendMessage()}
aria-label="Message input" />
<button
on:click={sendMessage}
class="ml-2 rounded-lg bg-orange-500 px-4 py-2 font-semibold text-white transition-colors duration-300 hover:bg-orange-600"
aria-label="Send message">
Send
</button>
</div>
<!-- Input Area -->
<div class="flex items-center bg-surface-100 px-4 py-3 dark:bg-surface-800">
<input
type="text"
bind:value={userInput}
placeholder="Type your message..."
class="flex-1 rounded-lg border border-surface-300 px-3 py-2 transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:border-surface-600 dark:bg-surface-700 dark:text-white"
on:keydown={(e) => e.key === "Enter" && sendMessage()}
aria-label="Message input" />
<button
on:click={sendMessage}
class="ml-2 rounded-lg bg-primary-500 px-4 py-2 font-semibold text-white transition-colors duration-300 hover:bg-primary-600"
aria-label="Send message">
Send
</button>
</div>
</div>

<style>
/* Custom Scrollbar */
.chat-box {
scrollbar-width: thin;
scrollbar-color: #ffa500 #2d2d2d;
scrollbar-color: rgba(var(--color-primary-500)) #2d2d2d;
}
.chat-box::-webkit-scrollbar {
width: 8px;
}
.chat-box::-webkit-scrollbar-track {
background: var(--tw-bg-opacity, 1) #f9fafb; /* Adjust based on light/dark */
}
.chat-box::-webkit-scrollbar-thumb {
background-color: #ffa500;
background-color: rgba(var(--color-primary-500));
border-radius: 4px;
}
</style>
69 changes: 63 additions & 6 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
import "../app.postcss";
import { dev } from "$app/environment";
import { inject } from "@vercel/analytics";
import { page } from "$app/stores";
import { writable } from "svelte/store";
import ChatBox from "$lib/components/ChatBox.svelte";
import { fade } from "svelte/transition";
inject({ mode: dev ? "development" : "production" });
import {
Expand Down Expand Up @@ -49,7 +53,6 @@
import ClipboardListIcon from "$lib/icons/ClipboardListIcon.svelte";
import Dollar from "$lib/icons/Dollar.svelte";
import Truck from "$lib/icons/Truck.svelte";
import Message from "$lib/icons/Message.svelte";
import Star from "$lib/icons/Star.svelte";
import { UserRole } from "@prisma/client";
import UserSettingsIcon from "$lib/icons/UserSettingsIcon.svelte";
Expand Down Expand Up @@ -99,18 +102,21 @@
href: "/rates-calculator",
icon: Truck as SvelteComponent,
},
{
name: "Support",
href: "/chatbot",
icon: Message as SvelteComponent,
},
{
name: "Leave a Review",
href: "/review",
icon: Star as SvelteComponent,
},
];
// Store to manage chatbot visibility
const isChatbotOpen = writable(false);
// Function to toggle chatbot
function toggleChatbot() {
isChatbotOpen.update((open) => !open);
}
if (data?.user?.role === UserRole.ADMIN) {
links.push({
name: "Admin",
Expand Down Expand Up @@ -193,3 +199,54 @@
</footer>
</svelte:fragment>
</AppShell>

<!--Check to see if user is signed in-->
{#if data.user}
<!--Add urls you dont want the chatbot to appear on-->
{#if $page.url.pathname !== "/some-excluded-page"}
<!-- Floating Chatbot Button -->
<div class="fixed bottom-6 right-6 z-[9999]">
<button
on:click={toggleChatbot}
class="rounded-full bg-primary-500 p-4 text-white shadow-lg transition-colors duration-300 hover:bg-orange-600"
aria-label="Open Chat">
{#if $isChatbotOpen}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12" />
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
</svg>
{/if}
</button>

<!-- Floating Chatbot Container -->
{#if $isChatbotOpen}
<div
transition:fade={{ duration: 300 }}
class="fixed bottom-24 right-6 z-[9999] w-full max-w-md rounded-lg shadow-2xl">
<ChatBox />
</div>
{/if}
</div>
{/if}
{/if}
10 changes: 8 additions & 2 deletions src/routes/api/chatbot/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ 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 }],
messages: [
{
role: "system",
content:
"You are a customer support chatbot for SvelteShip, a web-based delivery system similar to Canada Post. Assist users with tracking orders, providing shipping information, and answering general questions about SvelteShip services.",
},
{ role: "user", content: message },
],
max_tokens: 2048,
n: 1,
temperature: 0.7,
Expand Down
14 changes: 0 additions & 14 deletions src/routes/chatbot/+page.svelte

This file was deleted.

0 comments on commit 5fa8df7

Please sign in to comment.