diff --git a/py_server/handlers/chat.py b/py_server/handlers/chat.py index 2cb0571..4a6f469 100644 --- a/py_server/handlers/chat.py +++ b/py_server/handlers/chat.py @@ -41,8 +41,9 @@ async def chat_extension_handler(body: ChatBody): messages = [ - SystemMessagePromptTemplate.from_template("""You are PageAssist bot. Use the following pieces of context from this webpage to answer the question at the end. + SystemMessagePromptTemplate.from_template("""You are PageAssist bot. Use the following pieces of context from this webpage to answer the question from the user. If you don't know the answer, just say you don't know. DO NOT try to make up an answer. +If user want recommendation, helping based on the context then help the user. If the question is not related to the context, politely respond that you are tuned to only answer questions that are related to the context. Helpful answer in markdown: ----------------- {context} diff --git a/src/components/Chat/ChatBox.tsx b/src/components/Chat/ChatBox.tsx new file mode 100644 index 0000000..abcd94a --- /dev/null +++ b/src/components/Chat/ChatBox.tsx @@ -0,0 +1,170 @@ +import { TrashIcon } from "@heroicons/react/24/outline"; +import Link from "next/link"; +import React from "react"; +import { iconUrl } from "~/utils/icon"; + +type Message = { + isBot: boolean; + message: string; +}; + +type History = { + bot_response: string; + human_message: string; +}; + +type Props = { + title: string | null; + id: string; + created_at: Date | null; + icon: string | null; + url: string | null; +}; + +export const CahtBox = (props: Props) => { + const [messages, setMessages] = React.useState([ + { + isBot: true, + message: "Hi, I'm PageAssist Bot. How can I help you?", + }, + ]); + + const [history, setHistory] = React.useState([]); + const divRef = React.useRef(null); + + React.useEffect(() => { + //@ts-ignore + divRef.current.scrollIntoView({ behavior: "smooth" }); + }); + + return ( +
+ {/* header */} +
+ +
+ +
+
+

+ {props?.title && props?.title?.length > 100 + ? props?.title?.slice(0, 100) + "..." + : props?.title} +

+

+ {props.url && new URL(props.url).hostname} +

+
+ + +
+ +
+
+ {/* */} +
+
+ {messages.map((message, index) => { + return ( +
+
+
+

+ {/* {message.message} */} + {message.message} +

+
+
+
+ ); + })} +
+
+
+
+
{ + // setMessages([...messages, values]) + // form.reset() + // await sendToBotAsync(values.message) + // })} + > +
+
+ + + +
+ +
+
+
+
+
+
+ ); +}; diff --git a/src/components/Chat/index.tsx b/src/components/Chat/index.tsx new file mode 100644 index 0000000..3129308 --- /dev/null +++ b/src/components/Chat/index.tsx @@ -0,0 +1,26 @@ +import { useRouter } from "next/router"; +import React from "react"; +import { api } from "~/utils/api"; +import { CahtBox } from "./ChatBox"; + +export const DashboardChatBody = () => { + const router = useRouter(); + + const { id } = router.query; + + const { data: chat, status } = api.chat.getChatById.useQuery( + { id: id as string }, + { + onError: (err) => { + router.push("/dashboard"); + }, + } + ); + + return ( +
+ {status === "loading" &&
Loading...
} + {status === "success" && } +
+ ); +}; diff --git a/src/components/Dashboard/index.tsx b/src/components/Dashboard/index.tsx index ffb2f7d..89d8e86 100644 --- a/src/components/Dashboard/index.tsx +++ b/src/components/Dashboard/index.tsx @@ -3,27 +3,13 @@ import Empty from "./Empty"; import Loading from "./Loading"; import { api } from "~/utils/api"; import { ChevronRightIcon } from "@heroicons/react/24/outline"; +import Link from "next/link"; +import { iconUrl } from "~/utils/icon"; export default function DashboardBoby() { const { data: savedSites, status } = api.chat.getSavedSitesForChat.useQuery(); - const iconUrl = (icon: string, url: string) => { - // check if icon is valid url (http:// or https://) - if (icon.startsWith("http://") || icon.startsWith("https://")) { - return icon; - } - - // check if icon is valid url (//) - if (icon.startsWith("//")) { - return `https:${icon}`; - } - - const host = new URL(url).hostname; - const protocol = new URL(url).protocol; - - return `${protocol}//${host}/${icon}`; - }; - + return ( <> {status === "loading" && } @@ -31,7 +17,8 @@ export default function DashboardBoby() { {status === "success" && savedSites.data.length > 0 && (
{savedSites.data.map((site, idx) => ( -
@@ -65,7 +52,7 @@ export default function DashboardBoby() {
- + ))} )} diff --git a/src/components/Layouts/DashboardLayout.tsx b/src/components/Layouts/DashboardLayout.tsx index 2d61631..6131e96 100644 --- a/src/components/Layouts/DashboardLayout.tsx +++ b/src/components/Layouts/DashboardLayout.tsx @@ -7,13 +7,18 @@ import { XMarkIcon, } from "@heroicons/react/24/outline"; import { ChevronDownIcon } from "@heroicons/react/20/solid"; -import { useUser } from "@supabase/auth-helpers-react"; +import { useSupabaseClient, useUser } from "@supabase/auth-helpers-react"; import { useRouter } from "next/router"; import Link from "next/link"; const navigation = [ { name: "Home", href: "/dashboard", icon: HomeIcon, current: true }, - { name: "Settings", href: "/dashboard/settings", icon: CogIcon, current: false }, + { + name: "Settings", + href: "/dashboard/settings", + icon: CogIcon, + current: false, + }, ]; //@ts-ignore @@ -29,6 +34,7 @@ export default function DashboardLayout({ const [sidebarOpen, setSidebarOpen] = useState(false); const user = useUser(); const router = useRouter(); + const supabase = useSupabaseClient(); return ( <> @@ -127,13 +133,9 @@ export default function DashboardLayout({ - -
@@ -145,7 +147,7 @@ export default function DashboardLayout({ >
{navigation.map((item) => ( - + ))}
@@ -217,28 +221,31 @@ export default function DashboardLayout({ {({ active }) => ( - Settings - + )} {({ active }) => ( - { + await supabase.auth.signOut(); + router.push("/"); + }} className={classNames( active ? "bg-gray-100" : "", "block px-4 py-2 text-sm text-gray-700" )} > Logout - +
)} diff --git a/src/pages/dashboard/chat/[id]/index.tsx b/src/pages/dashboard/chat/[id]/index.tsx new file mode 100644 index 0000000..ba13b35 --- /dev/null +++ b/src/pages/dashboard/chat/[id]/index.tsx @@ -0,0 +1,38 @@ +import { createServerSupabaseClient } from "@supabase/auth-helpers-nextjs"; +import { GetServerSideProps, NextPage } from "next"; +import Head from "next/head"; +import { DashboardChatBody } from "~/components/Chat"; +import DashboardLayout from "~/components/Layouts/DashboardLayout"; + +export const getServerSideProps: GetServerSideProps = async (ctx) => { + const supabase = createServerSupabaseClient(ctx); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return { + redirect: { + destination: "/auth", + permanent: false, + }, + }; + } + + return { + props: {}, + }; +}; + +const DashboardChatPage: NextPage = () => { + return ( + + + Chat / PageAssist + + + + ); +}; + +export default DashboardChatPage; diff --git a/src/server/api/routers/chat.ts b/src/server/api/routers/chat.ts index 0071d17..327d1b8 100644 --- a/src/server/api/routers/chat.ts +++ b/src/server/api/routers/chat.ts @@ -15,19 +15,6 @@ export const chatRouter = createTRPCRouter({ }); } - const isUserExist = await prisma.user.findFirst({ - where: { - id: user.id, - }, - }); - - if (!isUserExist) { - throw new TRPCError({ - "code": "UNAUTHORIZED", - "message": "You are not authorized to access this resource", - }); - } - const sites = await prisma.website.findMany({ where: { user_id: user.id, @@ -48,4 +35,43 @@ export const chatRouter = createTRPCRouter({ length: sites.length, }; }), + + getChatById: publicProcedure.input(z.object({ + id: z.string(), + })).query(async ({ ctx, input }) => { + const user = ctx.user; + const prisma = ctx.prisma; + if (!user) { + throw new TRPCError({ + "code": "UNAUTHORIZED", + "message": "You are not authorized to access this resource", + }); + } + + + const site = await prisma.website.findFirst({ + where: { + id: input.id, + user_id: user.id, + }, + select: { + user_id: false, + id: true, + created_at: true, + html: false, + icon: true, + title: true, + url: true, + }, + }); + + if (!site) { + throw new TRPCError({ + "code": "NOT_FOUND", + "message": "Chat not found", + }); + } + + return site; + }), }); diff --git a/src/utils/icon.ts b/src/utils/icon.ts new file mode 100644 index 0000000..7ad25f1 --- /dev/null +++ b/src/utils/icon.ts @@ -0,0 +1,16 @@ +export const iconUrl = (icon: string, url: string) => { + // check if icon is valid url (http:// or https://) + if (icon.startsWith("http://") || icon.startsWith("https://")) { + return icon; + } + + // check if icon is valid url (//) + if (icon.startsWith("//")) { + return `https:${icon}`; + } + + const host = new URL(url).hostname; + const protocol = new URL(url).protocol; + + return `${protocol}//${host}/${icon}`; + };