Add content moderation

This commit is contained in:
Luke Vella 2025-03-03 19:26:38 +00:00
parent a8bcde45b3
commit b8df9ebeef
No known key found for this signature in database
GPG key ID: 469CAD687F0D784C
8 changed files with 337 additions and 6 deletions

View file

@ -18,6 +18,7 @@
"docker:start": "./scripts/docker-start.sh"
},
"dependencies": {
"@ai-sdk/openai": "^1.2.0",
"@auth/prisma-adapter": "^2.7.4",
"@aws-sdk/client-s3": "^3.645.0",
"@aws-sdk/s3-request-presigner": "^3.645.0",
@ -47,6 +48,7 @@
"@vercel/functions": "^1.5.2",
"@vercel/kv": "^2.0.0",
"accept-language-parser": "^1.5.0",
"ai": "^4.1.50",
"autoprefixer": "^10.4.13",
"calendar-link": "^2.6.0",
"class-variance-authority": "^0.7.0",
@ -70,6 +72,7 @@
"nanoid": "^5.0.9",
"next-auth": "^5.0.0-beta.25",
"next-i18next": "^13.0.3",
"openai": "^4.86.1",
"php-serialize": "^4.1.1",
"postcss": "^8.4.31",
"react-big-calendar": "^1.8.1",

View file

@ -9,6 +9,7 @@ import {
CardTitle,
} from "@rallly/ui/card";
import { Form } from "@rallly/ui/form";
import { useToast } from "@rallly/ui/hooks/use-toast";
import { useRouter } from "next/navigation";
import { signIn, useSession } from "next-auth/react";
import React from "react";
@ -42,6 +43,7 @@ export interface CreatePollPageProps {
export const CreatePoll: React.FunctionComponent = () => {
const router = useRouter();
const { user } = useUser();
const { toast } = useToast();
const session = useSession();
const form = useForm<NewEventData>({
defaultValues: {
@ -74,6 +76,14 @@ export const CreatePoll: React.FunctionComponent = () => {
});
}
},
onError: (error) => {
if (error.data?.code === "BAD_REQUEST") {
toast({
title: "Error",
description: error.message,
});
}
},
});
return (
@ -136,9 +146,7 @@ export const CreatePoll: React.FunctionComponent = () => {
<PollSettingsForm />
<hr />
<Button
loading={
form.formState.isSubmitting || form.formState.isSubmitSuccessful
}
loading={createPoll.isLoading || createPoll.isSuccess}
size="lg"
type="submit"
className="w-full"

View file

@ -1,4 +1,5 @@
import { usePostHog } from "@rallly/posthog/client";
import { useToast } from "@rallly/ui/hooks/use-toast";
import { signIn, useSession } from "next-auth/react";
import { usePoll } from "@/components/poll-context";
@ -101,11 +102,20 @@ export const useDeleteParticipantMutation = () => {
export const useUpdatePollMutation = () => {
const posthog = usePostHog();
const { toast } = useToast();
return trpc.polls.update.useMutation({
onSuccess: (_data, { urlId }) => {
posthog?.capture("updated poll", {
id: urlId,
});
},
onError: (error) => {
if (error.data?.code === "BAD_REQUEST") {
toast({
title: "Error",
description: error.message,
});
}
},
});
};

View file

@ -64,6 +64,11 @@ export const env = createEnv({
S3_ACCESS_KEY_ID: z.string().optional(),
S3_SECRET_ACCESS_KEY: z.string().optional(),
S3_REGION: z.string().optional(),
/**
* OpenAI Configuration for AI moderation
*/
OPENAI_API_KEY: z.string().optional(),
},
/*
* Environment variables available on the client (and server).
@ -113,6 +118,7 @@ export const env = createEnv({
SUPPORT_EMAIL: process.env.SUPPORT_EMAIL,
NOREPLY_EMAIL: process.env.NOREPLY_EMAIL,
NOREPLY_EMAIL_NAME: process.env.NOREPLY_EMAIL_NAME,
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
},
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
});

View file

@ -9,6 +9,7 @@ import * as ics from "ics";
import { z } from "zod";
import { getEmailClient } from "@/utils/emails";
import { moderateContent } from "@/utils/moderation";
import { getTimeZoneAbbreviation } from "../../utils/date";
import {
@ -156,6 +157,21 @@ export const polls = router({
const participantUrlId = nanoid();
const pollId = nanoid();
const isFlaggedContent = await moderateContent(
input.title,
input.description,
);
if (isFlaggedContent) {
console.warn(
`User ${ctx.user.id} attempted to create flagged content: ${input.title}`,
);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Content is flagged as spam",
});
}
const poll = await prisma.poll.create({
select: {
adminUrlId: true,
@ -233,6 +249,7 @@ export const polls = router({
return { id: poll.id };
}),
update: possiblyPublicProcedure
.use(requireUserMiddleware)
.input(
z.object({
urlId: z.string(),
@ -249,9 +266,23 @@ export const polls = router({
requireParticipantEmail: z.boolean().optional(),
}),
)
.mutation(async ({ input }) => {
.mutation(async ({ ctx, input }) => {
const pollId = await getPollIdFromAdminUrlId(input.urlId);
const isFlaggedContent = await moderateContent(
input.title,
input.description,
);
if (isFlaggedContent) {
console.warn(
`User ${ctx.user.id} attempted to create flagged content: ${input.title}`,
);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Content is flagged as spam",
});
}
if (input.optionsToDelete && input.optionsToDelete.length > 0) {
await prisma.option.deleteMany({
where: {

View file

@ -0,0 +1,72 @@
import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { env } from "@/env";
async function moderateContentWithAI(text: string) {
const result = await generateText({
model: openai("gpt-3.5-turbo"),
messages: [
{
role: "system",
content:
"You are a content moderator. Analyze the following text and determine if it is advertising illegal drugs or promoting prostitution. Respond with 'FLAGGED' if detected, otherwise 'SAFE'.",
},
{ role: "user", content: text },
],
});
return result.text.trim() === "FLAGGED";
}
// Custom pattern-based checks
function containsSuspiciousPatterns(text: string) {
if (!text) return false;
// Check for ALL CAPS (if more than 5 consecutive capital letters)
const allCapsPattern = /[A-Z]{5,}/;
// Check for excessive special characters
const excessiveSpecialCharsPattern =
/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]{4,}/;
// Check for repetitive characters
const repetitiveCharsPattern = /(.)\1{4,}/;
// Check for suspicious URLs
const suspiciousUrlPattern = /(bit\.ly|tinyurl|goo\.gl|t\.co|is\.gd)/i;
// Check for potential contact information
const contactInfoPattern =
/(\+\d{1,3}[-.\s]?)?(\d{3}[-.\s]?)?\d{3}[-.\s]?\d{4}|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/i;
return (
allCapsPattern.test(text) ||
excessiveSpecialCharsPattern.test(text) ||
repetitiveCharsPattern.test(text) ||
suspiciousUrlPattern.test(text) ||
contactInfoPattern.test(text)
);
}
export async function moderateContent(...content: Array<string | undefined>) {
if (!env.OPENAI_API_KEY) {
console.info("OPENAI_API_KEY not set, skipping moderation");
return true;
}
const textToModerate = content.filter(Boolean).join("\n");
const hasSuspiciousPatterns = containsSuspiciousPatterns(textToModerate);
if (hasSuspiciousPatterns) {
try {
return moderateContentWithAI(textToModerate);
} catch (error) {
console.error(error);
return false;
}
}
return true;
}

View file

@ -96,6 +96,7 @@
"OIDC_NAME_CLAIM_PATH",
"OIDC_NAME",
"OIDC_PICTURE_CLAIM_PATH",
"OPENAI_API_KEY",
"PORT",
"SECRET_PASSWORD",
"SENTRY_AUTH_TOKEN",

204
yarn.lock
View file

@ -7,6 +7,50 @@
resolved "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz"
integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
"@ai-sdk/openai@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@ai-sdk/openai/-/openai-1.2.0.tgz#a7199f66d1797ebf193900922520503dbfbe2c11"
integrity sha512-tzxH6OxKL5ffts4zJPdziQSJGGpSrQcJmuSrE92jCt7pJ4PAU5Dx4tjNNFIU8lSfwarLnywejZEt3Fz0uQZZOQ==
dependencies:
"@ai-sdk/provider" "1.0.9"
"@ai-sdk/provider-utils" "2.1.10"
"@ai-sdk/provider-utils@2.1.10":
version "2.1.10"
resolved "https://registry.yarnpkg.com/@ai-sdk/provider-utils/-/provider-utils-2.1.10.tgz#dfd671ccda12e321b58f347b6cbbdd982d139359"
integrity sha512-4GZ8GHjOFxePFzkl3q42AU0DQOtTQ5w09vmaWUf/pKFXJPizlnzKSUkF0f+VkapIUfDugyMqPMT1ge8XQzVI7Q==
dependencies:
"@ai-sdk/provider" "1.0.9"
eventsource-parser "^3.0.0"
nanoid "^3.3.8"
secure-json-parse "^2.7.0"
"@ai-sdk/provider@1.0.9":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@ai-sdk/provider/-/provider-1.0.9.tgz#01c2e69df8258aed5b1fe08fba380133a645800f"
integrity sha512-jie6ZJT2ZR0uVOVCDc9R2xCX5I/Dum/wEK28lx21PJx6ZnFAN9EzD2WsPhcDWfCgGx3OAZZ0GyM3CEobXpa9LA==
dependencies:
json-schema "^0.4.0"
"@ai-sdk/react@1.1.20":
version "1.1.20"
resolved "https://registry.yarnpkg.com/@ai-sdk/react/-/react-1.1.20.tgz#1611177c1c253726d562ba68b00f77ca680667dd"
integrity sha512-4QOM9fR9SryaRraybckDjrhl1O6XejqELdKmrM5g9y9eLnWAfjwF+W1aN0knkSHzbbjMqN77sy9B9yL8EuJbDw==
dependencies:
"@ai-sdk/provider-utils" "2.1.10"
"@ai-sdk/ui-utils" "1.1.16"
swr "^2.2.5"
throttleit "2.1.0"
"@ai-sdk/ui-utils@1.1.16":
version "1.1.16"
resolved "https://registry.yarnpkg.com/@ai-sdk/ui-utils/-/ui-utils-1.1.16.tgz#0159425d41b7737f406103174e20c0843f140d2f"
integrity sha512-jfblR2yZVISmNK2zyNzJZFtkgX57WDAUQXcmn3XUBJyo8LFsADu+/vYMn5AOyBi9qJT0RBk11PEtIxIqvByw3Q==
dependencies:
"@ai-sdk/provider" "1.0.9"
"@ai-sdk/provider-utils" "2.1.10"
zod-to-json-schema "^3.24.1"
"@alloc/quick-lru@^5.2.0":
version "5.2.0"
resolved "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz"
@ -3198,7 +3242,7 @@
dependencies:
"@opentelemetry/api" "^1.3.0"
"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.8", "@opentelemetry/api@^1.9.0":
"@opentelemetry/api@1.9.0", "@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.8", "@opentelemetry/api@^1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe"
integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==
@ -6316,6 +6360,11 @@
dependencies:
"@types/ms" "*"
"@types/diff-match-patch@^1.0.36":
version "1.0.36"
resolved "https://registry.yarnpkg.com/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz#dcef10a69d357fe9d43ac4ff2eca6b85dbf466af"
integrity sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==
"@types/estree-jsx@^1.0.0":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18"
@ -6467,6 +6516,14 @@
dependencies:
"@types/node" "*"
"@types/node-fetch@^2.6.4":
version "2.6.12"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03"
integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==
dependencies:
"@types/node" "*"
form-data "^4.0.0"
"@types/node@*":
version "20.8.9"
resolved "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz"
@ -6491,6 +6548,13 @@
resolved "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz"
integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==
"@types/node@^18.11.18":
version "18.19.79"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.79.tgz#82fde7ac17809f4738a494b22273f0f7e6754f6e"
integrity sha512-90K8Oayimbctc5zTPHPfZloc/lGVs7f3phUAAMcTgEPtg8kKquGZDERC8K4vkBYkQQh48msiYUslYtxTWvqcAg==
dependencies:
undici-types "~5.26.4"
"@types/node@^18.15.10":
version "18.15.10"
resolved "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz"
@ -7038,6 +7102,13 @@ abbrev@^2.0.0:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf"
integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"
accept-language-parser@^1.5.0:
version "1.5.0"
resolved "https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz"
@ -7138,6 +7209,25 @@ agent-base@6:
dependencies:
debug "4"
agentkeepalive@^4.2.1:
version "4.6.0"
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a"
integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==
dependencies:
humanize-ms "^1.2.1"
ai@^4.1.50:
version "4.1.50"
resolved "https://registry.yarnpkg.com/ai/-/ai-4.1.50.tgz#518ad651775e52a16a56062680da315bd711d8be"
integrity sha512-YBNeemrJKDrxoBQd3V9aaxhKm5q5YyRcF7PZE7W0NmLuvsdva/1aQNYTAsxs47gQFdvqfYmlFy4B0E+356OlPA==
dependencies:
"@ai-sdk/provider" "1.0.9"
"@ai-sdk/provider-utils" "2.1.10"
"@ai-sdk/react" "1.1.20"
"@ai-sdk/ui-utils" "1.1.16"
"@opentelemetry/api" "1.9.0"
jsondiffpatch "0.6.0"
ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
@ -7726,6 +7816,11 @@ chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^5.3.0:
version "5.4.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8"
integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==
character-entities-html4@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz"
@ -8343,6 +8438,11 @@ didyoumean@^1.2.2:
resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz"
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
diff-match-patch@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz"
@ -9243,11 +9343,21 @@ esutils@^2.0.2:
resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
events@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
eventsource-parser@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.0.tgz#9303e303ef807d279ee210a17ce80f16300d9f57"
integrity sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==
expect-type@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.1.0.tgz#a146e414250d13dfc49eafcfd1344a4060fa4c75"
@ -9440,6 +9550,11 @@ foreground-child@^3.1.0:
cross-spawn "^7.0.0"
signal-exit "^4.0.1"
form-data-encoder@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040"
integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
@ -9449,6 +9564,14 @@ form-data@^4.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
formdata-node@^4.3.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2"
integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==
dependencies:
node-domexception "1.0.0"
web-streams-polyfill "4.0.0-beta.3"
forwarded-parse@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/forwarded-parse/-/forwarded-parse-2.1.2.tgz#08511eddaaa2ddfd56ba11138eee7df117a09325"
@ -9956,6 +10079,13 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==
dependencies:
ms "^2.0.0"
hyphenate-style-name@^1.0.3:
version "1.0.4"
resolved "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz"
@ -10658,6 +10788,11 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
@ -10675,6 +10810,15 @@ json5@^2.2.2, json5@^2.2.3:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsondiffpatch@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz#daa6a25bedf0830974c81545568d5f671c82551f"
integrity sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==
dependencies:
"@types/diff-match-patch" "^1.0.36"
chalk "^5.3.0"
diff-match-patch "^1.0.5"
"jsx-ast-utils@^2.4.1 || ^3.0.0":
version "3.3.3"
resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz"
@ -11488,7 +11632,7 @@ ms@2.1.2:
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.1, ms@^2.1.3:
ms@^2.0.0, ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@ -11526,6 +11670,11 @@ nanoid@^3.1.23, nanoid@^3.3.6, nanoid@^3.3.7:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
nanoid@^3.3.8:
version "3.3.8"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
nanoid@^5.0.9:
version "5.0.9"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.9.tgz#977dcbaac055430ce7b1e19cf0130cea91a20e50"
@ -11637,6 +11786,11 @@ nice-try@^1.0.4:
resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-domexception@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-fetch@^2.6.7:
version "2.6.9"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz"
@ -11839,6 +11993,19 @@ onetime@^5.1.0:
dependencies:
mimic-fn "^2.1.0"
openai@^4.86.1:
version "4.86.1"
resolved "https://registry.yarnpkg.com/openai/-/openai-4.86.1.tgz#4147252d5e6255e2ae716ea59b1d4e54a1c1472a"
integrity sha512-x3iCLyaC3yegFVZaxOmrYJjitKxZ9hpVbLi+ZlT5UHuHTMlEQEbKXkGOM78z9qm2T5GF+XRUZCP2/aV4UPFPJQ==
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"
opener@^1.5.2:
version "1.5.2"
resolved "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz"
@ -13106,6 +13273,11 @@ section-matter@^1.0.0:
extend-shallow "^2.0.1"
kind-of "^6.0.0"
secure-json-parse@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862"
integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==
selderee@^0.11.0:
version "0.11.0"
resolved "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz"
@ -13732,6 +13904,14 @@ svgo@^2.8.0:
picocolors "^1.0.0"
stable "^0.1.8"
swr@^2.2.5:
version "2.3.2"
resolved "https://registry.yarnpkg.com/swr/-/swr-2.3.2.tgz#e7c4eb7115e7ff734e445ad0036e97812dd95191"
integrity sha512-RosxFpiabojs75IwQ316DGoDRmOqtiAj0tg8wCcbEu4CiLZBs/a9QNtHV7TUfDXmmlgqij/NqzKq/eLelyv9xA==
dependencies:
dequal "^2.0.3"
use-sync-external-store "^1.4.0"
tailwind-merge@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.2.0.tgz#b6bb1c63ef26283c9e6675ba237df83bbd554688"
@ -13860,6 +14040,11 @@ throttle-debounce@^3.0.1:
resolved "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz"
integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==
throttleit@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-2.1.0.tgz#a7e4aa0bf4845a5bd10daa39ea0c783f631a07b4"
integrity sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==
through2-filter@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz"
@ -14389,6 +14574,11 @@ use-sync-external-store@^1.2.0:
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
use-sync-external-store@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc"
integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
@ -14580,6 +14770,11 @@ wcwidth@^1.0.1:
dependencies:
defaults "^1.0.3"
web-streams-polyfill@4.0.0-beta.3:
version "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-vitals@^4.2.0:
version "4.2.4"
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.4.tgz#1d20bc8590a37769bd0902b289550936069184b7"
@ -14841,6 +15036,11 @@ yup@^0.32.9:
property-expr "^2.0.4"
toposort "^2.0.2"
zod-to-json-schema@^3.24.1:
version "3.24.3"
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.3.tgz#5958ba111d681f8d01c5b6b647425c9b8a6059e7"
integrity sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==
zod@^3.23.8:
version "3.23.8"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"