diff --git a/app/package.json b/app/package.json index cfc4c5f..7a48be6 100644 --- a/app/package.json +++ b/app/package.json @@ -48,6 +48,7 @@ "@trpc/react-query": "^10.26.0", "@trpc/server": "^10.26.0", "@vercel/og": "^0.5.9", + "archiver": "^6.0.0", "ast-types": "^0.14.2", "chroma-js": "^2.4.2", "concurrently": "^8.2.0", @@ -99,6 +100,7 @@ "replicate": "^0.12.3", "socket.io": "^4.7.1", "socket.io-client": "^4.7.1", + "stream-buffers": "^3.0.2", "superjson": "1.12.2", "trpc-openapi": "^1.2.0", "tsx": "^3.12.7", @@ -111,6 +113,7 @@ }, "devDependencies": { "@openapi-contrib/openapi-schema-to-json-schema": "^4.0.5", + "@types/archiver": "^5.3.2", "@types/babel__core": "^7.20.1", "@types/babel__standalone": "^7.1.4", "@types/chroma-js": "^2.4.0", @@ -127,6 +130,7 @@ "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", "@types/react-syntax-highlighter": "^15.5.7", + "@types/stream-buffers": "^3.0.4", "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^5.59.6", "@typescript-eslint/parser": "^5.59.6", diff --git a/app/src/components/requestLogs/ExportButton.tsx b/app/src/components/requestLogs/ExportButton.tsx new file mode 100644 index 0000000..512948b --- /dev/null +++ b/app/src/components/requestLogs/ExportButton.tsx @@ -0,0 +1,197 @@ +import { useState, useEffect } from "react"; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + ModalFooter, + HStack, + VStack, + Icon, + Text, + Button, + Checkbox, + NumberInput, + NumberInputField, + NumberInputStepper, + NumberIncrementStepper, + NumberDecrementStepper, + Collapse, + useDisclosure, + type UseDisclosureReturn, +} from "@chakra-ui/react"; +import { BiExport } from "react-icons/bi"; + +import { useHandledAsyncCallback } from "~/utils/hooks"; +import { api } from "~/utils/api"; +import { useAppStore } from "~/state/store"; +import ActionButton from "./ActionButton"; +import InputDropdown from "../InputDropdown"; +import { FiChevronUp, FiChevronDown } from "react-icons/fi"; + +const SUPPORTED_EXPORT_FORMATS = ["alpaca-finetune", "openai-fine-tune", "unformatted"]; + +const ExportButton = () => { + const selectedLogIds = useAppStore((s) => s.selectedLogs.selectedLogIds); + + const disclosure = useDisclosure(); + + return ( + <> + + + + ); +}; + +export default ExportButton; + +const ExportLogsModal = ({ disclosure }: { disclosure: UseDisclosureReturn }) => { + const selectedProjectId = useAppStore((s) => s.selectedProjectId); + const selectedLogIds = useAppStore((s) => s.selectedLogs.selectedLogIds); + const clearSelectedLogIds = useAppStore((s) => s.selectedLogs.clearSelectedLogIds); + + const [selectedExportFormat, setSelectedExportFormat] = useState(SUPPORTED_EXPORT_FORMATS[0]); + const [testingSplit, setTestingSplit] = useState(10); + const [removeDuplicates, setRemoveDuplicates] = useState(true); + const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); + + useEffect(() => { + if (disclosure.isOpen) { + setSelectedExportFormat(SUPPORTED_EXPORT_FORMATS[0]); + setTestingSplit(10); + setRemoveDuplicates(true); + } + }, [disclosure.isOpen]); + + const exportLogsMutation = api.loggedCalls.export.useMutation(); + + const [exportLogs, exportInProgress] = useHandledAsyncCallback(async () => { + if (!selectedProjectId || !selectedLogIds.size || !testingSplit || !selectedExportFormat) + return; + const response = await exportLogsMutation.mutateAsync({ + projectId: selectedProjectId, + selectedLogIds: Array.from(selectedLogIds), + testingSplit, + selectedExportFormat, + removeDuplicates, + }); + + const dataUrl = `data:application/pdf;base64,${response}`; + const blob = await fetch(dataUrl).then((res) => res.blob()); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + + a.href = url; + a.download = `data.zip`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + disclosure.onClose(); + clearSelectedLogIds(); + }, [ + exportLogsMutation, + selectedProjectId, + selectedLogIds, + testingSplit, + selectedExportFormat, + removeDuplicates, + ]); + + return ( + + + + + + + Export Logs + + + + + + + We'll export the {selectedLogIds.size} logs you have selected in the format of + your choice. + + + + + Format: + + setSelectedExportFormat(option)} + inputGroupProps={{ w: 48 }} + /> + + + + Testing Split: + + + setTestingSplit(num)} + min={1} + max={100} + w={48} + > + + + + + + + + + + + + + + setRemoveDuplicates(e.target.checked)} + > + Remove duplicates? (recommended) + + + + + + + + + + + + + + + ); +}; diff --git a/app/src/pages/request-logs/index.tsx b/app/src/pages/request-logs/index.tsx index 1cd5a4b..4799361 100644 --- a/app/src/pages/request-logs/index.tsx +++ b/app/src/pages/request-logs/index.tsx @@ -11,6 +11,7 @@ import { FiFilter } from "react-icons/fi"; import LogFilters from "~/components/requestLogs/LogFilters/LogFilters"; import ColumnVisiblityDropdown from "~/components/requestLogs/ColumnVisiblityDropdown"; import FineTuneButton from "~/components/requestLogs/FineTuneButton"; +import ExportButton from "~/components/requestLogs/ExportButton"; export default function LoggedCalls() { const selectedLogIds = useAppStore((s) => s.selectedLogs.selectedLogIds); @@ -35,6 +36,7 @@ export default function LoggedCalls() { icon={RiFlaskLine} isDisabled={selectedLogIds.size === 0} /> + { diff --git a/app/src/server/api/routers/loggedCalls.router.ts b/app/src/server/api/routers/loggedCalls.router.ts index 4798b1f..1c0281b 100644 --- a/app/src/server/api/routers/loggedCalls.router.ts +++ b/app/src/server/api/routers/loggedCalls.router.ts @@ -1,11 +1,16 @@ import { z } from "zod"; import { type Expression, type SqlBool, sql, type RawBuilder } from "kysely"; import { jsonArrayFrom } from "kysely/helpers/postgres"; +import archiver from "archiver"; +import { WritableStreamBuffer } from "stream-buffers"; +import { type JsonValue } from "type-fest"; +import { shuffle } from "lodash-es"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { kysely, prisma } from "~/server/db"; import { comparators, defaultFilterableFields } from "~/state/logFiltersSlice"; import { requireCanViewProject } from "~/utils/accessControl"; +import hashObject from "~/server/utils/hashObject"; // create comparator type based off of comparators const comparatorToSqlExpression = (comparator: (typeof comparators)[number], value: string) => { @@ -180,4 +185,101 @@ export const loggedCallsRouter = createTRPCRouter({ return tags.map((tag) => tag.name); }), + export: protectedProcedure + .input( + z.object({ + projectId: z.string(), + selectedLogIds: z.string().array(), + testingSplit: z.number(), + selectedExportFormat: z.string(), + removeDuplicates: z.boolean(), + }), + ) + .mutation(async ({ input, ctx }) => { + await requireCanViewProject(input.projectId, ctx); + + // Fetch the real data using Prisma + const loggedCallsFromDb = await ctx.prisma.loggedCallModelResponse.findMany({ + where: { + originalLoggedCall: { + projectId: input.projectId, + id: { in: input.selectedLogIds }, + }, + }, + }); + + // Convert the database data into the desired format + let formattedLoggedCalls: { input: JsonValue[]; output: JsonValue }[] = loggedCallsFromDb.map( + (call) => ({ + input: (call.reqPayload as unknown as Record).messages as JsonValue[], + output: (call.respPayload as unknown as { choices: { message: unknown }[] }).choices[0] + ?.message as JsonValue, + }), + ); + + if (input.removeDuplicates) { + const deduplicatedLoggedCalls = []; + const loggedCallHashSet = new Set(); + for (const loggedCall of formattedLoggedCalls) { + const loggedCallHash = hashObject(loggedCall); + if (!loggedCallHashSet.has(loggedCallHash)) { + loggedCallHashSet.add(loggedCallHash); + deduplicatedLoggedCalls.push(loggedCall); + } + } + formattedLoggedCalls = deduplicatedLoggedCalls; + } + + // Remove duplicate messages from input + const inputMessageHashMap = new Map(); + for (const loggedCall of formattedLoggedCalls) { + for (const message of loggedCall.input) { + const hash = hashObject(message); + if (inputMessageHashMap.has(hash)) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + inputMessageHashMap.set(hash, inputMessageHashMap.get(hash)! + 1); + } else { + inputMessageHashMap.set(hash, 0); + } + } + } + for (const loggedCall of formattedLoggedCalls) { + loggedCall.input = loggedCall.input.filter((message) => { + const hash = hashObject(message); + // If the same message appears in a single input multiple times, there is some danger of + // it being removed from all logged calls. This is enough of an edge case that we don't + // need to worry about it for now. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return inputMessageHashMap.get(hash)! < formattedLoggedCalls.length; + }); + } + + // Stringify inputs and outputs + const stringifiedLoggedCalls = shuffle(formattedLoggedCalls).map((loggedCall) => ({ + input: JSON.stringify(loggedCall.input), + output: JSON.stringify(loggedCall.output), + })); + + const splitIndex = Math.floor((stringifiedLoggedCalls.length * input.testingSplit) / 100); + + const testingData = stringifiedLoggedCalls.slice(0, splitIndex); + const trainingData = stringifiedLoggedCalls.slice(splitIndex); + + // Convert arrays to JSONL format + const trainingDataJSONL = trainingData.map((item) => JSON.stringify(item)).join("\n"); + const testingDataJSONL = testingData.map((item) => JSON.stringify(item)).join("\n"); + + const output = new WritableStreamBuffer(); + const archive = archiver("zip"); + + archive.pipe(output); + archive.append(trainingDataJSONL, { name: "train.jsonl" }); + archive.append(testingDataJSONL, { name: "test.jsonl" }); + await archive.finalize(); + + // Convert buffer to base64 + const base64 = output.getContents().toString("base64"); + + return base64; + }), }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 73723e0..4368004 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: '@vercel/og': specifier: ^0.5.9 version: 0.5.9 + archiver: + specifier: ^6.0.0 + version: 6.0.0 ast-types: specifier: ^0.14.2 version: 0.14.2 @@ -233,6 +236,9 @@ importers: socket.io-client: specifier: ^4.7.1 version: 4.7.1 + stream-buffers: + specifier: ^3.0.2 + version: 3.0.2 superjson: specifier: 1.12.2 version: 1.12.2 @@ -264,6 +270,9 @@ importers: '@openapi-contrib/openapi-schema-to-json-schema': specifier: ^4.0.5 version: 4.0.5 + '@types/archiver': + specifier: ^5.3.2 + version: 5.3.2 '@types/babel__core': specifier: ^7.20.1 version: 7.20.1 @@ -312,6 +321,9 @@ importers: '@types/react-syntax-highlighter': specifier: ^15.5.7 version: 15.5.7 + '@types/stream-buffers': + specifier: ^3.0.4 + version: 3.0.4 '@types/uuid': specifier: ^9.0.2 version: 9.0.2 @@ -2950,6 +2962,12 @@ packages: resolution: {integrity: sha512-+Wt0NFAeflVSNiUnHIDNN3C8jP7XIRmYrcgJ6IsAnm0lK4p/FkpCpeu1aig5qxrgZx30PHNDLZ/3FttVSEW2aQ==} dev: false + /@types/archiver@5.3.2: + resolution: {integrity: sha512-IctHreBuWE5dvBDz/0WeKtyVKVRs4h75IblxOACL92wU66v+HGAfEYAOyXkOFphvRJMhuXdI9huDXpX0FC6lCw==} + dependencies: + '@types/readdir-glob': 1.1.1 + dev: true + /@types/babel__core@7.20.1: resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==} dependencies: @@ -3265,6 +3283,12 @@ packages: '@types/scheduler': 0.16.3 csstype: 3.1.2 + /@types/readdir-glob@1.1.1: + resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==} + dependencies: + '@types/node': 20.4.10 + dev: true + /@types/request@2.48.8: resolution: {integrity: sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==} dependencies: @@ -3296,6 +3320,12 @@ packages: '@types/node': 20.4.10 dev: true + /@types/stream-buffers@3.0.4: + resolution: {integrity: sha512-qU/K1tb2yUdhXkLIATzsIPwbtX6BpZk0l3dPW6xqWyhfzzM1ECaQ/8faEnu3CNraLiQ9LHyQQPBGp7N9Fbs25w==} + dependencies: + '@types/node': 20.4.10 + dev: true + /@types/tough-cookie@4.0.2: resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==} dev: false @@ -3699,6 +3729,51 @@ packages: picomatch: 2.3.1 dev: false + /archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + dev: false + + /archiver-utils@3.0.3: + resolution: {integrity: sha512-fXzpEZTKgBJMWy0eUT0/332CAQnJ27OJd7sGcvNZzxS2Yzg7iITivMhXOm+zUTO4vT8ZqlPCqiaLPmB8qWhWRA==} + engines: {node: '>= 10'} + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + dev: false + + /archiver@6.0.0: + resolution: {integrity: sha512-EPGa+bYaxaMiCT8DCbEDqFz8IjeBSExrJzyUOJx2FBkFJ/OZzJuso3lMSk901M50gMqXxTQcumlGajOFlXhVhw==} + engines: {node: '>= 12.0.0'} + dependencies: + archiver-utils: 3.0.3 + async: 3.2.4 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.0 + dev: false + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -3837,6 +3912,10 @@ packages: tslib: 2.6.1 dev: false + /async@3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + dev: false + /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -3956,6 +4035,14 @@ packages: engines: {node: '>=8'} dev: false + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + /bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} dev: false @@ -4008,6 +4095,10 @@ packages: node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: false + /buffer-from@0.1.2: resolution: {integrity: sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==} dev: false @@ -4020,6 +4111,13 @@ packages: engines: {node: '>=4'} dev: false + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + /busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -4246,6 +4344,16 @@ packages: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} dev: false + /compress-commons@4.1.1: + resolution: {integrity: sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==} + engines: {node: '>= 10'} + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.2 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + dev: false + /compute-scroll-into-view@1.0.20: resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} dev: false @@ -4353,6 +4461,20 @@ packages: yaml: 1.10.2 dev: false + /crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + dev: false + + /crc32-stream@4.0.2: + resolution: {integrity: sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==} + engines: {node: '>= 10'} + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + dev: false + /create-emotion@10.0.27: resolution: {integrity: sha512-fIK73w82HPPn/RsAij7+Zt8eCE8SptcJ3WoRMfxMtjteYxud8GDTKKld7MYwAX2TVhrw29uR1N/bVGxeStHILg==} dependencies: @@ -4735,6 +4857,12 @@ packages: iconv-lite: 0.6.3 dev: false + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: false + /engine.io-client@6.5.2: resolution: {integrity: sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==} dependencies: @@ -5577,6 +5705,10 @@ packages: engines: {node: '>= 0.6'} dev: false + /fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + dev: false + /fs-extra@11.1.1: resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} engines: {node: '>=14.14'} @@ -5969,6 +6101,10 @@ packages: safer-buffer: 2.1.2 dev: false + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: false + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} @@ -6434,6 +6570,13 @@ packages: language-subtag-registry: 0.3.22 dev: true + /lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + dependencies: + readable-stream: 2.3.8 + dev: false + /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -6502,6 +6645,22 @@ packages: resolution: {integrity: sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==} dev: false + /lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + dev: false + + /lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + dev: false + + /lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + dev: false + + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: false + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -6510,6 +6669,10 @@ packages: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} dev: false + /lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + dev: false + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: false @@ -7855,6 +8018,21 @@ packages: util-deprecate: 1.0.2 dev: false + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: false + + /readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + dependencies: + minimatch: 5.1.6 + dev: false + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -8310,6 +8488,11 @@ packages: resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==} dev: true + /stream-buffers@3.0.2: + resolution: {integrity: sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==} + engines: {node: '>= 0.10.0'} + dev: false + /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -8457,6 +8640,17 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + /tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + /terser-webpack-plugin@5.3.9(webpack@5.88.2): resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} @@ -9341,6 +9535,15 @@ packages: resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} dev: false + /zip-stream@4.1.0: + resolution: {integrity: sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==} + engines: {node: '>= 10'} + dependencies: + archiver-utils: 2.1.0 + compress-commons: 4.1.1 + readable-stream: 3.6.2 + dev: false + /zod-to-json-schema@3.21.4(zod@3.21.4): resolution: {integrity: sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw==} peerDependencies: