11 Commits

Author SHA1 Message Date
ItzCrazyKns
80818983d8 feat(package): bump version 2024-07-03 20:49:13 +05:30
ItzCrazyKns
5217d21366 feat(dockerfile): revert to node:slim 2024-07-03 20:47:31 +05:30
ItzCrazyKns
57ede99b83 Merge branch 'master' of https://github.com/ItzCrazyKns/Perplexica 2024-07-02 10:52:02 +05:30
ItzCrazyKns
c74e16e01c feat(chats): add delete functionality 2024-07-02 10:51:47 +05:30
ItzCrazyKns
ce593daab9 Update README.md 2024-06-30 12:39:37 +05:30
ItzCrazyKns
fcf9b644af Create FUNDING.yml 2024-06-30 12:34:32 +05:30
ItzCrazyKns
6ae825999a feat(readme): update manual install 2024-06-30 10:45:35 +05:30
ItzCrazyKns
b291265944 feat(package): add @langchain/community 2024-06-30 10:42:01 +05:30
ItzCrazyKns
c62684407d feat(chat-window): handle notFound errors 2024-06-29 12:11:34 +05:30
ItzCrazyKns
f4b01a29bb feat(docs): update docs 2024-06-29 11:39:23 +05:30
ItzCrazyKns
022cf55db7 feat(docs): add update docs 2024-06-29 11:38:43 +05:30
11 changed files with 362 additions and 34 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
patreon: itzcrazykns

View File

@ -85,11 +85,12 @@ There are mainly 2 ways of installing Perplexica - With Docker, Without Docker.
### Non-Docker Installation
1. Clone the repository and rename the `sample.config.toml` file to `config.toml` in the root directory. Ensure you complete all required fields in this file.
2. Rename the `.env.example` file to `.env` in the `ui` folder and fill in all necessary fields.
3. After populating the configuration and environment files, run `npm i` in both the `ui` folder and the root directory.
4. Install the dependencies and then execute `npm run build` in both the `ui` folder and the root directory.
5. Finally, start both the frontend and the backend by running `npm run start` in both the `ui` folder and the root directory.
1. Install SearXNG and allow `JSON` format in the SearXNG settings.
2. Clone the repository and rename the `sample.config.toml` file to `config.toml` in the root directory. Ensure you complete all required fields in this file.
3. Rename the `.env.example` file to `.env` in the `ui` folder and fill in all necessary fields.
4. After populating the configuration and environment files, run `npm i` in both the `ui` folder and the root directory.
5. Install the dependencies and then execute `npm run build` in both the `ui` folder and the root directory.
6. Finally, start both the frontend and the backend by running `npm run start` in both the `ui` folder and the root directory.
**Note**: Using Docker is recommended as it simplifies the setup process, especially for managing environment variables and dependencies.
@ -146,11 +147,11 @@ If you find Perplexica useful, consider giving us a star on GitHub. This helps m
### Donations
We also accept donations to help sustain our project. If you would like to contribute, you can use the following button to make a donation in cryptocurrency. Thank you for your support!
We also accept donations to help sustain our project. If you would like to contribute, you can use the following options to donate. Thank you for your support!
<a href="https://nowpayments.io/donation?api_key=RFFKJH1-GRR4DQG-HFV1DZP-00G6MMK&source=lk_donation&medium=referral" target="_blank">
<img src="https://nowpayments.io/images/embeds/donation-button-white.svg" alt="Crypto donation button by NOWPayments">
</a>
| Cards | Ethereum |
|---|---|
| https://www.patreon.com/itzcrazykns | Address: `0xB025a84b2F269570Eb8D4b05DEdaA41D8525B6DD` |
## Contribution

View File

@ -1,4 +1,4 @@
FROM nikolaik/python-nodejs:python3.12-nodejs20-bullseye
FROM node:slim
ARG SEARXNG_API_URL

View File

