new changes

This commit is contained in:
n4ze3m 2023-04-15 18:00:11 +05:30
parent 4efc9e05e0
commit 92cb203503
9 changed files with 324 additions and 31 deletions

View File

@ -12,6 +12,7 @@
"dependencies": {
"@headlessui/react": "^1.7.13",
"@heroicons/react": "^2.0.17",
"@mantine/form": "^6.0.7",
"@prisma/client": "^4.11.0",
"@supabase/auth-helpers-nextjs": "^0.6.0",
"@supabase/auth-helpers-react": "^0.3.1",
@ -24,6 +25,7 @@
"@trpc/next": "^10.18.0",
"@trpc/react-query": "^10.18.0",
"@trpc/server": "^10.18.0",
"axios": "^1.3.5",
"langchain": "^0.0.55",
"next": "^13.2.4",
"react": "18.2.0",

36
pnpm-lock.yaml generated
View File

@ -7,6 +7,9 @@ dependencies:
'@heroicons/react':
specifier: ^2.0.17
version: 2.0.17(react@18.2.0)
'@mantine/form':
specifier: ^6.0.7
version: 6.0.7(react@18.2.0)
'@prisma/client':
specifier: ^4.11.0
version: 4.11.0(prisma@4.11.0)
@ -43,6 +46,9 @@ dependencies:
'@trpc/server':
specifier: ^10.18.0
version: 10.18.0
axios:
specifier: ^1.3.5
version: 1.3.5
langchain:
specifier: ^0.0.55
version: 0.0.55(@supabase/supabase-js@2.15.0)
@ -244,6 +250,16 @@ packages:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
/@mantine/form@6.0.7(react@18.2.0):
resolution: {integrity: sha512-5fApVmV9gqqh0h04KkeLzZ79LCBMt91Nydj/h1uzFNluiIxcLCpN/VrOvqiG9DbvIi6h6yeBP/7rbZheHjuXgg==}
peerDependencies:
react: '>=16.8.0'
dependencies:
fast-deep-equal: 3.1.3
klona: 2.0.6
react: 18.2.0
dev: false
/@next/env@13.2.4:
resolution: {integrity: sha512-+Mq3TtpkeeKFZanPturjcXt+KHfKYnLlX6jMLyCrmpq6OOs4i1GqBOAauSkii9QeKCMTYzGppar21JU57b/GEA==}
dev: false
@ -969,6 +985,16 @@ packages:
- debug
dev: false
/axios@1.3.5:
resolution: {integrity: sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==}
dependencies:
follow-redirects: 1.15.2
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: false
/axobject-query@3.1.1:
resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==}
dependencies:
@ -1672,7 +1698,6 @@ packages:
/fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
dev: true
/fast-glob@3.2.12:
resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
@ -2220,6 +2245,11 @@ packages:
object.assign: 4.1.4
dev: true
/klona@2.0.6:
resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
engines: {node: '>= 8'}
dev: false
/langchain@0.0.55(@supabase/supabase-js@2.15.0):
resolution: {integrity: sha512-ScL53LvBm2X0rIO1fdMLEoCFYESLVTmY0d71qX7qDrB1y8Y8nCtCA1ZiUNYl4WDQeEvKcvB39qWmAJ2XcB8tqQ==}
engines: {node: '>=18'}
@ -2891,6 +2921,10 @@ packages:
object-assign: 4.1.1
react-is: 16.13.1
/proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: false
/punycode@2.3.0:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'}

View File

@ -23,3 +23,18 @@ class SupaService:
"user_id": user_id
}).execute()
return result
def find_website(self, id: str, user_id: str):
result = self.supabase.table("Website").select("*").eq("id", id).eq("user_id", user_id).execute()
return result
def get_user(self, jwt: str):
try:
result = self.supabase.auth.get_user(jwt)
return result
except:
return None

View File

