diff --git a/package.json b/package.json
index 8f54ffd..e2456e6 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"@tanstack/react-query": "^5.17.19",
"antd": "^5.13.3",
"axios": "^1.6.7",
+ "html-to-text": "^9.0.5",
"langchain": "^0.1.9",
"plasmo": "0.84.1",
"property-information": "^6.4.1",
@@ -38,6 +39,7 @@
"devDependencies": {
"@plasmohq/prettier-plugin-sort-imports": "4.0.1",
"@types/chrome": "0.0.259",
+ "@types/html-to-text": "^9.0.4",
"@types/node": "20.11.9",
"@types/react": "18.2.48",
"@types/react-dom": "18.2.18",
diff --git a/src/background.ts b/src/background.ts
index 281cbf1..c781a4c 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -1,10 +1,13 @@
export {}
+
chrome.runtime.onMessage.addListener(async (message) => {
- chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
- const tab = tabs[0]
- await chrome.sidePanel.open({
- tabId: tab.id,
+ if (message.type === "sidepanel") {
+ chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
+ const tab = tabs[0]
+ await chrome.sidePanel.open({
+ tabId: tab.id
+ })
})
- })
+ }
})
diff --git a/src/chain/chat-with-website.ts b/src/chain/chat-with-website.ts
new file mode 100644
index 0000000..0d0acf0
--- /dev/null
+++ b/src/chain/chat-with-website.ts
@@ -0,0 +1,170 @@
+import { BaseLanguageModel } from "langchain/base_language";
+import { Document } from "langchain/document";
+import {
+ ChatPromptTemplate,
+ MessagesPlaceholder,
+ PromptTemplate,
+} from "langchain/prompts";
+import { AIMessage, BaseMessage, HumanMessage } from "langchain/schema";
+import { StringOutputParser } from "langchain/schema/output_parser";
+import {
+ Runnable,
+ RunnableBranch,
+ RunnableLambda,
+ RunnableMap,
+ RunnableSequence,
+} from "langchain/schema/runnable";
+type RetrievalChainInput = {
+ chat_history: string;
+ question: string;
+};
+
+export function groupMessagesByConversation(messages: any[]) {
+ if (messages.length % 2 !== 0) {
+ messages.pop();
+ }
+
+ const groupedMessages = [];
+ for (let i = 0; i < messages.length; i += 2) {
+ groupedMessages.push({
+ human: messages[i].content,
+ ai: messages[i + 1].content,
+ });
+ }
+
+ return groupedMessages;
+}
+
+const formatChatHistoryAsString = (history: BaseMessage[]) => {
+ return history
+ .map((message) => `${message._getType()}: ${message.content}`)
+ .join("\n");
+};
+
+const formatDocs = (docs: Document[]) => {
+ return docs
+ .map((doc, i) => `${doc.pageContent}`)
+ .join("\n");
+};
+
+const serializeHistory = (input: any) => {
+ const chatHistory = input.chat_history || [];
+ const convertedChatHistory = [];
+ for (const message of chatHistory) {
+ if (message.human !== undefined) {
+ convertedChatHistory.push(new HumanMessage({ content: message.human }));
+ }
+ if (message["ai"] !== undefined) {
+ convertedChatHistory.push(new AIMessage({ content: message.ai }));
+ }
+ }
+ return convertedChatHistory;
+};
+
+const createRetrieverChain = (
+ llm: BaseLanguageModel,
+ retriever: Runnable,
+ question_template: string
+) => {
+ const CONDENSE_QUESTION_PROMPT =
+ PromptTemplate.fromTemplate(question_template);
+ const condenseQuestionChain = RunnableSequence.from([
+ CONDENSE_QUESTION_PROMPT,
+ llm,
+ new StringOutputParser(),
+ ]).withConfig({
+ runName: "CondenseQuestion",
+ });
+ const hasHistoryCheckFn = RunnableLambda.from(
+ (input: RetrievalChainInput) => input.chat_history.length > 0
+ ).withConfig({ runName: "HasChatHistoryCheck" });
+ const conversationChain = condenseQuestionChain.pipe(retriever).withConfig({
+ runName: "RetrievalChainWithHistory",
+ });
+ const basicRetrievalChain = RunnableLambda.from(
+ (input: RetrievalChainInput) => input.question
+ )
+ .withConfig({
+ runName: "Itemgetter:question",
+ })
+ .pipe(retriever)
+ .withConfig({ runName: "RetrievalChainWithNoHistory" });
+
+ return RunnableBranch.from([
+ [hasHistoryCheckFn, conversationChain],
+ basicRetrievalChain,
+ ]).withConfig({
+ runName: "FindDocs",
+ });
+};
+
+export const createChatWithWebsiteChain = ({
+ llm,
+ question_template,
+ question_llm,
+ retriever,
+ response_template,
+}: {
+ llm: BaseLanguageModel;
+ question_llm: BaseLanguageModel;
+ retriever: Runnable;
+ question_template: string;
+ response_template: string;
+}) => {
+ const retrieverChain = createRetrieverChain(
+ question_llm,
+ retriever,
+ question_template
+ );
+ const context = RunnableMap.from({
+ context: RunnableSequence.from([
+ ({ question, chat_history }) => {
+ return {
+ question: question,
+ chat_history: formatChatHistoryAsString(chat_history),
+ };
+ },
+ retrieverChain,
+ RunnableLambda.from(formatDocs).withConfig({
+ runName: "FormatDocumentChunks",
+ }),
+ ]),
+ question: RunnableLambda.from(
+ (input: RetrievalChainInput) => input.question
+ ).withConfig({
+ runName: "Itemgetter:question",
+ }),
+ chat_history: RunnableLambda.from(
+ (input: RetrievalChainInput) => input.chat_history
+ ).withConfig({
+ runName: "Itemgetter:chat_history",
+ }),
+ }).withConfig({ tags: ["RetrieveDocs"] });
+ const prompt = ChatPromptTemplate.fromMessages([
+ ["system", response_template],
+ new MessagesPlaceholder("chat_history"),
+ ["human", "{question}"],
+ ]);
+
+ const responseSynthesizerChain = RunnableSequence.from([
+ prompt,
+ llm,
+ new StringOutputParser(),
+ ]).withConfig({
+ tags: ["GenerateResponse"],
+ });
+ return RunnableSequence.from([
+ {
+ question: RunnableLambda.from(
+ (input: RetrievalChainInput) => input.question
+ ).withConfig({
+ runName: "Itemgetter:question",
+ }),
+ chat_history: RunnableLambda.from(serializeHistory).withConfig({
+ runName: "SerializeHistory",
+ }),
+ },
+ context,
+ responseSynthesizerChain,
+ ]);
+};
\ No newline at end of file
diff --git a/src/components/Sidepanel/Chat/empty.tsx b/src/components/Sidepanel/Chat/empty.tsx
index 1f606dd..7573cad 100644
--- a/src/components/Sidepanel/Chat/empty.tsx
+++ b/src/components/Sidepanel/Chat/empty.tsx
@@ -98,9 +98,13 @@ export const EmptySidePanel = () => {
}}
value={selectedModel}
className="bg-gray-100 truncate w-full dark:bg-black dark:text-gray-100 rounded-md px-4 py-2 mt-2">
-
- {ollamaInfo.models.map((model) => (
-
+
+ {ollamaInfo.models.map((model, index) => (
+
))}
diff --git a/src/content.ts b/src/content.ts
index b9110c0..ecc2c2a 100644
--- a/src/content.ts
+++ b/src/content.ts
@@ -23,6 +23,7 @@ const sidePanelController = async () => {
chrome.runtime.sendMessage({ type: "sidepanel" })
}
})
+
}
sidePanelController()
diff --git a/src/hooks/useMessage.tsx b/src/hooks/useMessage.tsx
index fb679c7..380bf73 100644
--- a/src/hooks/useMessage.tsx
+++ b/src/hooks/useMessage.tsx
@@ -4,6 +4,13 @@ import { getOllamaURL, systemPromptForNonRag } from "~services/ollama"
import { useStoreMessage, type ChatHistory, type Message } from "~store"
import { ChatOllama } from "@langchain/community/chat_models/ollama"
import { HumanMessage, AIMessage } from "@langchain/core/messages"
+import { getHtmlOfCurrentTab } from "~libs/get-html"
+import { PageAssistHtmlLoader } from "~loader/html"
+import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
+import { OllamaEmbeddings } from "langchain/embeddings/ollama"
+import { Voy as VoyClient } from "voy-search"
+import { createChatWithWebsiteChain } from "~chain/chat-with-website"
+import { MemoryVectorStore } from "langchain/vectorstores/memory"
export type BotResponse = {
bot: {
@@ -70,6 +77,10 @@ export const useMessage = () => {
const abortControllerRef = React.useRef(null)
+ const [keepTrackOfEmbedding, setKeepTrackOfEmbedding] = React.useState<{
+ [key: string]: MemoryVectorStore
+ }>({})
+
const clearChat = () => {
stopStreamingRequest()
setMessages([])
@@ -78,6 +89,149 @@ export const useMessage = () => {
setIsFirstMessage(true)
}
+ const voyEmbedding = async (
+ url: string,
+ html: string,
+ ollamaEmbedding: OllamaEmbeddings
+ ) => {
+ const loader = new PageAssistHtmlLoader({
+ html,
+ url
+ })
+ const docs = await loader.load()
+ const textSplitter = new RecursiveCharacterTextSplitter({
+ chunkSize: 1000,
+ chunkOverlap: 200
+ })
+
+ const chunks = await textSplitter.splitDocuments(docs)
+
+ const store = new MemoryVectorStore(ollamaEmbedding)
+
+ await store.addDocuments(chunks)
+
+ setKeepTrackOfEmbedding({
+ ...keepTrackOfEmbedding,
+ [url]: store
+ })
+
+ return store
+ }
+
+ const chatWithWebsiteMode = async (message: string) => {
+ const ollamaUrl = await getOllamaURL()
+ const { html, url } = await getHtmlOfCurrentTab()
+ const isAlreadyExistEmbedding = keepTrackOfEmbedding[url]
+ let newMessage: Message[] = [
+ ...messages,
+ {
+ isBot: false,
+ name: "You",
+ message,
+ sources: []
+ },
+ {
+ isBot: true,
+ name: selectedModel,
+ message: "▋",
+ sources: []
+ }
+ ]
+
+ const appendingIndex = newMessage.length - 1
+ setMessages(newMessage)
+ const ollamaEmbedding = new OllamaEmbeddings({
+ model: selectedModel,
+ baseUrl: cleanUrl(ollamaUrl)
+ })
+
+ const ollamaChat = new ChatOllama({
+ model: selectedModel,
+ baseUrl: cleanUrl(ollamaUrl)
+ })
+
+ let vectorstore: MemoryVectorStore
+
+ if (isAlreadyExistEmbedding) {
+ vectorstore = isAlreadyExistEmbedding
+ } else {
+ vectorstore = await voyEmbedding(url, html, ollamaEmbedding)
+ }
+
+ const questionPrompt =
+ "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question. Chat History: {chat_history} Follow Up Input: {question} Standalone question:"
+
+ const systemPrompt = `You are a helpful AI assistant. Use the following pieces of context to answer the question at the end. If you don't know the answer, just say you don't know. DO NOT try to make up an answer. 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. {context} Question: {question} Helpful answer in markdown:`
+
+ const sanitizedQuestion = message.trim().replaceAll("\n", " ")
+
+ const chain = createChatWithWebsiteChain({
+ llm: ollamaChat,
+ question_llm: ollamaChat,
+ question_template: questionPrompt,
+ response_template: systemPrompt,
+ retriever: vectorstore.asRetriever()
+ })
+
+
+
+ try {
+ const chunks = await chain.stream({
+ question: sanitizedQuestion
+ })
+ let count = 0
+ for await (const chunk of chunks) {
+ if (count === 0) {
+ setIsProcessing(true)
+ newMessage[appendingIndex].message = chunk + "▋"
+ setMessages(newMessage)
+ } else {
+ newMessage[appendingIndex].message =
+ newMessage[appendingIndex].message.slice(0, -1) + chunk + "▋"
+ setMessages(newMessage)
+ }
+
+ count++
+ }
+
+ newMessage[appendingIndex].message = newMessage[
+ appendingIndex
+ ].message.slice(0, -1)
+
+ setHistory([
+ ...history,
+ {
+ role: "user",
+ content: message
+ },
+ {
+ role: "assistant",
+ content: newMessage[appendingIndex].message
+ }
+ ])
+
+ setIsProcessing(false)
+ } catch (e) {
+ console.log(e)
+ setIsProcessing(false)
+ setStreaming(false)
+
+ setMessages([
+ ...messages,
+ {
+ isBot: true,
+ name: selectedModel,
+ message: `Something went wrong. Check out the following logs:
+ \`\`\`
+ ${e?.message}
+ \`\`\`
+ `,
+ sources: []
+ }
+ ])
+ }
+ }
+
const normalChatMode = async (message: string) => {
const url = await getOllamaURL()
@@ -110,13 +264,8 @@ export const useMessage = () => {
try {
const prompt = await systemPromptForNonRag()
-
-
-
-
const chunks = await ollama.stream(
[
-
...generateHistory(history),
new HumanMessage({
content: [
@@ -187,7 +336,7 @@ export const useMessage = () => {
}
const onSubmit = async (message: string) => {
- await normalChatMode(message)
+ await chatWithWebsiteMode(message)
}
const stopStreamingRequest = () => {
diff --git a/src/libs/get-html.ts b/src/libs/get-html.ts
new file mode 100644
index 0000000..699352a
--- /dev/null
+++ b/src/libs/get-html.ts
@@ -0,0 +1,26 @@
+const _getHtml = () => {
+ const url = window.location.href
+ const html = document.documentElement.outerHTML
+ return { url, html }
+}
+
+export const getHtmlOfCurrentTab = async () => {
+ const result = new Promise((resolve) => {
+ chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
+ const tab = tabs[0]
+ const data = await chrome.scripting.executeScript({
+ target: { tabId: tab.id },
+ func: _getHtml
+ })
+
+ if (data.length > 0) {
+ resolve(data[0].result)
+ }
+ })
+ }) as Promise<{
+ url: string
+ html: string
+ }>
+
+ return result
+}
diff --git a/src/loader/html.ts b/src/loader/html.ts
new file mode 100644
index 0000000..08842c3
--- /dev/null
+++ b/src/loader/html.ts
@@ -0,0 +1,31 @@
+import { BaseDocumentLoader } from "langchain/document_loaders/base"
+import { Document } from "langchain/document"
+import { compile } from "html-to-text"
+
+export interface WebLoaderParams {
+ html: string
+ url: string
+}
+
+export class PageAssistHtmlLoader
+ extends BaseDocumentLoader
+ implements WebLoaderParams
+{
+ html: string
+ url: string
+
+ constructor({ html, url }: WebLoaderParams) {
+ super()
+ this.html = html
+ this.url = url
+ }
+
+ async load(): Promise>[]> {
+ const htmlCompiler = compile({
+ wordwrap: false
+ })
+ const text = htmlCompiler(this.html)
+ const metadata = { source: this.url }
+ return [new Document({ pageContent: text, metadata })]
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index c6f136b..6819c7b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2002,6 +2002,14 @@
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.5.0.tgz#57618e57942a5f0131374a9fdb0167e25a117fdc"
integrity sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==
+"@selderee/plugin-htmlparser2@^0.11.0":
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz#d5b5e29a7ba6d3958a1972c7be16f4b2c188c517"
+ integrity sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==
+ dependencies:
+ domhandler "^5.0.3"
+ selderee "^0.11.0"
+
"@sindresorhus/is@^5.2.0":
version "5.6.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-5.6.0.tgz#41dd6093d34652cddb5d5bdeee04eafc33826668"
@@ -2355,6 +2363,11 @@
dependencies:
"@types/unist" "^2"
+"@types/html-to-text@^9.0.4":
+ version "9.0.4"
+ resolved "https://registry.yarnpkg.com/@types/html-to-text/-/html-to-text-9.0.4.tgz#4a83dd8ae8bfa91457d0b1ffc26f4d0537eff58c"
+ integrity sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==
+
"@types/http-cache-semantics@^4.0.2":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4"
@@ -3381,7 +3394,7 @@ deep-extend@^0.6.0:
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
-deepmerge@^4.2.2:
+deepmerge@^4.2.2, deepmerge@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
@@ -3466,7 +3479,16 @@ dom-serializer@^1.0.1:
domhandler "^4.2.0"
entities "^2.0.0"
-domelementtype@^2.0.1, domelementtype@^2.2.0:
+dom-serializer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
+ integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.2"
+ entities "^4.2.0"
+
+domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
@@ -3485,6 +3507,13 @@ domhandler@^4.2.0, domhandler@^4.2.2, domhandler@^4.3.1:
dependencies:
domelementtype "^2.2.0"
+domhandler@^5.0.2, domhandler@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+ integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
+ dependencies:
+ domelementtype "^2.3.0"
+
domutils@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
@@ -3494,6 +3523,15 @@ domutils@^2.8.0:
domelementtype "^2.2.0"
domhandler "^4.2.0"
+domutils@^3.0.1:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e"
+ integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==
+ dependencies:
+ dom-serializer "^2.0.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+
dotenv-expand@10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37"
@@ -3551,7 +3589,7 @@ entities@^3.0.1:
resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4"
integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==
-entities@^4.4.0:
+entities@^4.2.0, entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
@@ -4108,6 +4146,17 @@ html-encoding-sniffer@^3.0.0:
dependencies:
whatwg-encoding "^2.0.0"
+html-to-text@^9.0.5:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-9.0.5.tgz#6149a0f618ae7a0db8085dca9bbf96d32bb8368d"
+ integrity sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==
+ dependencies:
+ "@selderee/plugin-htmlparser2" "^0.11.0"
+ deepmerge "^4.3.1"
+ dom-serializer "^2.0.0"
+ htmlparser2 "^8.0.2"
+ selderee "^0.11.0"
+
htmlnano@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/htmlnano/-/htmlnano-2.1.0.tgz#67b31b3cd3fad23f0b610ca628fdb48382209c3c"
@@ -4127,6 +4176,16 @@ htmlparser2@^7.1.1:
domutils "^2.8.0"
entities "^3.0.1"
+htmlparser2@^8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
+ integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+ entities "^4.4.0"
+
http-cache-semantics@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
@@ -4592,6 +4651,11 @@ langsmith@~0.0.48, langsmith@~0.0.59:
p-retry "4"
uuid "^9.0.0"
+leac@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912"
+ integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==
+
less@^4.1.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/less/-/less-4.2.0.tgz#cbefbfaa14a4cd388e2099b2b51f956e1465c450"
@@ -5827,6 +5891,14 @@ parse5@^7.1.1:
dependencies:
entities "^4.4.0"
+parseley@^0.12.0:
+ version "0.12.1"
+ resolved "https://registry.yarnpkg.com/parseley/-/parseley-0.12.1.tgz#4afd561d50215ebe259e3e7a853e62f600683aef"
+ integrity sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==
+ dependencies:
+ leac "^0.6.0"
+ peberminta "^0.9.0"
+
path-key@^3.0.0, path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
@@ -5850,6 +5922,11 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+peberminta@^0.9.0:
+ version "0.9.0"
+ resolved "https://registry.yarnpkg.com/peberminta/-/peberminta-0.9.0.tgz#8ec9bc0eb84b7d368126e71ce9033501dca2a352"
+ integrity sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==
+
periscopic@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/periscopic/-/periscopic-3.1.0.tgz#7e9037bf51c5855bd33b48928828db4afa79d97a"
@@ -6835,6 +6912,13 @@ scroll-into-view-if-needed@^3.1.0:
dependencies:
compute-scroll-into-view "^3.0.2"
+selderee@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/selderee/-/selderee-0.11.0.tgz#6af0c7983e073ad3e35787ffe20cefd9daf0ec8a"
+ integrity sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==
+ dependencies:
+ parseley "^0.12.0"
+
semver@7.5.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.4:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"