@ -0,0 +1,33 @@
# Update Perplexica to the latest version
To update Perplexica to the latest version, follow these steps:
## For Docker users
1. Clone the latest version of Perplexica from GitHub:
```bash
git clone https://github.com/ItzCrazyKns/Perplexica.git
```
2. Navigate to the Project Directory
3. Update and Rebuild Docker Containers:
```bash
docker compose up -d --build
```
4. Once the command completes running go to http://localhost:3000 and verify the latest changes.
## For non Docker users
1. Clone the latest version of Perplexica from GitHub:
```bash
git clone https://github.com/ItzCrazyKns/Perplexica.git
```
2. Navigate to the Project Directory
3. Execute `npm i` in both the `ui` folder and the root directory.
4. Once packages are updated, execute `npm run build` in both the `ui` folder and the root directory.
5. Finally, start both the frontend and the backend by running `npm run start` in both the `ui` folder and the root directory.

View File

@ -1,6 +1,6 @@
{
"name": "perplexica-backend",
"version": "1.7.0",
"version": "1.7.1",
"license": "MIT",
"author": "ItzCrazyKns",
"scripts": {
@ -24,6 +24,7 @@
},
"dependencies": {
"@iarna/toml": "^2.2.5",
"@langchain/community": "^0.2.16",
"@langchain/openai": "^0.0.25",
"@xenova/transformers": "^2.17.1",
"axios": "^1.6.8",

View File

@ -40,4 +40,27 @@ router.get('/:id', async (req, res) => {
}
});
router.delete(`/:id`, async (req, res) => {
try {
const chatExists = await db.query.chats.findFirst({
where: eq(chats.id, req.params.id),
});
if (!chatExists) {
return res.status(404).json({ message: 'Chat not found' });
}
await db.delete(chats).where(eq(chats.id, req.params.id)).execute();
await db
.delete(messages)
.where(eq(messages.chatId, req.params.id))
.execute();
return res.status(200).json({ message: 'Chat deleted successfully' });
} catch (err) {
res.status(500).json({ message: 'An error has occurred.' });
logger.error(`Error in deleting chat: ${err.message}`);
}
});
export default router;

View File

@ -1,11 +1,12 @@
'use client';
import DeleteChat from '@/components/DeleteChat';
import { formatTimeDifference } from '@/lib/utils';
import { BookOpenText, ClockIcon, ScanEye } from 'lucide-react';
import { BookOpenText, ClockIcon, Delete, ScanEye } from 'lucide-react';
import Link from 'next/link';
import { useEffect, useState } from 'react';
interface Chat {
export interface Chat {
id: string;
title: string;
createdAt: string;
@ -92,6 +93,11 @@ const Page = () => {
{formatTimeDifference(new Date(), chat.createdAt)} Ago
</p>
</div>
<DeleteChat
chatId={chat.id}
chats={chats}
setChats={setChats}
/>
</div>
</div>
))}

View File

@ -9,6 +9,7 @@ import crypto from 'crypto';
import { toast } from 'sonner';
import { useSearchParams } from 'next/navigation';
import { getSuggestions } from '@/lib/actions';
import Error from 'next/error';
export type Message = {
messageId: string;
@ -158,6 +159,7 @@ const loadMessages = async (
setIsMessagesLoaded: (loaded: boolean) => void,
setChatHistory: (history: [string, string][]) => void,
setFocusMode: (mode: string) => void,
setNotFound: (notFound: boolean) => void,
) => {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/chats/${chatId}`,
@ -169,6 +171,12 @@ const loadMessages = async (
},
);
if (res.status === 404) {
setNotFound(true);
setIsMessagesLoaded(true);
return;
}
const data = await res.json();
const messages = data.messages.map((msg: any) => {
@ -190,7 +198,6 @@ const loadMessages = async (
setChatHistory(history);
setFocusMode(data.chat.focusMode);
console.log(data);
setIsMessagesLoaded(true);
};
@ -221,6 +228,8 @@ const ChatWindow = ({ id }: { id?: string }) => {
const [isMessagesLoaded, setIsMessagesLoaded] = useState(false);
const [notFound, setNotFound] = useState(false);
useEffect(() => {
if (
chatId &&
@ -234,6 +243,7 @@ const ChatWindow = ({ id }: { id?: string }) => {
setIsMessagesLoaded,
setChatHistory,
setFocusMode,
setNotFound,
);
} else if (!chatId) {
setNewChatCreated(true);
@ -416,26 +426,30 @@ const ChatWindow = ({ id }: { id?: string }) => {
}
return isReady ? (
<div>
{messages.length > 0 ? (
<>
<Navbar messages={messages} />
<Chat
loading={loading}
messages={messages}
notFound ? (
<Error statusCode={404} />
) : (
<div>
{messages.length > 0 ? (
<>
<Navbar messages={messages} />
<Chat
loading={loading}
messages={messages}
sendMessage={sendMessage}
messageAppeared={messageAppeared}
rewrite={rewrite}
/>
</>
) : (
<EmptyChat
sendMessage={sendMessage}
messageAppeared={messageAppeared}
rewrite={rewrite}
focusMode={focusMode}
setFocusMode={setFocusMode}
/>
</>
) : (
<EmptyChat
sendMessage={sendMessage}
focusMode={focusMode}
setFocusMode={setFocusMode}
/>
)}
</div>
)}
</div>
)
) : (
<div className="flex flex-row items-center justify-center min-h-screen">
<svg

View File

@ -0,0 +1,114 @@
import { Delete, Trash } from 'lucide-react';
import { Dialog, Transition } from '@headlessui/react';
import { Fragment, useState } from 'react';
import { toast } from 'sonner';
import { Chat } from '@/app/library/page';
const DeleteChat = ({
chatId,
chats,
setChats,
}: {
chatId: string;
chats: Chat[];
setChats: (chats: Chat[]) => void;
}) => {
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
const [loading, setLoading] = useState(false);
const handleDelete = async () => {
setLoading(true);
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/chats/${chatId}`,
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
},
);
if (res.status != 200) {
throw new Error('Failed to delete chat');
}
const newChats = chats.filter((chat) => chat.id !== chatId);
setChats(newChats);
} catch (err: any) {
toast.error(err.message);
} finally {
setConfirmationDialogOpen(false);
setLoading(false);
}
};
return (
<>
<button
onClick={() => {
setConfirmationDialogOpen(true);
}}
className="bg-transparent text-red-400 hover:scale-105 transition duration-200"
>
<Trash size={17} />
</button>
<Transition appear show={confirmationDialogOpen} as={Fragment}>
<Dialog
as="div"
className="relative z-50"
onClose={() => {
if (!loading) {
setConfirmationDialogOpen(false);
}
}}
>
<Dialog.Backdrop className="fixed inset-0 bg-black/30" />
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-200"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-100"
leaveFrom="opacity-100 scale-200"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform rounded-2xl bg-light-secondary dark:bg-dark-secondary border border-light-200 dark:border-dark-200 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title className="text-lg font-medium leading-6 dark:text-white">
Delete Confirmation
</Dialog.Title>
<Dialog.Description className="text-sm dark:text-white/70 text-black/70">
Are you sure you want to delete this chat?
</Dialog.Description>
<div className="flex flex-row items-end justify-end space-x-4 mt-6">
<button
onClick={() => {
if (!loading) {
setConfirmationDialogOpen(false);
}
}}
className="text-black/50 dark:text-white/50 text-sm hover:text-black/70 hover:dark:text-white/70 transition duration-200"
>
Cancel
</button>
<button
onClick={handleDelete}
className="text-red-400 text-sm hover:text-red-500 transition duration200"
>
Delete
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</>
);
};
export default DeleteChat;