@ -1,4 +1,4 @@
from models import ChatBody
from models import ChatBody, ChatAppBody
from bs4 import BeautifulSoup
from langchain.docstore.document import Document as LDocument
@ -14,21 +14,43 @@ from langchain.prompts.chat import (
)
from langchain.vectorstores import Chroma
from db.supa import SupaService
async def chat_extension_handler(body: ChatBody):
supabase = SupaService()
async def chat_app_handler(body: ChatAppBody, jwt: str):
try:
soup = BeautifulSoup(body.html, 'lxml')
iframe = soup.find('iframe', id='pageassist-iframe')
if iframe:
iframe.decompose()
div = soup.find('div', id='pageassist-icon')
if div:
div.decompose()
div = soup.find('div', id='__plasmo-loading__')
if div:
div.decompose()
text = soup.get_text()
user = supabase.get_user(jwt)
if not user:
return {
"bot_response": "You are not logged in",
"human_message": body.user_message,
}
user_id = user.user.id
website_response = supabase.find_website(body.id, user_id)
website = website_response.data
if len(website) == 0:
return {
"bot_response": "Website not found",
"human_message": body.user_message,
}
website = website[0]
text = website["html"]
result = [LDocument(page_content=text, metadata={"source": "test"})]
token_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
@ -43,7 +65,76 @@ 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 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 user want recommendation, help from the context, or any other information, please provide it.
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}
"""),
HumanMessagePromptTemplate.from_template("{question}")
]
prompt = ChatPromptTemplate.from_messages(messages)
chat = ConversationalRetrievalChain.from_llm(OpenAI(temperature=0, model_name="gpt-3.5-turbo"), vectorstore.as_retriever(search_kwargs={"k": 1}), return_source_documents=True, qa_prompt=prompt,)
history = [(d["human_message"], d["bot_response"]) for d in body.history]
response = chat({
"question": body.user_message,
"chat_history": history
})
answer = response["answer"]
answer = answer[answer.find(":")+1:].strip()
return {
"bot_response": answer,
"human_message": body.user_message,
}
except Exception as e:
print(e)
return {
"bot_response": "Something went wrong please try again later",
"human_message": body.user_message,
}
async def chat_extension_handler(body: ChatBody):
try:
soup = BeautifulSoup(body.html, 'lxml')
iframe = soup.find('iframe', id='pageassist-iframe')
if iframe:
iframe.decompose()
div = soup.find('div', id='pageassist-icon')
if div:
div.decompose()
div = soup.find('div', id='__plasmo-loading__')
if div:
div.decompose()
text = soup.get_text()
result = [LDocument(page_content=text, metadata={"source": "test"})]
token_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
doc = token_splitter.split_documents(result)
print(f'Number of documents: {len(doc)}')
vectorstore = Chroma.from_documents(doc, OpenAIEmbeddings())
messages = [
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, help from the context, or any other information, please provide it.
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}

View File

@ -1,2 +1,2 @@
from .chat import ChatBody
from .chat import ChatBody, ChatAppBody
from .user import UserValidation, SaveChatToApp

View File

@ -5,3 +5,9 @@ class ChatBody(BaseModel):
html: str
history: list
# url: str
class ChatAppBody(BaseModel):
id: str
user_message: str
url: str
history: list

View File

@ -1,9 +1,14 @@
from fastapi import APIRouter
from models import ChatBody
from handlers.chat import chat_extension_handler
from fastapi import APIRouter, Header
from models import ChatBody, ChatAppBody
from handlers.chat import chat_extension_handler, chat_app_handler
router = APIRouter(prefix="/api/v1")
@router.post("/chat/chrome", tags=["chat"])
async def chat_extension(body: ChatBody):
return await chat_extension_handler(body)
@router.post("/chat/app", tags=["chat"])
async def chat_app(body: ChatAppBody, x_auth_token: str = Header()):
return await chat_app_handler(body, x_auth_token)

View File

@ -1,8 +1,15 @@
import { TrashIcon } from "@heroicons/react/24/outline";
import { useSupabaseClient } from "@supabase/auth-helpers-react";
import Link from "next/link";
import { useRouter } from "next/router";
import React from "react";
import { api } from "~/utils/api";
import { iconUrl } from "~/utils/icon";
import { useForm } from "@mantine/form";
import axios from "axios";
import { useMutation } from "@tanstack/react-query";
type Message = {
isBot: boolean;
message: string;
@ -22,6 +29,51 @@ type Props = {
};
export const CahtBox = (props: Props) => {
const supabase = useSupabaseClient();
const form = useForm({
initialValues: {
message: "",
isBot: false,
},
});
const sendToBot = async (message: string) => {
const { data } = await supabase.auth.getSession();
const response = await axios.post(
`${process.env.NEXT_PUBLIC_PAGEASSIST_URL}/api/v1/chat/app`,
{
user_message: message,
history: history,
url: props.url,
id: props.id,
},
{
headers: {
"X-Auth-Token": data.session?.access_token,
},
}
);
return response.data;
};
const { mutateAsync: sendToBotAsync, isLoading: isSending } = useMutation(
sendToBot,
{
onSuccess: (data) => {
setMessages([...messages, { isBot: true, message: data.bot_response }]);
setHistory([...history, data]);
},
onError: (error) => {
setMessages([
...messages,
{ isBot: true, message: "Something went wrong" },
]);
},
}
);
const [messages, setMessages] = React.useState<Message[]>([
{
isBot: true,
@ -29,6 +81,12 @@ export const CahtBox = (props: Props) => {
},
]);
// const fetchSession = async () => {
// const {data}= await supabase.auth.getSession();
// data.session?.access_token
// }
const [history, setHistory] = React.useState<History[]>([]);
const divRef = React.useRef(null);
@ -37,6 +95,15 @@ export const CahtBox = (props: Props) => {
divRef.current.scrollIntoView({ behavior: "smooth" });
});
const router = useRouter();
const { mutateAsync: deleteChatByIdAsync, isLoading: isDeleting } =
api.chat.deleteChatById.useMutation({
onSuccess: () => {
router.push("/dashboard");
},
});
return (
<div className="flex flex-col border bg-white">
{/* header */}
@ -67,10 +134,40 @@ export const CahtBox = (props: Props) => {
<div className="flex">
<button
onClick={async () => {
const isOk = confirm(
"Are you sure you want to delete this chat?"
);
if (isOk) {
await deleteChatByIdAsync({
id: props.id,
});
}
}}
disabled={isDeleting}
type="button"
className="inline-flex items-center rounded-full border border-transparent bg-red-600 p-1.5 text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
>
{isDeleting ? (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
className="h-5 w-5 animate-spin fill-white text-white dark:text-gray-600"
viewBox="0 0 100 101"
>
<path
fill="currentColor"
d="M100 50.59c0 27.615-22.386 50.001-50 50.001s-50-22.386-50-50 22.386-50 50-50 50 22.386 50 50zm-90.919 0c0 22.6 18.32 40.92 40.919 40.92 22.599 0 40.919-18.32 40.919-40.92 0-22.598-18.32-40.918-40.919-40.918-22.599 0-40.919 18.32-40.919 40.919z"
></path>
<path
fill="currentFill"
d="M93.968 39.04c2.425-.636 3.894-3.128 3.04-5.486A50 50 0 0041.735 1.279c-2.474.414-3.922 2.919-3.285 5.344.637 2.426 3.12 3.849 5.6 3.484a40.916 40.916 0 0144.131 25.769c.902 2.34 3.361 3.802 5.787 3.165z"
></path>
</svg>
) : (
<TrashIcon className="h-5 w-5" aria-hidden="true" />
)}
</button>
</div>
</div>
@ -107,22 +204,31 @@ export const CahtBox = (props: Props) => {
</div>
);
})}
{isSending && (
<div className="mt-2 flex w-full max-w-xs space-x-3">
<div>
<div className="rounded-r-lg rounded-bl-lg bg-gray-300 p-3">
<p className="text-sm">Hold on, I'm looking...</p>
</div>
</div>
</div>
)}
<div ref={divRef} />
</div>
</div>
<div className="items-center bg-gray-300 px-4 py-4">
<form
// onSubmit={form.onSubmit(async (values) => {
// setMessages([...messages, values])
// form.reset()
// await sendToBotAsync(values.message)
// })}
onSubmit={form.onSubmit(async (values) => {
setMessages([...messages, values]);
form.reset();
await sendToBotAsync(values.message);
})}
>
<div className="flex-grow space-y-6">
<div className="flex">
<span className="mr-3">
<button
// disabled={isSending || isSaving}
disabled={isSending}
onClick={() => {
setHistory([]);
setMessages([
@ -153,12 +259,12 @@ export const CahtBox = (props: Props) => {
</span>
<div className="flex-grow">
<input
// disabled={isSending || isSaving}
disabled={isSending}
className="flex h-10 w-full items-center rounded px-3 text-sm"
type="text"
required
placeholder="Type your message…"
// {...form.getInputProps("message")}
{...form.getInputProps("message")}
/>
</div>
</div>

View File

@ -48,7 +48,6 @@ export const chatRouter = createTRPCRouter({
});
}
const site = await prisma.website.findFirst({
where: {
id: input.id,
@ -74,4 +73,39 @@ export const chatRouter = createTRPCRouter({
return site;
}),
deleteChatById: publicProcedure.input(z.object({
id: z.string(),
})).mutation(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,
},
});
if (!site) {
throw new TRPCError({
"code": "NOT_FOUND",
"message": "Chat not found",
});
}
await prisma.website.delete({
where: {
id: input.id,
},
});
return site;
}),
});