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" | ||||
|           > | ||||
|             {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