View File

@ -1,6 +1,6 @@
{
"name": "perplexica-frontend",
"version": "1.7.0",
"version": "1.7.1",
"license": "MIT",
"author": "ItzCrazyKns",
"scripts": {

135
yarn.lock
View File

@ -307,6 +307,23 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@langchain/community@^0.2.16":
version "0.2.16"
resolved "https://registry.yarnpkg.com/@langchain/community/-/community-0.2.16.tgz#5888baf7fc7ea272c5f91aaa0e71bc444167262d"
integrity sha512-dFDcMabKACvuRd0w6EIRLWf1ubPGZEeEwFt9v1jiEr4HCFxH0OF+iM1QUCcVRbB2fK5lqmKeTD1XAeZV8+AyXA==
dependencies:
"@langchain/core" "~0.2.11"
"@langchain/openai" "~0.1.0"
binary-extensions "^2.2.0"
expr-eval "^2.0.2"
flat "^5.0.2"
js-yaml "^4.1.0"
langchain "0.2.3"
langsmith "~0.1.30"
uuid "^9.0.0"
zod "^3.22.3"
zod-to-json-schema "^3.22.5"
"@langchain/community@~0.0.41":
version "0.0.43"
resolved "https://registry.yarnpkg.com/@langchain/community/-/community-0.0.43.tgz#017e2f9b3209b3999482f10df5aec2520731a63c"
@ -320,6 +337,24 @@
uuid "^9.0.0"
zod "^3.22.3"
"@langchain/core@>0.1.56 <0.3.0", "@langchain/core@>0.2.0 <0.3.0", "@langchain/core@>=0.2.5 <0.3.0", "@langchain/core@~0.2.0", "@langchain/core@~0.2.11":
version "0.2.11"
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.2.11.tgz#5f47467e20e56b250831baef20083657c6facb4c"
integrity sha512-d4SNL7WI0c3oHrV4WxCRH1/TNqdePXEzYjYwIb4aEH6lW1aM0utGhLbNthX+aYkOL4Ynx2FoG4h91ECIipiKWQ==
dependencies:
ansi-styles "^5.0.0"
camelcase "6"
decamelize "1.2.0"
js-tiktoken "^1.0.12"
langsmith "~0.1.30"
ml-distance "^4.0.0"
mustache "^4.2.0"
p-queue "^6.6.2"
p-retry "4"
uuid "^9.0.0"
zod "^3.22.4"
zod-to-json-schema "^3.22.3"
"@langchain/core@~0.1.44", "@langchain/core@~0.1.45":
version "0.1.52"
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.1.52.tgz#7619310b83ffa841628efe2e1eda873ca714d068"
@ -348,6 +383,36 @@
zod "^3.22.4"
zod-to-json-schema "^3.22.3"
"@langchain/openai@~0.0.28":
version "0.0.34"
resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.0.34.tgz#36c9bca0721ab9f7e5d40927e7c0429cacbd5b56"
integrity sha512-M+CW4oXle5fdoz2T2SwdOef8pl3/1XmUx1vjn2mXUVM/128aO0l23FMF0SNBsAbRV6P+p/TuzjodchJbi0Ht/A==
dependencies:
"@langchain/core" ">0.1.56 <0.3.0"
js-tiktoken "^1.0.12"
openai "^4.41.1"
zod "^3.22.4"
zod-to-json-schema "^3.22.3"
"@langchain/openai@~0.1.0":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.1.3.tgz#6eb0994e970d85ffa9aaeafb94449024ccf6ca63"
integrity sha512-riv/JC9x2A8b7GcHu8sx+mlZJ8KAwSSi231IPTlcciYnKozmrQ5H0vrtiD31fxiDbaRsk7tyCpkSBIOQEo7CyQ==
dependencies:
"@langchain/core" ">=0.2.5 <0.3.0"
js-tiktoken "^1.0.12"
openai "^4.49.1"
zod "^3.22.4"
zod-to-json-schema "^3.22.3"
"@langchain/textsplitters@~0.0.0":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@langchain/textsplitters/-/textsplitters-0.0.3.tgz#1a3cc93dd2ab330edb225400ded190a22fea14e3"
integrity sha512-cXWgKE3sdWLSqAa8ykbCcUsUF1Kyr5J3HOWYGuobhPEycXW4WI++d5DhzdpL238mzoEXTi90VqfSCra37l5YqA==
dependencies:
"@langchain/core" ">0.2.0 <0.3.0"
js-tiktoken "^1.0.12"
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
@ -1508,6 +1573,13 @@ is-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
js-tiktoken@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.12.tgz#af0f5cf58e5e7318240d050c8413234019424211"
integrity sha512-L7wURW1fH9Qaext0VzaUDpFGVQgjkdE3Dgsy9/+yXyGEpBKnylTd0mU0bfbNkKDlXRb6TEsZkwuflu1B8uQbJQ==
dependencies:
base64-js "^1.5.1"
js-tiktoken@^1.0.7, js-tiktoken@^1.0.8:
version "1.0.10"
resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.10.tgz#2b343ec169399dcee8f9ef9807dbd4fafd3b30dc"
@ -1532,6 +1604,28 @@ kuler@^2.0.0:
resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
langchain@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.2.3.tgz#c14bb05cf871b21bd63b84b3ab89580b1d62539f"
integrity sha512-T9xR7zd+Nj0oXy6WoYKmZLy0DlQiDLFPGYWdOXDxy+AvqlujoPdVQgDSpdqiOHvAjezrByAoKxoHCz5XMwTP/Q==
dependencies:
"@langchain/core" "~0.2.0"
"@langchain/openai" "~0.0.28"
"@langchain/textsplitters" "~0.0.0"
binary-extensions "^2.2.0"
js-tiktoken "^1.0.12"
js-yaml "^4.1.0"
jsonpointer "^5.0.1"
langchainhub "~0.0.8"
langsmith "~0.1.7"
ml-distance "^4.0.0"
openapi-types "^12.1.3"
p-retry "4"
uuid "^9.0.0"
yaml "^2.2.1"
zod "^3.22.4"
zod-to-json-schema "^3.22.3"
langchain@^0.1.30:
version "0.1.30"
resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.1.30.tgz#e1adb3f1849fcd5c596c668300afd5dc8cb37a97"
@ -1571,6 +1665,23 @@ langsmith@~0.1.1, langsmith@~0.1.7:
p-retry "4"
uuid "^9.0.0"
langsmith@~0.1.30:
version "0.1.34"
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.1.34.tgz#801310495fef258ed9c22bb5575120e2c06d51cf"
integrity sha512-aMv2k8kEaovhTuZnK6/6DMCoM7Jurvm1AzdESn+yN+HramRxp3sK32jFRz3ogkXP6GjAjOIofcnNkzhHXSUXGA==
dependencies:
"@types/uuid" "^9.0.1"
commander "^10.0.1"
lodash.set "^4.3.2"
p-queue "^6.6.2"
p-retry "4"
uuid "^9.0.0"
lodash.set@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
integrity sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==
logform@^2.3.2, logform@^2.4.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/logform/-/logform-2.6.0.tgz#8c82a983f05d6eaeb2d75e3decae7a768b2bf9b5"
@ -1714,6 +1825,11 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
mustache@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
napi-build-utils@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
@ -1858,6 +1974,20 @@ openai@^4.26.0:
node-fetch "^2.6.7"
web-streams-polyfill "^3.2.1"
openai@^4.41.1, openai@^4.49.1:
version "4.52.2"
resolved "https://registry.yarnpkg.com/openai/-/openai-4.52.2.tgz#5d67271f3df84c0b54676b08990eaa9402151759"
integrity sha512-mMc0XgFuVSkcm0lRIi8zaw++otC82ZlfkCur1qguXYWPETr/+ZwL9A/vvp3YahX+shpaT6j03dwsmUyLAfmEfg==
dependencies:
"@types/node" "^18.11.18"
"@types/node-fetch" "^2.6.4"
abort-controller "^3.0.0"
agentkeepalive "^4.2.1"
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"
resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3"
@ -2462,6 +2592,11 @@ zod-to-json-schema@^3.22.3:
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.22.5.tgz#3646e81cfc318dbad2a22519e5ce661615418673"
integrity sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q==
zod-to-json-schema@^3.22.5:
version "3.23.1"
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.23.1.tgz#5225925b8ed5fa20096bd99be076c4b29b53d309"
integrity sha512-oT9INvydob1XV0v1d2IadrR74rLtDInLvDFfAa1CG0Pmg/vxATk7I2gSelfj271mbzeM4Da0uuDQE/Nkj3DWNw==
zod@^3.22.3, zod@^3.22.4:
version "3.22.4"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"