@@ -59,7 +98,7 @@ export function SidebarInput({
-
-
- {text.length} / 10,000
-
+
+ {text.length.toLocaleString()} /{' '}
+ {MAX_MESSAGE_LENGTH.toLocaleString()}
+
+
+
+ {!delayedLoading ? sendButton : stopButton}
- {!delayedLoading ? (
-
- ) : (
-
- )}
diff --git a/src/components/Sidebar/chat/WebPageContentToggle.tsx b/src/components/Sidebar/chat/WebPageContentToggle.tsx
new file mode 100644
index 000000000..c8441bbe3
--- /dev/null
+++ b/src/components/Sidebar/chat/WebPageContentToggle.tsx
@@ -0,0 +1,31 @@
+import React from 'react'
+import { useSettings } from '../../../hooks/useSettings'
+import * as Switch from '@radix-ui/react-switch'
+
+const WebPageContentToggle = () => {
+ const [settings, setSettings] = useSettings()
+ return (
+
+
+ setSettings({
+ ...settings,
+ general: {
+ ...settings.general,
+ webpageContext: value,
+ },
+ })
+ }
+ className="cdx-w-[28px] cdx-h-[16px] cdx-bg-neutral-500 cdx-rounded-full cdx-relative data-[state=checked]:cdx-bg-blue-500 cdx-outline-none cdx-cursor-default"
+ >
+
+
+
+
+ )
+}
+
+export default WebPageContentToggle
diff --git a/src/components/Sidebar/chat/index.tsx b/src/components/Sidebar/chat/index.tsx
index 6997d7143..ee69ca2c8 100644
--- a/src/components/Sidebar/chat/index.tsx
+++ b/src/components/Sidebar/chat/index.tsx
@@ -1,4 +1,4 @@
-import { useEffect } from 'react'
+import React, { useEffect } from 'react'
import ChatList from './ChatList'
import { SidebarInput } from './ChatInput'
import { useChatCompletion } from '../../../hooks/useChatCompletion'
@@ -33,7 +33,7 @@ const Chat = ({ settings }: ChatProps) => {
return () => {
window.removeEventListener('message', handleWindowMessage)
}
- }, [])
+ }, [submitQuery])
return (
<>
@@ -44,6 +44,7 @@ const Chat = ({ settings }: ChatProps) => {
chatIsEmpty={messages.length <= 1}
clearMessages={clearMessages}
cancelRequest={cancelRequest}
+ isWebpageContextOn={settings.general.webpageContext}
/>
>
)
diff --git a/src/config/settings/index.ts b/src/config/settings/index.ts
index 4330967f4..c335af5ba 100644
--- a/src/config/settings/index.ts
+++ b/src/config/settings/index.ts
@@ -35,6 +35,7 @@ export type Settings = {
}
general: {
theme: ThemeOptions
+ webpageContext: boolean
}
}
@@ -51,5 +52,6 @@ export const defaultSettings: Settings = {
},
general: {
theme: ThemeOptions.SYSTEM,
+ webpageContext: true,
},
}
diff --git a/src/hooks/useChatCompletion.ts b/src/hooks/useChatCompletion.ts
index 852dfd270..49e7b3697 100644
--- a/src/hooks/useChatCompletion.ts
+++ b/src/hooks/useChatCompletion.ts
@@ -1,9 +1,10 @@
-import { AvailableModels, Mode } from '../config/settings'
+import endent from 'endent'
import { ChatOpenAI } from 'langchain/chat_models/openai'
-import { useCurrentChat, ChatRole } from './useCurrentChat'
-import { useMemo } from 'react'
import { AIMessage, HumanMessage, SystemMessage } from 'langchain/schema'
-import { useState } from 'react'
+import { useMemo, useState } from 'react'
+import { AvailableModels, Mode } from '../config/settings'
+import { ChatRole, useCurrentChat } from './useCurrentChat'
+import { getMatchedContent } from '../lib/getMatchedContent'
interface UseChatCompletionProps {
model: AvailableModels
@@ -37,7 +38,7 @@ export const useChatCompletion = ({
} = useCurrentChat()
const [generating, setGenerating] = useState(false)
- const chat = useMemo(
+ const llm = useMemo(
() =>
new ChatOpenAI({
streaming: true,
@@ -45,7 +46,7 @@ export const useChatCompletion = ({
modelName: model,
temperature: Number(mode),
}),
- [],
+ [apiKey, model, mode],
)
const previousMessages = messages.map((msg) => {
@@ -61,22 +62,43 @@ export const useChatCompletion = ({
const controller = new AbortController()
- const submitQuery = async (query: string) => {
+ const submitQuery = async (query: string, context?: string) => {
await addNewMessage(ChatRole.USER, query)
- const messages = [
- new SystemMessage(systemPrompt),
- ...previousMessages,
- new HumanMessage(query),
- ]
const options = {
signal: controller.signal,
callbacks: [{ handleLLMNewToken: updateAssistantMessage }],
}
+
setGenerating(true)
- chat.call(messages, options).then(() => {
- commitToStoredMessages()
- setGenerating(false)
- })
+
+ /**
+ * If context is provided, we need to use the LLM to get the relevant documents
+ * and then run the LLM on those documents. We use in memory vector store to
+ * get the relevant documents
+ */
+ let matchedContext
+ if (context) {
+ matchedContext = await getMatchedContent(query, context, apiKey)
+ }
+
+ const expandedQuery = matchedContext
+ ? endent`
+ ### Context
+ ${matchedContext}
+ ### Question:
+ ${query}
+ `
+ : query
+
+ const messages = [
+ new SystemMessage(systemPrompt),
+ ...previousMessages,
+ new HumanMessage(expandedQuery),
+ ]
+
+ await llm.call(messages, options)
+ commitToStoredMessages()
+ setGenerating(false)
}
const cancelRequest = () => {
diff --git a/src/hooks/useStorage.ts b/src/hooks/useStorage.ts
index b3f3a5a02..80061d888 100644
--- a/src/hooks/useStorage.ts
+++ b/src/hooks/useStorage.ts
@@ -18,6 +18,7 @@ export function useStorage
(
): [T, SetValue] {
const [storedValue, setStoredValue] = useAtom(atom)
+ // biome-ignore lint/correctness/useExhaustiveDependencies: This works fine. i don't want to change it.
useEffect(() => {
readStorage(key, area).then((res) => {
if (res) setStoredValue(res)
diff --git a/src/lib/createSHA256Hash.ts b/src/lib/createSHA256Hash.ts
new file mode 100644
index 000000000..982aa1135
--- /dev/null
+++ b/src/lib/createSHA256Hash.ts
@@ -0,0 +1,10 @@
+export const createSHA256Hash = async (content: string): Promise => {
+ const encoder = new TextEncoder()
+ const data = encoder.encode(content)
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data)
+ const hashArray = Array.from(new Uint8Array(hashBuffer))
+ const hashHex = hashArray
+ .map((byte) => byte.toString(16).padStart(2, '0'))
+ .join('')
+ return hashHex
+}
diff --git a/src/utils/generateReadableDate.ts b/src/lib/generateReadableDate.ts
similarity index 100%
rename from src/utils/generateReadableDate.ts
rename to src/lib/generateReadableDate.ts
diff --git a/src/lib/getMatchedContent.ts b/src/lib/getMatchedContent.ts
new file mode 100644
index 000000000..e0110c41d
--- /dev/null
+++ b/src/lib/getMatchedContent.ts
@@ -0,0 +1,48 @@
+import { OpenAIEmbeddings } from 'langchain/embeddings/openai'
+import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter'
+import { MemoryVectorStore } from 'langchain/vectorstores/memory'
+import { createSHA256Hash } from './createSHA256Hash'
+
+/**
+ * This function is responsible for getting the matched content
+ * from the context and query
+ */
+export const getMatchedContent = async (
+ query: string,
+ context: string,
+ apiKey: string,
+) => {
+ const vectorStore = await getContextVectorStore(context, apiKey)
+ const retriever = vectorStore.asRetriever()
+ const relevantDocs = await retriever.getRelevantDocuments(query)
+ return relevantDocs.map((doc) => doc.pageContent).join('\n')
+}
+
+/**
+ * This function is responsible for getting the context vector store
+ * from the context. It caches the vector store in the local storage
+ * for faster retrieval
+ */
+const getContextVectorStore = async (context: string, apiKey: string) => {
+ const embeddings = new OpenAIEmbeddings({ openAIApiKey: apiKey })
+ const hashKey = `SYNCIA_STORE_EMBEDDINGS_${await createSHA256Hash(context)}`
+ const memoryVectors: [] | null = JSON.parse(
+ localStorage.getItem(hashKey) || 'null',
+ )
+
+ if (!memoryVectors) {
+ const textSplitter = new RecursiveCharacterTextSplitter({
+ chunkSize: 1000,
+ })
+ const docs = await textSplitter.createDocuments([context])
+ const store = await MemoryVectorStore.fromDocuments(docs, embeddings)
+ localStorage.setItem(hashKey, JSON.stringify(store.memoryVectors))
+ return store
+ }
+
+ console.log({ memoryVectors })
+
+ const store = new MemoryVectorStore(embeddings)
+ store.memoryVectors = memoryVectors
+ return store
+}
diff --git a/src/utils/validApiKey.ts b/src/lib/validApiKey.ts
similarity index 100%
rename from src/utils/validApiKey.ts
rename to src/lib/validApiKey.ts
diff --git a/src/pages/background/sidebar/sendSidebarShortcut.ts b/src/pages/background/sidebar/sendSidebarShortcut.ts
index 78eb9e229..01f5e3e4e 100644
--- a/src/pages/background/sidebar/sendSidebarShortcut.ts
+++ b/src/pages/background/sidebar/sendSidebarShortcut.ts
@@ -12,11 +12,17 @@ export const sendSidebarShortcut = () => {
// Send shortcut to client
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
- if (tabs[0].id)
- chrome.tabs.sendMessage(tabs[0].id, {
- action: 'sidebar-shortcut',
- shortcut,
+ if (tabs[0].id) {
+ chrome.tabs.onUpdated.addListener(function listener(tabId, info) {
+ if (info.status === 'complete' && tabId === tabs[0].id) {
+ chrome.tabs.sendMessage(tabs[0].id, {
+ action: 'sidebar-shortcut',
+ shortcut,
+ })
+ chrome.tabs.onUpdated.removeListener(listener)
+ }
})
+ }
})
})
}
diff --git a/src/pages/content/sidebar.tsx b/src/pages/content/sidebar.tsx
index f717f853f..53525c3ad 100644
--- a/src/pages/content/sidebar.tsx
+++ b/src/pages/content/sidebar.tsx
@@ -17,6 +17,12 @@ iframe.id = 'syncia_sidebar'
document.body.appendChild(iframe)
+/**
+ * BG SCRIPT <-> CONTENT SCRIPT
+ * Event listener for messages from the background script.
+ * To open the sidebar, the background script sends a message with the action 'open-sidebar'.
+ * The sidebar is opened by setting the width of the iframe to 400px.
+ */
chrome.runtime.onMessage.addListener(function (msg) {
if (msg.action === 'open-sidebar') {
if (iframe.style.width === '0px') {
@@ -26,3 +32,25 @@ chrome.runtime.onMessage.addListener(function (msg) {
}
}
})
+
+/**
+ * SIDEBAR <-> CONTENT SCRIPT
+ * Event listener for messages from the sidebar.
+ * To get the page content, the sidebar sends a message with the action 'get-page-content'.
+ * The page content is sent back to the sidebar by posting a message with the action 'get-page-content'.
+ */
+window.addEventListener('message', (event) => {
+ const { action, _payload } = event.data as { action: string; _payload: any }
+
+ if (action === 'get-page-content') {
+ console.log('get-page-content Triggered')
+ const pageContent = document.body.innerText
+ iframe.contentWindow?.postMessage(
+ {
+ action: 'get-page-content',
+ pageContent,
+ },
+ '*',
+ )
+ }
+})
diff --git a/yarn.lock b/yarn.lock
index 5c7fc339e..4e1d6e5ff 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -15,10 +15,10 @@
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
-"@anthropic-ai/sdk@^0.6.2":
- version "0.6.2"
- resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.6.2.tgz#4be415e6b1d948df6f8e03af84aedf102ec74b70"
- integrity sha512-fB9PUj9RFT+XjkL+E9Ol864ZIJi+1P8WnbHspN3N3/GK2uSzjd0cbVIKTGgf4v3N8MwaQu+UWnU7C4BG/fap/g==
+"@anthropic-ai/sdk@^0.10.0":
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.10.0.tgz#df21d266520b6b439e39ab4853a2ee787239d85c"
+ integrity sha512-OqTbjjZzDjkCSe0RcNTNeVrHLrC3RUzUvTJB/qt6seVaETnLkKt6b4hkOnkhkXnBHH8+w20zmB4Ekcwir2B/sw==
dependencies:
"@types/node" "^18.11.18"
"@types/node-fetch" "^2.6.4"
@@ -28,6 +28,7 @@
form-data-encoder "1.7.2"
formdata-node "^4.3.2"
node-fetch "^2.6.7"
+ web-streams-polyfill "^3.2.1"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4":
version "7.21.4"
@@ -815,6 +816,39 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
+"@langchain/anthropic@^0.0.3":
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/@langchain/anthropic/-/anthropic-0.0.3.tgz#a0d365971895f838cf1ec528010eacd35fff6eca"
+ integrity sha512-iSp0S7h02cUS1ClyrKRBKqp7E8y1qjjZg5Tu5HLTOrRRBDgi22P7Jph1BNB8hazX/+YotDCTlChaBoolhsspXQ==
+ dependencies:
+ "@anthropic-ai/sdk" "^0.10.0"
+ "@langchain/core" "~0.0.1"
+
+"@langchain/core@^0.0.1", "@langchain/core@~0.0.1":
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.0.1.tgz#1a835016bfd9c6b196e317d5118775fe93b262a0"
+ integrity sha512-VgpKcfvtC+oN5ZfCX1k05fXmMs87/U0j4KzOiGbgIjwO9xfE8bIMesodinDQO1H5ohF1SEDE1tSeVXz/bTuang==
+ dependencies:
+ ansi-styles "^5.0.0"
+ camelcase "6"
+ decamelize "1.2.0"
+ js-tiktoken "^1.0.7"
+ langsmith "^0.0.48"
+ p-queue "^6.6.2"
+ p-retry "4"
+ uuid "^9.0.0"
+ zod "^3.22.3"
+
+"@langchain/openai@^0.0.1":
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.0.1.tgz#a6e36c757abdbad6cacf2022831d4d627f86cf6e"
+ integrity sha512-AYmsaiI4Vh1q1llGu1+P77oDqeSuSLavUzaaT25xJtCLl0o/2mqF6BQ1Wa0/HJ1+vOKxRDvCk9jhB4RjJSkuTg==
+ dependencies:
+ "@langchain/core" "~0.0.1"
+ js-tiktoken "^1.0.7"
+ openai "^4.19.0"
+ zod-to-json-schema "3.20.3"
+
"@manypkg/find-root@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@manypkg/find-root/-/find-root-1.1.0.tgz#a62d8ed1cd7e7d4c11d9d52a8397460b5d4ad29f"
@@ -2387,7 +2421,7 @@ decamelize-keys@^1.1.0:
decamelize "^1.1.0"
map-obj "^1.0.0"
-decamelize@^1.1.0, decamelize@^1.2.0:
+decamelize@1.2.0, decamelize@^1.1.0, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
@@ -3850,43 +3884,39 @@ kleur@^4.0.3, kleur@^4.1.5:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
-langchain@^0.0.151:
- version "0.0.151"
- resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.0.151.tgz#10c1ebdda0d772e49dbca755ada6307c9280f601"
- integrity sha512-RA7/ELK5dqUgv5glIP5Wm5JmbnrjH/eeROYdKGDGaDUNZrRJ2CLuEu+oJH7hcE5hpPoPlkLBCs/vz4hvr/YtYw==
+langchain@^0.0.197-rc.1:
+ version "0.0.197-rc.1"
+ resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.0.197-rc.1.tgz#4447ae83d09f400e928bfb2c6e15fc8635c76f48"
+ integrity sha512-KyIolBPL7LxktSUPPm0qvTJP0z5OTLzbk4zEJu55iNm3r5W8ucWUEnvv+kqJPubF+z2+b+aZoqT8+MevsFwfEA==
dependencies:
- "@anthropic-ai/sdk" "^0.6.2"
- ansi-styles "^5.0.0"
+ "@langchain/anthropic" "^0.0.3"
+ "@langchain/core" "^0.0.1"
+ "@langchain/openai" "^0.0.1"
binary-extensions "^2.2.0"
- camelcase "6"
- decamelize "^1.2.0"
expr-eval "^2.0.2"
flat "^5.0.2"
js-tiktoken "^1.0.7"
js-yaml "^4.1.0"
jsonpointer "^5.0.1"
langchainhub "~0.0.6"
- langsmith "~0.0.31"
+ langsmith "~0.0.48"
ml-distance "^4.0.0"
- object-hash "^3.0.0"
- openai "~4.4.0"
openapi-types "^12.1.3"
- p-queue "^6.6.2"
p-retry "4"
uuid "^9.0.0"
yaml "^2.2.1"
- zod "^3.21.4"
- zod-to-json-schema "^3.20.4"
+ zod "^3.22.3"
+ zod-to-json-schema "3.20.3"
langchainhub@~0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/langchainhub/-/langchainhub-0.0.6.tgz#9d2d06e4ce0807b4e8a31e19611f57aef990b54d"
integrity sha512-SW6105T+YP1cTe0yMf//7kyshCgvCTyFBMTgH2H3s9rTAR4e+78DA/BBrUL/Mt4Q5eMWui7iGuAYb3pgGsdQ9w==
-langsmith@~0.0.31:
- version "0.0.38"
- resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.0.38.tgz#7946b4f6b91c66a450132d8853cf010e3c4a44ac"
- integrity sha512-SNeje3mF+90aAX2Br919rraA21+EaNw1IfaXSrWxkqYJjJifWs0knLlAki8Vg1Sg5uv5ay/6MOO8v5mU5mL0yw==
+langsmith@^0.0.48, langsmith@~0.0.48:
+ version "0.0.48"
+ resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.0.48.tgz#3a9a8ce257271ddb43d01ebf585c4370a3a3ba79"
+ integrity sha512-s0hW8iZ90Q9XLTnDK0Pgee245URV3b1cXQjPDj5OKm1+KN7iSK1pKx+4CO7RcFLz58Ixe7Mt+mVcomYqUuryxQ==
dependencies:
"@types/uuid" "^9.0.1"
commander "^10.0.1"
@@ -4761,10 +4791,10 @@ open@^9.1.0:
is-inside-container "^1.0.0"
is-wsl "^2.2.0"
-openai@~4.4.0:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/openai/-/openai-4.4.0.tgz#dbaab326eb044ddec479951b245850c482678031"
- integrity sha512-JN0t628Kh95T0IrXl0HdBqnlJg+4Vq0Bnh55tio+dfCnyzHvMLiWyCM9m726MAJD2YkDU4/8RQB6rNbEq9ct2w==
+openai@^4.19.0:
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/openai/-/openai-4.20.0.tgz#d171b5afb38e0c5ba07f9dd7b15b5c4bbc1f1bda"
+ integrity sha512-VbAYerNZFfIIeESS+OL9vgDkK8Mnri55n+jN0UN/HZeuM0ghGh6nDN6UGRZxslNgyJ7XmY/Ca9DO4YYyvrszGA==
dependencies:
"@types/node" "^18.11.18"
"@types/node-fetch" "^2.6.4"
@@ -4774,6 +4804,7 @@ openai@~4.4.0:
form-data-encoder "1.7.2"
formdata-node "^4.3.2"
node-fetch "^2.6.7"
+ web-streams-polyfill "^3.2.1"
openapi-types@^12.1.3:
version "12.1.3"
@@ -6286,6 +6317,11 @@ web-streams-polyfill@4.0.0-beta.3:
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38"
integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==
+web-streams-polyfill@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
+ integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
+
webextension-polyfill@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz#ccb28101c910ba8cf955f7e6a263e662d744dbb8"
@@ -6496,15 +6532,15 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
-zod-to-json-schema@^3.20.4:
- version "3.21.4"
- resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.21.4.tgz#de97c5b6d4a25e9d444618486cb55c0c7fb949fd"
- integrity sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw==
+zod-to-json-schema@3.20.3:
+ version "3.20.3"
+ resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.20.3.tgz#8c95d8c20f20455ffa0b4b526c29703f35f6d787"
+ integrity sha512-/Q3wnyxAfCt94ZcrGiXXoiAfRqasxl9CX64LZ9fj+4dKH68zulUtU0uk1WMxQPfAxQ0ZI70dKzcoW7hHj+DwSQ==
-zod@^3.21.4:
- version "3.22.2"
- resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b"
- integrity sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==
+zod@^3.22.3:
+ version "3.22.4"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
+ integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==
zwitch@^2.0.0:
version "2.0.4"