new changes
This commit is contained in:
parent
4efc9e05e0
commit
92cb203503
@ -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
36
pnpm-lock.yaml
generated
@ -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'}
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -1,2 +1,2 @@
|
||||
from .chat import ChatBody
|
||||
from .chat import ChatBody, ChatAppBody
|
||||
from .user import UserValidation, SaveChatToApp
|
@ -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
|
@ -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)
|
@ -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"
|
||||
>
|
||||
<TrashIcon className="h-5 w-5" aria-hidden="true" />
|
||||
{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>
|
||||
|
@ -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;
|
||||
}),
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user