From f39c4ac4a7a56ff7a6af76f29d71279c059def50 Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Wed, 30 Oct 2024 13:02:36 +0000 Subject: [PATCH 01/61] chore: linting strings to match the new linting rules (#302) --- .../quiz-designer/convertToGIFTFormat.ts | 2 +- apps/nextjs/src/app/actions.ts | 4 +- apps/nextjs/src/app/aila/help/index.tsx | 2 +- apps/nextjs/src/app/layout.tsx | 2 +- apps/nextjs/src/app/manifest.ts | 61 ++++++++++--------- apps/nextjs/src/components/Feedback/index.tsx | 10 +-- apps/nextjs/src/hooks/useDemoLocking.ts | 2 +- packages/api/src/middleware/adminAuth.ts | 4 +- packages/api/src/middleware/auth.ts | 2 +- packages/api/src/middleware/rateLimiter.ts | 2 +- packages/api/src/router/admin.ts | 9 +-- packages/api/src/router/appSessions.ts | 11 ++-- .../analytics/posthogAiBetaServerClient.ts | 4 +- .../requestImageDescription.ts | 3 +- .../src/functions/slack/notifyRateLimit.ts | 2 +- .../parts/interactingWithTheUser.ts | 16 ++--- .../lesson-assistant/parts/protocol.ts | 2 +- .../prompts/lesson-assistant/parts/schema.ts | 2 +- .../variants/main/parts/output.ts | 2 +- .../variants/main/parts/output.ts | 2 +- .../import-new-lessons/importNewLessons.ts | 4 +- .../workers/generations/requestGeneration.ts | 22 +++---- .../src/import-lessons/graphql/client.ts | 2 +- packages/ingest/src/steps/0-start.ts | 4 +- 24 files changed, 84 insertions(+), 92 deletions(-) diff --git a/apps/nextjs/src/ai-apps/quiz-designer/convertToGIFTFormat.ts b/apps/nextjs/src/ai-apps/quiz-designer/convertToGIFTFormat.ts index d77ec7350..f7eb577e9 100644 --- a/apps/nextjs/src/ai-apps/quiz-designer/convertToGIFTFormat.ts +++ b/apps/nextjs/src/ai-apps/quiz-designer/convertToGIFTFormat.ts @@ -29,7 +29,7 @@ export function convertToGIFTFormat( } } - giftString += `\n}\n`; + giftString += "\n}\n"; } return giftString; diff --git a/apps/nextjs/src/app/actions.ts b/apps/nextjs/src/app/actions.ts index 6bd363009..7cc3bb9b5 100644 --- a/apps/nextjs/src/app/actions.ts +++ b/apps/nextjs/src/app/actions.ts @@ -16,7 +16,7 @@ function parseChatAndReportError({ userId: string; }): AilaPersistedChat | undefined { if (typeof sessionOutput !== "object") { - throw new Error(`sessionOutput is not an object`); + throw new Error("sessionOutput is not an object"); } const parseResult = chatSchema.safeParse({ ...sessionOutput, @@ -25,7 +25,7 @@ function parseChatAndReportError({ }); if (!parseResult.success) { - const error = new Error(`Failed to parse chat`); + const error = new Error("Failed to parse chat"); Sentry.captureException(error, { extra: { id, diff --git a/apps/nextjs/src/app/aila/help/index.tsx b/apps/nextjs/src/app/aila/help/index.tsx index a58146343..649a9431d 100644 --- a/apps/nextjs/src/app/aila/help/index.tsx +++ b/apps/nextjs/src/app/aila/help/index.tsx @@ -99,7 +99,7 @@ const Help = () => {
Back to Aila diff --git a/apps/nextjs/src/app/layout.tsx b/apps/nextjs/src/app/layout.tsx index 5c476fa6d..70fb045da 100644 --- a/apps/nextjs/src/app/layout.tsx +++ b/apps/nextjs/src/app/layout.tsx @@ -44,7 +44,7 @@ export const metadata = { metadataBase: new URL(vercel_url), title: { default: "Oak AI Experiments", - template: `%s - AI Lesson Planner`, + template: "%s - AI Lesson Planner", }, description: "Oak AI experiments offers some experimental generative AI tools designed for and freely available to teachers. We are actively looking for your feedback to refine and optimise these tools, making them more effective and time-saving.", diff --git a/apps/nextjs/src/app/manifest.ts b/apps/nextjs/src/app/manifest.ts index 90755d770..17d91bd8d 100644 --- a/apps/nextjs/src/app/manifest.ts +++ b/apps/nextjs/src/app/manifest.ts @@ -1,45 +1,46 @@ -import type { MetadataRoute } from 'next' - +import type { MetadataRoute } from "next"; + export default function manifest(): MetadataRoute.Manifest { return { name: "Aila: Oak's AI Lesson Assistant", - short_name: 'Aila', - description: 'An AI lesson assistant chatbot for UK teachers to create lessons personalised for their classes, with the aim of reducing teacher workload.', - start_url: '/', - display: 'minimal-ui', - background_color: '#BEF2BD', - theme_color: '#BEF2BD', + short_name: "Aila", + description: + "An AI lesson assistant chatbot for UK teachers to create lessons personalised for their classes, with the aim of reducing teacher workload.", + start_url: "/", + display: "minimal-ui", + background_color: "#BEF2BD", + theme_color: "#BEF2BD", icons: [ { - "src": "/favicon/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" + src: "/favicon/android-chrome-192x192.png", + sizes: "192x192", + type: "image/png", }, { - "src": "/favicon/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" + src: "/favicon/android-chrome-512x512.png", + sizes: "512x512", + type: "image/png", }, { - "src": "/favicon/apple-touch-icon.png", - "sizes": "180x180", - "type": "image/png" + src: "/favicon/apple-touch-icon.png", + sizes: "180x180", + type: "image/png", }, { - "src": "/favicon/favicon-16x16.png", - "sizes": "16x16", - "type": "image/png" + src: "/favicon/favicon-16x16.png", + sizes: "16x16", + type: "image/png", }, { - "src": "/favicon/favicon-32x32.png", - "sizes": "32x32", - "type": "image/png" + src: "/favicon/favicon-32x32.png", + sizes: "32x32", + type: "image/png", }, { - "src": "/favicon/favicon.ico", - "sizes": "48x48 16x16 32x32", - "type": "image/x-icon" - } - ] - } -} \ No newline at end of file + src: "/favicon/favicon.ico", + sizes: "48x48 16x16 32x32", + type: "image/x-icon", + }, + ], + }; +} diff --git a/apps/nextjs/src/components/Feedback/index.tsx b/apps/nextjs/src/components/Feedback/index.tsx index 61be0aec2..8f9f32a7c 100644 --- a/apps/nextjs/src/components/Feedback/index.tsx +++ b/apps/nextjs/src/components/Feedback/index.tsx @@ -50,7 +50,7 @@ const FeedBack = ({ // first survey key should be $survey_response // see https://posthog.com/docs/surveys/implementing-custom-surveys under Capturing multiple responses const surveyResponseKey = - i === 0 ? `$survey_response` : `$survey_response_${i}`; + i === 0 ? "$survey_response" : `$survey_response_${i}`; if (question.type === "rating") { return (
{ setUsersResponse((prevState) => ({ ...prevState, @@ -83,8 +85,8 @@ const FeedBack = ({ className={`rounded-sm border-2 p-8 px-9 text-lg sm:px-15 ${ usersResponse[surveyResponseKey] === feedback.number.toString() - ? `border-black bg-black text-white` - : ` border-oakGrey3 bg-white text-black` + ? "border-black bg-black text-white" + : " border-oakGrey3 bg-white text-black" }`} > {feedback.number} diff --git a/apps/nextjs/src/hooks/useDemoLocking.ts b/apps/nextjs/src/hooks/useDemoLocking.ts index d0d68c40e..815309601 100644 --- a/apps/nextjs/src/hooks/useDemoLocking.ts +++ b/apps/nextjs/src/hooks/useDemoLocking.ts @@ -31,7 +31,7 @@ const DEMO_MESSAGES_AFTER_COMPLETE = parseInt( ); const stateLineHasAllSections = (line: string) => { - if (!line.includes(`"type":"state"`)) { + if (!line.includes('"type":"state"')) { return false; } diff --git a/packages/api/src/middleware/adminAuth.ts b/packages/api/src/middleware/adminAuth.ts index 0c85c8b7c..1ff69a4b1 100644 --- a/packages/api/src/middleware/adminAuth.ts +++ b/packages/api/src/middleware/adminAuth.ts @@ -15,7 +15,7 @@ const log = aiLogger("auth"); */ const isAdminMiddleware = t.middleware(async ({ next, ctx }) => { if (!ctx.auth.userId) { - log.info({ auth: ctx.auth, url: ctx.req.url }, `User not an admin`); + log.info({ auth: ctx.auth, url: ctx.req.url }, "User not an admin"); throw new TRPCError({ code: "UNAUTHORIZED", message: "Not an admin", @@ -29,7 +29,7 @@ const isAdminMiddleware = t.middleware(async ({ next, ctx }) => { email.emailAddress.endsWith("@thenational.academy"), ) ) { - log.info({ auth: ctx.auth, url: ctx.req.url }, `User not an admin`); + log.info({ auth: ctx.auth, url: ctx.req.url }, "User not an admin"); throw new TRPCError({ code: "UNAUTHORIZED", message: "Not an admin", diff --git a/packages/api/src/middleware/auth.ts b/packages/api/src/middleware/auth.ts index c29814503..82da78603 100644 --- a/packages/api/src/middleware/auth.ts +++ b/packages/api/src/middleware/auth.ts @@ -9,7 +9,7 @@ const log = aiLogger("auth"); export const isLoggedInMiddleware = t.middleware(async ({ next, ctx }) => { if (!ctx.auth.userId) { - log.info({ auth: ctx.auth, url: ctx.req.url }, `User not authenticated`); + log.info({ auth: ctx.auth, url: ctx.req.url }, "User not authenticated"); throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authenticated", diff --git a/packages/api/src/middleware/rateLimiter.ts b/packages/api/src/middleware/rateLimiter.ts index c1b7a14e2..f3e69be07 100644 --- a/packages/api/src/middleware/rateLimiter.ts +++ b/packages/api/src/middleware/rateLimiter.ts @@ -46,7 +46,7 @@ function createRateLimiterMiddleware(rateLimiter: RateLimiter) { }); throw new TRPCError({ code: "TOO_MANY_REQUESTS", - message: `Too many requests, please try again later.`, + message: "Too many requests, please try again later.", cause: e, }); } diff --git a/packages/api/src/router/admin.ts b/packages/api/src/router/admin.ts index 619ba966b..c5f06298f 100644 --- a/packages/api/src/router/admin.ts +++ b/packages/api/src/router/admin.ts @@ -4,11 +4,8 @@ import { aiLogger } from "@oakai/logger"; import { z } from "zod"; import { getSessionModerations } from "../../../aila/src/features/moderation/getSessionModerations"; -import type { - AilaPersistedChat} from "../../../aila/src/protocol/schema"; -import { - chatSchema, -} from "../../../aila/src/protocol/schema"; +import type { AilaPersistedChat } from "../../../aila/src/protocol/schema"; +import { chatSchema } from "../../../aila/src/protocol/schema"; import { adminProcedure } from "../middleware/adminAuth"; import { router } from "../trpc"; @@ -43,7 +40,7 @@ export const adminRouter = router({ const output = chatRecord.output; if (typeof output !== "object") { - throw new Error(`sessionOutput is not an object`); + throw new Error("sessionOutput is not an object"); } const parseResult = chatSchema.safeParse({ ...output, diff --git a/packages/api/src/router/appSessions.ts b/packages/api/src/router/appSessions.ts index 653c22a39..ce46e6572 100644 --- a/packages/api/src/router/appSessions.ts +++ b/packages/api/src/router/appSessions.ts @@ -11,11 +11,8 @@ import { z } from "zod"; import { getSessionModerations } from "../../../aila/src/features/moderation/getSessionModerations"; import { generateChatId } from "../../../aila/src/helpers/chat/generateChatId"; -import type { - AilaPersistedChat} from "../../../aila/src/protocol/schema"; -import { - chatSchema, -} from "../../../aila/src/protocol/schema"; +import type { AilaPersistedChat } from "../../../aila/src/protocol/schema"; +import { chatSchema } from "../../../aila/src/protocol/schema"; import { protectedProcedure } from "../middleware/auth"; import { router } from "../trpc"; @@ -33,7 +30,7 @@ function parseChatAndReportError({ userId: string; }) { if (typeof sessionOutput !== "object") { - throw new Error(`sessionOutput is not an object`); + throw new Error("sessionOutput is not an object"); } const parseResult = chatSchema.safeParse({ ...sessionOutput, @@ -42,7 +39,7 @@ function parseChatAndReportError({ }); if (!parseResult.success) { - const error = new Error(`Failed to parse chat`); + const error = new Error("Failed to parse chat"); Sentry.captureException(error, { extra: { id, diff --git a/packages/core/src/analytics/posthogAiBetaServerClient.ts b/packages/core/src/analytics/posthogAiBetaServerClient.ts index 4d80e0ad2..3e7f23ef4 100644 --- a/packages/core/src/analytics/posthogAiBetaServerClient.ts +++ b/packages/core/src/analytics/posthogAiBetaServerClient.ts @@ -4,10 +4,10 @@ const host = process.env.NEXT_PUBLIC_POSTHOG_HOST as string; const apiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY || "*"; /** - * This is the posthog nodejs client cofigured to send events to the + * This is the posthog nodejs client configured to send events to the * posthog AI BETA instance. * * This is the main Oak posthog instance used for tracking user events - * on the clientside. + * on the client-side. */ export const posthogAiBetaServerClient = new PostHog(apiKey, { host }); diff --git a/packages/core/src/functions/generation/imageAltTextRequestGeneration.ts/requestImageDescription.ts b/packages/core/src/functions/generation/imageAltTextRequestGeneration.ts/requestImageDescription.ts index a4fa79d63..46c60d847 100644 --- a/packages/core/src/functions/generation/imageAltTextRequestGeneration.ts/requestImageDescription.ts +++ b/packages/core/src/functions/generation/imageAltTextRequestGeneration.ts/requestImageDescription.ts @@ -6,7 +6,8 @@ const log = aiLogger("generation"); const openai = createOpenAIClient({ app: "image-alt-text" }); -const prompt = `You are a content creator and an accessibility specialist for visually impaired users. You must write a clear and accurate ALT text for this image. You should accurately describe the image foreground and background, the colours and any objects in the image. Write out any text if it is present, and describe any people, their clothes, and their expressions if they are present. The literacy level of the text should be understandable to a 6-year-old pupil and written with exceptional spelling, punctuation and grammar in British English, think about the language that is understandable to this age group. Do not exceed 300 characters. If you believe you can accurately complete the task in less than 300 characters than do so. `; +const prompt = + "You are a content creator and an accessibility specialist for visually impaired users. You must write a clear and accurate ALT text for this image. You should accurately describe the image foreground and background, the colours and any objects in the image. Write out any text if it is present, and describe any people, their clothes, and their expressions if they are present. The literacy level of the text should be understandable to a 6-year-old pupil and written with exceptional spelling, punctuation and grammar in British English, think about the language that is understandable to this age group. Do not exceed 300 characters. If you believe you can accurately complete the task in less than 300 characters than do so. "; const promptInQuizMode = `You are a content creator and an accessibility specialist for visually impaired users. diff --git a/packages/core/src/functions/slack/notifyRateLimit.ts b/packages/core/src/functions/slack/notifyRateLimit.ts index c1bae15b9..11a2a3773 100644 --- a/packages/core/src/functions/slack/notifyRateLimit.ts +++ b/packages/core/src/functions/slack/notifyRateLimit.ts @@ -26,7 +26,7 @@ export const notifyRateLimit = inngest.createFunction( const response = await slackWebClient.chat.postMessage({ channel: slackNotificationChannelId, - text: `User rate limit exceeded`, + text: "User rate limit exceeded", blocks: [ { type: "header", diff --git a/packages/core/src/prompts/lesson-assistant/parts/interactingWithTheUser.ts b/packages/core/src/prompts/lesson-assistant/parts/interactingWithTheUser.ts index 4918bc804..9c73d7347 100644 --- a/packages/core/src/prompts/lesson-assistant/parts/interactingWithTheUser.ts +++ b/packages/core/src/prompts/lesson-assistant/parts/interactingWithTheUser.ts @@ -29,14 +29,14 @@ const lessonConstructionSteps = ( lessonPlan.title && lessonPlan.keyStage && lessonPlan.subject ? undefined : { - title: `ENSURE YOU HAVE A TITLE, KEY STAGE, SUBJECT, AND TOPIC`, + title: "ENSURE YOU HAVE A TITLE, KEY STAGE, SUBJECT, AND TOPIC", content: `In order to start the lesson plan you need to be provided with title, keyStage, subject, topic (optionally) in the lesson plan. These values are not all present, so ask the user for the missing values.`, }, hasRelevantLessons && !Object.keys(lessonPlan).includes("basedOn") ? { - title: `ASK THE USER IF THEY WANT TO BASE THEIR LESSON PLAN ON AN EXISTING LESSON`, + title: "ASK THE USER IF THEY WANT TO BASE THEIR LESSON PLAN ON AN EXISTING LESSON", content: `Ask if the user would like to adapt one of the Oak lessons as a starting point for their new lesson. Provide a list of lessons for the user as numbered options, with the title of each lesson. The user will then respond with the number of the lesson they would like to adapt. @@ -56,7 +56,7 @@ END OF EXAMPLE RESPONSE`, !hasRelevantLessons ? { sections: ["learningOutcome", "learningCycles"] as LessonPlanKeys[], - title: `GENERATE SECTION GROUP [learningOutcome, learningCycles]`, + title: "GENERATE SECTION GROUP [learningOutcome, learningCycles]", content: `Generate learning outcomes and the learning cycles overview. Generate both of these sections together in one interaction with the user. Do not add any additional explanation about the content you have generated. @@ -64,7 +64,7 @@ In some cases it is possible for the user to base their lesson on existing ones, } : { sections: ["learningOutcome", "learningCycles"] as LessonPlanKeys[], - title: `GENERATE SECTION GROUP [basedOn, learningOutcome, learningCycles]`, + title: "GENERATE SECTION GROUP [basedOn, learningOutcome, learningCycles]", content: `You need to generate three sections in one interaction with the user. Do these all in one interaction. * basedOn - store the reference to the basedOn lesson in the lesson plan unless it is already set. * learningOutcome - generate learning outcomes. @@ -86,14 +86,14 @@ Generate all of these sections together and respond to the user within this one "misconceptions", "keywords", ], - title: `GENERATE SECTION GROUP [priorKnowledge, keyLearningPoints, misconceptions, keywords]`, + title: "GENERATE SECTION GROUP [priorKnowledge, keyLearningPoints, misconceptions, keywords]", content: `Generate these four sections together in one single interaction. You should not ask the user for feedback after generating them one-by-one. Generate them all together in one response.`, }, { sections: ["starterQuiz", "cycle1", "cycle2", "cycle3", "exitQuiz"], - title: `GENERATE SECTION GROUP [starterQuiz, cycle1, cycle2, cycle3, exitQuiz]`, + title: "GENERATE SECTION GROUP [starterQuiz, cycle1, cycle2, cycle3, exitQuiz]", content: `Generate the bulk of the lesson. Generate all of these sections in one interaction. Your response should include the starter quiz, each of the three learning cycles, and the exit quiz all within a single response. Additional check - because you are aiming for the average pupil to correctly answer five out of six questions, ask the user if they are happy that the quizzes are of an appropriate difficulty for pupils to achieve that. @@ -105,7 +105,7 @@ END OF EXAMPLE RESPONSE`, }, { sections: ["additionalMaterials"], - title: `GENERATE SECTION GROUP [additionalMaterials]`, + title: "GENERATE SECTION GROUP [additionalMaterials]", content: `Create any additional materials that may be helpful in the delivery of the lesson plan. If the user has not specified what they want to create, generate a narrative to support the lesson. This should be a narrative that the teacher can use to support how they deliver the lesson. @@ -119,7 +119,7 @@ END OF EXAMPLE RESPONSE`, }, { - title: `CONSISTENCY CHECK / LESSON COMPLETE`, + title: "CONSISTENCY CHECK / LESSON COMPLETE", content: `Go through the lesson plan and check for any inconsistencies in language or content. Ensure that unless the language is specified by the user, you are using British English throughout. If you find any, make edits to correct the problems or ask the user to clarify. diff --git a/packages/core/src/prompts/lesson-assistant/parts/protocol.ts b/packages/core/src/prompts/lesson-assistant/parts/protocol.ts index ada336e27..70e613f05 100644 --- a/packages/core/src/prompts/lesson-assistant/parts/protocol.ts +++ b/packages/core/src/prompts/lesson-assistant/parts/protocol.ts @@ -1,6 +1,6 @@ import type { TemplateProps } from ".."; -const responseFormatWithStructuredOutputs = `{"response":"llmMessage", patches:[{},{}...], prompt:{}}`; +const responseFormatWithStructuredOutputs = "{\"response\":\"llmMessage\", patches:[{},{}...], prompt:{}}"; const responseFormatWithoutStructuredOutputs = `A series of JSON documents separated using the JSON Text Sequences specification, where each row is separated by the ␞ character and ends with a new line character. Your response should be a series of patches followed by one and only one prompt to the user.`; diff --git a/packages/core/src/prompts/lesson-assistant/parts/schema.ts b/packages/core/src/prompts/lesson-assistant/parts/schema.ts index 6cdc3f5f7..2c013d61e 100644 --- a/packages/core/src/prompts/lesson-assistant/parts/schema.ts +++ b/packages/core/src/prompts/lesson-assistant/parts/schema.ts @@ -1,6 +1,6 @@ import type { TemplateProps } from ".."; -const interactiveOnly = `This is a JSON object that should be generated through the patch instructions that you generate.`; +const interactiveOnly = "This is a JSON object that should be generated through the patch instructions that you generate."; export const schema = ({ responseMode, diff --git a/packages/core/src/prompts/lesson-planner/extend-lesson-plan-quiz/variants/main/parts/output.ts b/packages/core/src/prompts/lesson-planner/extend-lesson-plan-quiz/variants/main/parts/output.ts index efd11e8a7..d7c09d256 100644 --- a/packages/core/src/prompts/lesson-planner/extend-lesson-plan-quiz/variants/main/parts/output.ts +++ b/packages/core/src/prompts/lesson-planner/extend-lesson-plan-quiz/variants/main/parts/output.ts @@ -1 +1 @@ -export default `Take the current lesson plan provided and return only JSON with the key "{quizType}".`; +export default "Take the current lesson plan provided and return only JSON with the key \"{quizType}\"."; diff --git a/packages/core/src/prompts/lesson-planner/regenerate-lesson-plan/variants/main/parts/output.ts b/packages/core/src/prompts/lesson-planner/regenerate-lesson-plan/variants/main/parts/output.ts index d8fefab04..f566b3664 100644 --- a/packages/core/src/prompts/lesson-planner/regenerate-lesson-plan/variants/main/parts/output.ts +++ b/packages/core/src/prompts/lesson-planner/regenerate-lesson-plan/variants/main/parts/output.ts @@ -1 +1 @@ -export default `Take the current lesson plan provided and return only JSON with the key "{sectionName}".`; +export default "Take the current lesson plan provided and return only JSON with the key \"{sectionName}\"."; diff --git a/packages/core/src/scripts/import-new-lessons/importNewLessons.ts b/packages/core/src/scripts/import-new-lessons/importNewLessons.ts index 64c78b9ea..e810e92b9 100644 --- a/packages/core/src/scripts/import-new-lessons/importNewLessons.ts +++ b/packages/core/src/scripts/import-new-lessons/importNewLessons.ts @@ -1,4 +1,4 @@ -import type { Quiz} from "@oakai/db"; +import type { Quiz } from "@oakai/db"; import { prisma } from "@oakai/db"; import { GraphQLClient, gql } from "graphql-request"; @@ -62,7 +62,7 @@ type Lesson = { }; const graphqlClient = new GraphQLClient( - `https://hasura.thenational.academy/v1/graphql`, + "https://hasura.thenational.academy/v1/graphql", { headers: { "x-oak-auth-key": process.env.OAK_GRAPHQL_SECRET as string, diff --git a/packages/core/src/workers/generations/requestGeneration.ts b/packages/core/src/workers/generations/requestGeneration.ts index bea0b16dd..d35593610 100644 --- a/packages/core/src/workers/generations/requestGeneration.ts +++ b/packages/core/src/workers/generations/requestGeneration.ts @@ -1,11 +1,7 @@ -import type { Prisma} from "@oakai/db"; +import type { Prisma } from "@oakai/db"; import { GenerationStatus, ModerationType, prisma } from "@oakai/db"; -import type { - StructuredLogger} from "@oakai/logger"; -import { - structuredLogger as baseLogger, - aiLogger -} from "@oakai/logger"; +import type { StructuredLogger } from "@oakai/logger"; +import { structuredLogger as baseLogger, aiLogger } from "@oakai/logger"; import { Redis } from "@upstash/redis"; import { NonRetriableError } from "inngest"; import type { z } from "zod"; @@ -13,9 +9,7 @@ import type { z } from "zod"; import { createOpenAIModerationsClient } from "../../llm/openai"; import { SafetyViolations } from "../../models"; import { Generations } from "../../models/generations"; -import type { - CompletionResult, - Json} from "../../models/prompts"; +import type { CompletionResult, Json } from "../../models/prompts"; import { LLMCompletionError, LLMRefusalError, @@ -89,7 +83,7 @@ export function requestGenerationWorker({ async function invoke({ data, user }: RequestGenerationArgs) { baseLogger.info( - `Requesting generation for promptId %s`, + "Requesting generation for promptId %s", data?.promptId ?? "Unknown prompt", ); baseLogger.debug({ eventData: data }, "Event data for generation"); @@ -288,7 +282,7 @@ async function invoke({ data, user }: RequestGenerationArgs) { let completion: CompletionResult | undefined = undefined; try { - logger.info(`Requesting completion for generationId=%s`, generationId); + logger.info("Requesting completion for generationId=%s", generationId); /** * Stream partial response JSON to redis as the new tokens come in, @@ -340,7 +334,7 @@ async function invoke({ data, user }: RequestGenerationArgs) { } } catch (err) { const errorMessage = - err instanceof Error ? err.message : `Unknown generation error`; + err instanceof Error ? err.message : "Unknown generation error"; logger.error(err, errorMessage); @@ -386,7 +380,7 @@ async function invoke({ data, user }: RequestGenerationArgs) { ); logger.info( - `Successfully completed generation, generationId=%s`, + "Successfully completed generation, generationId=%s", generationId, ); } diff --git a/packages/ingest/src/import-lessons/graphql/client.ts b/packages/ingest/src/import-lessons/graphql/client.ts index 52ba7e6ab..643f04b58 100644 --- a/packages/ingest/src/import-lessons/graphql/client.ts +++ b/packages/ingest/src/import-lessons/graphql/client.ts @@ -5,7 +5,7 @@ if (!process.env.OAK_GRAPHQL_SECRET) { } export const graphqlClient = new GraphQLClient( - `https://hasura.thenational.academy/v1/graphql`, + "https://hasura.thenational.academy/v1/graphql", { headers: { "x-oak-auth-key": process.env.OAK_GRAPHQL_SECRET, diff --git a/packages/ingest/src/steps/0-start.ts b/packages/ingest/src/steps/0-start.ts index 776a2f286..1754afe6c 100644 --- a/packages/ingest/src/steps/0-start.ts +++ b/packages/ingest/src/steps/0-start.ts @@ -45,7 +45,7 @@ export async function ingestStart({ case "csv": if (config.sourcePartsToInclude !== "title-subject-key-stage") { throw new IngestError( - `sourcePartsToInclude must be "title-subject-key-stage" when importing from a CSV file`, + "sourcePartsToInclude must be title-subject-key-stage when importing from a CSV file", ); } await importLessonsFromCSV({ @@ -55,7 +55,7 @@ export async function ingestStart({ }); break; default: - throw new IngestError(`Unsupported source type: ${config.source}`); + throw new IngestError("Unsupported source type"); } log.info(`Ingest started with id: ${ingestId}`); From 5f9845cfacabe10a92f7a6407328608589c178ce Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Wed, 30 Oct 2024 13:03:20 +0000 Subject: [PATCH 02/61] chore: remove unused text encoder from PatchEnqueuer (#312) --- packages/aila/src/core/chat/PatchEnqueuer.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/aila/src/core/chat/PatchEnqueuer.ts b/packages/aila/src/core/chat/PatchEnqueuer.ts index 724e0c789..90c2d3816 100644 --- a/packages/aila/src/core/chat/PatchEnqueuer.ts +++ b/packages/aila/src/core/chat/PatchEnqueuer.ts @@ -5,11 +5,9 @@ import type { JsonPatchDocumentOptional } from "../../protocol/jsonPatchProtocol const log = aiLogger("aila:protocol"); export class PatchEnqueuer { - private encoder: TextEncoder; private controller?: ReadableStreamDefaultController; constructor(controller?: ReadableStreamDefaultController) { - this.encoder = new TextEncoder(); this.controller = controller; } From 1e05c1ad177d2e385fc4fa6cc9bb76548d039194 Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Wed, 30 Oct 2024 13:20:10 +0000 Subject: [PATCH 03/61] fix: await the save download event call (#317) --- apps/nextjs/src/app/api/qd-download/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/nextjs/src/app/api/qd-download/route.ts b/apps/nextjs/src/app/api/qd-download/route.ts index d6dbbc838..49e30af9e 100644 --- a/apps/nextjs/src/app/api/qd-download/route.ts +++ b/apps/nextjs/src/app/api/qd-download/route.ts @@ -1,6 +1,6 @@ import { auth } from "@clerk/nextjs/server"; import type { LessonExportType } from "@oakai/db"; -import { prisma } from "@oakai/db"; +import { prisma } from "@oakai/db/client"; import { downloadDriveFile } from "@oakai/exports"; import * as Sentry from "@sentry/node"; @@ -129,7 +129,7 @@ async function getHandler(req: Request) { }); } - saveDownloadEvent({ + await saveDownloadEvent({ exportId: qdExport.id, downloadedBy: userId, ext, From 1824aead23e65654a4651abb91e589d218cb9bb1 Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Wed, 30 Oct 2024 13:20:41 +0000 Subject: [PATCH 04/61] fix: promisify chat API route get handler (#316) --- apps/nextjs/src/app/api/chat/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/nextjs/src/app/api/chat/route.ts b/apps/nextjs/src/app/api/chat/route.ts index 992bee59e..5e68ed27d 100644 --- a/apps/nextjs/src/app/api/chat/route.ts +++ b/apps/nextjs/src/app/api/chat/route.ts @@ -12,7 +12,7 @@ async function postHandler(req: NextRequest): Promise { } async function getHandler(): Promise { - return new Response("Server is ready", { status: 200 }); + return Promise.resolve(new Response("Server is ready", { status: 200 })); } export const POST = withSentry(postHandler); From 6ca5163644b32adaaaf2943ac39ed0a642c9beaa Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:04:27 +0100 Subject: [PATCH 05/61] build: temporarily disable the prompts dependency when building RCs (#322) --- turbo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 812ab5d62..77afe9df6 100644 --- a/turbo.json +++ b/turbo.json @@ -39,7 +39,7 @@ }, "build": { "cache": true, - "dependsOn": ["^db-generate:no-engine", "^prompts"], + "dependsOn": ["^db-generate:no-engine"], "outputs": [ ".next/**", "!.next/cache/**", From 7ce6d110fbec2012141f4bcb5b30c1d6b1472894 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:04:27 +0100 Subject: [PATCH 06/61] build: temporarily disable the prompts dependency when building RCs (#322) --- turbo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 812ab5d62..77afe9df6 100644 --- a/turbo.json +++ b/turbo.json @@ -39,7 +39,7 @@ }, "build": { "cache": true, - "dependsOn": ["^db-generate:no-engine", "^prompts"], + "dependsOn": ["^db-generate:no-engine"], "outputs": [ ".next/**", "!.next/cache/**", From 2719f7f72cc795fc36c2c1336f56fd95332b586d Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Wed, 30 Oct 2024 15:51:49 +0000 Subject: [PATCH 07/61] chore: use the chat context provider in more components (#310) --- .../AppComponents/Chat/chat-layout.tsx | 17 ++++------------- .../AppComponents/Chat/chat-left-hand-side.tsx | 7 ++----- .../Chat/chat-right-hand-side-lesson.tsx | 14 +++++++------- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-layout.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-layout.tsx index ef9971da2..76863f255 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/chat-layout.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/chat-layout.tsx @@ -13,14 +13,8 @@ export interface ChatLayoutProps { } export const ChatLayout = ({ className }: Readonly) => { - const { - id, - messages, - isLoading, - chatAreaRef, - lessonPlan, - ailaStreamingStatus, - } = useLessonChat(); + const { isLoading, lessonPlan, messages, ailaStreamingStatus } = + useLessonChat(); const demo = useDemoUser(); const isDemoLocked = useDemoLocking(messages, isLoading); @@ -37,8 +31,7 @@ export const ChatLayout = ({ className }: Readonly) => { className={`flex h-full flex-row justify-start ${demo.isDemoUser ? "pt-22" : ""}`} > ) => { isDemoUser={demo.isDemoUser} /> ; - messages: Message[]; isDemoLocked: boolean; showLessonMobile: boolean; setShowLessonMobile: (value: boolean) => void; @@ -22,14 +20,13 @@ type ChatLeftHandSideProps = { }; const ChatLeftHandSide = ({ - chatAreaRef, - messages, isDemoLocked, showLessonMobile, setShowLessonMobile, demo, isDemoUser, }: Readonly) => { + const { messages, chatAreaRef } = useLessonChat(); return ( void; demo: DemoContextProps; }; const ChatRightHandSideLesson = ({ - id, - messages, showLessonMobile, closeMobileLessonPullOut, demo, }: Readonly) => { + const { id, messages } = useLessonChat(); const { setDialogWindow } = useDialog(); const chatEndRef = useRef(null); @@ -36,7 +32,10 @@ const ChatRightHandSideLesson = ({ const documentContainerRef = useRef(null); const [showScrollButton, setShowScrollButton] = useState(false); + + // This retains this existing bug, but is fixed on subsequent PRs const sectionRefs = {}; + const scrollToBottom = () => { if (chatEndRef.current) { setShowScrollButton(false); @@ -59,6 +58,7 @@ const ChatRightHandSideLesson = ({ }; const endOfDocRef = useRef(null); + return (
Date: Wed, 30 Oct 2024 15:52:07 +0000 Subject: [PATCH 08/61] fix: add missing dependencies to lesson plan tracking context (#307) --- apps/nextjs/src/lib/analytics/lessonPlanTrackingContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/nextjs/src/lib/analytics/lessonPlanTrackingContext.tsx b/apps/nextjs/src/lib/analytics/lessonPlanTrackingContext.tsx index 3d2412218..5d5e9304d 100644 --- a/apps/nextjs/src/lib/analytics/lessonPlanTrackingContext.tsx +++ b/apps/nextjs/src/lib/analytics/lessonPlanTrackingContext.tsx @@ -64,7 +64,7 @@ const LessonPlanTrackingProvider: FC<{ setAction(null); setUserMessageContent(""); }, - [track, action, userMessageContent], + [chatId, userMessageContent, track, action], ); const onSubmitText = useCallback((text: string) => { setAction("submit_text"); From 0398a1e4f31a0e6a83bda8cbd8ea293f5217dc68 Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Wed, 30 Oct 2024 15:52:24 +0000 Subject: [PATCH 09/61] chore: add JSON patch protocol test (#305) --- .../aila/src/protocol/jsonPatchProtocol.test.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/aila/src/protocol/jsonPatchProtocol.test.ts b/packages/aila/src/protocol/jsonPatchProtocol.test.ts index f0b06608a..59b448479 100644 --- a/packages/aila/src/protocol/jsonPatchProtocol.test.ts +++ b/packages/aila/src/protocol/jsonPatchProtocol.test.ts @@ -1,6 +1,19 @@ -import { parseMessageParts } from "./jsonPatchProtocol"; +import { extractPatches, parseMessageParts } from "./jsonPatchProtocol"; describe("parseMessageParts", () => { + it("correctly extracts patches from a partial, streaming message", () => { + const content = `␞ +{"type":"comment","value":"CHAT_START"} +␞ +{"type":"llmMessage","patches":[{"type":"patch","reasoning":"The starter quiz is designed to assess pupils' prior knowledge about the Roman Empire and Roman Britain, which is essential before delving into the lesson's main content.","value":{"type":"quiz","op":"add","path":"/starterQuiz","value":[{"question":"What was the Roman Empire?","answers":["A large empire that ruled many territories including Britain."],"distractors":["A small kingdom in Europe.","A modern European country."]},{"question":"When did the Romans begin ruling Britain?","answers":["AD 43"],"distractors":["AD 410","AD 1066"]},{"question":"What was a Roman legion?","answers":["A unit of the Roman army."],"distractors":["A type of Roman building.","A Roman festival."]},{"question":"What influence did the Romans have on British architecture?","answers":["Introduced new building techniques and styles."],"distractors":["Made no impact on architecture.","Destroyed all previous architectural styles."]},{"question":"What was the primary language of Roman Britain?","answers":["Latin"],"distractors":["Greek","Celtic"]},{"question":"What marked the end of Roman rule in Britain?","answers":["The withdrawal of Roman legions."],"distractors":["The invasion of the Saxons.","The collapse of the Roman Senate."]}]},"status":"complete"},{"type":"patch","reasoning":"The first learning cycle focuses on identifying the key events that marked the end of Roman rule in Britain, providing a foundational understanding for pupils.","value":{"type":"cycle","op":"add","path":"/cycle1","value":{"title":"Identifying Key Events of Roman Withdrawal","durationInMinutes":15,"explanation":{"spokenExplanation":["Begin by explaining the significant events that led to the end of Roman rule in Britain, such as political instability and military needs elsewhere in the empire.","Discuss the role of key figures, such as the Roman Emperor Honorius, in the decision to withdraw troops.","Highlight the year AD 410 as a pivotal moment when the Romans officially abandoned Britain.","Explain how the withdrawal was a gradual process rather than a single event.","Use maps to show the extent of Roman control and the regions affected by the withdrawal."],"accompanyingSlideDetails":"A timeline highlighting key events from AD 400 to AD 410, showing the stages of Roman withdrawal from Britain.","imagePrompt":"Roman Britain timeline AD 400-410","slideText":"Key events leading to the end of Roman rule in Britain included political instability and the withdrawal of Roman legions by AD 410."},"checkForUnderstanding":[{"question":"Who was the Roman Emperor during the withdrawal from Britain?","answers":["Emperor Honorius"],"distractors":["Emperor Nero","Emperor Augustus"]},{"question":"In which year did the Romans officially abandon Britain?","answers":["AD 410"],"distractors":["AD 43","AD 1066"]}],"practice":"Create a timeline showing the key events from AD 400 to AD 410 that led to the end of Roman rule in Britain. Include at least three events with brief descriptions.","feedback":"Model answer: The timeline should include the following events: AD 400 - increasing pressures on Roman borders; AD 406 - crossing of the Rhine by Germanic tribes; AD 410 - Emperor Honorius' rescript advising Britons to defend themselves."}},"status":"complete"},{"type":"patch","reasoning":"The second learning cycle delves into explaining the causes behind the Roman withdrawal from Britain, enhancing pupils' understanding of historical causation.","value":{"type":"cycle","op":"add","path":"/cycle2","value":{"title":"Explaining Causes of Roman Withdrawal","durationInMinutes":15,"explanation":{"spokenExplanation":["Discuss the external pressures faced by the Roman Empire, such as invasions by Germanic tribes and the need to protect other parts of the empire.","Explain the economic strains on the Roman Empire, including overextension and resource allocation issues.","Explore the internal instability within the Roman government and its impact on decisions to withdraw.","Illustrate how these factors combined to make it unsustainable for Rome to maintain its hold over Britain.","Use examples of other territories that faced similar withdrawals during this period."],"accompanyingSlideDetails":"A map showing Roman Empire territories and the areas affected by external pressures and economic strains.","imagePrompt":"Roman Empire map external pressures AD 400","slideText":"External pressures and economic strains led to the Roman Empire withdrawing from Britain by AD 410."},"checkForUnderstanding":[{"question":"What external pressures did the Roman Empire face during its decline?","answers":["Invasions by Germanic tribes"],"distractors":["Economic prosperity","Peaceful expansion"]},{"question":"Why was maintaining control over Britain unsustainable for the Romans?","answers":["Due to overextension and resource issues."],"distractors":["Because Britain was too small.","Because of a lack of interest."]}],"practice":"Write a short paragraph explaining the main reasons for the Roman withdrawal from Britain, using evidence from the lesson to support your points.","feedback":"Model answer: The Roman withdrawal from Britain was primarily due to external pressures such as Germanic invasions, economic strains from overextension, and internal instability within the Roman government."}},"status":"complete"},{"type":"patch","reasoning":"The third learning cycle focuses on analysing the impact of Roman withdrawal, allowing pupils to consider the broader implications on society and culture.","value":{"type":"cycle","op":"add","path":"/cycle3","value":{"title":"Analysing Impact of Roman Withdrawal on Britain","durationInMinutes":15,"explanation":{"spokenExplanation":["Explain the immediate effects of the Roman withdrawal on British society, such as increased vulnerability to invasions and loss of centralized governance.","Discuss the long-term cultural impacts, including the retention of Roman infrastructure and legal systems.","Highlight the transition to the Anglo-Saxon period and its implications for British history.","Provide examples of Roman cultural and architectural influences that persisted, like roads and villas.","Encourage discussion on how the absence of Roman protection affected daily life in Britain."],"accompanyingSlideDetails":"Images of Roman roads and villas in Britain, and a`; + const { validPatches } = extractPatches(content); + + expect(validPatches).toHaveLength(3); + expect(validPatches[0]?.type).toBe("patch"); + expect(validPatches[1]?.type).toBe("patch"); + expect(validPatches[2]?.type).toBe("patch"); + }); + it("correctly handles complex output with multiple parts", () => { const content = `{"type":"llmMessage","patches":[{"type":"patch","reasoning":"Learning outcomes help to define what the students will achieve by the end of the lesson.","value":{"op":"add","path":"/learningOutcome","value":"I can explain the factors that led to the end of Roman Britain and its impact."}},{"type":"patch","reasoning":"Learning cycles guide the teacher on specific content delivery in manageable chunks.","value":{"op":"add","path":"/learningCycles","value":["Describe the key events leading to the end of Roman Britain","Explain the impact of the end of Roman Britain on the local population","Analyse the legacy left by the Romans in Britain"]}}],"prompt":{"type":"text","value":"I have set the learning outcomes. Would you like to continue?"}} ␞ From 33bee19e5e631428b648159452ef5af5dbec36bd Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Wed, 30 Oct 2024 15:56:06 +0000 Subject: [PATCH 10/61] feat: allow us to configure the server port for local testing (#308) --- apps/nextjs/tests-e2e/config/config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/nextjs/tests-e2e/config/config.ts b/apps/nextjs/tests-e2e/config/config.ts index c5d0f9bdd..215dd347a 100644 --- a/apps/nextjs/tests-e2e/config/config.ts +++ b/apps/nextjs/tests-e2e/config/config.ts @@ -5,7 +5,8 @@ dotenv.config({ path: path.join(process.cwd(), ".env") }); export const TEST_USER_EMAIL = process.env.TEST_USER_EMAIL; export const TEST_USER_PASSWORD = process.env.TEST_USER_PASSWORD; +export const PORT = process.env.PORT ?? 2525; export const TEST_BASE_URL = - process.env.TEST_BASE_URL || "http://localhost:2525"; + process.env.TEST_BASE_URL ?? `http://localhost:${PORT}`; export const VERCEL_AUTOMATION_BYPASS_SECRET = process.env.VERCEL_AUTOMATION_BYPASS_SECRET; From 18a0f7061bc9d8bd72c4f6d44eec003acd878df1 Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Wed, 30 Oct 2024 16:38:25 +0000 Subject: [PATCH 11/61] fix: remaining linting fixes (#272) --- .../src/ai-apps/common/copyLinkToClipboard.ts | 14 ++++-- .../src/ai-apps/common/copyTextToClipboard.ts | 14 ++++-- .../chat/fixtures/FixtureRecordLLMService.ts | 4 +- .../chat/fixtures/FixtureReplayLLMService.ts | 2 +- .../src/app/api/chat/webActionsPlugin.ts | 2 +- .../lesson-planner/preview/[slug]/preview.tsx | 6 +-- .../nextjs/src/app/user/[[...index]]/page.tsx | 2 +- .../src/hooks/useGenerationCallbacks.ts | 1 + apps/nextjs/src/lib/hooks/use-at-bottom.tsx | 22 ++++---- apps/nextjs/src/lib/sentry/SentryIdentify.ts | 10 ++-- .../tests-e2e/tests/aila-chat/helpers.ts | 6 ++- packages/aila/index.ts | 2 +- .../aila/src/core/chat/AilaStreamHandler.ts | 4 +- packages/aila/src/core/index.ts | 5 -- packages/aila/src/features/index.ts | 6 --- packages/aila/src/index.ts | 4 -- packages/api/.eslintrc.cjs | 7 +++ packages/api/src/router/exports.ts | 50 +++++++++---------- packages/core/.eslintrc.cjs | 7 +++ packages/db/.eslintrc.cjs | 7 +++ packages/db/index.ts | 2 +- packages/exports/.eslintrc.cjs | 7 +++ packages/ingest/.eslintrc.cjs | 7 +++ .../import-lessons/importLessonsFromCSV.ts | 2 +- packages/ingest/src/index.ts | 14 +++--- packages/ingest/src/utils/jsonlToArray.ts | 13 +++-- 26 files changed, 132 insertions(+), 88 deletions(-) delete mode 100644 packages/aila/src/core/index.ts delete mode 100644 packages/aila/src/features/index.ts delete mode 100644 packages/aila/src/index.ts create mode 100644 packages/api/.eslintrc.cjs create mode 100644 packages/core/.eslintrc.cjs create mode 100644 packages/db/.eslintrc.cjs create mode 100644 packages/exports/.eslintrc.cjs create mode 100644 packages/ingest/.eslintrc.cjs diff --git a/apps/nextjs/src/ai-apps/common/copyLinkToClipboard.ts b/apps/nextjs/src/ai-apps/common/copyLinkToClipboard.ts index 7b840909f..a69f16e19 100644 --- a/apps/nextjs/src/ai-apps/common/copyLinkToClipboard.ts +++ b/apps/nextjs/src/ai-apps/common/copyLinkToClipboard.ts @@ -1,6 +1,14 @@ +import { aiLogger } from "@oakai/logger"; + +const log = aiLogger("chat"); export const copyLinkToClipboard = () => { const currentURL = window.location.href; - navigator.clipboard.writeText(currentURL).then(() => { - alert("Link copied to clipboard!"); - }); + navigator.clipboard + .writeText(currentURL) + .then(() => { + alert("Link copied to clipboard!"); + }) + .catch((error) => { + log.error(error); + }); }; diff --git a/apps/nextjs/src/ai-apps/common/copyTextToClipboard.ts b/apps/nextjs/src/ai-apps/common/copyTextToClipboard.ts index c2110a2b1..a64ba1bbe 100644 --- a/apps/nextjs/src/ai-apps/common/copyTextToClipboard.ts +++ b/apps/nextjs/src/ai-apps/common/copyTextToClipboard.ts @@ -1,3 +1,6 @@ +import { aiLogger } from "@oakai/logger"; + +const log = aiLogger("chat"); export const copyTextToClipboard = () => { const elementsToCopy = document.querySelectorAll(".copy-to-clipboard"); @@ -5,7 +8,12 @@ export const copyTextToClipboard = () => { .map((element) => element.innerText) .join("\n"); - navigator.clipboard.writeText(textToCopy).then(() => { - alert("Text copied to clipboard!"); - }); + navigator.clipboard + .writeText(textToCopy) + .then(() => { + alert("Text copied to clipboard!"); + }) + .catch((error) => { + log.error(error); + }); }; diff --git a/apps/nextjs/src/app/api/chat/fixtures/FixtureRecordLLMService.ts b/apps/nextjs/src/app/api/chat/fixtures/FixtureRecordLLMService.ts index fa68531f1..b928aff0d 100644 --- a/apps/nextjs/src/app/api/chat/fixtures/FixtureRecordLLMService.ts +++ b/apps/nextjs/src/app/api/chat/fixtures/FixtureRecordLLMService.ts @@ -1,4 +1,4 @@ -import type { Message } from "@oakai/aila"; +import type { Message } from "@oakai/aila/src/core/chat"; import type { LLMService } from "@oakai/aila/src/core/llm/LLMService"; import { OpenAIService } from "@oakai/aila/src/core/llm/OpenAIService"; import { aiLogger } from "@oakai/logger"; @@ -8,7 +8,7 @@ import type { ZodSchema } from "zod"; const log = aiLogger("fixtures"); export class FixtureRecordLLMService implements LLMService { - name = "FixureRecordLLM"; + name = "FixtureRecordLLM"; private _openAIService: OpenAIService; constructor( diff --git a/apps/nextjs/src/app/api/chat/fixtures/FixtureReplayLLMService.ts b/apps/nextjs/src/app/api/chat/fixtures/FixtureReplayLLMService.ts index 86c989ce9..e03382e3d 100644 --- a/apps/nextjs/src/app/api/chat/fixtures/FixtureReplayLLMService.ts +++ b/apps/nextjs/src/app/api/chat/fixtures/FixtureReplayLLMService.ts @@ -5,7 +5,7 @@ import fs from "fs"; const log = aiLogger("fixtures"); export class FixtureReplayLLMService extends MockLLMService { - name = "FixureReplayLLM"; + name = "FixtureReplayLLM"; constructor(fixtureName: string) { const fileUrl = `${process.cwd()}/tests-e2e/recordings/${fixtureName}.chunks.txt`; diff --git a/apps/nextjs/src/app/api/chat/webActionsPlugin.ts b/apps/nextjs/src/app/api/chat/webActionsPlugin.ts index 46daa425b..08b5d547d 100644 --- a/apps/nextjs/src/app/api/chat/webActionsPlugin.ts +++ b/apps/nextjs/src/app/api/chat/webActionsPlugin.ts @@ -1,8 +1,8 @@ import type { AilaPlugin } from "@oakai/aila/src/core/plugins"; import { AilaThreatDetectionError } from "@oakai/aila/src/features/threatDetection"; import { handleHeliconeError } from "@oakai/aila/src/utils/moderation/moderationErrorHandling"; -import { SafetyViolations as defaultSafetyViolations } from "@oakai/core"; import { inngest } from "@oakai/core/src/inngest"; +import { SafetyViolations as defaultSafetyViolations } from "@oakai/core/src/models/safetyViolations"; import { UserBannedError } from "@oakai/core/src/models/userBannedError"; import type { PrismaClientWithAccelerate } from "@oakai/db"; import { aiLogger } from "@oakai/logger"; diff --git a/apps/nextjs/src/app/lesson-planner/preview/[slug]/preview.tsx b/apps/nextjs/src/app/lesson-planner/preview/[slug]/preview.tsx index ffa500f71..ace5b5b67 100644 --- a/apps/nextjs/src/app/lesson-planner/preview/[slug]/preview.tsx +++ b/apps/nextjs/src/app/lesson-planner/preview/[slug]/preview.tsx @@ -42,7 +42,7 @@ export const LessonPlanPreview = ({ planSections }) => {
    {planSections?.planSections.keyLearningPoints.map( (learningPoint) => ( -
  1. +
  2. {learningPoint.value}

  3. ), @@ -58,7 +58,7 @@ export const LessonPlanPreview = ({ planSections }) => { return (
  4. {miscon.value.misconception}

    {miscon.value.description}

    @@ -152,7 +152,7 @@ const Quiz = ({ question }: Readonly) => {
      {question.distractors.map((distractor) => { return ( -
    • +
    • {distractor.value}

    • ); diff --git a/apps/nextjs/src/app/user/[[...index]]/page.tsx b/apps/nextjs/src/app/user/[[...index]]/page.tsx index 340bc036c..5392a23f4 100644 --- a/apps/nextjs/src/app/user/[[...index]]/page.tsx +++ b/apps/nextjs/src/app/user/[[...index]]/page.tsx @@ -5,5 +5,5 @@ export default function Page() {
      - ) + ); } diff --git a/apps/nextjs/src/hooks/useGenerationCallbacks.ts b/apps/nextjs/src/hooks/useGenerationCallbacks.ts index fb25cf64d..a636abad1 100644 --- a/apps/nextjs/src/hooks/useGenerationCallbacks.ts +++ b/apps/nextjs/src/hooks/useGenerationCallbacks.ts @@ -16,6 +16,7 @@ import { import type { z } from "zod"; import useAnalytics from "@/lib/analytics/useAnalytics"; + import { usePreviousValue } from "./usePreviousValue"; type UseGenerationCallbackTypes = { diff --git a/apps/nextjs/src/lib/hooks/use-at-bottom.tsx b/apps/nextjs/src/lib/hooks/use-at-bottom.tsx index d37c8cf41..cf8624d2f 100644 --- a/apps/nextjs/src/lib/hooks/use-at-bottom.tsx +++ b/apps/nextjs/src/lib/hooks/use-at-bottom.tsx @@ -1,23 +1,23 @@ -import * as React from 'react' +import * as React from "react"; export function useAtBottom(offset = 0) { - const [isAtBottom, setIsAtBottom] = React.useState(false) + const [isAtBottom, setIsAtBottom] = React.useState(false); React.useEffect(() => { const handleScroll = () => { setIsAtBottom( window.innerHeight + window.scrollY >= - document.body.offsetHeight - offset - ) - } + document.body.offsetHeight - offset, + ); + }; - window.addEventListener('scroll', handleScroll, { passive: true }) - handleScroll() + window.addEventListener("scroll", handleScroll, { passive: true }); + handleScroll(); return () => { - window.removeEventListener('scroll', handleScroll) - } - }, [offset]) + window.removeEventListener("scroll", handleScroll); + }; + }, [offset]); - return isAtBottom + return isAtBottom; } diff --git a/apps/nextjs/src/lib/sentry/SentryIdentify.ts b/apps/nextjs/src/lib/sentry/SentryIdentify.ts index c869e0be3..af82420f8 100644 --- a/apps/nextjs/src/lib/sentry/SentryIdentify.ts +++ b/apps/nextjs/src/lib/sentry/SentryIdentify.ts @@ -4,10 +4,12 @@ import * as Sentry from "@sentry/nextjs"; import { useClerkIdentify } from "../clerk/useClerkIdentify"; -const sentrySetUser = ({ userId }) => { - Sentry.setUser({ - id: userId, - }); +const sentrySetUser = ({ userId }: { userId: string }) => { + if (userId) { + Sentry.setUser({ + id: userId, + }); + } }; const sentryUnsetUser = () => { Sentry.setUser(null); diff --git a/apps/nextjs/tests-e2e/tests/aila-chat/helpers.ts b/apps/nextjs/tests-e2e/tests/aila-chat/helpers.ts index 7f33455bd..63a1ba0f2 100644 --- a/apps/nextjs/tests-e2e/tests/aila-chat/helpers.ts +++ b/apps/nextjs/tests-e2e/tests/aila-chat/helpers.ts @@ -1,6 +1,7 @@ -import { expect, Page, test, TestInfo } from "@playwright/test"; +import type { Page, TestInfo } from "@playwright/test"; +import { expect, test } from "@playwright/test"; -import { AilaStreamingStatus } from "@/components/AppComponents/Chat/Chat/hooks/useAilaStreamingStatus"; +import type { AilaStreamingStatus } from "@/components/AppComponents/Chat/Chat/hooks/useAilaStreamingStatus"; export async function expectStreamingStatus( page: Page, @@ -20,6 +21,7 @@ export async function waitForStreamingStatusChange( await page.waitForFunction( ([currentStatus, expectedStatus]) => { const statusElement = document.querySelector( + // eslint-disable-next-line @typescript-eslint/quotes, quotes '[data-testid="chat-aila-streaming-status"]', ); return ( diff --git a/packages/aila/index.ts b/packages/aila/index.ts index 3bd16e178..fcbeae277 100644 --- a/packages/aila/index.ts +++ b/packages/aila/index.ts @@ -1 +1 @@ -export * from "./src"; +export { Aila } from "./src/core/Aila"; diff --git a/packages/aila/src/core/chat/AilaStreamHandler.ts b/packages/aila/src/core/chat/AilaStreamHandler.ts index bf7c1098a..4ec0c014f 100644 --- a/packages/aila/src/core/chat/AilaStreamHandler.ts +++ b/packages/aila/src/core/chat/AilaStreamHandler.ts @@ -44,7 +44,7 @@ export class AilaStreamHandler { await this.readFromStream(); } } catch (e) { - this.handleStreamError(e); + await this.handleStreamError(e); log.info("Stream error", e, this._chat.iteration, this._chat.id); } finally { this._isStreaming = false; @@ -109,7 +109,7 @@ export class AilaStreamHandler { for (const plugin of this._chat.aila.plugins ?? []) { await plugin.onStreamError?.(error, { aila: this._chat.aila, - enqueue: this._chat.enqueue, + enqueue: (patch) => this._chat.enqueue(patch), }); } diff --git a/packages/aila/src/core/index.ts b/packages/aila/src/core/index.ts deleted file mode 100644 index ab5e1c3f3..000000000 --- a/packages/aila/src/core/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./Aila"; -export * from "./AilaError"; -export * from "./AilaServices"; -export * from "./types"; -export * from "./chat"; diff --git a/packages/aila/src/features/index.ts b/packages/aila/src/features/index.ts deleted file mode 100644 index 893944b97..000000000 --- a/packages/aila/src/features/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./analytics/AilaAnalytics"; -export * from "./generation"; -export * from "./persistence/AilaPersistence"; -export * from "./rag"; -export * from "./threatDetection"; -export * from "./errorReporting/types"; diff --git a/packages/aila/src/index.ts b/packages/aila/src/index.ts deleted file mode 100644 index 2d01b0b8d..000000000 --- a/packages/aila/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./core"; -export * from "./features/types"; -export * from "./features/errorReporting/types"; -export * from "./features/threatDetection/types"; diff --git a/packages/api/.eslintrc.cjs b/packages/api/.eslintrc.cjs new file mode 100644 index 000000000..9526c3c79 --- /dev/null +++ b/packages/api/.eslintrc.cjs @@ -0,0 +1,7 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: ["eslint-config-custom"], + parserOptions: { + project: __dirname + "/tsconfig.json", + }, +}; diff --git a/packages/api/src/router/exports.ts b/packages/api/src/router/exports.ts index b711a6fa4..28b2372c7 100644 --- a/packages/api/src/router/exports.ts +++ b/packages/api/src/router/exports.ts @@ -27,6 +27,31 @@ import { router } from "../trpc"; const log = aiLogger("exports"); +function getValidLink( + data: + | { + link: string; + canViewSourceDoc: boolean; + } + | { + error: unknown; + message: string; + }, +): string | undefined { + if (data && "link" in data && typeof data.link === "string") { + return data.link; + } + if ("error" in data && "message" in data) { + Sentry.captureException(data.error, { + extra: { + error: data.error, + message: data.message, + }, + }); + throw new Error(data.message); + } +} + export async function ailaSaveExport({ auth, prisma, @@ -643,31 +668,6 @@ export const exportsRouter = router({ }), ]); - function getValidLink( - data: - | { - link: string; - canViewSourceDoc: boolean; - } - | { - error: unknown; - message: string; - }, - ): string | undefined { - if (data && "link" in data && typeof data.link === "string") { - return data.link; - } - if ("error" in data && "message" in data) { - Sentry.captureException(data.error, { - extra: { - error: data.error, - message: data.message, - }, - }); - throw new Error(data.message); - } - } - const allExports = { lessonSlides: getValidLink(lessonSlides), lessonPlan: getValidLink(lessonPlan), diff --git a/packages/core/.eslintrc.cjs b/packages/core/.eslintrc.cjs new file mode 100644 index 000000000..9526c3c79 --- /dev/null +++ b/packages/core/.eslintrc.cjs @@ -0,0 +1,7 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: ["eslint-config-custom"], + parserOptions: { + project: __dirname + "/tsconfig.json", + }, +}; diff --git a/packages/db/.eslintrc.cjs b/packages/db/.eslintrc.cjs new file mode 100644 index 000000000..9526c3c79 --- /dev/null +++ b/packages/db/.eslintrc.cjs @@ -0,0 +1,7 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: ["eslint-config-custom"], + parserOptions: { + project: __dirname + "/tsconfig.json", + }, +}; diff --git a/packages/db/index.ts b/packages/db/index.ts index 534b81b20..57895c1e1 100644 --- a/packages/db/index.ts +++ b/packages/db/index.ts @@ -1,4 +1,4 @@ -export * from "./client"; export * from "@prisma/client"; export * from "./prisma/zod-schemas"; export * from "./schemas"; +export * from "./client"; diff --git a/packages/exports/.eslintrc.cjs b/packages/exports/.eslintrc.cjs new file mode 100644 index 000000000..9526c3c79 --- /dev/null +++ b/packages/exports/.eslintrc.cjs @@ -0,0 +1,7 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: ["eslint-config-custom"], + parserOptions: { + project: __dirname + "/tsconfig.json", + }, +}; diff --git a/packages/ingest/.eslintrc.cjs b/packages/ingest/.eslintrc.cjs new file mode 100644 index 000000000..9526c3c79 --- /dev/null +++ b/packages/ingest/.eslintrc.cjs @@ -0,0 +1,7 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: ["eslint-config-custom"], + parserOptions: { + project: __dirname + "/tsconfig.json", + }, +}; diff --git a/packages/ingest/src/import-lessons/importLessonsFromCSV.ts b/packages/ingest/src/import-lessons/importLessonsFromCSV.ts index b503db7a7..8e473ef27 100644 --- a/packages/ingest/src/import-lessons/importLessonsFromCSV.ts +++ b/packages/ingest/src/import-lessons/importLessonsFromCSV.ts @@ -6,7 +6,7 @@ import fs from "node:fs"; import { IngestError } from "../IngestError"; import { chunkAndPromiseAll } from "../utils/chunkAndPromiseAll"; import { getDataHash } from "../utils/getDataHash"; -import type { RawLesson} from "../zod-schema/zodSchema"; +import type { RawLesson } from "../zod-schema/zodSchema"; import { RawLessonSchema } from "../zod-schema/zodSchema"; const log = aiLogger("ingest"); diff --git a/packages/ingest/src/index.ts b/packages/ingest/src/index.ts index c2f461faf..67329af6f 100644 --- a/packages/ingest/src/index.ts +++ b/packages/ingest/src/index.ts @@ -27,25 +27,25 @@ async function main() { const ingestId = process.argv[3] ?? (await getLatestIngestId({ prisma })); switch (command) { case "start": - ingestStart({ prisma, log }); + await ingestStart({ prisma, log }); break; case "captions": - captions({ prisma, log, ingestId }); + await captions({ prisma, log, ingestId }); break; case "lp-start": - lpBatchStart({ prisma, log, ingestId }); + await lpBatchStart({ prisma, log, ingestId }); break; case "lp-sync": - lpBatchSync({ prisma, log, ingestId }); + await lpBatchSync({ prisma, log, ingestId }); break; case "chunk": - lpChunking({ prisma, log, ingestId }); + await lpChunking({ prisma, log, ingestId }); break; case "embed-start": - lpPartsEmbedStart({ prisma, log, ingestId }); + await lpPartsEmbedStart({ prisma, log, ingestId }); break; case "embed-sync": - lpPartsEmbedSync({ prisma, log, ingestId }); + await lpPartsEmbedSync({ prisma, log, ingestId }); break; default: log.error("Unknown command"); diff --git a/packages/ingest/src/utils/jsonlToArray.ts b/packages/ingest/src/utils/jsonlToArray.ts index cff0b22b4..8eca1e86e 100644 --- a/packages/ingest/src/utils/jsonlToArray.ts +++ b/packages/ingest/src/utils/jsonlToArray.ts @@ -1,6 +1,9 @@ -export function jsonlToArray(jsonl: string) { - return jsonl - .split("\n") - .filter((line) => line.trim() !== "") - .map((line) => JSON.parse(line)); +export function jsonlToArray(jsonl: string): unknown[] { + return ( + jsonl + .split("\n") + .filter((line) => line.trim() !== "") + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + .map((line) => JSON.parse(line)) + ); } From 07e6b5f3ef7c2ae10d45aaa292d503e9cbcb4ecb Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Wed, 30 Oct 2024 16:40:05 +0000 Subject: [PATCH 12/61] chore: make organiseSections a shared file (#301) --- .../Chat/chat-lessonPlanDisplay.tsx | 22 +--------------- .../src/lib/lessonPlan/organiseSections.ts | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 21 deletions(-) create mode 100644 apps/nextjs/src/lib/lessonPlan/organiseSections.ts diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-lessonPlanDisplay.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-lessonPlanDisplay.tsx index 81a8637c0..d82b74b3f 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/chat-lessonPlanDisplay.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/chat-lessonPlanDisplay.tsx @@ -5,6 +5,7 @@ import { Flex, Text } from "@radix-ui/themes"; import { cva } from "class-variance-authority"; import { useLessonChat } from "@/components/ContextProviders/ChatProvider"; +import { organiseSections } from "@/lib/lessonPlan/organiseSections"; import Skeleton from "../common/Skeleton"; import DropDownSection from "./drop-down-section"; @@ -41,27 +42,6 @@ const displayStyles = cva( "relative flex flex-col space-y-10 px-14 pb-28 opacity-100 sm:px-24 ", ); -const organiseSections = [ - { - trigger: "learningOutcome", - dependants: ["learningOutcome", "learningCycles"], - }, - { - trigger: "priorKnowledge", - dependants: [ - "priorKnowledge", - "keyLearningPoints", - "misconceptions", - "keywords", - ], - }, - { - trigger: "starterQuiz", - dependants: ["starterQuiz", "cycle1", "cycle2", "cycle3", "exitQuiz"], - }, - { trigger: "additionalMaterials", dependants: ["additionalMaterials"] }, -]; - export const LessonPlanDisplay = ({ chatEndRef, sectionRefs, diff --git a/apps/nextjs/src/lib/lessonPlan/organiseSections.ts b/apps/nextjs/src/lib/lessonPlan/organiseSections.ts new file mode 100644 index 000000000..4e5c1592f --- /dev/null +++ b/apps/nextjs/src/lib/lessonPlan/organiseSections.ts @@ -0,0 +1,25 @@ +import { LessonPlanKeys } from "@oakai/aila/src/protocol/schema"; + +export const organiseSections: { + trigger: LessonPlanKeys; + dependants: LessonPlanKeys[]; +}[] = [ + { + trigger: "learningOutcome", + dependants: ["learningOutcome", "learningCycles"], + }, + { + trigger: "priorKnowledge", + dependants: [ + "priorKnowledge", + "keyLearningPoints", + "misconceptions", + "keywords", + ], + }, + { + trigger: "starterQuiz", + dependants: ["starterQuiz", "cycle1", "cycle2", "cycle3", "exitQuiz"], + }, + { trigger: "additionalMaterials", dependants: ["additionalMaterials"] }, +]; From 15e8a67e9a1ce8e08baa5642c7fffbd2193d3b64 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Tue, 29 Oct 2024 23:59:42 +0100 Subject: [PATCH 13/61] feat: bootstrap posthog feature flags with local evaluation --- apps/nextjs/src/app/layout.tsx | 45 ++++++++++++++- .../ContextProviders/AnalyticsProvider.tsx | 4 +- apps/nextjs/src/lib/posthog/posthog.ts | 6 +- .../analytics/posthogAiBetaServerClient.ts | 55 ++++++++++++++++++- packages/logger/index.ts | 1 + 5 files changed, 107 insertions(+), 4 deletions(-) diff --git a/apps/nextjs/src/app/layout.tsx b/apps/nextjs/src/app/layout.tsx index 70fb045da..17ba9087f 100644 --- a/apps/nextjs/src/app/layout.tsx +++ b/apps/nextjs/src/app/layout.tsx @@ -2,18 +2,23 @@ import React from "react"; import { Toaster } from "react-hot-toast"; import { ClerkProvider } from "@clerk/nextjs"; +import { auth } from "@clerk/nextjs/server"; import "@fontsource/lexend"; import "@fontsource/lexend/500.css"; import "@fontsource/lexend/600.css"; import "@fontsource/lexend/700.css"; import "@fontsource/lexend/800.css"; import "@fontsource/lexend/900.css"; +import { posthogAiBetaServerClient } from "@oakai/core/src/analytics/posthogAiBetaServerClient"; +import { aiLogger } from "@oakai/logger"; import { Theme } from "@radix-ui/themes"; import "@radix-ui/themes/styles.css"; +import cookie from "cookie"; import { GeistMono } from "geist/font/mono"; import { Lexend } from "next/font/google"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; +import invariant from "tiny-invariant"; import "@/app/globals.css"; import "@/app/theme-config.css"; @@ -66,7 +71,42 @@ interface RootLayoutProps { children: React.ReactNode; } -export default function RootLayout({ children }: Readonly) { +declare global { + interface CustomJwtSessionClaims { + email: string; + } +} + +function getDistinctIdFromCookie(): string | null { + const cookieHeader = headers().get("cookie"); + invariant(cookieHeader, "No cookie header"); + const cookies = cookie.parse(cookieHeader) as Record; + const phCookieKey = `ph_${process.env.NEXT_PUBLIC_POSTHOG_API_KEY}_posthog`; + const phCookie = cookies[phCookieKey]; + if (!phCookie) { + return null; + } + return JSON.parse(phCookie)["distinct_id"]; +} + +async function getBootstrapFeatures() { + const log = aiLogger("analytics:feature-flags"); + const { userId, sessionClaims } = auth(); + const email = sessionClaims?.email; + + const distinctId = userId ?? getDistinctIdFromCookie() ?? "0"; + log.info("Evaluating feature flags for", distinctId); + const features = await posthogAiBetaServerClient.getAllFlags(distinctId, { + personProperties: email ? { email } : {}, + onlyEvaluateLocally: true, + }); + log.info("Bootstrapping feature flags", features); + return features; +} + +export default async function RootLayout({ + children, +}: Readonly) { const nonce = headers().get("x-nonce"); if (!nonce) { // Our middleware path matching excludes static paths like /_next/static/... @@ -75,6 +115,8 @@ export default function RootLayout({ children }: Readonly) { return redirect("/not-found"); } + const bootstrappedFeatures = await getBootstrapFeatures(); + return ( @@ -108,6 +150,7 @@ export default function RootLayout({ children }: Readonly) { }), }, }} + bootstrappedFeatures={bootstrappedFeatures} > {children} diff --git a/apps/nextjs/src/components/ContextProviders/AnalyticsProvider.tsx b/apps/nextjs/src/components/ContextProviders/AnalyticsProvider.tsx index 8ed3b2680..d2fbc22d1 100644 --- a/apps/nextjs/src/components/ContextProviders/AnalyticsProvider.tsx +++ b/apps/nextjs/src/components/ContextProviders/AnalyticsProvider.tsx @@ -74,6 +74,7 @@ export const analyticsContext = createContext(null); export type AnalyticsProviderProps = { children?: React.ReactNode; avoOptions?: Partial; + bootstrappedFeatures: Record; }; if ( @@ -130,6 +131,7 @@ const posthogClientAiBeta = new PostHog(); export const AnalyticsProvider: React.FC = ({ children, avoOptions, + bootstrappedFeatures, }) => { const [hubspotScriptLoaded, setHubspotScriptLoadedFn] = useState(false); const setHubspotScriptLoaded = useCallback(() => { @@ -162,7 +164,7 @@ export const AnalyticsProvider: React.FC = ({ ); const posthogAiBeta = useAnalyticsService({ service: posthogServiceAiBeta, - config: posthogAiBetaConfig, + config: { ...posthogAiBetaConfig, bootstrappedFeatures }, consentState: posthogConsentAiBeta, }); diff --git a/apps/nextjs/src/lib/posthog/posthog.ts b/apps/nextjs/src/lib/posthog/posthog.ts index ea20b44b8..f34a33f8f 100644 --- a/apps/nextjs/src/lib/posthog/posthog.ts +++ b/apps/nextjs/src/lib/posthog/posthog.ts @@ -12,18 +12,22 @@ export type PosthogConfig = { apiKey: string; apiHost: string; uiHost: string; + bootstrappedFeatures?: Record; }; export const posthogToAnalyticsService = ( client: PostHog, ): AnalyticsService => ({ name: "posthog", - init: ({ apiKey, apiHost, uiHost }) => + init: ({ apiKey, apiHost, uiHost, bootstrappedFeatures }) => new Promise((resolve) => { client.init(apiKey, { api_host: apiHost, ui_host: uiHost, persistence: "localStorage+cookie", + bootstrap: { + featureFlags: bootstrappedFeatures, + }, loaded: (posthog) => { // Enable debug mode in development if (process.env.NEXT_PUBLIC_POSTHOG_DEBUG === "true") { diff --git a/packages/core/src/analytics/posthogAiBetaServerClient.ts b/packages/core/src/analytics/posthogAiBetaServerClient.ts index 3e7f23ef4..9d9430daf 100644 --- a/packages/core/src/analytics/posthogAiBetaServerClient.ts +++ b/packages/core/src/analytics/posthogAiBetaServerClient.ts @@ -1,7 +1,56 @@ +import { aiLogger } from "@oakai/logger"; +import { kv } from "@vercel/kv"; import { PostHog } from "posthog-node"; const host = process.env.NEXT_PUBLIC_POSTHOG_HOST as string; const apiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY || "*"; +const personalApiKey = process.env.POSTHOG_PERSONAL_KEY; + +const log = aiLogger("analytics:feature-flags"); + +const KV_KEY = "posthog-feature-flag-local-evaluation"; + +const setKv = async (response: Response) => { + const value = await response.text(); + await kv.set(KV_KEY, value, { ex: 30 }); +}; + +const getKv = async () => { + const value = await kv.get(KV_KEY); + if (!value) { + return null; + } + return { + status: 200, + json: () => Promise.resolve(value), + text: () => Promise.resolve(JSON.stringify(value)), + }; +}; + +const cachedFetch: PostHog["fetch"] = async (url, options) => { + if (url.includes("api/feature_flag/local_evaluation")) { + const kvCachedResponse = await getKv(); + if (kvCachedResponse) { + log.info("fetched from KV"); + return kvCachedResponse; + } + const result = await fetch(url, options); + + if (result.ok) { + const cachedResult = result.clone(); + await setKv(cachedResult); + log.info("saved to KV"); + } + + return result; + } + + if (url.includes("/decide")) { + log.warn("WARN: feature flag loaded through API"); + } + + return await fetch(url, options); +}; /** * This is the posthog nodejs client configured to send events to the @@ -10,4 +59,8 @@ const apiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY || "*"; * This is the main Oak posthog instance used for tracking user events * on the client-side. */ -export const posthogAiBetaServerClient = new PostHog(apiKey, { host }); +export const posthogAiBetaServerClient = new PostHog(apiKey, { + host, + personalApiKey, + fetch: cachedFetch, +}); diff --git a/packages/logger/index.ts b/packages/logger/index.ts index fcc81e334..c19b06a67 100644 --- a/packages/logger/index.ts +++ b/packages/logger/index.ts @@ -31,6 +31,7 @@ type ChildKey = | "aila:rag" | "aila:testing" | "analytics" + | "analytics:feature-flags" | "app" | "auth" | "chat" From 57ade5fcc8e777fba801326034b69243fd5e1f48 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:29:17 +0100 Subject: [PATCH 14/61] Tidy --- apps/nextjs/src/app/layout.tsx | 41 +---------------- .../nextjs/src/lib/feature-flags/bootstrap.ts | 46 +++++++++++++++++++ apps/nextjs/src/middleware.ts | 2 + packages/api/src/router/appSessions.ts | 1 + .../featureFlagEvaluation.ts} | 35 ++++++-------- .../posthogAiBetaServerClient/index.ts | 26 +++++++++++ 6 files changed, 90 insertions(+), 61 deletions(-) create mode 100644 apps/nextjs/src/lib/feature-flags/bootstrap.ts rename packages/core/src/analytics/{posthogAiBetaServerClient.ts => posthogAiBetaServerClient/featureFlagEvaluation.ts} (57%) create mode 100644 packages/core/src/analytics/posthogAiBetaServerClient/index.ts diff --git a/apps/nextjs/src/app/layout.tsx b/apps/nextjs/src/app/layout.tsx index 17ba9087f..08366e7f3 100644 --- a/apps/nextjs/src/app/layout.tsx +++ b/apps/nextjs/src/app/layout.tsx @@ -2,23 +2,18 @@ import React from "react"; import { Toaster } from "react-hot-toast"; import { ClerkProvider } from "@clerk/nextjs"; -import { auth } from "@clerk/nextjs/server"; import "@fontsource/lexend"; import "@fontsource/lexend/500.css"; import "@fontsource/lexend/600.css"; import "@fontsource/lexend/700.css"; import "@fontsource/lexend/800.css"; import "@fontsource/lexend/900.css"; -import { posthogAiBetaServerClient } from "@oakai/core/src/analytics/posthogAiBetaServerClient"; -import { aiLogger } from "@oakai/logger"; import { Theme } from "@radix-ui/themes"; import "@radix-ui/themes/styles.css"; -import cookie from "cookie"; import { GeistMono } from "geist/font/mono"; import { Lexend } from "next/font/google"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; -import invariant from "tiny-invariant"; import "@/app/globals.css"; import "@/app/theme-config.css"; @@ -28,6 +23,7 @@ import { CookieConsentProvider } from "@/components/ContextProviders/CookieConse import FontProvider from "@/components/ContextProviders/FontProvider"; import { GleapProvider } from "@/components/ContextProviders/GleapProvider"; import { WebDebuggerPosition } from "@/lib/avo/Avo"; +import { getBootstrappedFeatures } from "@/lib/feature-flags/bootstrap"; import { SentryIdentify } from "@/lib/sentry/SentryIdentify"; import { cn } from "@/lib/utils"; import { TRPCReactProvider } from "@/utils/trpc"; @@ -71,39 +67,6 @@ interface RootLayoutProps { children: React.ReactNode; } -declare global { - interface CustomJwtSessionClaims { - email: string; - } -} - -function getDistinctIdFromCookie(): string | null { - const cookieHeader = headers().get("cookie"); - invariant(cookieHeader, "No cookie header"); - const cookies = cookie.parse(cookieHeader) as Record; - const phCookieKey = `ph_${process.env.NEXT_PUBLIC_POSTHOG_API_KEY}_posthog`; - const phCookie = cookies[phCookieKey]; - if (!phCookie) { - return null; - } - return JSON.parse(phCookie)["distinct_id"]; -} - -async function getBootstrapFeatures() { - const log = aiLogger("analytics:feature-flags"); - const { userId, sessionClaims } = auth(); - const email = sessionClaims?.email; - - const distinctId = userId ?? getDistinctIdFromCookie() ?? "0"; - log.info("Evaluating feature flags for", distinctId); - const features = await posthogAiBetaServerClient.getAllFlags(distinctId, { - personProperties: email ? { email } : {}, - onlyEvaluateLocally: true, - }); - log.info("Bootstrapping feature flags", features); - return features; -} - export default async function RootLayout({ children, }: Readonly) { @@ -115,7 +78,7 @@ export default async function RootLayout({ return redirect("/not-found"); } - const bootstrappedFeatures = await getBootstrapFeatures(); + const bootstrappedFeatures = await getBootstrappedFeatures(headers()); return ( diff --git a/apps/nextjs/src/lib/feature-flags/bootstrap.ts b/apps/nextjs/src/lib/feature-flags/bootstrap.ts new file mode 100644 index 000000000..0f49aa8a5 --- /dev/null +++ b/apps/nextjs/src/lib/feature-flags/bootstrap.ts @@ -0,0 +1,46 @@ +"use server"; + +import { auth } from "@clerk/nextjs/server"; +import { posthogAiBetaServerClient } from "@oakai/core/src/analytics/posthogAiBetaServerClient"; +import { aiLogger } from "@oakai/logger"; +import cookie from "cookie"; +import type { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers"; +import invariant from "tiny-invariant"; + +const log = aiLogger("analytics:feature-flags"); + +/** + * We use posthog feature flags to toggle functionality without deploying code changes. + * Fething feature flags on the frontend hasn't been reliable for us: + * - you have to wait for a round trip to the posthog API + * - we currently have a bug where denying cookie consent prevents feature flags from loading + * + * Instead, we can bootstrap feature flags by evaluating them on the server. + * https://posthog.com/docs/feature-flags/bootstrapping + */ + +function getDistinctIdFromCookie(headers: ReadonlyHeaders) { + const cookieHeader = headers.get("cookie"); + invariant(cookieHeader, "No cookie header"); + const cookies = cookie.parse(cookieHeader) as Record; + const phCookieKey = `ph_${process.env.NEXT_PUBLIC_POSTHOG_API_KEY}_posthog`; + const phCookie = cookies[phCookieKey]; + if (!phCookie) { + return null; + } + return (JSON.parse(phCookie) as { distinct_id: string })["distinct_id"]; +} + +export async function getBootstrappedFeatures(headers: ReadonlyHeaders) { + const { userId } = auth(); + + const distinctId = userId ?? getDistinctIdFromCookie(headers) ?? "0"; + log.info("Evaluating feature flags for", distinctId); + const features = await posthogAiBetaServerClient.getAllFlags(distinctId, { + // Only bootstrap flags which don't depend on user properties + // These are typically flags representing new features + onlyEvaluateLocally: true, + }); + log.info("Bootstrapping feature flags", features); + return features; +} diff --git a/apps/nextjs/src/middleware.ts b/apps/nextjs/src/middleware.ts index 5a4f0aacd..7e3c23962 100644 --- a/apps/nextjs/src/middleware.ts +++ b/apps/nextjs/src/middleware.ts @@ -1,3 +1,5 @@ +import { auth } from "@clerk/nextjs/dist/types/server"; +import { posthogAiBetaServerClient } from "@oakai/core/src/analytics/posthogAiBetaServerClient"; import type { NextMiddlewareResult } from "next/dist/server/web/types"; import type { NextFetchEvent, NextMiddleware, NextRequest } from "next/server"; import { NextResponse } from "next/server"; diff --git a/packages/api/src/router/appSessions.ts b/packages/api/src/router/appSessions.ts index ce46e6572..b01c7bccc 100644 --- a/packages/api/src/router/appSessions.ts +++ b/packages/api/src/router/appSessions.ts @@ -1,6 +1,7 @@ import type { SignedInAuthObject } from "@clerk/backend/internal"; import { clerkClient } from "@clerk/nextjs/server"; import { demoUsers } from "@oakai/core"; +import { posthogAiBetaServerClient } from "@oakai/core/src/analytics/posthogAiBetaServerClient"; import { rateLimits } from "@oakai/core/src/utils/rateLimiting/rateLimit"; import { RateLimitExceededError } from "@oakai/core/src/utils/rateLimiting/userBasedRateLimiter"; import type { Prisma, PrismaClientWithAccelerate } from "@oakai/db"; diff --git a/packages/core/src/analytics/posthogAiBetaServerClient.ts b/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts similarity index 57% rename from packages/core/src/analytics/posthogAiBetaServerClient.ts rename to packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts index 9d9430daf..e6ddca552 100644 --- a/packages/core/src/analytics/posthogAiBetaServerClient.ts +++ b/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts @@ -1,18 +1,16 @@ import { aiLogger } from "@oakai/logger"; import { kv } from "@vercel/kv"; -import { PostHog } from "posthog-node"; +import type { PostHog } from "posthog-node"; -const host = process.env.NEXT_PUBLIC_POSTHOG_HOST as string; -const apiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY || "*"; -const personalApiKey = process.env.POSTHOG_PERSONAL_KEY; +const KV_KEY = "posthog-feature-flag-local-evaluation"; +const ONE_DAY = 24 * 60 * 60 * 1000; +const ONE_MINUTE = 60 * 1000; const log = aiLogger("analytics:feature-flags"); -const KV_KEY = "posthog-feature-flag-local-evaluation"; - const setKv = async (response: Response) => { const value = await response.text(); - await kv.set(KV_KEY, value, { ex: 30 }); + await kv.set(KV_KEY, value, { ex: ONE_MINUTE }); }; const getKv = async () => { @@ -27,11 +25,11 @@ const getKv = async () => { }; }; -const cachedFetch: PostHog["fetch"] = async (url, options) => { +export const cachedFetch: PostHog["fetch"] = async (url, options) => { if (url.includes("api/feature_flag/local_evaluation")) { const kvCachedResponse = await getKv(); if (kvCachedResponse) { - log.info("fetched from KV"); + log.info("evaluations fetched from KV"); return kvCachedResponse; } const result = await fetch(url, options); @@ -39,7 +37,7 @@ const cachedFetch: PostHog["fetch"] = async (url, options) => { if (result.ok) { const cachedResult = result.clone(); await setKv(cachedResult); - log.info("saved to KV"); + log.info("evaluations cached to KV"); } return result; @@ -52,15 +50,8 @@ const cachedFetch: PostHog["fetch"] = async (url, options) => { return await fetch(url, options); }; -/** - * This is the posthog nodejs client configured to send events to the - * posthog AI BETA instance. - * - * This is the main Oak posthog instance used for tracking user events - * on the client-side. - */ -export const posthogAiBetaServerClient = new PostHog(apiKey, { - host, - personalApiKey, - fetch: cachedFetch, -}); +export const featureFlagsPollingInterval = + process.env.NODE_ENV === "production" + ? ONE_MINUTE + : // prevent polling timeout from stacking when HMR replaces posthogAiBetaServerClient + ONE_DAY; diff --git a/packages/core/src/analytics/posthogAiBetaServerClient/index.ts b/packages/core/src/analytics/posthogAiBetaServerClient/index.ts new file mode 100644 index 000000000..0597d2286 --- /dev/null +++ b/packages/core/src/analytics/posthogAiBetaServerClient/index.ts @@ -0,0 +1,26 @@ +import { PostHog } from "posthog-node"; + +import { + cachedFetch, + featureFlagsPollingInterval, +} from "./featureFlagEvaluation"; + +const host = process.env.NEXT_PUBLIC_POSTHOG_HOST as string; +const apiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY || "*"; +const personalApiKey = process.env.POSTHOG_PERSONAL_KEY; + +/** + * This is the posthog nodejs client configured to send events to the + * posthog AI BETA instance. + */ +export const posthogAiBetaServerClient = new PostHog(apiKey, { + host, + personalApiKey, + + // We evaluate user feature flags on the server to prevent round-trips to posthog. + // See https://posthog.com/docs/feature-flags/local-evaluation + // As we use edge functions, we can't hold the flag definitions in memory. + // Instead we cache them in KV through a custom fetch implementation. + fetch: cachedFetch, + featureFlagsPollingInterval, +}); From 45c0551cbdb463192d4548f3eb23d894770dc54f Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:52:34 +0100 Subject: [PATCH 15/61] PR feedback --- .../posthogAiBetaServerClient/featureFlagEvaluation.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts b/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts index e6ddca552..e15d9d465 100644 --- a/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts +++ b/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts @@ -51,7 +51,5 @@ export const cachedFetch: PostHog["fetch"] = async (url, options) => { }; export const featureFlagsPollingInterval = - process.env.NODE_ENV === "production" - ? ONE_MINUTE - : // prevent polling timeout from stacking when HMR replaces posthogAiBetaServerClient - ONE_DAY; + // prevent polling timeout from stacking when HMR replaces posthogAiBetaServerClient + process.env.NODE_ENV === "development" ? ONE_DAY : ONE_MINUTE; From 90a06171514a7a569a73a8b7f78fe6a782c801b1 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Wed, 30 Oct 2024 18:25:36 +0100 Subject: [PATCH 16/61] fixes --- .../posthogAiBetaServerClient/featureFlagEvaluation.ts | 8 +++++--- .../core/src/analytics/posthogAiBetaServerClient/index.ts | 6 ++++-- turbo.json | 3 ++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts b/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts index e15d9d465..1acaa1dfd 100644 --- a/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts +++ b/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts @@ -3,14 +3,12 @@ import { kv } from "@vercel/kv"; import type { PostHog } from "posthog-node"; const KV_KEY = "posthog-feature-flag-local-evaluation"; -const ONE_DAY = 24 * 60 * 60 * 1000; -const ONE_MINUTE = 60 * 1000; const log = aiLogger("analytics:feature-flags"); const setKv = async (response: Response) => { const value = await response.text(); - await kv.set(KV_KEY, value, { ex: ONE_MINUTE }); + await kv.set(KV_KEY, value, { ex: 60 }); }; const getKv = async () => { @@ -38,6 +36,8 @@ export const cachedFetch: PostHog["fetch"] = async (url, options) => { const cachedResult = result.clone(); await setKv(cachedResult); log.info("evaluations cached to KV"); + } else { + log.error("failed to load evaluations", { status: result.status }); } return result; @@ -50,6 +50,8 @@ export const cachedFetch: PostHog["fetch"] = async (url, options) => { return await fetch(url, options); }; +const ONE_DAY = 24 * 60 * 60 * 1000; +const ONE_MINUTE = 60 * 1000; export const featureFlagsPollingInterval = // prevent polling timeout from stacking when HMR replaces posthogAiBetaServerClient process.env.NODE_ENV === "development" ? ONE_DAY : ONE_MINUTE; diff --git a/packages/core/src/analytics/posthogAiBetaServerClient/index.ts b/packages/core/src/analytics/posthogAiBetaServerClient/index.ts index 0597d2286..48a7d4d18 100644 --- a/packages/core/src/analytics/posthogAiBetaServerClient/index.ts +++ b/packages/core/src/analytics/posthogAiBetaServerClient/index.ts @@ -1,4 +1,5 @@ import { PostHog } from "posthog-node"; +import invariant from "tiny-invariant"; import { cachedFetch, @@ -7,7 +8,8 @@ import { const host = process.env.NEXT_PUBLIC_POSTHOG_HOST as string; const apiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY || "*"; -const personalApiKey = process.env.POSTHOG_PERSONAL_KEY; +const personalApiKey = process.env.POSTHOG_PERSONAL_KEY_FLAGS; +invariant(personalApiKey, "POSTHOG_PERSONAL_KEY_FLAGS is required"); /** * This is the posthog nodejs client configured to send events to the @@ -15,7 +17,6 @@ const personalApiKey = process.env.POSTHOG_PERSONAL_KEY; */ export const posthogAiBetaServerClient = new PostHog(apiKey, { host, - personalApiKey, // We evaluate user feature flags on the server to prevent round-trips to posthog. // See https://posthog.com/docs/feature-flags/local-evaluation @@ -23,4 +24,5 @@ export const posthogAiBetaServerClient = new PostHog(apiKey, { // Instead we cache them in KV through a custom fetch implementation. fetch: cachedFetch, featureFlagsPollingInterval, + personalApiKey, }); diff --git a/turbo.json b/turbo.json index 77afe9df6..7bd629f0a 100644 --- a/turbo.json +++ b/turbo.json @@ -131,6 +131,7 @@ "STRICT_CSP", "TELEMETRY_ENABLED", "UPSTASH_*", - "WOLFRAM_CLIENT_SECRET" + "WOLFRAM_CLIENT_SECRET", + "POSTHOG_PERSONAL_KEY_FLAGS" ] } From e8647dc0577d0e300ae07f8f52e22c4055f96ba5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 31 Oct 2024 08:52:36 +0000 Subject: [PATCH 17/61] build(release v1.13.0): See CHANGE_LOG.md --- CHANGE_LOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index 05222b8da..a58cbc0f8 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,3 +1,29 @@ +# [1.13.0](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.12.1...v1.13.0) (2024-10-31) + + +### Bug Fixes + +* await persisting the generation ([#292](https://github.com/oaknational/oak-ai-lesson-assistant/issues/292)) ([730dbb9](https://github.com/oaknational/oak-ai-lesson-assistant/commit/730dbb94a786f9f8a6e03e9ed4d9ff90853522fd)) +* await the enqueue method in web actions ([#293](https://github.com/oaknational/oak-ai-lesson-assistant/issues/293)) ([c9cb82b](https://github.com/oaknational/oak-ai-lesson-assistant/commit/c9cb82b2df566b0ec2a952b21d22e87d61fcd094)) +* await the save download event call ([#317](https://github.com/oaknational/oak-ai-lesson-assistant/issues/317)) ([1e05c1a](https://github.com/oaknational/oak-ai-lesson-assistant/commit/1e05c1ad177d2e385fc4fa6cc9bb76548d039194)) +* completion and system messages do not need to be async ([#294](https://github.com/oaknational/oak-ai-lesson-assistant/issues/294)) ([be79158](https://github.com/oaknational/oak-ai-lesson-assistant/commit/be791584805d219db1da4949a8a32c0a0f40b589)) +* linting for app components ([#295](https://github.com/oaknational/oak-ai-lesson-assistant/issues/295)) ([1212eb5](https://github.com/oaknational/oak-ai-lesson-assistant/commit/1212eb50f5e2e3ee913b57ec7bffc2fcabe981c8)) +* pages that do not load data do not need to be async ([#296](https://github.com/oaknational/oak-ai-lesson-assistant/issues/296)) ([a7eaefa](https://github.com/oaknational/oak-ai-lesson-assistant/commit/a7eaefa5acef4d89a60dd79301cdf4be61db6830)) +* promisify chat API route get handler ([#316](https://github.com/oaknational/oak-ai-lesson-assistant/issues/316)) ([1824aea](https://github.com/oaknational/oak-ai-lesson-assistant/commit/1824aead23e65654a4651abb91e589d218cb9bb1)) +* reintroduce custom eslint ([#284](https://github.com/oaknational/oak-ai-lesson-assistant/issues/284)) ([adc1efa](https://github.com/oaknational/oak-ai-lesson-assistant/commit/adc1efa686a7cc332a1755a3e77f5e50a831df7d)) +* rename accordian to accordion ([#297](https://github.com/oaknational/oak-ai-lesson-assistant/issues/297)) ([cd35dc1](https://github.com/oaknational/oak-ai-lesson-assistant/commit/cd35dc15b97960a18196119540cc5848f58eaa47)) +* set VS Code to prefer type imports ([#291](https://github.com/oaknational/oak-ai-lesson-assistant/issues/291)) ([bc26948](https://github.com/oaknational/oak-ai-lesson-assistant/commit/bc269484ed8857b10aac36e06a0dab18c8308277)) +* update imports (type prefixes, paths) ([#298](https://github.com/oaknational/oak-ai-lesson-assistant/issues/298)) ([c332717](https://github.com/oaknational/oak-ai-lesson-assistant/commit/c33271721e15e3c06374c4648fe69611e306ac14)) +* use browserLogger for errors in the browser ([#280](https://github.com/oaknational/oak-ai-lesson-assistant/issues/280)) ([647c904](https://github.com/oaknational/oak-ai-lesson-assistant/commit/647c90415c6ea5f71e75973caad627abe49bfd0c)) +* use noengine in ci ([#283](https://github.com/oaknational/oak-ai-lesson-assistant/issues/283)) ([3a1bfd4](https://github.com/oaknational/oak-ai-lesson-assistant/commit/3a1bfd4df3d2489dda9387752f19c79b16e5da92)) +* use prisma generate --no-engine and reinstate prompts script ([#282](https://github.com/oaknational/oak-ai-lesson-assistant/issues/282)) ([a157b9a](https://github.com/oaknational/oak-ai-lesson-assistant/commit/a157b9a6fececed09f43b7cca3989edd5e636a59)) + + +### Features + +* add an incrementing iteration number to the chat ([#263](https://github.com/oaknational/oak-ai-lesson-assistant/issues/263)) ([5aaa1d9](https://github.com/oaknational/oak-ai-lesson-assistant/commit/5aaa1d91146b92c1c449ecbf4725c61ec226ec87)) +* selectively include Americanisms, RAG, analytics when instantiating Aila ([#287](https://github.com/oaknational/oak-ai-lesson-assistant/issues/287)) ([4e5e1f2](https://github.com/oaknational/oak-ai-lesson-assistant/commit/4e5e1f22a4ea6262114033e45ffe4817b483f379)) + ## [1.12.1](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.12.0...v1.12.1) (2024-10-28) From caa92244f6c0e955e5ada6d15c005fbb76f67a0d Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:21:59 +0100 Subject: [PATCH 18/61] hotfix: check for prd env in lhs panel --- .../src/components/AppComponents/Chat/chat-lhs-header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-lhs-header.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-lhs-header.tsx index f01676dec..45fadb88e 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/chat-lhs-header.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/chat-lhs-header.tsx @@ -23,7 +23,7 @@ const ChatLhsHeader = ({ return ( <>
      - {process.env.NEXT_PUBLIC_ENVIRONMENT !== "production" && ( + {process.env.NEXT_PUBLIC_ENVIRONMENT !== "prd" && (
      {chat.ailaStreamingStatus} From c44e1f10e98dcda37367f2cd03b324078c7b910f Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:28:25 +0100 Subject: [PATCH 19/61] fix: disable feature flagg polling in tests --- .../core/src/analytics/posthogAiBetaServerClient/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/analytics/posthogAiBetaServerClient/index.ts b/packages/core/src/analytics/posthogAiBetaServerClient/index.ts index 48a7d4d18..f5840a200 100644 --- a/packages/core/src/analytics/posthogAiBetaServerClient/index.ts +++ b/packages/core/src/analytics/posthogAiBetaServerClient/index.ts @@ -11,6 +11,8 @@ const apiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY || "*"; const personalApiKey = process.env.POSTHOG_PERSONAL_KEY_FLAGS; invariant(personalApiKey, "POSTHOG_PERSONAL_KEY_FLAGS is required"); +const enableLocalEvaluation = process.env.NODE_ENV !== "test"; + /** * This is the posthog nodejs client configured to send events to the * posthog AI BETA instance. @@ -24,5 +26,5 @@ export const posthogAiBetaServerClient = new PostHog(apiKey, { // Instead we cache them in KV through a custom fetch implementation. fetch: cachedFetch, featureFlagsPollingInterval, - personalApiKey, + personalApiKey: enableLocalEvaluation ? personalApiKey : undefined, }); From 761f4315a32c4a1fd5c9751061a3d6944236780f Mon Sep 17 00:00:00 2001 From: Tom Wise <79859203+tomwisecodes@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:54:52 +0000 Subject: [PATCH 20/61] chore: error pages (#327) --- apps/nextjs/src/app/_error.tsx | 21 +++++++++++++++++++++ apps/nextjs/src/app/global-error.tsx | 17 +++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 apps/nextjs/src/app/_error.tsx diff --git a/apps/nextjs/src/app/_error.tsx b/apps/nextjs/src/app/_error.tsx new file mode 100644 index 000000000..8ae2a3b7b --- /dev/null +++ b/apps/nextjs/src/app/_error.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { useEffect } from "react"; + +/** + * + * This page should never be reached as it is deprecated using the app router. + * By having this page we will silence warnings from vercel. + * This page redirects to the global handle all error page. + */ + +export default function DeprecatedErrorPage({ + error, +}: { + error: Error & { digest?: string }; +}) { + // redirect to global-error.tsx + useEffect(() => { + window.location.href = "/global-error"; + }, [error]); +} diff --git a/apps/nextjs/src/app/global-error.tsx b/apps/nextjs/src/app/global-error.tsx index 2f3c583e0..b655cdfa5 100644 --- a/apps/nextjs/src/app/global-error.tsx +++ b/apps/nextjs/src/app/global-error.tsx @@ -3,7 +3,8 @@ import { useEffect } from "react"; import * as Sentry from "@sentry/nextjs"; -import NextError from "next/error"; + +import FullPageWarning from "@/components/FullPageWarning"; export default function GlobalError({ error, @@ -19,11 +20,15 @@ export default function GlobalError({ return ( - {/* `NextError` is the default Next.js error page component. Its type - definition requires a `statusCode` prop. However, since the App Router - does not expose status codes for errors, we simply pass 0 to render a - generic error message. */} - + + + + Something went wrong! + + + AI Experiments homepage + + ); From a71e7bc4b6416ef01b48de4c950805f8244e658b Mon Sep 17 00:00:00 2001 From: Tom Wise <79859203+tomwisecodes@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:57:27 +0000 Subject: [PATCH 21/61] feat: update emails to use html (#319) --- packages/api/src/router/exports.ts | 60 ++++++++++--------- packages/core/src/utils/sendEmail.ts | 18 ++++-- .../sendEmailRequestingMoreGenerations.ts | 4 +- .../src/utils/sendJudgementFeedbackEmail.ts | 26 ++++---- .../core/src/utils/sendQuizFeedbackEmail.ts | 30 +++++----- packages/core/src/utils/stripHtml.ts | 3 + 6 files changed, 77 insertions(+), 64 deletions(-) create mode 100644 packages/core/src/utils/stripHtml.ts diff --git a/packages/api/src/router/exports.ts b/packages/api/src/router/exports.ts index 28b2372c7..8d1c53de4 100644 --- a/packages/api/src/router/exports.ts +++ b/packages/api/src/router/exports.ts @@ -724,23 +724,23 @@ export const exportsRouter = router({ to: userEmail, name: "Oak National Academy", subject: "Download your lesson made with Aila: " + lessonTitle, - body: ` -Hi ${userFirstName}, - -These are the lesson resources that you created with Aila. -You can use the following links to copy the lesson resources to your Google Drive. - -Lesson plan: ${`${lessonPlanLink.split("/edit")[0]}/copy`} -Slides: ${`${slidesLink.split("/edit")[0]}/copy`} -Worksheet: ${`${worksheetLink.split("/edit")[0]}/copy`} -Starter quiz: ${`${starterQuizLink.split("/edit")[0]}/copy`} -Exit quiz: ${`${exitQuizLink.split("/edit")[0]}/copy`} -Additional materials: ${`${additionalMaterialsLink.split("/edit")[0]}/copy`} - -We hope the lesson goes well for you and your class. If you have any feedback for us, please let us know. You can simply reply to this email. - -Aila, -Oak National Academy`, + htmlBody: `

      Hi ${userFirstName},

      + +

      These are the lesson resources that you created with Aila.
      + You can use the following links to copy the lesson resources to your Google Drive.

      + + Lesson plan
      + Slides
      + Worksheet
      + Starter quiz
      + Exit quiz
      + Additional materials


      + + +

      We hope the lesson goes well for you and your class. If you have any feedback for us, please let us know. You can simply reply to this email.

      + +

      Aila
      + Oak National Academy

      `, }); return emailSent ? true : false; @@ -769,22 +769,26 @@ Oak National Academy`, return false; } + const htmlBody = `

      Hi ${userFirstName},

      + +

      We made the lesson: ${lessonTitle}

      + +

      You can use the following link to copy the lesson resources ${title.toLowerCase()} to your Google Drive: + ${`${link.split("/edit")[0]}/copy`} +

      + +

      We hope the lesson goes well for you and your class. If you have any feedback for us, please let us know. You can simply reply to this email.

      + +

      Best regards,
      + Aila,
      + Oak National Academy

      `; + const emailSent = await sendEmail({ from: "aila@thenational.academy", to: userEmail, name: "Oak National Academy", subject: "Download your lesson made with Aila: " + lessonTitle, - body: ` -Hi ${userFirstName}, - -We made the lesson: ${lessonTitle} - -You can use the following link to copy the lesson resources ${title.toLowerCase()} to your Google Drive: ${`${link.split("/edit")[0]}/copy`} - -We hope the lesson goes well for you and your class. If you have any feedback for us, please let us know. You can simply reply to this email. - -Aila, -Oak National Academy`, + htmlBody: htmlBody, }); return emailSent ? true : false; diff --git a/packages/core/src/utils/sendEmail.ts b/packages/core/src/utils/sendEmail.ts index 59a0c6052..c1b2c68b1 100644 --- a/packages/core/src/utils/sendEmail.ts +++ b/packages/core/src/utils/sendEmail.ts @@ -1,7 +1,9 @@ +import { stripHtml } from "./stripHtml"; + type EmailParams = { from: string; to: string; - body: string; + htmlBody: string; subject: string; name?: string; }; @@ -16,8 +18,9 @@ export const sendEmail = async ({ from, to, subject, - body, + name, + htmlBody, }: EmailParams) => { const url = "https://api.postmarkapp.com/email"; const headers = { @@ -26,14 +29,17 @@ export const sendEmail = async ({ "X-Postmark-Server-Token": POSTMARK_SERVER_TOKEN, }; + const formattedFrom = name + ? `${name} <${from}>` + : `Oak National Academy <${from}>`; + const bodyJSON = JSON.stringify({ - From: from, + From: formattedFrom, To: to, Subject: subject, - Name: name ?? "Oak National Academy", ReplyTo: "help@thenational.academy", - TextBody: body, - HtmlBody: `
      ${body}
      `, + TextBody: stripHtml(htmlBody), + HtmlBody: `${htmlBody}`, MessageStream: "outbound", }); diff --git a/packages/core/src/utils/sendEmailRequestingMoreGenerations.ts b/packages/core/src/utils/sendEmailRequestingMoreGenerations.ts index 03a3ada3d..25325dc67 100644 --- a/packages/core/src/utils/sendEmailRequestingMoreGenerations.ts +++ b/packages/core/src/utils/sendEmailRequestingMoreGenerations.ts @@ -12,13 +12,13 @@ export const sendEmailRequestingMoreGenerations = async (input: { userEmail: string; }) => { const emailContent = ` - User ${input.userEmail} has requested more generations for app ${input.appSlug}. +

      User ${input.userEmail} has requested more generations for app ${input.appSlug}.

      `; return sendEmail({ from: "ai.feedback@thenational.academy", to: NEXT_PUBLIC_GLEAP_FEEDBACK_EMAIL_ADDR, subject: "Feedback: generation flagged", - body: emailContent, + htmlBody: emailContent, }); }; diff --git a/packages/core/src/utils/sendJudgementFeedbackEmail.ts b/packages/core/src/utils/sendJudgementFeedbackEmail.ts index a562adee3..35f5017c5 100644 --- a/packages/core/src/utils/sendJudgementFeedbackEmail.ts +++ b/packages/core/src/utils/sendJudgementFeedbackEmail.ts @@ -22,27 +22,27 @@ export const sendJudgementFeedbackEmail = async (input: { const { user, feedback, flaggedItem } = input; const inputDataAsString = JSON.stringify(input); - const emailContent = ` - User ${user.email} has clicked on the flag button on a comparative judgement. - Please check the database for more information: + const emailContent = `

      + User ${user.email} has clicked on the flag button on a comparative judgement.
      + Please check the database for more information:
      +
      +
      - + ${flaggedItem}
      + Feedback: ${feedback.typedFeedback}
      + Inappropriate content: ${feedback.contentIsInappropriate}
      + Factually incorrect content: ${feedback.contentIsFactuallyIncorrect}
      + Not helpful content: ${feedback.contentIsNotHelpful}
      + Full data of the time of the flag:
      - ${flaggedItem} - Feedback: ${feedback.typedFeedback} - Inappropriate content: ${feedback.contentIsInappropriate} - Factually incorrect content: ${feedback.contentIsFactuallyIncorrect} - Not helpful content: ${feedback.contentIsNotHelpful} - Full data of the time of the flag: - - ${inputDataAsString} + ${inputDataAsString}

      `; return sendEmail({ from: "ai.feedback@thenational.academy", to: NEXT_PUBLIC_GLEAP_FEEDBACK_EMAIL_ADDR, subject: "Feedback: comparative judgement result flagged", - body: emailContent, + htmlBody: emailContent, }); }; diff --git a/packages/core/src/utils/sendQuizFeedbackEmail.ts b/packages/core/src/utils/sendQuizFeedbackEmail.ts index 5410b18af..d53859af9 100644 --- a/packages/core/src/utils/sendQuizFeedbackEmail.ts +++ b/packages/core/src/utils/sendQuizFeedbackEmail.ts @@ -24,26 +24,26 @@ export const sendQuizFeedbackEmail = async (input: { const { user, feedback, flaggedItem, generationResponse } = input; const inputDataAsString = JSON.stringify(input); - const emailContent = ` - User ${user.email} has clicked on the flag button. - - Flagged content type: ${flaggedItem.type} - Flagged content: ${flaggedItem.value} - Generation ID: ${flaggedItem.lastGenerationId} - Feedback: ${feedback.typedFeedback} - Inappropriate content: ${feedback.contentIsInappropriate} - Factually incorrect content: ${feedback.contentIsFactuallyIncorrect} - Not helpful content: ${feedback.contentIsNotHelpful} - Generation response: ${generationResponse} - Full data of the time of the flag: - - ${inputDataAsString} + const emailContent = `

      + User ${user.email} has clicked on the flag button.
      +
      + Flagged content type: ${flaggedItem.type}
      + Flagged content: ${flaggedItem.value}
      + Generation ID: ${flaggedItem.lastGenerationId}
      + Feedback: ${feedback.typedFeedback}
      + Inappropriate content: ${feedback.contentIsInappropriate}
      + Factually incorrect content: ${feedback.contentIsFactuallyIncorrect}
      + Not helpful content: ${feedback.contentIsNotHelpful}
      + Generation response: ${generationResponse}
      + Full data of the time of the flag:
      +
      + ${inputDataAsString}

      `; return sendEmail({ from: "ai.feedback@thenational.academy", to: NEXT_PUBLIC_GLEAP_FEEDBACK_EMAIL_ADDR, subject: "Feedback: generation flagged", - body: emailContent, + htmlBody: emailContent, }); }; diff --git a/packages/core/src/utils/stripHtml.ts b/packages/core/src/utils/stripHtml.ts new file mode 100644 index 000000000..23671bb08 --- /dev/null +++ b/packages/core/src/utils/stripHtml.ts @@ -0,0 +1,3 @@ +export function stripHtml(html: string) { + return html.replace(/<[^>]*>?/gm, ""); +} From 4d6fd3b963710703b547cda5b2085bfc7f00f941 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:55:07 +0100 Subject: [PATCH 22/61] fix: skip instrumentation when running turbopack - fix HMR --- apps/nextjs/src/instrumentation.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/nextjs/src/instrumentation.ts b/apps/nextjs/src/instrumentation.ts index 72a66aa19..128ccd6c0 100644 --- a/apps/nextjs/src/instrumentation.ts +++ b/apps/nextjs/src/instrumentation.ts @@ -1,6 +1,12 @@ import * as Sentry from "@sentry/nextjs"; export async function register() { + // https://github.com/oaknational/oak-ai-lesson-assistant/pull/328 + // https://github.com/vercel/next.js/issues/70424 + if (process.env.TURBOPACK) { + return; + } + if (process.env.NEXT_RUNTIME === "nodejs") { await import("../sentry.server.config"); } From 759534a8b8189c25d665b34db1cdf7f0b7a8f31e Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:26:50 +0100 Subject: [PATCH 23/61] feat: use featureFlagGroup claim for feature flags --- apps/nextjs/src/lib/feature-flags/bootstrap.ts | 12 +++++++++--- apps/nextjs/src/middlewares/auth.middleware.ts | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/nextjs/src/lib/feature-flags/bootstrap.ts b/apps/nextjs/src/lib/feature-flags/bootstrap.ts index 0f49aa8a5..caa566a99 100644 --- a/apps/nextjs/src/lib/feature-flags/bootstrap.ts +++ b/apps/nextjs/src/lib/feature-flags/bootstrap.ts @@ -32,15 +32,21 @@ function getDistinctIdFromCookie(headers: ReadonlyHeaders) { } export async function getBootstrappedFeatures(headers: ReadonlyHeaders) { - const { userId } = auth(); + const { userId, sessionClaims } = auth(); const distinctId = userId ?? getDistinctIdFromCookie(headers) ?? "0"; log.info("Evaluating feature flags for", distinctId); + + const personProperties = sessionClaims?.labs?.featureFlagGroup + ? { featureFlagGroup: sessionClaims.labs.featureFlagGroup } + : undefined; + const features = await posthogAiBetaServerClient.getAllFlags(distinctId, { - // Only bootstrap flags which don't depend on user properties - // These are typically flags representing new features + // Only bootstrap flags which don't depend on PII onlyEvaluateLocally: true, + personProperties, }); + log.info("Bootstrapping feature flags", features); return features; } diff --git a/apps/nextjs/src/middlewares/auth.middleware.ts b/apps/nextjs/src/middlewares/auth.middleware.ts index f67bbb0ba..d282fd323 100644 --- a/apps/nextjs/src/middlewares/auth.middleware.ts +++ b/apps/nextjs/src/middlewares/auth.middleware.ts @@ -13,6 +13,7 @@ declare global { labs: { isDemoUser: boolean | null; isOnboarded: boolean | null; + featureFlagGroup: string | null; }; } } From 46b4f13a1e887177035a9d195886f34647a87ac9 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Thu, 31 Oct 2024 20:57:58 +0100 Subject: [PATCH 24/61] feat: sync featureFlagGroup from clerk to posthog --- apps/nextjs/package.json | 1 + .../src/app/api/webhooks/clerk/route.ts | 95 +++++++++++++++++++ .../nextjs/src/middlewares/auth.middleware.ts | 1 + packages/logger/index.ts | 3 +- pnpm-lock.yaml | 32 +++++++ turbo.json | 5 +- 6 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 apps/nextjs/src/app/api/webhooks/clerk/route.ts diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index fd0e25a4f..d0fdb7636 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -109,6 +109,7 @@ "remeda": "^1.29.0", "storybook": "^8.2.6", "superjson": "^1.9.1", + "svix": "^1.37.0", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7", "tiny-invariant": "^1.3.1", diff --git a/apps/nextjs/src/app/api/webhooks/clerk/route.ts b/apps/nextjs/src/app/api/webhooks/clerk/route.ts new file mode 100644 index 000000000..c1d72947f --- /dev/null +++ b/apps/nextjs/src/app/api/webhooks/clerk/route.ts @@ -0,0 +1,95 @@ +import type { UserJSON } from "@clerk/backend"; +import type { WebhookEvent } from "@clerk/nextjs/server"; +import { posthogAiBetaServerClient } from "@oakai/core/src/analytics/posthogAiBetaServerClient"; +import { aiLogger } from "@oakai/logger"; +import * as Sentry from "@sentry/node"; +import { headers } from "next/headers"; +import { Webhook } from "svix"; +import invariant from "tiny-invariant"; + +const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET; + +const log = aiLogger("webhooks"); + +declare global { + interface UserPublicMetadata { + labs?: { + isDemoUser?: boolean; + isOnboarded?: boolean; + featureFlagGroup?: string | null; + }; + } +} + +function getPrimaryEmail(user: UserJSON): string { + const primaryEmail = user.email_addresses.find( + (email) => email.id === user.primary_email_address_id, + ); + invariant(primaryEmail); + return primaryEmail.email_address; +} + +async function syncUserToPosthog(user: UserJSON) { + const featureFlagGroup = user.public_metadata.labs?.featureFlagGroup ?? ""; + posthogAiBetaServerClient.identify({ + distinctId: getPrimaryEmail(user), + properties: { featureFlagGroup }, + }); + await posthogAiBetaServerClient.shutdown(); + log.info("featureFlagGroup synced:", user.id, featureFlagGroup); +} + +export async function POST(req: Request) { + try { + const headerPayload = headers(); + const svixId = headerPayload.get("svix-id"); + const svixTimestamp = headerPayload.get("svix-timestamp"); + const svixSignature = headerPayload.get("svix-signature"); + + if (!svixId || !svixTimestamp || !svixSignature) { + return new Response("Error occured -- no svix headers", { + status: 400, + }); + } + + // Create a new Svix instance with your secret. + invariant(WEBHOOK_SECRET, "Missing CLERK_WEBHOOK_SECRET"); + const wh = new Webhook(WEBHOOK_SECRET); + + let evt: WebhookEvent; + + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const payload = await req.json(); + const body = JSON.stringify(payload); + evt = wh.verify(body, { + "svix-id": svixId, + "svix-timestamp": svixTimestamp, + "svix-signature": svixSignature, + }) as WebhookEvent; + } catch (err) { + log.error("Error verifying webhook:", err); + return new Response("Error occured", { + status: 400, + }); + } + + const eventType = evt.type; + switch (eventType) { + case "user.updated": { + const user = evt.data; + log.info("user.updated", user.id); + await syncUserToPosthog(user); + return new Response("Updated", { status: 200 }); + } + default: + log.error("Unknown event type:", eventType); + return new Response(`Unknown event type: ${eventType}`, { + status: 400, + }); + } + } catch (error) { + Sentry.captureException(error); + throw error; + } +} diff --git a/apps/nextjs/src/middlewares/auth.middleware.ts b/apps/nextjs/src/middlewares/auth.middleware.ts index d282fd323..361fea1e9 100644 --- a/apps/nextjs/src/middlewares/auth.middleware.ts +++ b/apps/nextjs/src/middlewares/auth.middleware.ts @@ -47,6 +47,7 @@ const publicRoutes = [ "/monitoring", "/sign-in(.*)", "/sign-up(.*)", + "/api/webhooks/clerk", ]; if ( process.env.NODE_ENV === "development" || diff --git a/packages/logger/index.ts b/packages/logger/index.ts index c19b06a67..456383f0a 100644 --- a/packages/logger/index.ts +++ b/packages/logger/index.ts @@ -59,7 +59,8 @@ type ChildKey = | "tracing" | "transcripts" | "trpc" - | "ui"; + | "ui" + | "webhooks"; const errorLogger = typeof window === "undefined" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2cd61e20..7ede231f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -326,6 +326,9 @@ importers: superjson: specifier: ^1.9.1 version: 1.12.0 + svix: + specifier: ^1.37.0 + version: 1.37.0 tailwind-merge: specifier: ^2.2.1 version: 2.2.1 @@ -8213,6 +8216,10 @@ packages: - debug dev: false + /@stablelib/base64@1.0.1: + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + dev: false + /@storybook/addon-actions@8.2.7(storybook@8.2.7): resolution: {integrity: sha512-wDnMGGmaogAForkNncfCx8BEDiwxeK8zC0lj8HkRPUuH6vTr81U5RIb12Wa2TnnNKLKMFAtyPSnofHf3OAfzZQ==} peerDependencies: @@ -13904,6 +13911,10 @@ packages: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} dev: true + /fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + dev: false + /fast-uri@3.0.1: resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} dev: true @@ -22251,6 +22262,27 @@ packages: periscopic: 3.1.0 dev: false + /svix-fetch@3.0.0: + resolution: {integrity: sha512-rcADxEFhSqHbraZIsjyZNh4TF6V+koloX1OzZ+AQuObX9mZ2LIMhm1buZeuc5BIZPftZpJCMBsSiBaeszo9tRw==} + dependencies: + node-fetch: 2.7.0 + whatwg-fetch: 3.6.18 + transitivePeerDependencies: + - encoding + dev: false + + /svix@1.37.0: + resolution: {integrity: sha512-zXtHLEN54ajW/qLreLLWbAyjHQVrpUnULIZY7WgneBVS/LdWVc3xR5vqLKzGVA4XWl/B9GXrs4+OL8EoFNWfIQ==} + dependencies: + '@stablelib/base64': 1.0.1 + es6-promise: 4.2.8 + fast-sha256: 1.3.0 + svix-fetch: 3.0.0 + url-parse: 1.5.10 + transitivePeerDependencies: + - encoding + dev: false + /swap-case@1.1.2: resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==} dependencies: diff --git a/turbo.json b/turbo.json index 7bd629f0a..0cb26a988 100644 --- a/turbo.json +++ b/turbo.json @@ -91,6 +91,7 @@ "globalEnv": [ "AILA_FIXTURES_ENABLED", "CLERK_*", + "CLERK_WEBHOOK_SECRET", "CLOUDINARY_*", "COHERE_*", "DATABASE_*", @@ -117,6 +118,7 @@ "OAI_INTERNAL_API_KEY", "OAK_GRAPHQL_SECRET", "OPENAI_*", + "POSTHOG_PERSONAL_KEY_FLAGS", "POSTMARK_SERVER_TOKEN", "PRISMA_ACCELERATE_DATABASE_URL", "PROMPT_PLAYBACK_ENABLED", @@ -131,7 +133,6 @@ "STRICT_CSP", "TELEMETRY_ENABLED", "UPSTASH_*", - "WOLFRAM_CLIENT_SECRET", - "POSTHOG_PERSONAL_KEY_FLAGS" + "WOLFRAM_CLIENT_SECRET" ] } From bde27770edf8e7030fbe512a6e341ca3992ea281 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Thu, 31 Oct 2024 22:09:17 +0100 Subject: [PATCH 25/61] PR feedback --- apps/nextjs/src/app/api/webhooks/clerk/route.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/nextjs/src/app/api/webhooks/clerk/route.ts b/apps/nextjs/src/app/api/webhooks/clerk/route.ts index c1d72947f..41f5768e7 100644 --- a/apps/nextjs/src/app/api/webhooks/clerk/route.ts +++ b/apps/nextjs/src/app/api/webhooks/clerk/route.ts @@ -35,7 +35,7 @@ async function syncUserToPosthog(user: UserJSON) { distinctId: getPrimaryEmail(user), properties: { featureFlagGroup }, }); - await posthogAiBetaServerClient.shutdown(); + await posthogAiBetaServerClient.flush(); log.info("featureFlagGroup synced:", user.id, featureFlagGroup); } @@ -45,6 +45,10 @@ export async function POST(req: Request) { const svixId = headerPayload.get("svix-id"); const svixTimestamp = headerPayload.get("svix-timestamp"); const svixSignature = headerPayload.get("svix-signature"); + Sentry.addBreadcrumb({ + message: "Received webhook message", + data: { svixId }, + }); if (!svixId || !svixTimestamp || !svixSignature) { return new Response("Error occured -- no svix headers", { @@ -52,12 +56,10 @@ export async function POST(req: Request) { }); } - // Create a new Svix instance with your secret. invariant(WEBHOOK_SECRET, "Missing CLERK_WEBHOOK_SECRET"); const wh = new Webhook(WEBHOOK_SECRET); let evt: WebhookEvent; - try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const payload = await req.json(); From 2622337d4e069671e068a77144cbe0aa78c3fbcf Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Thu, 31 Oct 2024 22:54:38 +0100 Subject: [PATCH 26/61] PR feedback --- apps/nextjs/src/lib/feature-flags/bootstrap.ts | 2 +- .../posthogAiBetaServerClient/featureFlagEvaluation.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/nextjs/src/lib/feature-flags/bootstrap.ts b/apps/nextjs/src/lib/feature-flags/bootstrap.ts index caa566a99..8869ff42e 100644 --- a/apps/nextjs/src/lib/feature-flags/bootstrap.ts +++ b/apps/nextjs/src/lib/feature-flags/bootstrap.ts @@ -35,11 +35,11 @@ export async function getBootstrappedFeatures(headers: ReadonlyHeaders) { const { userId, sessionClaims } = auth(); const distinctId = userId ?? getDistinctIdFromCookie(headers) ?? "0"; - log.info("Evaluating feature flags for", distinctId); const personProperties = sessionClaims?.labs?.featureFlagGroup ? { featureFlagGroup: sessionClaims.labs.featureFlagGroup } : undefined; + log.info("Evaluating", distinctId, personProperties ?? "(no properties)"); const features = await posthogAiBetaServerClient.getAllFlags(distinctId, { // Only bootstrap flags which don't depend on PII diff --git a/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts b/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts index 1acaa1dfd..ff08de520 100644 --- a/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts +++ b/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts @@ -27,7 +27,7 @@ export const cachedFetch: PostHog["fetch"] = async (url, options) => { if (url.includes("api/feature_flag/local_evaluation")) { const kvCachedResponse = await getKv(); if (kvCachedResponse) { - log.info("evaluations fetched from KV"); + log.info("Evaluations fetched from KV"); return kvCachedResponse; } const result = await fetch(url, options); @@ -35,7 +35,7 @@ export const cachedFetch: PostHog["fetch"] = async (url, options) => { if (result.ok) { const cachedResult = result.clone(); await setKv(cachedResult); - log.info("evaluations cached to KV"); + log.info("Evaluations cached to KV"); } else { log.error("failed to load evaluations", { status: result.status }); } From 151ce1bbc247a70a2540b6d2f39a1b921fe6407a Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Thu, 31 Oct 2024 23:08:09 +0100 Subject: [PATCH 27/61] chore: unify feature flag loggers --- apps/nextjs/src/lib/feature-flags/bootstrap.ts | 2 +- .../posthogAiBetaServerClient/featureFlagEvaluation.ts | 2 +- packages/logger/index.ts | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/nextjs/src/lib/feature-flags/bootstrap.ts b/apps/nextjs/src/lib/feature-flags/bootstrap.ts index 8869ff42e..e916408b8 100644 --- a/apps/nextjs/src/lib/feature-flags/bootstrap.ts +++ b/apps/nextjs/src/lib/feature-flags/bootstrap.ts @@ -7,7 +7,7 @@ import cookie from "cookie"; import type { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers"; import invariant from "tiny-invariant"; -const log = aiLogger("analytics:feature-flags"); +const log = aiLogger("feature-flags"); /** * We use posthog feature flags to toggle functionality without deploying code changes. diff --git a/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts b/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts index ff08de520..6913426fb 100644 --- a/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts +++ b/packages/core/src/analytics/posthogAiBetaServerClient/featureFlagEvaluation.ts @@ -4,7 +4,7 @@ import type { PostHog } from "posthog-node"; const KV_KEY = "posthog-feature-flag-local-evaluation"; -const log = aiLogger("analytics:feature-flags"); +const log = aiLogger("feature-flags"); const setKv = async (response: Response) => { const value = await response.text(); diff --git a/packages/logger/index.ts b/packages/logger/index.ts index 456383f0a..255319ba1 100644 --- a/packages/logger/index.ts +++ b/packages/logger/index.ts @@ -31,7 +31,6 @@ type ChildKey = | "aila:rag" | "aila:testing" | "analytics" - | "analytics:feature-flags" | "app" | "auth" | "chat" From 4db0dd09bad0a51813abc2f4e922b8fe6a1666ba Mon Sep 17 00:00:00 2001 From: MG Date: Mon, 4 Nov 2024 14:15:51 +0000 Subject: [PATCH 28/61] chore: update docs for local db port 8432 (#334) --- README.md | 31 ++++++++++---------- packages/db/scripts/import-export/db_seed.sh | 2 +- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 5e3c3f28e..fb3ee86ba 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ pnpm install turbo --global ## Postgres Setup ### Prerequisites + - This project set up following the Installation steps. - Docker installed. - Optional: A Postgres GUI tool (such as pgAdmin or Postico) to view the data. @@ -71,7 +72,7 @@ pnpm install turbo --global cd packages/db ``` -2. Build and run the Docker container to create a database named `oai`, with the username and password both as `oai`, bound to port 5432. It will also install `pgvector` and `postgresql-contrib`. +2. Build and run the Docker container to create a database named `oai`, with the username and password both as `oai`, bound to port 8432. It will also install `pgvector` and `postgresql-contrib`. ```shell pnpm run docker-bootstrap @@ -79,27 +80,27 @@ pnpm run docker-bootstrap 3. Seed your database, to do this you have two options: - 3a. Replicate Production/Staging (Slow) + 3a. Replicate Production/Staging (Slow) - This will import the schema and tables from production. Note: due to the size of the production database this could take a significant amount of time. + This will import the schema and tables from production. Note: due to the size of the production database this could take a significant amount of time. - ```shell - pnpm run db-restore-from:prd or pnpm run db-restore-from:stg - ``` + ```shell + pnpm run db-restore-from:prd or pnpm run db-restore-from:stg + ``` - 3b. Local Prisma with Essential Tables Seeded from a Live Environment (Fast) + 3b. Local Prisma with Essential Tables Seeded from a Live Environment (Fast) - 1. Apply the Prisma schema to your local database: + 1. Apply the Prisma schema to your local database: - ```shell - pnpm run db-push - ``` + ```shell + pnpm run db-push + ``` - 2. Seed from stg/prd (where `:prd` can be either `:prd` or `:stg`, matching the Doppler environments). This will only seed the apps table and lesson-related tables used for RAG. + 2. Seed from stg/prd (where `:prd` can be either `:prd` or `:stg`, matching the Doppler environments). This will only seed the apps table and lesson-related tables used for RAG. - ```shell - pnpm run db-seed-local-from:prd - ``` + ```shell + pnpm run db-seed-local-from:prd + ``` ### Utility Commands diff --git a/packages/db/scripts/import-export/db_seed.sh b/packages/db/scripts/import-export/db_seed.sh index facd976f1..dad772bb0 100644 --- a/packages/db/scripts/import-export/db_seed.sh +++ b/packages/db/scripts/import-export/db_seed.sh @@ -1,7 +1,7 @@ #!/bin/sh get_local_database_admin_url() { - read -p "Please enter the local DATABASE_ADMIN_URL (e.g. postgresql://oai:oai@localhost:5432/oai): " local_url + read -p "Please enter the local DATABASE_ADMIN_URL (e.g. postgresql://oai:oai@localhost:8432/oai): " local_url echo "$local_url" } From 26af2371d17597c9e7674811076d2bf2dc76b479 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:39:08 +0100 Subject: [PATCH 29/61] chore: create sidebar storybook folder --- .../src/components/AppComponents/Chat/chat-history.stories.tsx | 2 +- .../src/components/AppComponents/Chat/clear-history.stories.tsx | 2 +- .../components/AppComponents/Chat/sidebar-actions.stories.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-history.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-history.stories.tsx index 6569cc3e9..ee875272b 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/chat-history.stories.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/chat-history.stories.tsx @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { ChatHistory } from "./chat-history"; const meta: Meta = { - title: "Components/Chat/ChatHistory", + title: "Components/Sidebar/ChatHistory", component: ChatHistory, parameters: { layout: "centered", diff --git a/apps/nextjs/src/components/AppComponents/Chat/clear-history.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/clear-history.stories.tsx index eddb7ebef..084254fb1 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/clear-history.stories.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/clear-history.stories.tsx @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { ClearHistory } from "./clear-history"; const meta: Meta = { - title: "Components/Chat/ClearHistory", + title: "Components/Sidebar/ClearHistory", component: ClearHistory, tags: ["autodocs"], argTypes: { diff --git a/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx index 9466ceccb..395fef328 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { SidebarActions } from "./sidebar-actions"; const meta: Meta = { - title: "Components/Chat/SidebarActions", + title: "Components/Sidebar/Actions", component: SidebarActions, parameters: { layout: "centered", From a6e23b7833e8c444087b531ead6253bf2cefb742 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:47:12 +0100 Subject: [PATCH 30/61] chore: bump storybook to v8.4 --- apps/nextjs/package.json | 18 +- pnpm-lock.yaml | 1335 +++++++++++++++----------------------- 2 files changed, 535 insertions(+), 818 deletions(-) diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index d0fdb7636..ab4ba940a 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -107,7 +107,7 @@ "react-textarea-autosize": "^8.5.3", "remark-gfm": "^4.0.0", "remeda": "^1.29.0", - "storybook": "^8.2.6", + "storybook": "^8.4.1", "superjson": "^1.9.1", "svix": "^1.37.0", "tailwind-merge": "^2.2.1", @@ -130,14 +130,14 @@ "@graphql-codegen/typescript-operations": "^4.2.3", "@playwright/test": "^1.47.0", "@sentry/webpack-plugin": "^2.21.1", - "@storybook/addon-essentials": "^8.2.6", - "@storybook/addon-interactions": "^8.2.6", - "@storybook/addon-links": "^8.2.6", - "@storybook/addon-onboarding": "^8.2.6", - "@storybook/blocks": "^8.2.6", - "@storybook/nextjs": "^8.2.6", - "@storybook/react": "^8.2.6", - "@storybook/test": "^8.2.6", + "@storybook/addon-essentials": "^8.4.1", + "@storybook/addon-interactions": "^8.4.1", + "@storybook/addon-links": "^8.4.1", + "@storybook/addon-onboarding": "^8.4.1", + "@storybook/blocks": "^8.4.1", + "@storybook/nextjs": "^8.4.1", + "@storybook/react": "^8.4.1", + "@storybook/test": "^8.4.1", "@tailwindcss/typography": "^0.5.10", "@types/file-saver": "^2.0.6", "@types/jest": "^29.5.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ede231f0..41edba29b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -166,7 +166,7 @@ importers: version: 8.35.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0)(@opentelemetry/instrumentation@0.53.0)(@opentelemetry/sdk-trace-base@1.26.0)(next@14.2.5)(react@18.2.0)(webpack@5.93.0) '@storybook/testing-react': specifier: ^2.0.1 - version: 2.0.1(@storybook/client-logger@7.6.20)(@storybook/preview-api@7.6.20)(@storybook/react@8.2.7)(@storybook/types@7.6.20)(react@18.2.0) + version: 2.0.1(@storybook/client-logger@7.6.20)(@storybook/preview-api@7.6.20)(@storybook/react@8.4.1)(@storybook/types@7.6.20)(react@18.2.0) '@tanstack/react-query': specifier: ^4.16.1 version: 4.19.1(react-dom@18.2.0)(react@18.2.0) @@ -321,8 +321,8 @@ importers: specifier: ^1.29.0 version: 1.29.0 storybook: - specifier: ^8.2.6 - version: 8.2.7 + specifier: ^8.4.1 + version: 8.4.1(prettier@3.3.3) superjson: specifier: ^1.9.1 version: 1.12.0 @@ -385,29 +385,29 @@ importers: specifier: ^2.21.1 version: 2.21.1(webpack@5.93.0) '@storybook/addon-essentials': - specifier: ^8.2.6 - version: 8.2.7(storybook@8.2.7) + specifier: ^8.4.1 + version: 8.4.1(@types/react@18.2.57)(storybook@8.4.1) '@storybook/addon-interactions': - specifier: ^8.2.6 - version: 8.2.7(@types/jest@29.5.12)(jest@29.7.0)(storybook@8.2.7) + specifier: ^8.4.1 + version: 8.4.1(storybook@8.4.1) '@storybook/addon-links': - specifier: ^8.2.6 - version: 8.2.7(react@18.2.0)(storybook@8.2.7) + specifier: ^8.4.1 + version: 8.4.1(react@18.2.0)(storybook@8.4.1) '@storybook/addon-onboarding': - specifier: ^8.2.6 - version: 8.2.7(react@18.2.0)(storybook@8.2.7) + specifier: ^8.4.1 + version: 8.4.1(react@18.2.0)(storybook@8.4.1) '@storybook/blocks': - specifier: ^8.2.6 - version: 8.2.7(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7) + specifier: ^8.4.1 + version: 8.4.1(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1) '@storybook/nextjs': - specifier: ^8.2.6 - version: 8.2.7(@types/jest@29.5.12)(esbuild@0.21.5)(jest@29.7.0)(next@14.2.5)(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7)(typescript@5.3.3)(webpack@5.93.0) + specifier: ^8.4.1 + version: 8.4.1(esbuild@0.21.5)(next@14.2.5)(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1)(typescript@5.3.3)(webpack@5.93.0) '@storybook/react': - specifier: ^8.2.6 - version: 8.2.7(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7)(typescript@5.3.3) + specifier: ^8.4.1 + version: 8.4.1(@storybook/test@8.4.1)(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1)(typescript@5.3.3) '@storybook/test': - specifier: ^8.2.6 - version: 8.2.7(@types/jest@29.5.12)(jest@29.7.0)(storybook@8.2.7) + specifier: ^8.4.1 + version: 8.4.1(storybook@8.4.1) '@tailwindcss/typography': specifier: ^0.5.10 version: 0.5.10(tailwindcss@3.4.1) @@ -1131,10 +1131,6 @@ packages: '@babel/highlight': 7.24.7 picocolors: 1.0.1 - /@babel/compat-data@7.24.4: - resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} - engines: {node: '>=6.9.0'} - /@babel/compat-data@7.25.2: resolution: {integrity: sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==} engines: {node: '>=6.9.0'} @@ -1144,14 +1140,14 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.2 + '@babel/code-frame': 7.24.7 '@babel/generator': 7.25.0 - '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) + '@babel/helper-compilation-targets': 7.25.2 + '@babel/helper-module-transforms': 7.25.2(@babel/core@7.24.5) '@babel/helpers': 7.24.5 - '@babel/parser': 7.24.5 + '@babel/parser': 7.25.3 '@babel/template': 7.25.0 - '@babel/traverse': 7.24.5 + '@babel/traverse': 7.25.3(supports-color@5.5.0) '@babel/types': 7.25.2 convert-source-map: 2.0.0 debug: 4.3.7(supports-color@5.5.0) @@ -1212,16 +1208,7 @@ packages: '@babel/types': 7.25.2 transitivePeerDependencies: - supports-color - - /@babel/helper-compilation-targets@7.23.6: - resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/compat-data': 7.24.4 - '@babel/helper-validator-option': 7.23.5 - browserslist: 4.23.0 - lru-cache: 5.1.1 - semver: 6.3.1 + dev: true /@babel/helper-compilation-targets@7.25.2: resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} @@ -1249,6 +1236,7 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: true /@babel/helper-create-regexp-features-plugin@7.25.2(@babel/core@7.24.5): resolution: {integrity: sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==} @@ -1260,6 +1248,7 @@ packages: '@babel/helper-annotate-as-pure': 7.24.7 regexpu-core: 5.3.2 semver: 6.3.1 + dev: true /@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.5): resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} @@ -1274,6 +1263,7 @@ packages: resolve: 1.22.8 transitivePeerDependencies: - supports-color + dev: true /@babel/helper-environment-visitor@7.22.20: resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} @@ -1300,12 +1290,7 @@ packages: '@babel/types': 7.25.2 transitivePeerDependencies: - supports-color - - /@babel/helper-module-imports@7.24.3: - resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.5 + dev: true /@babel/helper-module-imports@7.24.7(supports-color@5.5.0): resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} @@ -1316,19 +1301,6 @@ packages: transitivePeerDependencies: - supports-color - /@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5): - resolution: {integrity: sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-module-imports': 7.24.3 - '@babel/helper-simple-access': 7.24.5 - '@babel/helper-split-export-declaration': 7.24.5 - '@babel/helper-validator-identifier': 7.24.5 - /@babel/helper-module-transforms@7.25.2(@babel/core@7.24.5): resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==} engines: {node: '>=6.9.0'} @@ -1348,6 +1320,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.25.2 + dev: true /@babel/helper-plugin-utils@7.24.7: resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} @@ -1356,6 +1329,7 @@ packages: /@babel/helper-plugin-utils@7.24.8: resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} engines: {node: '>=6.9.0'} + dev: true /@babel/helper-remap-async-to-generator@7.25.0(@babel/core@7.24.5): resolution: {integrity: sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==} @@ -1369,6 +1343,7 @@ packages: '@babel/traverse': 7.25.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color + dev: true /@babel/helper-replace-supers@7.25.0(@babel/core@7.24.5): resolution: {integrity: sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==} @@ -1382,12 +1357,7 @@ packages: '@babel/traverse': 7.25.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color - - /@babel/helper-simple-access@7.24.5: - resolution: {integrity: sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.5 + dev: true /@babel/helper-simple-access@7.24.7: resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} @@ -1406,6 +1376,7 @@ packages: '@babel/types': 7.25.2 transitivePeerDependencies: - supports-color + dev: true /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} @@ -1446,10 +1417,6 @@ packages: resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-option@7.23.5: - resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} - engines: {node: '>=6.9.0'} - /@babel/helper-validator-option@7.24.8: resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} engines: {node: '>=6.9.0'} @@ -1463,6 +1430,7 @@ packages: '@babel/types': 7.25.2 transitivePeerDependencies: - supports-color + dev: true /@babel/helpers@7.24.5: resolution: {integrity: sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==} @@ -1531,6 +1499,7 @@ packages: '@babel/traverse': 7.25.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.0(@babel/core@7.24.5): resolution: {integrity: sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==} @@ -1540,6 +1509,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.0(@babel/core@7.24.5): resolution: {integrity: sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==} @@ -1549,6 +1519,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==} @@ -1562,6 +1533,7 @@ packages: '@babel/plugin-transform-optional-chaining': 7.24.8(@babel/core@7.24.5) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.0(@babel/core@7.24.5): resolution: {integrity: sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==} @@ -1574,6 +1546,7 @@ packages: '@babel/traverse': 7.25.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.24.5): resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} @@ -1611,6 +1584,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.5 + dev: true /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.5): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} @@ -1619,6 +1593,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.5): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} @@ -1626,7 +1601,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 dev: true /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.5): @@ -1636,6 +1611,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.5): resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} @@ -1645,6 +1621,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.5): resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} @@ -1653,6 +1630,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.5): resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} @@ -1661,6 +1639,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-syntax-flow@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-9G8GYT/dxn/D1IIKOUBmGX0mnmj46mGH9NnZyJLwtCpgh5f7D2VbuKodb+2s9m1Yavh1s7ASQN8lf0eqrb1LTw==} @@ -1670,6 +1649,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-syntax-import-assertions@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==} @@ -1679,6 +1659,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==} @@ -1688,6 +1669,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.5): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} @@ -1696,6 +1678,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.5): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} @@ -1704,6 +1687,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} @@ -1731,6 +1715,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.5): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} @@ -1739,6 +1724,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.5): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} @@ -1747,6 +1733,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.5): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} @@ -1755,6 +1742,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.5): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} @@ -1763,6 +1751,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.5): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} @@ -1771,6 +1760,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.5): resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} @@ -1780,6 +1770,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.5): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} @@ -1789,6 +1780,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==} @@ -1798,6 +1790,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.7 + dev: true /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.5): resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} @@ -1808,6 +1801,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==} @@ -1817,6 +1811,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-async-generator-functions@7.25.0(@babel/core@7.24.5): resolution: {integrity: sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==} @@ -1831,6 +1826,7 @@ packages: '@babel/traverse': 7.25.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-async-to-generator@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==} @@ -1844,6 +1840,7 @@ packages: '@babel/helper-remap-async-to-generator': 7.25.0(@babel/core@7.24.5) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==} @@ -1853,6 +1850,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-block-scoping@7.25.0(@babel/core@7.24.5): resolution: {integrity: sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==} @@ -1862,6 +1860,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-class-properties@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==} @@ -1871,9 +1870,10 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-class-static-block@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==} @@ -1887,6 +1887,7 @@ packages: '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.5) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-classes@7.25.0(@babel/core@7.24.5): resolution: {integrity: sha512-xyi6qjr/fYU304fiRwFbekzkqVJZ6A7hOjWZd+89FVcBqPV3S9Wuozz82xdpLspckeaafntbzglaW4pqpzvtSw==} @@ -1903,6 +1904,7 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-computed-properties@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==} @@ -1913,6 +1915,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 '@babel/template': 7.25.0 + dev: true /@babel/plugin-transform-destructuring@7.24.8(@babel/core@7.24.5): resolution: {integrity: sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==} @@ -1922,6 +1925,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-dotall-regex@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==} @@ -1932,6 +1936,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-duplicate-keys@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==} @@ -1941,6 +1946,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.0(@babel/core@7.24.5): resolution: {integrity: sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==} @@ -1951,6 +1957,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-dynamic-import@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==} @@ -1961,6 +1968,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.5) + dev: true /@babel/plugin-transform-exponentiation-operator@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==} @@ -1973,6 +1981,7 @@ packages: '@babel/helper-plugin-utils': 7.24.8 transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-export-namespace-from@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==} @@ -1983,6 +1992,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.5) + dev: true /@babel/plugin-transform-flow-strip-types@7.25.2(@babel/core@7.24.5): resolution: {integrity: sha512-InBZ0O8tew5V0K6cHcQ+wgxlrjOw1W4wDXLkOTjLRD8GYhTSkxTVBtdy3MMtvYBrbAWa1Qm3hNoTc1620Yj+Mg==} @@ -1993,6 +2003,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.24.5) + dev: true /@babel/plugin-transform-for-of@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==} @@ -2005,6 +2016,7 @@ packages: '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-function-name@7.25.1(@babel/core@7.24.5): resolution: {integrity: sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==} @@ -2018,6 +2030,7 @@ packages: '@babel/traverse': 7.25.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-json-strings@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==} @@ -2028,6 +2041,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.5) + dev: true /@babel/plugin-transform-literals@7.25.2(@babel/core@7.24.5): resolution: {integrity: sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==} @@ -2037,6 +2051,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-logical-assignment-operators@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==} @@ -2047,6 +2062,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.5) + dev: true /@babel/plugin-transform-member-expression-literals@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==} @@ -2056,6 +2072,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-modules-amd@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==} @@ -2068,6 +2085,7 @@ packages: '@babel/helper-plugin-utils': 7.24.8 transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-modules-commonjs@7.24.8(@babel/core@7.24.5): resolution: {integrity: sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==} @@ -2081,6 +2099,7 @@ packages: '@babel/helper-simple-access': 7.24.7 transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-modules-systemjs@7.25.0(@babel/core@7.24.5): resolution: {integrity: sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==} @@ -2095,6 +2114,7 @@ packages: '@babel/traverse': 7.25.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-modules-umd@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==} @@ -2107,6 +2127,7 @@ packages: '@babel/helper-plugin-utils': 7.24.8 transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-named-capturing-groups-regex@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==} @@ -2117,6 +2138,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-new-target@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==} @@ -2126,6 +2148,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-nullish-coalescing-operator@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==} @@ -2134,8 +2157,9 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.5) + dev: true /@babel/plugin-transform-numeric-separator@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==} @@ -2146,6 +2170,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.5) + dev: true /@babel/plugin-transform-object-rest-spread@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==} @@ -2158,6 +2183,7 @@ packages: '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.5) '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.5) + dev: true /@babel/plugin-transform-object-super@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==} @@ -2170,6 +2196,7 @@ packages: '@babel/helper-replace-supers': 7.25.0(@babel/core@7.24.5) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-optional-catch-binding@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==} @@ -2180,6 +2207,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.5) + dev: true /@babel/plugin-transform-optional-chaining@7.24.8(@babel/core@7.24.5): resolution: {integrity: sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==} @@ -2193,6 +2221,7 @@ packages: '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.5) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-parameters@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==} @@ -2202,6 +2231,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-private-methods@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==} @@ -2211,9 +2241,10 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-private-property-in-object@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==} @@ -2228,6 +2259,7 @@ packages: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.5) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-property-literals@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==} @@ -2237,6 +2269,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-react-display-name@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==} @@ -2296,6 +2329,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 regenerator-transform: 0.15.2 + dev: true /@babel/plugin-transform-reserved-words@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==} @@ -2305,6 +2339,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-runtime@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==} @@ -2331,6 +2366,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-spread@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==} @@ -2343,6 +2379,7 @@ packages: '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==} @@ -2352,6 +2389,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==} @@ -2361,6 +2399,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-typeof-symbol@7.24.8(@babel/core@7.24.5): resolution: {integrity: sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==} @@ -2370,6 +2409,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-typescript@7.25.2(@babel/core@7.24.5): resolution: {integrity: sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==} @@ -2385,6 +2425,7 @@ packages: '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.5) transitivePeerDependencies: - supports-color + dev: true /@babel/plugin-transform-unicode-escapes@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==} @@ -2394,6 +2435,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-unicode-property-regex@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==} @@ -2404,6 +2446,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-unicode-regex@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==} @@ -2414,6 +2457,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/plugin-transform-unicode-sets-regex@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==} @@ -2424,6 +2468,7 @@ packages: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.8 + dev: true /@babel/preset-env@7.25.3(@babel/core@7.24.5): resolution: {integrity: sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==} @@ -2517,17 +2562,7 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - - /@babel/preset-flow@7.24.7(@babel/core@7.24.5): - resolution: {integrity: sha512-NL3Lo0NorCU607zU3NwRyJbpaB6E3t0xtd3LfAQKDfkeX4/ggcDXvkmkW42QWT5owUeW/jAe4hn+2qvkV1IbfQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.8 - '@babel/plugin-transform-flow-strip-types': 7.25.2(@babel/core@7.24.5) + dev: true /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.5): resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} @@ -2538,6 +2573,7 @@ packages: '@babel/helper-plugin-utils': 7.24.8 '@babel/types': 7.25.2 esutils: 2.0.3 + dev: true /@babel/preset-react@7.24.7(@babel/core@7.24.5): resolution: {integrity: sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==} @@ -2563,29 +2599,18 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 '@babel/helper-validator-option': 7.24.8 '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.5) '@babel/plugin-transform-modules-commonjs': 7.24.8(@babel/core@7.24.5) '@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.24.5) transitivePeerDependencies: - supports-color - - /@babel/register@7.24.6(@babel/core@7.24.5): - resolution: {integrity: sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.5 - clone-deep: 4.0.1 - find-cache-dir: 2.1.0 - make-dir: 2.1.0 - pirates: 4.0.6 - source-map-support: 0.5.21 + dev: true /@babel/regjsgen@0.8.0: resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} + dev: true /@babel/runtime@7.22.11: resolution: {integrity: sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==} @@ -2721,9 +2746,6 @@ packages: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - /@base2/pretty-print-object@1.0.1: - resolution: {integrity: sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==} - /@bcoe/v8-coverage@0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true @@ -8173,6 +8195,7 @@ packages: /@sindresorhus/merge-streams@2.3.0: resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} + dev: false /@sinonjs/commons@3.0.1: resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} @@ -8220,117 +8243,105 @@ packages: resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} dev: false - /@storybook/addon-actions@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-wDnMGGmaogAForkNncfCx8BEDiwxeK8zC0lj8HkRPUuH6vTr81U5RIb12Wa2TnnNKLKMFAtyPSnofHf3OAfzZQ==} + /@storybook/addon-actions@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-D6KohTIA4JCHNol1X7Whp4LpOVU4cS5FfyOorwYo/WIzpHrUYc4Pw/+ex6DOmU/kgrk14mr8d9obVehKW7iNtA==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: '@storybook/global': 5.0.0 '@types/uuid': 9.0.3 dequal: 2.0.3 polished: 4.3.1 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) uuid: 9.0.1 dev: true - /@storybook/addon-backgrounds@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-kEL3kzYB0qNfpznchlGBnQm4iydyzdTYDPlCFsKUAxfUmJFnpz2H52Sl5lB+qJC/4OREp1Usltag7cUjeuyzMQ==} + /@storybook/addon-backgrounds@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-DIT1E9R9Sds8KTC+0m2X5cVa8hTNcKY1XKYTI9QdzQvdZzOt+K93AJqq2x8k5glingqUVpB6v2fSDmCUXp4+4g==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: '@storybook/global': 5.0.0 memoizerific: 1.11.3 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) ts-dedent: 2.2.0 dev: true - /@storybook/addon-controls@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-u3MruX0Zh6l1iNkoJdXwx+zPVqpDKypVrC0YdN3qQ3+mtTwqt35rgetYqtOkDnJ8mXKxo8A5giERKPIyzH9iBA==} + /@storybook/addon-controls@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-3ahbYdDx7iFUd4X1KelMSuPqVnladc0bH4m6DQZyN+wkRxdRlOD6iOGuOe2qi1Gv0b2VuVAt253i75tK/TPNLw==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: + '@storybook/global': 5.0.0 dequal: 2.0.3 - lodash: 4.17.21 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) ts-dedent: 2.2.0 dev: true - /@storybook/addon-docs@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-icLbvUWp95WUxq2sY+0xgJ49MaQ2HqtWY9RUJUZswJ/ZPJTCCpIoa6HP/NOB9A90Oec9n8sW+1CdDL4CxfxfZg==} + /@storybook/addon-docs@8.4.1(@types/react@18.2.57)(storybook@8.4.1): + resolution: {integrity: sha512-yPD/NssJf7pMJzaKvma02C6yX8ykPVnEjhRbNYcBNM8s8g/cT5JkROvIB+FOb4T81yhdfbGg9bGkpAXGX270IQ==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: - '@babel/core': 7.24.5 '@mdx-js/react': 3.0.1(@types/react@18.2.57)(react@18.2.0) - '@storybook/blocks': 8.2.7(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7) - '@storybook/csf-plugin': 8.2.7(storybook@8.2.7) - '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 8.2.7(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7) - '@types/react': 18.2.57 - fs-extra: 11.2.0 + '@storybook/blocks': 8.4.1(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1) + '@storybook/csf-plugin': 8.4.1(storybook@8.4.1) + '@storybook/react-dom-shim': 8.4.1(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rehype-external-links: 3.0.0 - rehype-slug: 6.0.0 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) ts-dedent: 2.2.0 transitivePeerDependencies: - - supports-color + - '@types/react' dev: true - /@storybook/addon-essentials@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-5qe7La9B2Z4Y9Fet3C35y8zOZwKgrqduNk8yAUmPRAOwopdo8SGKYpnFTnAtTfTCVk6Y+AZlRfQq0yLUk0Wl3g==} + /@storybook/addon-essentials@8.4.1(@types/react@18.2.57)(storybook@8.4.1): + resolution: {integrity: sha512-Hmb5fpVzQgyCacDtHeE7HJqIfolzeOnedsLyJVYVpKns/uOWXqpDuU8Fc0s3yTjr1QPIRKtbqV1STxoyXj2how==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: - '@storybook/addon-actions': 8.2.7(storybook@8.2.7) - '@storybook/addon-backgrounds': 8.2.7(storybook@8.2.7) - '@storybook/addon-controls': 8.2.7(storybook@8.2.7) - '@storybook/addon-docs': 8.2.7(storybook@8.2.7) - '@storybook/addon-highlight': 8.2.7(storybook@8.2.7) - '@storybook/addon-measure': 8.2.7(storybook@8.2.7) - '@storybook/addon-outline': 8.2.7(storybook@8.2.7) - '@storybook/addon-toolbars': 8.2.7(storybook@8.2.7) - '@storybook/addon-viewport': 8.2.7(storybook@8.2.7) - storybook: 8.2.7 + '@storybook/addon-actions': 8.4.1(storybook@8.4.1) + '@storybook/addon-backgrounds': 8.4.1(storybook@8.4.1) + '@storybook/addon-controls': 8.4.1(storybook@8.4.1) + '@storybook/addon-docs': 8.4.1(@types/react@18.2.57)(storybook@8.4.1) + '@storybook/addon-highlight': 8.4.1(storybook@8.4.1) + '@storybook/addon-measure': 8.4.1(storybook@8.4.1) + '@storybook/addon-outline': 8.4.1(storybook@8.4.1) + '@storybook/addon-toolbars': 8.4.1(storybook@8.4.1) + '@storybook/addon-viewport': 8.4.1(storybook@8.4.1) + storybook: 8.4.1(prettier@3.3.3) ts-dedent: 2.2.0 transitivePeerDependencies: - - supports-color + - '@types/react' dev: true - /@storybook/addon-highlight@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-YhiLtyJ3NBNV3FQoQo8RFjj59QGSmmeSwRvCjoac6No2DY5vkMW5a8mW6ORr6QYd7ratRNtd3AsPqksZIehRwQ==} + /@storybook/addon-highlight@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-BBkUd6+i7lUEWZwoJDlUIwrs7EXkk+EoREUi27iiA1Lilw+NNhoC3kcBmj3+MccjRyeMeIWAgYyXF5qeB2s/JQ==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: '@storybook/global': 5.0.0 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) dev: true - /@storybook/addon-interactions@8.2.7(@types/jest@29.5.12)(jest@29.7.0)(storybook@8.2.7): - resolution: {integrity: sha512-WZXlwpBNLE483uKuR70A7nm+ZbcZNEmuVz/J1/u7dbi0BUWzmJUa9YIgVeQ/1KTwW8KTkxvB0TuUUH3aA4ZKlA==} + /@storybook/addon-interactions@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-rMxKehtQogV6Scjb/oqMFM0Mwn8NJRuGFDRJE3TBijNSJ2HPJms+xXp8KVZJengadlsF5HFwQBbnZzIeFDQRLw==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.2.7(storybook@8.2.7) - '@storybook/test': 8.2.7(@types/jest@29.5.12)(jest@29.7.0)(storybook@8.2.7) + '@storybook/instrumenter': 8.4.1(storybook@8.4.1) + '@storybook/test': 8.4.1(storybook@8.4.1) polished: 4.3.1 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) ts-dedent: 2.2.0 - transitivePeerDependencies: - - '@jest/globals' - - '@types/bun' - - '@types/jest' - - jest - - vitest dev: true - /@storybook/addon-links@8.2.7(react@18.2.0)(storybook@8.2.7): - resolution: {integrity: sha512-BJdR+vdj7S6Rtx8XqBNQzLsRGH/FYHJ6B6BPWGp0awVx0jNWJnxepINQov8i+GAddUVQGCNG+r4LI3QSD3tNAA==} + /@storybook/addon-links@8.4.1(react@18.2.0)(storybook@8.4.1): + resolution: {integrity: sha512-wg83rNKo6mq5apV7f1qMn4q8xZ8wVx/42EEWxTOmnM37Q5kXltEBu+rUyBpPNDU8zBuXr/MRKIhK5h2k4WfWcg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.7 + storybook: ^8.4.1 peerDependenciesMeta: react: optional: true @@ -8338,64 +8349,64 @@ packages: '@storybook/csf': 0.1.11 '@storybook/global': 5.0.0 react: 18.2.0 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) ts-dedent: 2.2.0 dev: true - /@storybook/addon-measure@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-cS5njwlzrgrUjigUKjhbgJMT8bhPmVDK3FwrQqGhw6xYP4cd9/YBJ4RLNPWhOgGJ+EUTz7eFZ/Rkli5mNrhYcQ==} + /@storybook/addon-measure@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-Pg1ROj29hKt7grL/HmbIJ10WrkZf1Unx35SsP373bkPQ1ggYi9oxGqtfNchTF2zCb1xUpIikLYSJgkwdjqWxhA==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: '@storybook/global': 5.0.0 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) tiny-invariant: 1.3.3 dev: true - /@storybook/addon-onboarding@8.2.7(react@18.2.0)(storybook@8.2.7): - resolution: {integrity: sha512-YgpQY0uhYRi+3vny2BjnFlY19ga9/GGZlcFTfaf2wCO7KhjXdES3bp4VTtdNkAwbVaXYY+b33ERd4x1UQ7jSkA==} + /@storybook/addon-onboarding@8.4.1(react@18.2.0)(storybook@8.4.1): + resolution: {integrity: sha512-xcMCQ2HPvl8O/NHt2V5T1hDlzitRGI5ATSrfo7o7JjcsAgFNSNtcFn7YU8M5SIpHd5u4v24ubE/pMrsskpp/+Q==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: react-confetti: 6.1.0(react@18.2.0) - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) transitivePeerDependencies: - react dev: true - /@storybook/addon-outline@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-oFSo3o5eEUSsdGUSPV22pGoJ5lL0PGcqjcee2hyl0Rc60ovsnB1BEGOoaGk7/bmkywMxRZm8D6j85V8HftA/kg==} + /@storybook/addon-outline@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-LPZ0gGHfbru66Lkw1whnc3F/r1hfnoORBoF98Hp+cjH34gR4t8te6xq5qSiupRUULGdSLdBRs/4EGRBeELfVjQ==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: '@storybook/global': 5.0.0 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) ts-dedent: 2.2.0 dev: true - /@storybook/addon-toolbars@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-lEq0/uiogQSxS8pM5AqIexPiG2mudHUxgBiVWSspbTQDUbGBUxB64VYeYERat50N/GyS2iCymlfSkC+OUXaYLQ==} + /@storybook/addon-toolbars@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-yrzX6BFeJM5KFY0+ZAYfRax2QgWi2e5vF6yPz+MGIPr4nhHay0wTkOHhkBhIPBjQO9x0vqc7MS2EBDydCBWqlg==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) dev: true - /@storybook/addon-viewport@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-d4+klwM/duTukNED1WCeBgIMqL5Jvm/iUs2rUc5HI1FGMEDYnoLVR2ztjivQs+6f1cJWuGwWZD/toB5pKHuR/A==} + /@storybook/addon-viewport@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-O6DcuUfXQTytjl7mj4ld4ZX9x2pUUWKUx1TxiuMuH0EKb612RyYcdpXpDQQwsIzLV/f2BOetk9jmO2/MymfbWQ==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: memoizerific: 1.11.3 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) dev: true - /@storybook/blocks@8.2.7(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7): - resolution: {integrity: sha512-lZB4EzmY4ftgubkf7hmkALEhmfMhRkDRD5QjrgTZLRpdVXPzFUyljgLlTBhv34YTN+ZLYK618/4uSVJBpgoKeQ==} + /@storybook/blocks@8.4.1(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1): + resolution: {integrity: sha512-C4w5T5fhg0iONXozHQ1bh9im2Lr1BiY7Bj/9XoFjkc5YeCzxlMpujFA6Nmo4ToUFW90QbvKN7/QVhbrtY9O1Jg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.7 + storybook: ^8.4.1 peerDependenciesMeta: react: optional: true @@ -8403,35 +8414,24 @@ packages: optional: true dependencies: '@storybook/csf': 0.1.11 - '@storybook/global': 5.0.0 - '@storybook/icons': 1.2.10(react-dom@18.2.0)(react@18.2.0) - '@types/lodash': 4.14.202 - color-convert: 2.0.1 - dequal: 2.0.3 - lodash: 4.17.21 - markdown-to-jsx: 7.4.7(react@18.2.0) - memoizerific: 1.11.3 - polished: 4.3.1 + '@storybook/icons': 1.2.12(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 - react-colorful: 5.6.1(react-dom@18.2.0)(react@18.2.0) react-dom: 18.2.0(react@18.2.0) - storybook: 8.2.7 - telejson: 7.2.0 + storybook: 8.4.1(prettier@3.3.3) ts-dedent: 2.2.0 - util-deprecate: 1.0.2 dev: true - /@storybook/builder-webpack5@8.2.7(esbuild@0.21.5)(storybook@8.2.7)(typescript@5.3.3): - resolution: {integrity: sha512-3SWN0X6qB14jnCrpMWd5tCshxzLEcRK5Sw/vBIW9HUsUx9OVMPxWp+Ti6NZHqj6FfHVbJb+qOwHl493JAJaFdg==} + /@storybook/builder-webpack5@8.4.1(esbuild@0.21.5)(storybook@8.4.1)(typescript@5.3.3): + resolution: {integrity: sha512-rqSJcxcYiQyceNFSrT9qnI6hrW4/petb1n+oN8nG5HrRsl0zxOVzamMVyNzZxrAMKvq+VMJtLe1rQi8FnJNunw==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@storybook/core-webpack': 8.2.7(storybook@8.2.7) - '@types/node': 18.18.5 + '@storybook/core-webpack': 8.4.1(storybook@8.4.1) + '@types/node': 22.8.7 '@types/semver': 7.5.0 browser-assert: 1.2.1 case-sensitive-paths-webpack-plugin: 2.4.0 @@ -8439,15 +8439,13 @@ packages: constants-browserify: 1.0.0 css-loader: 6.11.0(webpack@5.93.0) es-module-lexer: 1.5.4 - express: 4.19.2 fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.3)(webpack@5.93.0) - fs-extra: 11.2.0 html-webpack-plugin: 5.6.0(webpack@5.93.0) magic-string: 0.30.10 path-browserify: 1.0.1 process: 0.11.10 semver: 7.6.3 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) style-loader: 3.3.4(webpack@5.93.0) terser-webpack-plugin: 5.3.10(esbuild@0.21.5)(webpack@5.93.0) ts-dedent: 2.2.0 @@ -8463,7 +8461,6 @@ packages: - '@rspack/core' - '@swc/core' - esbuild - - supports-color - uglify-js - webpack-cli dev: true @@ -8485,33 +8482,12 @@ packages: '@storybook/global': 5.0.0 dev: false - /@storybook/codemod@8.2.7: - resolution: {integrity: sha512-D2sJcZMUO6Y7DNja4LvdT6uBee4bZbQKB904kEG9Kpr0XF20IHAP9BbkfG8HEFaS0GbJwvGvE03Sg+S1y+vO6Q==} - dependencies: - '@babel/core': 7.24.5 - '@babel/preset-env': 7.25.3(@babel/core@7.24.5) - '@babel/types': 7.24.5 - '@storybook/core': 8.2.7 - '@storybook/csf': 0.1.11 - '@types/cross-spawn': 6.0.2 - cross-spawn: 7.0.3 - globby: 14.0.1 - jscodeshift: 0.15.2(@babel/preset-env@7.25.3) - lodash: 4.17.21 - prettier: 3.3.3 - recast: 0.23.9 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - /@storybook/components@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-FXhnoHl9S+tKSFc62iUG3EWplQP9ojGQaSMhqP4QTus6xmo53oSsPzuTPQilKVHkGxFQW8eGgKKsfHw3G2NT2g==} + /@storybook/components@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-bMPclbBhrWxhFlwqrC/h4fPLl05ouoi5D8SkQTHjeVxWN9eDnMVi76xM0YDct302Z3f0x5S3plIulp+4XRxrvg==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 dependencies: - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) /@storybook/core-events@7.6.20: resolution: {integrity: sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==} @@ -8519,28 +8495,34 @@ packages: ts-dedent: 2.2.0 dev: false - /@storybook/core-webpack@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-eVtizQZdjPePjjPBfMw+74ha2yZw68AQZu5TK01Vetdjz1h+SSt+p/otWcJWPMGpZOg9p+n0krWvlcYHBsZsbA==} + /@storybook/core-webpack@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-TptbDGaj9a8wJMF4g+C8t02CXl4BSd0BA/qGWBvzn3j4FJqeQ/m8elOXLYZrPbQKI6PjP0J4ayHkXdX2h0/tUw==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: - '@types/node': 18.18.5 - storybook: 8.2.7 + '@types/node': 22.8.7 + storybook: 8.4.1(prettier@3.3.3) ts-dedent: 2.2.0 dev: true - /@storybook/core@8.2.7: - resolution: {integrity: sha512-vgw5MYN9Bq2/ZsObCOEHbBHwi4RpbYCHPFtKkr4kTnWID++FCSiSVd7jY3xPvcNxWqCxOyH6dThpBi+SsB/ZAA==} + /@storybook/core@8.4.1(prettier@3.3.3): + resolution: {integrity: sha512-q3Q4OFBj7MHHbIFYk/Beejlqv5j7CC3+VWhGcr0TK3SGvdCIZ7EliYuc5JIOgDlEPsnTIk+lkgWI4LAA9mLzSw==} + peerDependencies: + prettier: ^2 || ^3 + peerDependenciesMeta: + prettier: + optional: true dependencies: '@storybook/csf': 0.1.11 - '@types/express': 4.17.21 - '@types/node': 18.18.5 + better-opn: 3.0.2 browser-assert: 1.2.1 esbuild: 0.21.5 esbuild-register: 3.6.0(esbuild@0.21.5) - express: 4.19.2 + jsdoc-type-pratt-parser: 4.1.0 + prettier: 3.3.3 process: 0.11.10 recast: 0.23.9 + semver: 7.6.3 util: 0.12.5 ws: 8.18.0 transitivePeerDependencies: @@ -8548,12 +8530,12 @@ packages: - supports-color - utf-8-validate - /@storybook/csf-plugin@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-rBdplL6xcVJcuq+uM0eidomMQ5BtAlVAejYrOTNiqBk/zVh5JSvchYzYG9n6Fo2PdKLLKdlZ874zhsVuNriNBQ==} + /@storybook/csf-plugin@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-MdQkyq6mJ31lBsWCG9VNtx8O0oLSc5h4kvWDPyIP6Dn58K0Hv2z9qvxxSvtFjXA7ES9X+ivjorTke1kearifhg==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) unplugin: 1.12.0 dev: true @@ -8571,8 +8553,8 @@ packages: /@storybook/global@5.0.0: resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} - /@storybook/icons@1.2.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-310apKdDcjbbX2VSLWPwhEwAgjxTzVagrwucVZIdGPErwiAppX8KvBuWZgPo+rQLVrtH8S+pw1dbUwjcE6d7og==} + /@storybook/icons@1.2.12(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UxgyK5W3/UV4VrI3dl6ajGfHM4aOqMAkFLWe2KibeQudLf6NJpDrDMSHwZj+3iKC4jFU7dkKbbtH2h/al4sW3Q==} engines: {node: '>=14.0.0'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -8582,32 +8564,30 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/instrumenter@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-Zm6Ty4uWFTNchKUviuJ9vfcMb7+qU8eyrFXVY80XRpr62JEWkYj4eCwx4OG8GzlQahTh9aSv9+hzV6p/5Ld4mw==} + /@storybook/instrumenter@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-MgrhrLVW78jqno+Dh9h9Es06Ja3867TlrIUd8B3K3U1hsCFUQuFKXJBuGjNJF8U0QJY/aSIRnAgUBurHdVkPcw==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: '@storybook/global': 5.0.0 - '@vitest/utils': 1.6.0 - storybook: 8.2.7 - util: 0.12.5 - dev: true + '@vitest/utils': 2.1.4 + storybook: 8.4.1(prettier@3.3.3) - /@storybook/manager-api@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-BXjz6eNl1GyFcMwzRQTIokslcIY71AYblJUscPcy03X93oqI0GjFVa1xuSMwYw/oXWn7SHhKmqtqEG19lvBGRQ==} + /@storybook/manager-api@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-7hb2k4zsp6lREGZbQ85QOlsC8EIMZXuY9Pg12VUgaZd+LmLjLuaqtrxRz3SwIgIWsRpFun9AHO0X37DmYNGTSw==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 dependencies: - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) - /@storybook/nextjs@8.2.7(@types/jest@29.5.12)(esbuild@0.21.5)(jest@29.7.0)(next@14.2.5)(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7)(typescript@5.3.3)(webpack@5.93.0): - resolution: {integrity: sha512-q3SO8XIHXdHhGRmY8qoVtddIpjBMKh71A//LyZFac77ltgIKx+02vExKMVCtj7h8QcRm86SiobXfTXR7faRDmA==} + /@storybook/nextjs@8.4.1(esbuild@0.21.5)(next@14.2.5)(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1)(typescript@5.3.3)(webpack@5.93.0): + resolution: {integrity: sha512-SOEI8qOY+yLsRsvjokSevqtA+E+cXHDBObwPUJKmRxIAEwbP1uJEFnbvpZULs1pQl1gYsZQxEndbkTmM5pwtoQ==} engines: {node: '>=18.0.0'} peerDependencies: next: ^13.5.0 || ^14.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.7 + storybook: ^8.4.1 typescript: '*' webpack: ^5.0.0 peerDependenciesMeta: @@ -8630,16 +8610,15 @@ packages: '@babel/preset-typescript': 7.24.7(@babel/core@7.24.5) '@babel/runtime': 7.24.5 '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(react-refresh@0.14.2)(webpack@5.93.0) - '@storybook/builder-webpack5': 8.2.7(esbuild@0.21.5)(storybook@8.2.7)(typescript@5.3.3) - '@storybook/preset-react-webpack': 8.2.7(esbuild@0.21.5)(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7)(typescript@5.3.3) - '@storybook/react': 8.2.7(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7)(typescript@5.3.3) - '@storybook/test': 8.2.7(@types/jest@29.5.12)(jest@29.7.0)(storybook@8.2.7) - '@types/node': 18.18.5 + '@storybook/builder-webpack5': 8.4.1(esbuild@0.21.5)(storybook@8.4.1)(typescript@5.3.3) + '@storybook/preset-react-webpack': 8.4.1(@storybook/test@8.4.1)(esbuild@0.21.5)(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1)(typescript@5.3.3) + '@storybook/react': 8.4.1(@storybook/test@8.4.1)(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1)(typescript@5.3.3) + '@storybook/test': 8.4.1(storybook@8.4.1) + '@types/node': 22.8.7 '@types/semver': 7.5.0 babel-loader: 9.1.3(@babel/core@7.24.5)(webpack@5.93.0) css-loader: 6.11.0(webpack@5.93.0) find-up: 5.0.0 - fs-extra: 11.2.0 image-size: 1.0.2 loader-utils: 3.3.1 next: 14.2.5(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(react-dom@18.2.0)(react@18.2.0) @@ -8651,11 +8630,11 @@ packages: react-dom: 18.2.0(react@18.2.0) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 - sass-loader: 12.6.0(webpack@5.93.0) - semver: 7.6.2 - storybook: 8.2.7 + sass-loader: 13.3.3(webpack@5.93.0) + semver: 7.6.3 + storybook: 8.4.1(prettier@3.3.3) style-loader: 3.3.4(webpack@5.93.0) - styled-jsx: 5.1.1(@babel/core@7.24.5)(react@18.2.0) + styled-jsx: 5.1.6(@babel/core@7.24.5)(react@18.2.0) ts-dedent: 2.2.0 tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.1.0 @@ -8664,16 +8643,12 @@ packages: optionalDependencies: sharp: 0.33.4 transitivePeerDependencies: - - '@jest/globals' - '@rspack/core' - '@swc/core' - - '@types/bun' - - '@types/jest' - '@types/webpack' - babel-plugin-macros - esbuild - fibers - - jest - node-sass - sass - sass-embedded @@ -8681,43 +8656,42 @@ packages: - supports-color - type-fest - uglify-js - - vitest - webpack-cli - webpack-dev-server - webpack-hot-middleware - webpack-plugin-serve dev: true - /@storybook/preset-react-webpack@8.2.7(esbuild@0.21.5)(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7)(typescript@5.3.3): - resolution: {integrity: sha512-kJIgSub9wmoQgpLDfDlugK3nXeHL+skzRhUNH1ft80Il79hfQsNg7MLv6fXPvAJbjHYiQubYMwfdL4+Zwajopw==} + /@storybook/preset-react-webpack@8.4.1(@storybook/test@8.4.1)(esbuild@0.21.5)(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1)(typescript@5.3.3): + resolution: {integrity: sha512-Cm+u3/avHdoneEFHnvFRMPAElWtxyyOkcVsWHkM0rVhj7bxkzOyrBrenm1GiB8NamRosumsEnhREYFo2lthU2A==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.7 + storybook: ^8.4.1 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@storybook/core-webpack': 8.2.7(storybook@8.2.7) - '@storybook/react': 8.2.7(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7)(typescript@5.3.3) + '@storybook/core-webpack': 8.4.1(storybook@8.4.1) + '@storybook/react': 8.4.1(@storybook/test@8.4.1)(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1)(typescript@5.3.3) '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0) - '@types/node': 18.18.5 + '@types/node': 22.8.7 '@types/semver': 7.5.0 find-up: 5.0.0 - fs-extra: 11.2.0 magic-string: 0.30.10 react: 18.2.0 react-docgen: 7.0.3 react-dom: 18.2.0(react@18.2.0) resolve: 1.22.8 semver: 7.6.3 - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) tsconfig-paths: 4.2.0 typescript: 5.3.3 webpack: 5.93.0(esbuild@0.21.5) transitivePeerDependencies: + - '@storybook/test' - '@swc/core' - esbuild - supports-color @@ -8744,12 +8718,12 @@ packages: util-deprecate: 1.0.2 dev: false - /@storybook/preview-api@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-lNZBTjZaYNSwBY8dEcDZdkOBvq1/JoVWpuvqDEKvGmp5usTe77xAOwGyncEb96Cx1BbXXkMiDrqbV5G23PFRYA==} + /@storybook/preview-api@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-VdnESYfXCUasNtMd5s1Q8DPqMnAUdpROn8mE8UAD79Cy7DSNesI1q0SATuJqh5iYCT/+3Tpjfghsr2zC/mOh8w==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 dependencies: - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) /@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.3.3)(webpack@5.93.0): resolution: {integrity: sha512-KUqXC3oa9JuQ0kZJLBhVdS4lOneKTOopnNBK4tUAgoxWQ3u/IjzdueZjFr7gyBrXMoU6duutk3RQR9u8ZpYJ4Q==} @@ -8770,78 +8744,60 @@ packages: - supports-color dev: true - /@storybook/react-dom-shim@8.2.7(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7): - resolution: {integrity: sha512-9VI+NrC09DAr0QQZsFmU5Fd9eqdJp/1AHK+sm9BOZretGGGJwn22xS7UXhHIiFpfXJQnr3TNcYWRzXFyuaE/Sw==} + /@storybook/react-dom-shim@8.4.1(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1): + resolution: {integrity: sha512-XhvuqkpqtcUjDA8XE4osq140SCddX3VHMdj+IwlrMdoSl32CAya01TH5YDDx6YMy6hM/QQbyVKaemG7RB/oU4Q==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) - /@storybook/react@8.2.7(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7)(typescript@5.3.3): - resolution: {integrity: sha512-Qkw1K1iBDk+E9dlHrEWOOkn0trUU6wSt4mvzyOekiApM290esnPtw6GYXvxfBgFwNXfXbaGG3QNYGAFevf7qHw==} + /@storybook/react@8.4.1(@storybook/test@8.4.1)(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1)(typescript@5.3.3): + resolution: {integrity: sha512-ZwszrzV47nWQEZ0X4LyNgv5OFq4iy/7LpmxW6IncIO7PWm70OWG2BVtKFNsNQx0LY+hOtllWZbvg06mPQzahFA==} engines: {node: '>=18.0.0'} peerDependencies: + '@storybook/test': 8.4.1 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.7 + storybook: ^8.4.1 typescript: '>= 4.2.x' peerDependenciesMeta: + '@storybook/test': + optional: true typescript: optional: true dependencies: - '@storybook/components': 8.2.7(storybook@8.2.7) + '@storybook/components': 8.4.1(storybook@8.4.1) '@storybook/global': 5.0.0 - '@storybook/manager-api': 8.2.7(storybook@8.2.7) - '@storybook/preview-api': 8.2.7(storybook@8.2.7) - '@storybook/react-dom-shim': 8.2.7(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7) - '@storybook/theming': 8.2.7(storybook@8.2.7) - '@types/escodegen': 0.0.6 - '@types/estree': 0.0.51 - '@types/node': 18.18.5 - acorn: 7.4.1 - acorn-jsx: 5.3.2(acorn@7.4.1) - acorn-walk: 7.2.0 - escodegen: 2.1.0 - html-tags: 3.3.1 - lodash: 4.17.21 - prop-types: 15.8.1 + '@storybook/manager-api': 8.4.1(storybook@8.4.1) + '@storybook/preview-api': 8.4.1(storybook@8.4.1) + '@storybook/react-dom-shim': 8.4.1(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1) + '@storybook/test': 8.4.1(storybook@8.4.1) + '@storybook/theming': 8.4.1(storybook@8.4.1) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-element-to-jsx-string: 15.0.0(react-dom@18.2.0)(react@18.2.0) - semver: 7.6.2 - storybook: 8.2.7 - ts-dedent: 2.2.0 - type-fest: 2.19.0 + storybook: 8.4.1(prettier@3.3.3) typescript: 5.3.3 - util-deprecate: 1.0.2 - /@storybook/test@8.2.7(@types/jest@29.5.12)(jest@29.7.0)(storybook@8.2.7): - resolution: {integrity: sha512-7xypGR0zjJaM5MkxIz513SYiGs5vDJZL1bbkG1YKeBMff+ZRpa8y8VDYn/WDWuDw76KcFEXoPsPzKwktGhvnpw==} + /@storybook/test@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-najn9kCxB8NaHykhD7Fv+Iq0FnxmIJYOJlYiI8NMgVLwaSDFf6gnqAY6HHVPRqkhej8TuT1L2e2RxKqzWEB+mA==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.4.1 dependencies: '@storybook/csf': 0.1.11 - '@storybook/instrumenter': 8.2.7(storybook@8.2.7) - '@testing-library/dom': 10.1.0 - '@testing-library/jest-dom': 6.4.5(@types/jest@29.5.12)(jest@29.7.0) - '@testing-library/user-event': 14.5.2(@testing-library/dom@10.1.0) - '@vitest/expect': 1.6.0 - '@vitest/spy': 1.6.0 - storybook: 8.2.7 - util: 0.12.5 - transitivePeerDependencies: - - '@jest/globals' - - '@types/bun' - - '@types/jest' - - jest - - vitest - dev: true - - /@storybook/testing-react@2.0.1(@storybook/client-logger@7.6.20)(@storybook/preview-api@7.6.20)(@storybook/react@8.2.7)(@storybook/types@7.6.20)(react@18.2.0): + '@storybook/global': 5.0.0 + '@storybook/instrumenter': 8.4.1(storybook@8.4.1) + '@testing-library/dom': 10.4.0 + '@testing-library/jest-dom': 6.5.0 + '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) + '@vitest/expect': 2.0.5 + '@vitest/spy': 2.0.5 + storybook: 8.4.1(prettier@3.3.3) + + /@storybook/testing-react@2.0.1(@storybook/client-logger@7.6.20)(@storybook/preview-api@7.6.20)(@storybook/react@8.4.1)(@storybook/types@7.6.20)(react@18.2.0): resolution: {integrity: sha512-D0fT7f0TUU8+usR+0NSyCYQCYDWernQqGZou32ISsoBkY9IiH1JMY4hQE8zUaGozu9eMlaa2wieWIMsjWovpdw==} engines: {node: '>=10'} deprecated: In Storybook 7, this package has been promoted to a first-class Storybook functionality. This means that you no longer need it! Instead, you can import the same utilities, but from the @storybook/react package. Please migrate when you can. @@ -8855,17 +8811,17 @@ packages: '@storybook/client-logger': 7.6.20 '@storybook/csf': 0.1.11 '@storybook/preview-api': 7.6.20 - '@storybook/react': 8.2.7(react-dom@18.2.0)(react@18.2.0)(storybook@8.2.7)(typescript@5.3.3) + '@storybook/react': 8.4.1(@storybook/test@8.4.1)(react-dom@18.2.0)(react@18.2.0)(storybook@8.4.1)(typescript@5.3.3) '@storybook/types': 7.6.20 react: 18.2.0 dev: false - /@storybook/theming@8.2.7(storybook@8.2.7): - resolution: {integrity: sha512-+iqm0GfRkshrjjNSOzwl7AD2m+LtJGXJCr93ke1huDK497WUKbX1hbbw51h5E1tEkx0c2wIqUlaqCM+7XMYcpw==} + /@storybook/theming@8.4.1(storybook@8.4.1): + resolution: {integrity: sha512-Sz24isryVFZaVahXkjgnCsMAQqQeeKg41AtLsldlYdesIo6fr5tc6/SkTUy+CYadK4Dkhqp+vVRDnwToYYRGhA==} peerDependencies: - storybook: ^8.2.7 + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 dependencies: - storybook: 8.2.7 + storybook: 8.4.1(prettier@3.3.3) /@storybook/types@7.6.20: resolution: {integrity: sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==} @@ -8937,53 +8893,46 @@ packages: dom-accessibility-api: 0.5.16 lz-string: 1.5.0 pretty-format: 27.5.1 + dev: false - /@testing-library/jest-dom@6.4.5(@types/jest@29.5.12)(jest@29.7.0): - resolution: {integrity: sha512-AguB9yvTXmCnySBP1lWjfNNUwpbElsaQ567lt2VdGqAdHtpieLgjmcVyv1q7PMIvLbgpDdkWV5Ydv3FEejyp2A==} + /@testing-library/dom@10.4.0: + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/runtime': 7.24.5 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + /@testing-library/jest-dom@6.4.8: + resolution: {integrity: sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - peerDependencies: - '@jest/globals': '>= 28' - '@types/bun': latest - '@types/jest': '>= 28' - jest: '>= 28' - vitest: '>= 0.32' - peerDependenciesMeta: - '@jest/globals': - optional: true - '@types/bun': - optional: true - '@types/jest': - optional: true - jest: - optional: true - vitest: - optional: true dependencies: '@adobe/css-tools': 4.4.0 '@babel/runtime': 7.24.5 - '@types/jest': 29.5.12 aria-query: 5.3.0 chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 - jest: 29.7.0(@types/node@18.18.5)(ts-node@10.9.2) lodash: 4.17.21 redent: 3.0.0 - dev: true + dev: false - /@testing-library/jest-dom@6.4.8: - resolution: {integrity: sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==} + /@testing-library/jest-dom@6.5.0: + resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} dependencies: '@adobe/css-tools': 4.4.0 - '@babel/runtime': 7.24.5 - aria-query: 5.3.0 + aria-query: 5.3.2 chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 lodash: 4.17.21 redent: 3.0.0 - dev: false /@testing-library/react@16.0.0(@testing-library/dom@10.1.0)(@types/react-dom@18.2.19)(@types/react@18.2.57)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-guuxUKRWQ+FgNX0h0NS0FIq3Q3uLtWVpBzcLOggmfMoUpgBnzBzvLLd4fbm6yS8ydJd94cIfY4yP9qUQjM2KwQ==} @@ -9008,14 +8957,13 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@testing-library/user-event@14.5.2(@testing-library/dom@10.1.0): + /@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0): resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' dependencies: - '@testing-library/dom': 10.1.0 - dev: true + '@testing-library/dom': 10.4.0 /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} @@ -9144,6 +9092,7 @@ packages: dependencies: '@types/connect': 3.4.38 '@types/node': 18.18.5 + dev: false /@types/buffer-from@1.1.3: resolution: {integrity: sha512-2lq4YC9uLUMGHkl2IDtX4tCXSo2+hwMpOJcY1qiIk1kybc31rIlPyM1HCVJhkPFIo75a/pOVxqyvwuf5TpCG/w==} @@ -9169,11 +9118,13 @@ packages: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: '@types/node': 18.18.5 + dev: false /@types/cross-spawn@6.0.2: resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==} dependencies: '@types/node': 18.18.5 + dev: true /@types/debug@4.1.12: resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -9195,12 +9146,6 @@ packages: resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} dev: true - /@types/emscripten@1.39.13: - resolution: {integrity: sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==} - - /@types/escodegen@0.0.6: - resolution: {integrity: sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==} - /@types/eslint-scope@3.7.7: resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: @@ -9213,9 +9158,6 @@ packages: '@types/estree': 1.0.5 '@types/json-schema': 7.0.13 - /@types/estree@0.0.51: - resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} - /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -9234,6 +9176,7 @@ packages: '@types/qs': 6.9.15 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 + dev: false /@types/express@4.17.21: resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} @@ -9242,6 +9185,7 @@ packages: '@types/express-serve-static-core': 4.19.3 '@types/qs': 6.9.15 '@types/serve-static': 1.15.7 + dev: false /@types/file-saver@2.0.6: resolution: {integrity: sha512-Mw671DVqoMHbjw0w4v2iiOro01dlT/WhWp5uwecBa0Wg8c+bcZOjgF1ndBnlaxhtvFCgTRBtsGivSVhrK/vnag==} @@ -9269,6 +9213,7 @@ packages: resolution: {integrity: sha512-B5hZHgHsXvfCoO3xgNJvBnX7N8p86TqQeGKXcokW4XXi+qY4vxxPSFYofytvVmpFxzPv7oxDQzjg5Un5m2/xiw==} dependencies: '@types/unist': 3.0.1 + dev: false /@types/hoist-non-react-statics@3.3.5: resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==} @@ -9286,6 +9231,7 @@ packages: /@types/http-errors@2.0.4: resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + dev: false /@types/istanbul-lib-coverage@2.0.6: resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -9342,6 +9288,7 @@ packages: /@types/lodash@4.14.202: resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} + dev: false /@types/mdast@4.0.2: resolution: {integrity: sha512-tYR83EignvhYO9iU3kDg8V28M0jqyh9zzp5GV+EO+AYnyUl3P5ltkTeJuTiFZQFz670FSb3EwT/6LQdX+UdKfw==} @@ -9355,6 +9302,7 @@ packages: /@types/mime@1.3.5: resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + dev: false /@types/minimist@1.2.3: resolution: {integrity: sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==} @@ -9389,6 +9337,12 @@ packages: undici-types: 5.25.3 dev: false + /@types/node@22.8.7: + resolution: {integrity: sha512-LidcG+2UeYIWcMuMUpBKOnryBWG/rnmOHQR5apjn8myTQcx3rinFRn7DcIFhMnS0PPFSC6OafdIKEad0lj6U0Q==} + dependencies: + undici-types: 6.19.8 + dev: true + /@types/normalize-package-data@2.4.2: resolution: {integrity: sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==} dev: false @@ -9426,6 +9380,7 @@ packages: /@types/qs@6.9.15: resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} + dev: false /@types/ramda@0.30.2: resolution: {integrity: sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==} @@ -9435,6 +9390,7 @@ packages: /@types/range-parser@1.2.7: resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + dev: false /@types/react-dom@18.2.19: resolution: {integrity: sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==} @@ -9474,12 +9430,14 @@ packages: /@types/semver@7.5.0: resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} + dev: true /@types/send@0.17.4: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 '@types/node': 18.18.5 + dev: false /@types/serve-static@1.15.7: resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} @@ -9487,6 +9445,7 @@ packages: '@types/http-errors': 2.0.4 '@types/node': 18.18.5 '@types/send': 0.17.4 + dev: false /@types/set-cookie-parser@2.4.9: resolution: {integrity: sha512-bCorlULvl0xTdjj4BPUHX4cqs9I+go2TfW/7Do1nnFYWS0CPP429Qr1AY42kiFhCwLpvAkWFr1XIBHd8j6/MCQ==} @@ -9519,6 +9478,7 @@ packages: /@types/unist@3.0.1: resolution: {integrity: sha512-ue/hDUpPjC85m+PM9OQDMZr3LywT+CT6mPsQq8OJtCLiERkGRcQUFvu9XASF5XWqyZFXbf15lvb3JFJ4dRLWPg==} + dev: false /@types/uuid@9.0.3: resolution: {integrity: sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==} @@ -9790,28 +9750,43 @@ packages: - encoding dev: false - /@vitest/expect@1.6.0: - resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + /@vitest/expect@2.0.5: + resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} dependencies: - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - chai: 4.4.1 - dev: true + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.2 + tinyrainbow: 1.2.0 - /@vitest/spy@1.6.0: - resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + /@vitest/pretty-format@2.0.5: + resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} dependencies: - tinyspy: 2.2.1 - dev: true + tinyrainbow: 1.2.0 - /@vitest/utils@1.6.0: - resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + /@vitest/pretty-format@2.1.4: + resolution: {integrity: sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==} dependencies: - diff-sequences: 29.6.3 + tinyrainbow: 1.2.0 + + /@vitest/spy@2.0.5: + resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} + dependencies: + tinyspy: 3.0.2 + + /@vitest/utils@2.0.5: + resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} + dependencies: + '@vitest/pretty-format': 2.0.5 estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 - dev: true + loupe: 3.1.2 + tinyrainbow: 1.2.0 + + /@vitest/utils@2.1.4: + resolution: {integrity: sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==} + dependencies: + '@vitest/pretty-format': 2.1.4 + loupe: 3.1.2 + tinyrainbow: 1.2.0 /@vue/compiler-core@3.4.26: resolution: {integrity: sha512-N9Vil6Hvw7NaiyFUFBPXrAyETIGlQ8KcFMkyk6hW1Cl6NvoqvP+Y8p1Eqvx+UdqsnrnI9+HMUEJegzia3mhXmQ==} @@ -10025,20 +10000,6 @@ packages: /@xtuc/long@4.2.2: resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - /@yarnpkg/fslib@2.10.3: - resolution: {integrity: sha512-41H+Ga78xT9sHvWLlFOZLIhtU6mTGZ20pZ29EiZa97vnxdohJD2AF42rCoAoWfqUz486xY6fhjMH+DYEM9r14A==} - engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} - dependencies: - '@yarnpkg/libzip': 2.3.0 - tslib: 1.14.1 - - /@yarnpkg/libzip@2.3.0: - resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==} - engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} - dependencies: - '@types/emscripten': 1.39.13 - tslib: 1.14.1 - /JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -10079,13 +10040,6 @@ packages: dependencies: acorn: 8.12.1 - /acorn-jsx@5.3.2(acorn@7.4.1): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 7.4.1 - /acorn-jsx@5.3.2(acorn@8.11.3): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -10093,19 +10047,10 @@ packages: dependencies: acorn: 8.11.3 - /acorn-walk@7.2.0: - resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} - engines: {node: '>=0.4.0'} - /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} - /acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true - /acorn@8.11.3: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} @@ -10441,6 +10386,7 @@ packages: /array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + dev: true /array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} @@ -10565,9 +10511,9 @@ packages: util: 0.12.5 dev: true - /assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - dev: true + /assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} /ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -10692,13 +10638,6 @@ packages: resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} dev: false - /babel-core@7.0.0-bridge.0(@babel/core@7.24.5): - resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.5 - /babel-jest@29.7.0(@babel/core@7.24.5): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -10764,6 +10703,7 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: true /babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.24.5): resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==} @@ -10775,6 +10715,7 @@ packages: core-js-compat: 3.37.1 transitivePeerDependencies: - supports-color + dev: true /babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.24.5): resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} @@ -10785,6 +10726,7 @@ packages: '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.5) transitivePeerDependencies: - supports-color + dev: true /babel-plugin-styled-components@2.1.4(@babel/core@7.24.5)(styled-components@5.3.11)(supports-color@5.5.0): resolution: {integrity: sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==} @@ -10917,6 +10859,12 @@ packages: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} dev: false + /better-opn@3.0.2: + resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} + engines: {node: '>=12.0.0'} + dependencies: + open: 8.4.0 + /big-integer@1.6.52: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} @@ -10944,6 +10892,7 @@ packages: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 + dev: true /bl@5.1.0: resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} @@ -10983,6 +10932,7 @@ packages: unpipe: 1.0.0 transitivePeerDependencies: - supports-color + dev: true /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -11150,6 +11100,7 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + dev: true /buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} @@ -11302,18 +11253,15 @@ packages: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} dev: false - /chai@4.4.1: - resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} - engines: {node: '>=4'} + /chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.4 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 - dev: true + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -11425,11 +11373,9 @@ packages: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} dev: false - /check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - dependencies: - get-func-name: 2.0.2 - dev: true + /check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} /cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} @@ -11487,6 +11433,7 @@ packages: /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + dev: true /chromatic@11.7.0: resolution: {integrity: sha512-Afblm4MWK6GXutxHPJVWKoY1PxCD98Uw0S3/f1a2wu4VTQy97g4+G8vPVqutSMpZFGzG5NjH9QdzKPFMmZczpw==} @@ -11522,11 +11469,6 @@ packages: safe-buffer: 5.2.1 dev: true - /citty@0.1.6: - resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - dependencies: - consola: 3.2.3 - /cjs-module-lexer@1.3.1: resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} @@ -11576,6 +11518,7 @@ packages: engines: {node: '>=8'} dependencies: restore-cursor: 3.1.0 + dev: true /cli-cursor@4.0.0: resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} @@ -11586,6 +11529,7 @@ packages: /cli-spinners@2.9.2: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + dev: true /cli-table3@0.6.4: resolution: {integrity: sha512-Lm3L0p+/npIQWNIiyF/nAn7T5dnOwR3xNTHXYEBFBFVPXzCVNZ5lqEC/1eo/EVfpDsQ1I+TX4ORPQgp+UI0CRw==} @@ -11648,17 +11592,10 @@ packages: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - /clone-deep@4.0.1: - resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} - engines: {node: '>=6'} - dependencies: - is-plain-object: 2.0.4 - kind-of: 6.0.3 - shallow-clone: 3.0.1 - /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + dev: true /cloudinary-core@2.13.0(lodash@4.17.21): resolution: {integrity: sha512-Nt0Q5I2FtenmJghtC4YZ3MZZbGg1wLm84SsxcuVwZ83OyJqG9CNIGp86CiI6iDv3QobaqBUpOT7vg+HqY5HxEA==} @@ -11808,10 +11745,6 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - /commander@6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} - /commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -11873,9 +11806,6 @@ packages: yargs: 17.7.2 dev: true - /confbox@0.1.7: - resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} - /config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} dependencies: @@ -11896,6 +11826,7 @@ packages: /consola@3.2.3: resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} engines: {node: ^14.18.0 || >=16.10.0} + dev: false /console-browserify@1.2.0: resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} @@ -11929,6 +11860,7 @@ packages: /content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + dev: true /conventional-changelog-angular@6.0.0: resolution: {integrity: sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg==} @@ -11988,6 +11920,7 @@ packages: /cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + dev: true /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} @@ -11997,6 +11930,7 @@ packages: /cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + dev: true /cookie@0.7.0: resolution: {integrity: sha512-qCf+V4dtlNhSRXGAZatc1TasyFO6GjohcOul807YOb5ik3+kQSnb4d7iajeCL8QHaJ4uZEjCgiCJerKXwdRVlQ==} @@ -12013,7 +11947,8 @@ packages: /core-js-compat@3.37.1: resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} dependencies: - browserslist: 4.23.0 + browserslist: 4.23.3 + dev: true /core-js-pure@3.37.1: resolution: {integrity: sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==} @@ -12437,6 +12372,7 @@ packages: optional: true dependencies: ms: 2.0.0 + dev: true /debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} @@ -12531,12 +12467,9 @@ packages: optional: true dev: true - /deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + /deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - dependencies: - type-detect: 4.0.8 - dev: true /deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} @@ -12600,6 +12533,7 @@ packages: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} dependencies: clone: 1.0.4 + dev: true /defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} @@ -12638,9 +12572,6 @@ packages: resolution: {integrity: sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ==} dev: false - /defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - /delay@5.0.0: resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} engines: {node: '>=10'} @@ -12691,6 +12622,7 @@ packages: /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: true /detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} @@ -12943,6 +12875,7 @@ packages: /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + dev: true /electron-to-chromium@1.4.676: resolution: {integrity: sha512-uHt4FB8SeYdhcOsj2ix/C39S7sPSNFJpzShjxGOm1KdF4MHyGqGi389+T5cErsodsijojXilYaHIKKqJfqh7uQ==} @@ -12986,6 +12919,7 @@ packages: /encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + dev: true /encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} @@ -13039,11 +12973,6 @@ packages: engines: {node: '>=6'} dev: true - /envinfo@7.13.0: - resolution: {integrity: sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==} - engines: {node: '>=4'} - hasBin: true - /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -13292,6 +13221,7 @@ packages: /escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: true /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} @@ -13320,6 +13250,7 @@ packages: esutils: 2.0.3 optionalDependencies: source-map: 0.6.1 + dev: false /eslint-config-next@15.0.1(eslint@8.56.0)(typescript@5.3.3): resolution: {integrity: sha512-3cYCrgbH6GS/ufApza7XCKz92vtq4dAdYhx++rMFNlH2cAV+/GsAKkrr4+bohYOACmzG2nAOR+uWprKC1Uld6A==} @@ -13684,6 +13615,7 @@ packages: /etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + dev: true /event-lite@0.1.3: resolution: {integrity: sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw==} @@ -13767,6 +13699,7 @@ packages: onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 + dev: false /exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} @@ -13824,6 +13757,7 @@ packages: vary: 1.1.2 transitivePeerDependencies: - supports-color + dev: true /extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -13965,12 +13899,7 @@ packages: ua-parser-js: 1.0.39 transitivePeerDependencies: - encoding - dev: true - - /fd-package-json@1.2.0: - resolution: {integrity: sha512-45LSPmWf+gC5tdCQMNH4s9Sr00bIkiD9aN7dc5hqkrEw1geRYyDQS1v1oMHAW3ysfxfndqGsrDREHHjNNbKUfA==} - dependencies: - walk-up-path: 3.0.1 + dev: true /fflate@0.4.8: resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} @@ -14043,14 +13972,7 @@ packages: unpipe: 1.0.0 transitivePeerDependencies: - supports-color - - /find-cache-dir@2.1.0: - resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} - engines: {node: '>=6'} - dependencies: - commondir: 1.0.1 - make-dir: 2.1.0 - pkg-dir: 3.0.0 + dev: true /find-cache-dir@3.3.2: resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} @@ -14084,12 +14006,6 @@ packages: locate-path: 2.0.0 dev: false - /find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} - dependencies: - locate-path: 3.0.0 - /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -14133,10 +14049,6 @@ packages: /flatted@3.2.7: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} - /flow-parser@0.242.1: - resolution: {integrity: sha512-E3ml21Q1S5cMAyPbtYslkvI6yZO5oCS/S2EoteeFH8Kx9iKOv/YOJ+dGd/yMf+H3YKfhMKjnOpyNwrO7NdddWA==} - engines: {node: '>=0.4.0'} - /follow-redirects@1.15.6: resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} engines: {node: '>=4.0'} @@ -14169,7 +14081,7 @@ packages: dependencies: '@babel/code-frame': 7.24.7 chalk: 4.1.2 - chokidar: 3.5.3 + chokidar: 3.6.0 cosmiconfig: 7.1.0 deepmerge: 4.3.1 fs-extra: 10.1.0 @@ -14234,6 +14146,7 @@ packages: /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + dev: true /fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -14292,6 +14205,7 @@ packages: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 + dev: false /fs-extra@8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} @@ -14307,6 +14221,7 @@ packages: engines: {node: '>= 8'} dependencies: minipass: 3.3.6 + dev: true /fs-monkey@1.0.6: resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} @@ -14398,10 +14313,6 @@ packages: engines: {node: '>=18'} dev: false - /get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - dev: true - /get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -14454,6 +14365,7 @@ packages: /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} + dev: false /get-symbol-description@1.0.2: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} @@ -14468,19 +14380,6 @@ packages: dependencies: resolve-pkg-maps: 1.0.0 - /giget@1.2.3: - resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} - hasBin: true - dependencies: - citty: 0.1.6 - consola: 3.2.3 - defu: 6.1.4 - node-fetch-native: 1.6.4 - nypm: 0.3.9 - ohash: 1.1.3 - pathe: 1.1.2 - tar: 6.2.1 - /git-hooks-list@3.1.0: resolution: {integrity: sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA==} dev: false @@ -14496,10 +14395,6 @@ packages: traverse: 0.6.8 dev: false - /github-slugger@2.0.0: - resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} - dev: true - /gleap@11.1.5: resolution: {integrity: sha512-UVurhbp2WdUyyXcSS5gTwHOR0XX2FSFGv+jSL7YfVq8PLqGBQpSTK4ioCBzgkgyxc0/XAyyC0t6M5RS+pFBMgQ==} dev: false @@ -14638,6 +14533,7 @@ packages: path-type: 5.0.0 slash: 5.1.0 unicorn-magic: 0.1.0 + dev: false /globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -14928,18 +14824,6 @@ packages: dependencies: function-bind: 1.1.2 - /hast-util-heading-rank@3.0.0: - resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} - dependencies: - '@types/hast': 3.0.2 - dev: true - - /hast-util-is-element@3.0.0: - resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} - dependencies: - '@types/hast': 3.0.2 - dev: true - /hast-util-parse-selector@2.2.5: resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} dev: false @@ -14958,12 +14842,6 @@ packages: vfile-message: 4.0.2 dev: false - /hast-util-to-string@3.0.0: - resolution: {integrity: sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==} - dependencies: - '@types/hast': 3.0.2 - dev: true - /hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} dependencies: @@ -15079,10 +14957,6 @@ packages: terser: 5.31.3 dev: true - /html-tags@3.3.1: - resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} - engines: {node: '>=8'} - /html-url-attributes@3.0.0: resolution: {integrity: sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==} dev: false @@ -15216,6 +15090,7 @@ packages: /human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + dev: false /humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} @@ -15271,6 +15146,7 @@ packages: /image-size@1.0.2: resolution: {integrity: sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==} engines: {node: '>=14.0.0'} + hasBin: true dependencies: queue: 6.0.2 @@ -15506,6 +15382,7 @@ packages: /ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + dev: true /iron-webcrypto@0.8.0: resolution: {integrity: sha512-gScdcWHjTGclCU15CIv2r069NoQrys1UeUFFfaO1hL++ytLHkVw7N5nXJmFf3J2LEDMz1PkrvC0m62JEeu1axQ==} @@ -15520,11 +15397,6 @@ packages: engines: {node: '>=8'} dev: true - /is-absolute-url@4.0.1: - resolution: {integrity: sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - /is-absolute@1.0.0: resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} engines: {node: '>=0.10.0'} @@ -15730,6 +15602,7 @@ packages: /is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} + dev: true /is-interactive@2.0.0: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} @@ -15799,16 +15672,6 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - /is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} - dependencies: - isobject: 3.0.1 - - /is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - /is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} dev: false @@ -15910,6 +15773,7 @@ packages: /is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + dev: true /is-unicode-supported@1.3.0: resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} @@ -15975,10 +15839,6 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - /isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} - /isomorphic-fetch@3.0.0: resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} dependencies: @@ -16606,38 +16466,9 @@ packages: dependencies: argparse: 2.0.1 - /jscodeshift@0.15.2(@babel/preset-env@7.25.3): - resolution: {integrity: sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA==} - hasBin: true - peerDependencies: - '@babel/preset-env': ^7.1.6 - peerDependenciesMeta: - '@babel/preset-env': - optional: true - dependencies: - '@babel/core': 7.24.5 - '@babel/parser': 7.24.5 - '@babel/plugin-transform-class-properties': 7.24.7(@babel/core@7.24.5) - '@babel/plugin-transform-modules-commonjs': 7.24.8(@babel/core@7.24.5) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.7(@babel/core@7.24.5) - '@babel/plugin-transform-optional-chaining': 7.24.8(@babel/core@7.24.5) - '@babel/plugin-transform-private-methods': 7.24.7(@babel/core@7.24.5) - '@babel/preset-env': 7.25.3(@babel/core@7.24.5) - '@babel/preset-flow': 7.24.7(@babel/core@7.24.5) - '@babel/preset-typescript': 7.24.7(@babel/core@7.24.5) - '@babel/register': 7.24.6(@babel/core@7.24.5) - babel-core: 7.0.0-bridge.0(@babel/core@7.24.5) - chalk: 4.1.2 - flow-parser: 0.242.1 - graceful-fs: 4.2.11 - micromatch: 4.0.5 - neo-async: 2.6.2 - node-dir: 0.1.17 - recast: 0.23.9 - temp: 0.8.4 - write-file-atomic: 2.4.3 - transitivePeerDependencies: - - supports-color + /jsdoc-type-pratt-parser@4.1.0: + resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} + engines: {node: '>=12.0.0'} /jsdom@20.0.3: resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} @@ -16683,6 +16514,7 @@ packages: /jsesc@0.5.0: resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} hasBin: true + dev: true /jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} @@ -16856,14 +16688,11 @@ packages: /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + dev: false /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - - /klona@2.0.6: - resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} - engines: {node: '>= 8'} dev: true /koalas@1.0.2: @@ -17260,6 +17089,7 @@ packages: /leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + dev: true /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -17401,13 +17231,6 @@ packages: path-exists: 3.0.0 dev: false - /locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} - dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 - /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -17509,6 +17332,7 @@ packages: dependencies: chalk: 4.1.2 is-unicode-supported: 0.1.0 + dev: true /log-symbols@5.1.0: resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} @@ -17557,11 +17381,8 @@ packages: dependencies: js-tokens: 4.0.0 - /loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - dependencies: - get-func-name: 2.0.2 - dev: true + /loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} /lower-case-first@1.0.2: resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==} @@ -17638,13 +17459,6 @@ packages: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - /make-dir@2.1.0: - resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} - engines: {node: '>=6'} - dependencies: - pify: 4.0.1 - semver: 5.7.2 - /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -17690,15 +17504,6 @@ packages: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} dev: false - /markdown-to-jsx@7.4.7(react@18.2.0): - resolution: {integrity: sha512-0+ls1IQZdU6cwM1yu0ZjjiVWYtkbExSyUIFU2ZeDIFuZM1W42Mh4OlJ4nb4apX4H8smxDHRdFaoIVJGwfv5hkg==} - engines: {node: '>= 10'} - peerDependencies: - react: '>= 0.14.0' - dependencies: - react: 18.2.0 - dev: true - /marked-terminal@5.2.0(marked@5.1.2): resolution: {integrity: sha512-Piv6yNwAQXGFjZSaiNljyNFw7jKDdGrw70FSbtxEyldLsyeuV5ZHm/1wW++kWbrOF1VPnUgYOhB2oLL0ZpnekA==} engines: {node: '>=14.13.1 || >=16.0.0'} @@ -18323,6 +18128,7 @@ packages: engines: {node: '>=8'} dependencies: yallist: 4.0.0 + dev: true /minipass@4.2.8: resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} @@ -18331,6 +18137,7 @@ packages: /minipass@5.0.0: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} + dev: true /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} @@ -18342,6 +18149,7 @@ packages: dependencies: minipass: 3.3.6 yallist: 4.0.0 + dev: true /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} @@ -18353,6 +18161,7 @@ packages: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true + dev: true /ml-array-mean@1.1.6: resolution: {integrity: sha512-MIdf7Zc8HznwIisyiJGRH9tRigg3Yf4FldW8DxKxpCCv/g5CafTw0RRu51nojVEOXuCQC7DRVVu5c7XXO/5joQ==} @@ -18385,14 +18194,6 @@ packages: num-sort: 2.1.0 dev: false - /mlly@1.7.1: - resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} - dependencies: - acorn: 8.12.1 - pathe: 1.1.2 - pkg-types: 1.1.3 - ufo: 1.5.4 - /modify-values@1.0.1: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} @@ -18417,6 +18218,7 @@ packages: /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: true /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -18626,12 +18428,6 @@ packages: resolution: {integrity: sha512-xwaoRXU9y5meKthFS9ZXju8Qg/TA1K2xI4cMSN56dgTLcTQtzq57huDrNqqMECUDasdE1qqEeAo3OWP3vUboCw==} dev: true - /node-dir@0.1.17: - resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} - engines: {node: '>= 0.10.5'} - dependencies: - minimatch: 3.1.2 - /node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -18651,9 +18447,6 @@ packages: resolution: {integrity: sha512-NsXBU0UgBxo2rQLOeWNZqS3fvflWePMECr8CoSWoSTqCqGbVVsvl9vZu1HfQicYN0g5piV9Gh8RTEvo/uP752w==} dev: false - /node-fetch-native@1.6.4: - resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} - /node-fetch@1.7.3: resolution: {integrity: sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==} dependencies: @@ -18914,18 +18707,6 @@ packages: resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} dev: false - /nypm@0.3.9: - resolution: {integrity: sha512-BI2SdqqTHg2d4wJh8P9A1W+bslg33vOE9IZDY6eR2QC+Pu1iNBVZUqczrd43rJb+fMzHU7ltAYKsEFY/kHMFcw==} - engines: {node: ^14.16.0 || >=16.10.0} - hasBin: true - dependencies: - citty: 0.1.6 - consola: 3.2.3 - execa: 8.0.1 - pathe: 1.1.2 - pkg-types: 1.1.3 - ufo: 1.5.4 - /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -18999,9 +18780,6 @@ packages: engines: {node: '>=12.0.0'} dev: false - /ohash@1.1.3: - resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==} - /on-exit-leak-free@2.1.0: resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} @@ -19017,6 +18795,7 @@ packages: engines: {node: '>= 0.8'} dependencies: ee-first: 1.1.1 + dev: true /on-headers@1.0.2: resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} @@ -19121,6 +18900,7 @@ packages: log-symbols: 4.1.0 strip-ansi: 6.0.1 wcwidth: 1.0.1 + dev: true /ora@6.3.1: resolution: {integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==} @@ -19218,12 +18998,6 @@ packages: p-limit: 1.3.0 dev: false - /p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} - dependencies: - p-limit: 2.3.0 - /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -19500,6 +19274,7 @@ packages: /path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} + dev: false /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} @@ -19562,17 +19337,15 @@ packages: /path-type@5.0.0: resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} engines: {node: '>=12'} + dev: false /pathe@1.1.1: resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} dev: false - /pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - - /pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - dev: true + /pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} /pbkdf2@3.1.2: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} @@ -19641,6 +19414,7 @@ packages: /pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + dev: false /pify@6.1.0: resolution: {integrity: sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==} @@ -19716,12 +19490,6 @@ packages: load-json-file: 4.0.0 dev: false - /pkg-dir@3.0.0: - resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} - engines: {node: '>=6'} - dependencies: - find-up: 3.0.0 - /pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -19736,13 +19504,6 @@ packages: find-up: 6.3.0 dev: true - /pkg-types@1.1.3: - resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} - dependencies: - confbox: 0.1.7 - mlly: 1.7.1 - pathe: 1.1.2 - /playwright-core@1.47.2: resolution: {integrity: sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==} engines: {node: '>=18'} @@ -20158,6 +19919,7 @@ packages: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 + dev: true /prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -20209,6 +19971,7 @@ packages: dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 + dev: true /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -20279,6 +20042,7 @@ packages: engines: {node: '>=0.6'} dependencies: side-channel: 1.0.6 + dev: true /qs@6.11.2: resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} @@ -20371,16 +20135,6 @@ packages: minimist: 1.2.8 strip-json-comments: 2.0.1 - /react-colorful@5.6.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: true - /react-confetti@6.1.0(react@18.2.0): resolution: {integrity: sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==} engines: {node: '>=10.18'} @@ -20426,18 +20180,6 @@ packages: react: 18.2.0 scheduler: 0.23.0 - /react-element-to-jsx-string@15.0.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==} - peerDependencies: - react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 - react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 - dependencies: - '@base2/pretty-print-object': 1.0.1 - is-plain-object: 5.0.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-is: 18.1.0 - /react-hook-form@7.45.4(react@18.2.0): resolution: {integrity: sha512-HGDV1JOOBPZj10LB3+OZgfDBTn+IeEsNOKiq/cxbQAIbKaiJUe/KV8DBUzsx0Gx/7IG/orWqRRm736JwOfUSWQ==} engines: {node: '>=12.22.0'} @@ -20480,9 +20222,6 @@ packages: /react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - /react-is@18.1.0: - resolution: {integrity: sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==} - /react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -20763,9 +20502,11 @@ packages: engines: {node: '>=4'} dependencies: regenerate: 1.4.2 + dev: true /regenerate@1.4.2: resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + dev: true /regenerator-runtime@0.11.1: resolution: {integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==} @@ -20782,6 +20523,7 @@ packages: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} dependencies: '@babel/runtime': 7.24.5 + dev: true /regex-parser@2.3.0: resolution: {integrity: sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==} @@ -20806,6 +20548,7 @@ packages: regjsparser: 0.9.1 unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.1.0 + dev: true /registry-auth-token@5.0.2: resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==} @@ -20824,26 +20567,6 @@ packages: hasBin: true dependencies: jsesc: 0.5.0 - - /rehype-external-links@3.0.0: - resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==} - dependencies: - '@types/hast': 3.0.2 - '@ungap/structured-clone': 1.2.0 - hast-util-is-element: 3.0.0 - is-absolute-url: 4.0.1 - space-separated-tokens: 2.0.2 - unist-util-visit: 5.0.0 - dev: true - - /rehype-slug@6.0.0: - resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} - dependencies: - '@types/hast': 3.0.2 - github-slugger: 2.0.0 - hast-util-heading-rank: 3.0.0 - hast-util-to-string: 3.0.0 - unist-util-visit: 5.0.0 dev: true /relateurl@0.2.7: @@ -21045,6 +20768,7 @@ packages: dependencies: onetime: 5.1.2 signal-exit: 3.0.7 + dev: true /restore-cursor@4.0.0: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} @@ -21077,13 +20801,6 @@ packages: /rfdc@1.3.0: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} - /rimraf@2.6.3: - resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - dependencies: - glob: 7.2.3 - /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true @@ -21187,12 +20904,12 @@ packages: /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - /sass-loader@12.6.0(webpack@5.93.0): - resolution: {integrity: sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==} - engines: {node: '>= 12.13.0'} + /sass-loader@13.3.3(webpack@5.93.0): + resolution: {integrity: sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==} + engines: {node: '>= 14.15.0'} peerDependencies: fibers: '>= 3.1.0' - node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 sass: ^1.3.0 sass-embedded: '*' webpack: ^5.0.0 @@ -21206,7 +20923,6 @@ packages: sass-embedded: optional: true dependencies: - klona: 2.0.6 neo-async: 2.6.2 webpack: 5.93.0(esbuild@0.21.5) dev: true @@ -21311,6 +21027,7 @@ packages: /semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true + dev: false /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} @@ -21353,6 +21070,7 @@ packages: statuses: 2.0.1 transitivePeerDependencies: - supports-color + dev: true /sentence-case@2.1.1: resolution: {integrity: sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==} @@ -21388,6 +21106,7 @@ packages: send: 0.18.0 transitivePeerDependencies: - supports-color + dev: true /server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} @@ -21442,12 +21161,6 @@ packages: safe-buffer: 5.2.1 dev: true - /shallow-clone@3.0.1: - resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} - engines: {node: '>=8'} - dependencies: - kind-of: 6.0.3 - /shallowequal@1.1.0: resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} dev: false @@ -21534,6 +21247,7 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + dev: false /signale@1.4.0: resolution: {integrity: sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==} @@ -21558,6 +21272,7 @@ packages: /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} @@ -21570,6 +21285,7 @@ packages: /slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} + dev: false /slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} @@ -21699,6 +21415,7 @@ packages: /space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + dev: false /spawn-command@0.0.2: resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} @@ -21814,40 +21531,18 @@ packages: internal-slot: 1.0.7 dev: false - /storybook@8.2.7: - resolution: {integrity: sha512-Jb9DXue1sr3tKkpuq66VP5ItOKTpxL6t99ze1wXDbjCvPiInTdPA5AyFEjBuKjOBIh28bayYoOZa6/xbMJV+Wg==} + /storybook@8.4.1(prettier@3.3.3): + resolution: {integrity: sha512-0tfFIFghjho9FtnFoiJMoxhcs2iIdvEF81GTSVnTsDVJrYA84nB+FxN3UY1fT0BcQ8BFlbf+OhSjZL7ufqqWKA==} hasBin: true + peerDependencies: + prettier: ^2 || ^3 + peerDependenciesMeta: + prettier: + optional: true dependencies: - '@babel/core': 7.24.5 - '@babel/types': 7.24.5 - '@storybook/codemod': 8.2.7 - '@storybook/core': 8.2.7 - '@types/semver': 7.5.0 - '@yarnpkg/fslib': 2.10.3 - '@yarnpkg/libzip': 2.3.0 - chalk: 4.1.2 - commander: 6.2.1 - cross-spawn: 7.0.3 - detect-indent: 6.1.0 - envinfo: 7.13.0 - execa: 5.1.1 - fd-package-json: 1.2.0 - find-up: 5.0.0 - fs-extra: 11.2.0 - giget: 1.2.3 - globby: 14.0.1 - jscodeshift: 0.15.2(@babel/preset-env@7.25.3) - leven: 3.1.0 - ora: 5.4.1 + '@storybook/core': 8.4.1(prettier@3.3.3) prettier: 3.3.3 - prompts: 2.4.2 - semver: 7.6.2 - strip-json-comments: 3.1.1 - tempy: 3.1.0 - tiny-invariant: 1.3.3 - ts-dedent: 2.2.0 transitivePeerDependencies: - - '@babel/preset-env' - bufferutil - supports-color - utf-8-validate @@ -22192,6 +21887,24 @@ packages: client-only: 0.0.1 react: 18.2.0 + /styled-jsx@5.1.6(@babel/core@7.24.5)(react@18.2.0): + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + '@babel/core': 7.24.5 + client-only: 0.0.1 + react: 18.2.0 + dev: true + /sucrase@3.34.0: resolution: {integrity: sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==} engines: {node: '>=8'} @@ -22407,6 +22120,7 @@ packages: minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 + dev: true /teeny-request@9.0.0: resolution: {integrity: sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==} @@ -22426,16 +22140,12 @@ packages: resolution: {integrity: sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==} dependencies: memoizerific: 1.11.3 + dev: false /temp-dir@3.0.0: resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} engines: {node: '>=14.16'} - - /temp@0.8.4: - resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==} - engines: {node: '>=6.0.0'} - dependencies: - rimraf: 2.6.3 + dev: false /tempy@3.1.0: resolution: {integrity: sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==} @@ -22445,6 +22155,7 @@ packages: temp-dir: 3.0.0 type-fest: 2.19.0 unique-string: 3.0.0 + dev: false /terser-webpack-plugin@5.3.10(esbuild@0.21.5)(webpack@5.93.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} @@ -22550,10 +22261,13 @@ packages: /tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - /tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + /tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + /tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - dev: true /title-case@2.1.1: resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==} @@ -22808,6 +22522,7 @@ packages: /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: true /tslib@2.4.1: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} @@ -23085,9 +22800,6 @@ packages: resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==} dev: false - /ufo@1.5.4: - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - /uglify-js@3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} engines: {node: '>=0.8.0'} @@ -23117,6 +22829,10 @@ packages: resolution: {integrity: sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==} dev: false + /undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + dev: true + /undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} engines: {node: '>=14.0'} @@ -23146,6 +22862,7 @@ packages: /unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} + dev: true /unicode-match-property-ecmascript@2.0.0: resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} @@ -23153,18 +22870,22 @@ packages: dependencies: unicode-canonical-property-names-ecmascript: 2.0.0 unicode-property-aliases-ecmascript: 2.1.0 + dev: true /unicode-match-property-value-ecmascript@2.1.0: resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} engines: {node: '>=4'} + dev: true /unicode-property-aliases-ecmascript@2.1.0: resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} engines: {node: '>=4'} + dev: true /unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} + dev: false /unified@11.0.4: resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} @@ -23193,6 +22914,7 @@ packages: resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} dependencies: '@types/unist': 3.0.1 + dev: false /unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} @@ -23211,6 +22933,7 @@ packages: dependencies: '@types/unist': 3.0.1 unist-util-is: 6.0.0 + dev: false /unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} @@ -23218,6 +22941,7 @@ packages: '@types/unist': 3.0.1 unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 + dev: false /universal-user-agent@6.0.1: resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} @@ -23476,7 +23200,7 @@ packages: is-arguments: 1.1.1 is-generator-function: 1.0.10 is-typed-array: 1.1.13 - which-typed-array: 1.1.14 + which-typed-array: 1.1.15 /utila@0.4.0: resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} @@ -23485,6 +23209,7 @@ packages: /utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + dev: true /uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} @@ -23526,6 +23251,7 @@ packages: /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + dev: true /vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} @@ -23569,9 +23295,6 @@ packages: xml-name-validator: 4.0.0 dev: false - /walk-up-path@3.0.1: - resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} - /walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: @@ -23589,6 +23312,7 @@ packages: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} dependencies: defaults: 1.0.4 + dev: true /web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} @@ -23870,13 +23594,6 @@ packages: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} requiresBuild: true - /write-file-atomic@2.4.3: - resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} - dependencies: - graceful-fs: 4.2.11 - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - /write-file-atomic@3.0.3: resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} dependencies: From f2164172b6d15d974524f9323eea3d465c4fa882 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:29:24 +0100 Subject: [PATCH 31/61] test: delete test users with null activeAt --- .../api/src/router/testSupport/prepareUser.ts | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/api/src/router/testSupport/prepareUser.ts b/packages/api/src/router/testSupport/prepareUser.ts index ce7a8b544..05ddc42c3 100644 --- a/packages/api/src/router/testSupport/prepareUser.ts +++ b/packages/api/src/router/testSupport/prepareUser.ts @@ -1,4 +1,5 @@ import { clerkClient } from "@clerk/nextjs/server"; +import { isClerkAPIResponseError } from "@clerk/shared"; import { aiLogger } from "@oakai/logger"; import { waitUntil } from "@vercel/functions"; import os from "os"; @@ -75,24 +76,50 @@ const generateEmailAddress = (personaName: keyof typeof personas) => { return `${parts.join("+")}@thenational.academy`; }; -const deleteLastUsedTestUser = async () => { - const users = await clerkClient.users.getUserList({ - orderBy: "+last_active_at", +const deleteOldTestUser = async () => { + const result = await clerkClient.users.getUserList({ limit: 500, }); const NUMBERS_USER = /\d{5,10}.*@/; // jim+010203@thenational.academy - const lastUsedTestUser = users.data.find((u) => { + const testUsers = result.data.filter((u) => { const email = u.primaryEmailAddress?.emailAddress ?? ""; return email.startsWith("test+") || email.match(NUMBERS_USER); }); - if (lastUsedTestUser) { - log.info( - "Deleting oldest test user", - lastUsedTestUser.primaryEmailAddress?.emailAddress, - ); - await clerkClient.users.deleteUser(lastUsedTestUser.id); + if (testUsers.length < 100) { + log.info(`less than 100 test users. Skipping cleanup.`); + return; + } + + const users = testUsers.sort( + (a, b) => + new Date(a.lastActiveAt ?? a.createdAt).getTime() - + new Date(b.lastActiveAt ?? b.createdAt).getTime(), + ); + + // If multiple personas are created at the same time and both try to delete the + // oldest user they will conflict. Add some randomness to reduce conflicts + const randomOffset = Math.floor(Math.random() * 8); + const userToDelete = users[randomOffset]; + + if (userToDelete) { + try { + await clerkClient.users.deleteUser(userToDelete.id); + log.info( + "Deleted old test user", + userToDelete.primaryEmailAddress?.emailAddress, + ); + } catch (e) { + if (isClerkAPIResponseError(e) && e.status === 404) { + log.info( + `${userToDelete.primaryEmailAddress?.emailAddress} already deleted, retrying`, + ); + deleteOldTestUser(); + } else { + throw e; + } + } } }; @@ -134,7 +161,7 @@ const findOrCreateUser = async ( }, }); - waitUntil(deleteLastUsedTestUser()); + waitUntil(deleteOldTestUser()); return newUser; }; From f221c2442617031fa694708da953b4a5f1ea5e6c Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:59:54 +0100 Subject: [PATCH 32/61] fix: set up server side styled-components --- apps/nextjs/next.config.js | 3 + apps/nextjs/src/app/layout.tsx | 104 ++++++++++++------------ apps/nextjs/src/app/styles-registry.tsx | 31 +++++++ 3 files changed, 88 insertions(+), 50 deletions(-) create mode 100644 apps/nextjs/src/app/styles-registry.tsx diff --git a/apps/nextjs/next.config.js b/apps/nextjs/next.config.js index 4cc207922..6f9dad15f 100644 --- a/apps/nextjs/next.config.js +++ b/apps/nextjs/next.config.js @@ -77,6 +77,9 @@ const getConfig = async (phase) => { domains: ["oaknationalacademy-res.cloudinary.com"], }, transpilePackages: ["@oakai/api", "@oakai/db", "@oakai/exports"], + compiler: { + styledComponents: true, + }, // We already do linting on GH actions eslint: { ignoreDuringBuilds: !!process.env.CI, diff --git a/apps/nextjs/src/app/layout.tsx b/apps/nextjs/src/app/layout.tsx index 08366e7f3..a81cfc132 100644 --- a/apps/nextjs/src/app/layout.tsx +++ b/apps/nextjs/src/app/layout.tsx @@ -28,6 +28,8 @@ import { SentryIdentify } from "@/lib/sentry/SentryIdentify"; import { cn } from "@/lib/utils"; import { TRPCReactProvider } from "@/utils/trpc"; +import StyledComponentsRegistry from "./styles-registry"; + const provided_vercel_url = process.env.VERCEL_URL && process.env.VERCEL_URL?.length > 0 ? process.env.VERCEL_URL @@ -82,61 +84,63 @@ export default async function RootLayout({ return ( - - - + + - - - - - - - - {children} - - - - - - + + + + + + + + + {children} + + + + + + - {/* react-hot-toast uses "goober" to set styles. + {/* react-hot-toast uses "goober" to set styles. Goober creates a _goober tag which would be blocked by CSP We can pre-create it with a nonce ourselves See https://github.com/cristianbote/goober/issues/471 */} - - - + + + + ); } diff --git a/apps/nextjs/src/app/styles-registry.tsx b/apps/nextjs/src/app/styles-registry.tsx new file mode 100644 index 000000000..53e3196b9 --- /dev/null +++ b/apps/nextjs/src/app/styles-registry.tsx @@ -0,0 +1,31 @@ +"use client"; + +import React, { useState } from "react"; + +import { useServerInsertedHTML } from "next/navigation"; +import { ServerStyleSheet, StyleSheetManager } from "styled-components"; + +// https://nextjs.org/docs/app/building-your-application/styling/css-in-js#styled-components +export default function StyledComponentsRegistry({ + children, +}: { + children: React.ReactNode; +}) { + // Only create stylesheet once with lazy initial state + // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state + const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet()); + + useServerInsertedHTML(() => { + const styles = styledComponentsStyleSheet.getStyleElement(); + styledComponentsStyleSheet.instance.clearTag(); + return <>{styles}; + }); + + if (typeof window !== "undefined") return <>{children}; + + return ( + + {children} + + ); +} From a0ac1de89dc6dda38ab64a02054e992072332fd5 Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Tue, 5 Nov 2024 08:28:49 +0000 Subject: [PATCH 33/61] fix: add missing prisma import (#342) --- packages/api/src/router/chats.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/src/router/chats.ts b/packages/api/src/router/chats.ts index bfc50a354..403dc29fb 100644 --- a/packages/api/src/router/chats.ts +++ b/packages/api/src/router/chats.ts @@ -1,3 +1,4 @@ +import { prisma } from "@oakai/db/client"; import * as Sentry from "@sentry/nextjs"; import { TRPCError } from "@trpc/server"; import { isTruthy } from "remeda"; From 625957e4c81c116f36847a62d4b1bc105b69e163 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Thu, 10 Oct 2024 20:25:55 +0200 Subject: [PATCH 34/61] test: add test for rate limiting --- .../AppComponents/Chat/chat-message/index.tsx | 4 + apps/nextjs/tests-e2e/helpers/auth/index.ts | 7 +- .../tests/aila-chat/rate-limiting.test.ts | 73 ++++++++++++++++++ .../rate-limited-error-No-persona-darwin.png | Bin 0 -> 17581 bytes .../api/src/router/testSupport/prepareUser.ts | 16 ++++ .../router/testSupport/rateLimiting/index.ts | 37 +++++++++ .../rateLimiting/userBasedRateLimiter.ts | 1 + 7 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts create mode 100644 apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts-snapshots/rate-limited-error-No-persona-darwin.png create mode 100644 packages/api/src/router/testSupport/rateLimiting/index.ts diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-message/index.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-message/index.tsx index 184bc6707..6bc267fde 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/chat-message/index.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/chat-message/index.tsx @@ -190,6 +190,9 @@ function MessageWrapper({ children, type, }: MessageWrapperProps) { + const testId = errorType + ? `chat-message-wrapper-error-${errorType}` + : `chat-message-wrapper-${type}`; return (
      {type === "aila" || (type === "editing" && ( diff --git a/apps/nextjs/tests-e2e/helpers/auth/index.ts b/apps/nextjs/tests-e2e/helpers/auth/index.ts index 420e2913a..7c1d4aa68 100644 --- a/apps/nextjs/tests-e2e/helpers/auth/index.ts +++ b/apps/nextjs/tests-e2e/helpers/auth/index.ts @@ -26,7 +26,12 @@ const trpc = createTRPCProxyClient({ export async function prepareUser( page: Page, - persona: "typical" | "demo" | "nearly-banned" | "sharing-chat", + persona: + | "typical" + | "demo" + | "nearly-banned" + | "nearly-rate-limited" + | "sharing-chat", ) { return await test.step("Prepare user", async () => { const [login] = await Promise.all([ diff --git a/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts b/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts new file mode 100644 index 000000000..fabea3989 --- /dev/null +++ b/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts @@ -0,0 +1,73 @@ +import { test, expect } from "@playwright/test"; + +import { TEST_BASE_URL } from "../../config/config"; +import { prepareUser } from "../../helpers/auth"; +import { bypassVercelProtection } from "../../helpers/vercel"; +import { applyLlmFixtures, continueChat, waitForGeneration } from "./helpers"; + +const GENERATION_TIMEOUT = 30000; + +test("User is restricted after message rate limit is reached", async ({ + page, +}) => { + test.setTimeout(GENERATION_TIMEOUT * 2); + + await test.step("Setup", async () => { + await bypassVercelProtection(page); + await prepareUser(page, "nearly-rate-limited"); + + await page.goto(`${TEST_BASE_URL}/aila`); + await expect(page.getByTestId("chat-h1")).toBeInViewport(); + }); + + const { setFixture } = await applyLlmFixtures(page, "replay"); + + await test.step("Fill in the chat box", async () => { + const textbox = page.getByTestId("chat-input"); + const sendMessage = page.getByTestId("send-message"); + + const message = + "Create a KS1 lesson on the end of Roman Britain. Ask a question for each quiz and cycle"; + await textbox.fill(message); + await expect(textbox).toContainText(message); + + // Temporary fix: The test goes quicker than a real user and submits before the demo status has loaded + // This means that a demo modal would be shown when submitting + await page.waitForTimeout(500); + + setFixture("roman-britain-1"); + await sendMessage.click(); + }); + + await test.step("Send first message", async () => { + await page.waitForURL(/\/aila\/.+/); + await waitForGeneration(page, GENERATION_TIMEOUT); + + await expect( + page.getByTestId("chat-message-wrapper-error-generic"), + ).not.toBeAttached(); + await expect(page.getByTestId("chat-message-wrapper-aila")).toContainText( + "There are no existing Oak lessons", + { timeout: 10000 }, + ); + }); + + await test.step("Send second message", async () => { + await continueChat(page); + await waitForGeneration(page, 10000); + + const errorMessage = page.getByTestId("chat-message-wrapper-error-generic"); + await expect(errorMessage).toContainText( + "Unfortunately you’ve exceeded your fair usage limit", + ); + }); + + await test.step("Send third message", async () => { + await continueChat(page); + await waitForGeneration(page, 10000); + + await expect( + page.getByTestId("chat-message-wrapper-error-generic").last(), + ).toContainText("Unfortunately you’ve exceeded your fair usage limit", {}); + }); +}); diff --git a/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts-snapshots/rate-limited-error-No-persona-darwin.png b/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts-snapshots/rate-limited-error-No-persona-darwin.png new file mode 100644 index 0000000000000000000000000000000000000000..d82b2b3bd830cec9bbbd12146b41f814be1d4b4b GIT binary patch literal 17581 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 literal 0 HcmV?d00001 diff --git a/packages/api/src/router/testSupport/prepareUser.ts b/packages/api/src/router/testSupport/prepareUser.ts index ce7a8b544..e395dbad5 100644 --- a/packages/api/src/router/testSupport/prepareUser.ts +++ b/packages/api/src/router/testSupport/prepareUser.ts @@ -5,6 +5,7 @@ import os from "os"; import { z } from "zod"; import { publicProcedure } from "../../trpc"; +import { setRateLimitTokens } from "./rateLimiting"; import { setSafetyViolations } from "./safetyViolations"; import { seedChat } from "./seedChat"; @@ -16,6 +17,7 @@ const personaNames = [ "typical", "demo", "nearly-banned", + "nearly-rate-limited", "sharing-chat", ] as const; @@ -25,6 +27,7 @@ type Persona = { region: "GB" | "US"; chatFixture: "typical" | null; safetyViolations: number; + rateLimitTokens: number; }; const personas: Record = { @@ -34,6 +37,7 @@ const personas: Record = { region: "GB", chatFixture: "typical", safetyViolations: 0, + rateLimitTokens: 0, }, // A user from a demo region demo: { @@ -41,6 +45,7 @@ const personas: Record = { region: "US", chatFixture: null, safetyViolations: 0, + rateLimitTokens: 0, }, // A user with 3 safety violations - will be banned with one more "nearly-banned": { @@ -48,6 +53,15 @@ const personas: Record = { region: "GB", chatFixture: null, safetyViolations: 3, + rateLimitTokens: 0, + }, + // A user with 119 of their 120 generations remaining + "nearly-rate-limited": { + isDemoUser: false, + region: "GB", + chatFixture: null, + safetyViolations: 0, + rateLimitTokens: 119, }, // Allows `chat.isShared` to be set/reset without leaking between tests/retries "sharing-chat": { @@ -55,6 +69,7 @@ const personas: Record = { region: "GB", chatFixture: "typical", safetyViolations: 0, + rateLimitTokens: 0, }, } as const; @@ -156,6 +171,7 @@ export const prepareUser = publicProcedure chatId = await seedChat(user.id, persona.chatFixture); } await setSafetyViolations(user.id, persona.safetyViolations); + await setRateLimitTokens(user.id, persona.rateLimitTokens); return { email, chatId }; }); diff --git a/packages/api/src/router/testSupport/rateLimiting/index.ts b/packages/api/src/router/testSupport/rateLimiting/index.ts new file mode 100644 index 000000000..cfe3adc82 --- /dev/null +++ b/packages/api/src/router/testSupport/rateLimiting/index.ts @@ -0,0 +1,37 @@ +import { Ratelimit } from "@upstash/ratelimit"; +import { kv } from "@vercel/kv"; + +if (!process.env.RATELIMIT_GENERATIONS_PER_24H) { + throw new Error("RATELIMIT_GENERATIONS_PER_24H is required"); +} +const GENERATIONS_PER_24H = parseInt( + process.env.RATELIMIT_GENERATIONS_PER_24H, + 10, +); + +const rateLimiter = new Ratelimit({ + redis: kv, + prefix: "rateLimit:generations:standard", + limiter: Ratelimit.slidingWindow(GENERATIONS_PER_24H, "24 h"), +}); + +async function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export const setRateLimitTokens = async (userId: string, count: number) => { + await rateLimiter.resetUsedTokens(userId); + if (count > 0) { + const { remaining, pending } = await rateLimiter.limit(userId, { + rate: count, + }); + await sleep(5000); + console.log({ userId }); + console.log(`Test support: User has ${remaining} remaining generations`); + await pending; + console.log(`Test support: User has ${remaining} remaining generations`); + } + const { remaining, reset } = await rateLimiter.getRemaining(userId); + console.log(`Test support: User has ${remaining} remaining generations`); + console.log(`Test support: Resets at ${reset}`); +}; diff --git a/packages/core/src/utils/rateLimiting/userBasedRateLimiter.ts b/packages/core/src/utils/rateLimiting/userBasedRateLimiter.ts index 1931aeefc..f09e5be3a 100644 --- a/packages/core/src/utils/rateLimiting/userBasedRateLimiter.ts +++ b/packages/core/src/utils/rateLimiting/userBasedRateLimiter.ts @@ -92,6 +92,7 @@ function userHasOakEmail(user: User) { (email) => email.emailAddress.endsWith("@thenational.academy") && !email.emailAddress.includes("rate-limit-me") && + !email.emailAddress.includes("rate-limited") && !email.emailAddress.includes("demo"), ); } From 49b357e16aa6ae529b2283b5cd7745900a6e8c9c Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Fri, 25 Oct 2024 00:00:01 +0200 Subject: [PATCH 35/61] wip: local version reusing cache --- .../rate-limited-error-No-persona-darwin.png | Bin 17581 -> 0 bytes .../api/src/router/testSupport/prepareUser.ts | 7 +++- .../router/testSupport/rateLimiting/index.ts | 36 +++++------------- .../rateLimiting/userBasedRateLimiter.ts | 18 ++++++--- 4 files changed, 28 insertions(+), 33 deletions(-) delete mode 100644 apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts-snapshots/rate-limited-error-No-persona-darwin.png diff --git a/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts-snapshots/rate-limited-error-No-persona-darwin.png b/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts-snapshots/rate-limited-error-No-persona-darwin.png deleted file mode 100644 index d82b2b3bd830cec9bbbd12146b41f814be1d4b4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17581 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 diff --git a/packages/api/src/router/testSupport/prepareUser.ts b/packages/api/src/router/testSupport/prepareUser.ts index e395dbad5..660a78489 100644 --- a/packages/api/src/router/testSupport/prepareUser.ts +++ b/packages/api/src/router/testSupport/prepareUser.ts @@ -13,6 +13,11 @@ const log = aiLogger("testing"); const branch = process.env.VERCEL_GIT_COMMIT_REF ?? os.hostname(); +const GENERATIONS_PER_24H = parseInt( + process.env.RATELIMIT_GENERATIONS_PER_24H || "120", + 10, +); + const personaNames = [ "typical", "demo", @@ -61,7 +66,7 @@ const personas: Record = { region: "GB", chatFixture: null, safetyViolations: 0, - rateLimitTokens: 119, + rateLimitTokens: GENERATIONS_PER_24H - 1, }, // Allows `chat.isShared` to be set/reset without leaking between tests/retries "sharing-chat": { diff --git a/packages/api/src/router/testSupport/rateLimiting/index.ts b/packages/api/src/router/testSupport/rateLimiting/index.ts index cfe3adc82..22bfa31e1 100644 --- a/packages/api/src/router/testSupport/rateLimiting/index.ts +++ b/packages/api/src/router/testSupport/rateLimiting/index.ts @@ -1,37 +1,19 @@ -import { Ratelimit } from "@upstash/ratelimit"; -import { kv } from "@vercel/kv"; +import { rateLimits } from "@oakai/core/src/utils/rateLimiting/rateLimit"; +import { aiLogger } from "@oakai/logger"; -if (!process.env.RATELIMIT_GENERATIONS_PER_24H) { - throw new Error("RATELIMIT_GENERATIONS_PER_24H is required"); -} -const GENERATIONS_PER_24H = parseInt( - process.env.RATELIMIT_GENERATIONS_PER_24H, - 10, -); +const log = aiLogger("testing"); -const rateLimiter = new Ratelimit({ - redis: kv, - prefix: "rateLimit:generations:standard", - limiter: Ratelimit.slidingWindow(GENERATIONS_PER_24H, "24 h"), -}); - -async function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} +// NOTE: The ratelimiter has an in-memory cache, so we reset the same instance +const rateLimiter = rateLimits.generations.standard; export const setRateLimitTokens = async (userId: string, count: number) => { await rateLimiter.resetUsedTokens(userId); if (count > 0) { - const { remaining, pending } = await rateLimiter.limit(userId, { + const result = await rateLimiter.check(userId, { rate: count, }); - await sleep(5000); - console.log({ userId }); - console.log(`Test support: User has ${remaining} remaining generations`); - await pending; - console.log(`Test support: User has ${remaining} remaining generations`); + if (result.isSubjectToRateLimiting) { + log.info(`User has ${result.remaining} remaining generations`); + } } - const { remaining, reset } = await rateLimiter.getRemaining(userId); - console.log(`Test support: User has ${remaining} remaining generations`); - console.log(`Test support: Resets at ${reset}`); }; diff --git a/packages/core/src/utils/rateLimiting/userBasedRateLimiter.ts b/packages/core/src/utils/rateLimiting/userBasedRateLimiter.ts index f09e5be3a..3a687c205 100644 --- a/packages/core/src/utils/rateLimiting/userBasedRateLimiter.ts +++ b/packages/core/src/utils/rateLimiting/userBasedRateLimiter.ts @@ -19,8 +19,9 @@ export type RateLimitInfo = }; export type RateLimiter = { - check: (userId: string) => Promise; + check: (userId: string, options?: { rate: number }) => Promise; getRemaining: (userId: string) => Promise; + resetUsedTokens: (userId: string) => Promise; }; export class RateLimitExceededError extends Error { @@ -46,7 +47,7 @@ export class RateLimitExceededError extends Error { */ export const userBasedRateLimiter = (rateLimit: Ratelimit): RateLimiter => { return { - check: async (userId: string) => { + check: async (userId, options) => { if (!userId) { throw new Error( "authenticated user is required for userBasedRateLimiter", @@ -58,12 +59,15 @@ export const userBasedRateLimiter = (rateLimit: Ratelimit): RateLimiter => { return { isSubjectToRateLimiting: false }; } - const { success, pending, ...rest } = await rateLimit.limit(userId); + const { success, pending, ...rest } = await rateLimit.limit( + userId, + options, + ); waitUntil(pending); if (!success) { - log.info("Rate limit exceeded for user %s", userId); + log.info("Rate limit exceeded for user %s", userId, rest); throw new RateLimitExceededError(userId, rest.limit, rest.reset); } @@ -73,9 +77,13 @@ export const userBasedRateLimiter = (rateLimit: Ratelimit): RateLimiter => { }; }, - getRemaining: async (userId: string) => { + getRemaining: async (userId) => { return (await rateLimit.getRemaining(userId)).remaining; }, + + resetUsedTokens: async (userId) => { + return await rateLimit.resetUsedTokens(userId); + }, }; }; From b0ca37744738e649c7d4b300eecdabb53c600e83 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:27:19 +0100 Subject: [PATCH 36/61] clarify testId --- .../src/components/AppComponents/Chat/chat-message/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-message/index.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-message/index.tsx index 6bc267fde..737983015 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/chat-message/index.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/chat-message/index.tsx @@ -191,7 +191,7 @@ function MessageWrapper({ type, }: MessageWrapperProps) { const testId = errorType - ? `chat-message-wrapper-error-${errorType}` + ? `chat-message-wrapper-${type}-${errorType}` : `chat-message-wrapper-${type}`; return (
      Date: Tue, 5 Nov 2024 09:56:10 +0100 Subject: [PATCH 37/61] fix fixture check --- apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts b/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts index fabea3989..03f415b72 100644 --- a/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts +++ b/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts @@ -47,7 +47,7 @@ test("User is restricted after message rate limit is reached", async ({ page.getByTestId("chat-message-wrapper-error-generic"), ).not.toBeAttached(); await expect(page.getByTestId("chat-message-wrapper-aila")).toContainText( - "There are no existing Oak lessons", + "Are the learning outcome and learning cycles appropriate for your pupils?", { timeout: 10000 }, ); }); From 97dc7375b5809b0eb02ce2d79820899e80225dd4 Mon Sep 17 00:00:00 2001 From: Tom Wise <79859203+tomwisecodes@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:01:35 +0000 Subject: [PATCH 38/61] chore: remove feature flag and reduce id to 5characters (#286) Co-authored-by: Adam Howard <91115+codeincontext@users.noreply.github.com> --- apps/nextjs/src/app/api/aila-download-all/route.ts | 2 +- .../AppComponents/download/DownloadAllButton.tsx | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/nextjs/src/app/api/aila-download-all/route.ts b/apps/nextjs/src/app/api/aila-download-all/route.ts index 7df412f2e..1ed389af7 100644 --- a/apps/nextjs/src/app/api/aila-download-all/route.ts +++ b/apps/nextjs/src/app/api/aila-download-all/route.ts @@ -153,7 +153,7 @@ async function getHandler(req: Request): Promise { const { data } = res; - const filename = `${lessonTitle} - ${lessonExport.id} - ${getReadableExportType( + const filename = `${lessonTitle} - ${lessonExport.id.slice(0, 5)} - ${getReadableExportType( lessonExport.exportType, )}.${ext}`; diff --git a/apps/nextjs/src/components/AppComponents/download/DownloadAllButton.tsx b/apps/nextjs/src/components/AppComponents/download/DownloadAllButton.tsx index 4e75ffbe2..3e1da9577 100644 --- a/apps/nextjs/src/components/AppComponents/download/DownloadAllButton.tsx +++ b/apps/nextjs/src/components/AppComponents/download/DownloadAllButton.tsx @@ -10,7 +10,6 @@ import { z } from "zod"; import useAnalytics from "@/lib/analytics/useAnalytics"; import { trackDownload } from "@/utils/trackDownload"; import { trpc } from "@/utils/trpc"; -import { useClientSideFeatureFlag } from "@/utils/useClientSideFeatureFlag"; import { getExportsConfig } from "../../ExportsDialogs/exports.helpers"; import { Icon } from "../../Icon"; @@ -99,12 +98,6 @@ export const DownloadAllButton = ({ const { mutateAsync: zipStatusMutateAsync } = trpc.exports.checkDownloadAllStatus.useMutation(); - const isFeatureEnabled = useClientSideFeatureFlag("download-all-button"); - if (!isFeatureEnabled) { - log.info("Download all button is disabled"); - return null; - } - if (data && !("error" in data)) { const fileIdsAndFormats = getFileIdsAndFormats(data); From fdd76ccfde3ee649fb98e6313edcc23e0175e4a2 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:06:22 +0100 Subject: [PATCH 39/61] Fix assertion --- apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts b/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts index 03f415b72..624f4f11f 100644 --- a/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts +++ b/apps/nextjs/tests-e2e/tests/aila-chat/rate-limiting.test.ts @@ -56,7 +56,9 @@ test("User is restricted after message rate limit is reached", async ({ await continueChat(page); await waitForGeneration(page, 10000); - const errorMessage = page.getByTestId("chat-message-wrapper-error-generic"); + const errorMessage = page.getByTestId( + "chat-message-wrapper-warning-generic", + ); await expect(errorMessage).toContainText( "Unfortunately you’ve exceeded your fair usage limit", ); @@ -67,7 +69,7 @@ test("User is restricted after message rate limit is reached", async ({ await waitForGeneration(page, 10000); await expect( - page.getByTestId("chat-message-wrapper-error-generic").last(), + page.getByTestId("chat-message-wrapper-warning-generic").last(), ).toContainText("Unfortunately you’ve exceeded your fair usage limit", {}); }); }); From 638f43fa0167f28974250f3da2d67ef95aaae57e Mon Sep 17 00:00:00 2001 From: MG Date: Tue, 5 Nov 2024 12:18:57 +0000 Subject: [PATCH 40/61] feat: add rag schema (migration) (#343) --- .../20241105095800_rag_schema/migration.sql | 49 +++++++++++++++++++ packages/db/prisma/schema.prisma | 39 +++++++++++++-- 2 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 packages/db/prisma/migrations/20241105095800_rag_schema/migration.sql diff --git a/packages/db/prisma/migrations/20241105095800_rag_schema/migration.sql b/packages/db/prisma/migrations/20241105095800_rag_schema/migration.sql new file mode 100644 index 000000000..e2aebdc7b --- /dev/null +++ b/packages/db/prisma/migrations/20241105095800_rag_schema/migration.sql @@ -0,0 +1,49 @@ +/* + Warnings: + + - You are about to drop the column `data` on the `ingest_lesson_plan_part` table. All the data in the column will be lost. + - You are about to drop the column `valueText` on the `ingest_lesson_plan_part` table. All the data in the column will be lost. + - Added the required column `value_json` to the `ingest_lesson_plan_part` table without a default value. This is not possible if the table is not empty. + - Added the required column `value_text` to the `ingest_lesson_plan_part` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateSchema +CREATE SCHEMA IF NOT EXISTS "rag"; + +-- AlterTable -- updated this to be RENAME instead of DROP and ADD +ALTER TABLE "ingest"."ingest_lesson_plan_part" + RENAME COLUMN data TO value_json; + +ALTER TABLE "ingest"."ingest_lesson_plan_part" + RENAME COLUMN "valueText" TO value_text; + +-- CreateTable +CREATE TABLE "rag"."rag_lesson_plans" ( + "id" TEXT NOT NULL, + "oak_lesson_id" INTEGER NOT NULL, + "ingest_lesson_id" TEXT, + "lesson_plan" JSONB NOT NULL, + "subject_slug" TEXT NOT NULL, + "key_stage_slug" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "rag_lesson_plans_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "rag"."rag_lesson_plan_parts" ( + "id" TEXT NOT NULL, + "rag_lesson_plan_id" TEXT NOT NULL, + "key" TEXT NOT NULL, + "value_text" TEXT NOT NULL, + "value_json" JSONB NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + "embedding" vector(256) NOT NULL, + + CONSTRAINT "rag_lesson_plan_parts_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "rag"."rag_lesson_plan_parts" ADD CONSTRAINT "rag_lesson_plan_parts_rag_lesson_plan_id_fkey" FOREIGN KEY ("rag_lesson_plan_id") REFERENCES "rag"."rag_lesson_plans"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 35c2d270c..1cef5a119 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -11,7 +11,7 @@ datasource db { url = env("PRISMA_ACCELERATE_DATABASE_URL") directUrl = env("DIRECT_DATABASE_URL") extensions = [vector, pg_trgm] - schemas = ["public", "ingest"] + schemas = ["public", "ingest", "rag"] } generator zod { @@ -985,7 +985,7 @@ enum QuizAnswerStatus { model Ingest { id String @id @default(cuid()) - config Json @default("{}") @map("config") @db.JsonB + config Json @map("config") @db.JsonB status String createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@ -1063,8 +1063,8 @@ model IngestLessonPlanPart { ingestId String @map("ingest_id") batchId String? @map("batch_id") key String - valueJson Json @map("data") @db.JsonB - valueText String + valueJson Json @map("value_json") @db.JsonB + valueText String @map("value_text") embedding Unsupported("vector(256)")? createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@ -1110,3 +1110,34 @@ model IngestError { @@map("ingest_error") @@schema("ingest") } + +model RagLessonPlan { + id String @id @default(cuid()) + oakLessonId Int @map("oak_lesson_id") + ingestLessonId String? @map("ingest_lesson_id") + lessonPlan Json @map("lesson_plan") @db.JsonB + subjectSlug String @map("subject_slug") + keyStageSlug String @map("key_stage_slug") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + ragLessonPlanPart RagLessonPlanPart[] + + @@map("rag_lesson_plans") + @@schema("rag") +} + +model RagLessonPlanPart { + id String @id @default(cuid()) + ragLessonPlanId String @map("rag_lesson_plan_id") + key String + valueText String @map("value_text") + valueJson Json @map("value_json") @db.JsonB + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + ragLessonPlan RagLessonPlan @relation(fields: [ragLessonPlanId], references: [id], onDelete: Cascade) + embedding Unsupported("vector(256)") + + @@map("rag_lesson_plan_parts") + @@schema("rag") +} From a593618b013607fbde75cfa98663d7298db7ff89 Mon Sep 17 00:00:00 2001 From: MG Date: Tue, 5 Nov 2024 14:08:25 +0000 Subject: [PATCH 41/61] fix: revert prisma to 5.16.1 (#336) --- apps/nextjs/package.json | 2 +- package.json | 2 +- packages/db/package.json | 4 +-- pnpm-lock.yaml | 78 ++++++++++++++++++++-------------------- 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index fd0e25a4f..5e1aaf4e2 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -47,7 +47,7 @@ "@oaknational/oak-components": "^1.26.0", "@oaknational/oak-consent-client": "^2.1.0", "@portabletext/react": "^3.1.0", - "@prisma/client": "^5.21.1", + "@prisma/client": "5.16.1", "@prisma/extension-accelerate": "^1.0.0", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-icons": "^1.3.0", diff --git a/package.json b/package.json index d504cac36..beac6e900 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@manypkg/cli": "^0.21.1", "@next/env": "14.2.15", "@oakai/prettier-config": "*", - "@prisma/client": "^5.21.1", + "@prisma/client": "5.16.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", diff --git a/packages/db/package.json b/packages/db/package.json index 0ea847800..961782624 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -58,7 +58,7 @@ "prettier": "@oakai/prettier-config", "dependencies": { "@oakai/logger": "*", - "@prisma/client": "^5.21.1", + "@prisma/client": "5.16.1", "@types/chunk-text": "^1.0.0", "cheerio": "1.0.0-rc.12", "chunk-text": "^2.0.1", @@ -73,7 +73,7 @@ "devDependencies": { "@oakai/prettier-config": "*", "dotenv-cli": "^6.0.0", - "prisma": "^5.21.0", + "prisma": "5.16.1", "typescript": "5.3.3", "zod-prisma": "^0.5.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2cd61e20..551fd5fbf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: '*' version: link:packages/prettier-config '@prisma/client': - specifier: ^5.21.1 - version: 5.21.1(prisma@5.21.1) + specifier: 5.16.1 + version: 5.16.1(prisma@5.16.1) '@semantic-release/changelog': specifier: ^6.0.3 version: 6.0.3(semantic-release@21.1.2) @@ -141,11 +141,11 @@ importers: specifier: ^3.1.0 version: 3.1.0(react@18.2.0) '@prisma/client': - specifier: ^5.21.1 - version: 5.21.1(prisma@5.21.1) + specifier: 5.16.1 + version: 5.16.1(prisma@5.16.1) '@prisma/extension-accelerate': specifier: ^1.0.0 - version: 1.0.0(@prisma/client@5.21.1) + version: 1.0.0(@prisma/client@5.16.1) '@radix-ui/react-accordion': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.2.19)(@types/react@18.2.57)(react-dom@18.2.0)(react@18.2.0) @@ -715,8 +715,8 @@ importers: specifier: '*' version: link:../logger '@prisma/client': - specifier: ^5.21.1 - version: 5.21.1(prisma@5.21.1) + specifier: 5.16.1 + version: 5.16.1(prisma@5.16.1) '@types/chunk-text': specifier: ^1.0.0 version: 1.0.0 @@ -755,14 +755,14 @@ importers: specifier: ^6.0.0 version: 6.0.0 prisma: - specifier: ^5.21.0 - version: 5.21.1 + specifier: 5.16.1 + version: 5.16.1 typescript: specifier: 5.3.3 version: 5.3.3 zod-prisma: specifier: ^0.5.4 - version: 0.5.4(prisma@5.21.1)(zod@3.23.8) + version: 0.5.4(prisma@5.16.1)(zod@3.23.8) packages/eslint-config-custom: dependencies: @@ -5876,8 +5876,8 @@ packages: engines: {node: ^14.13.1 || >=16.0.0 || >=18.0.0} dev: false - /@prisma/client@5.21.1(prisma@5.21.1): - resolution: {integrity: sha512-3n+GgbAZYjaS/k0M03yQsQfR1APbr411r74foknnsGpmhNKBG49VuUkxIU6jORgvJPChoD4WC4PqoHImN1FP0w==} + /@prisma/client@5.16.1(prisma@5.16.1): + resolution: {integrity: sha512-wM9SKQjF0qLxdnOZIVAIMKiz6Hu7vDt4FFAih85K1dk/Rr2mdahy6d3QP41K62N9O0DJJA//gUDA3Mp49xsKIg==} engines: {node: '>=16.13'} requiresBuild: true peerDependencies: @@ -5886,7 +5886,7 @@ packages: prisma: optional: true dependencies: - prisma: 5.21.1 + prisma: 5.16.1 dev: false /@prisma/debug@3.8.1: @@ -5897,36 +5897,36 @@ packages: strip-ansi: 6.0.1 dev: true - /@prisma/debug@5.21.1: - resolution: {integrity: sha512-uY8SAhcnORhvgtOrNdvWS98Aq/nkQ9QDUxrWAgW8XrCZaI3j2X7zb7Xe6GQSh6xSesKffFbFlkw0c2luHQviZA==} + /@prisma/debug@5.16.1: + resolution: {integrity: sha512-JsNgZAg6BD9RInLSrg7ZYzo11N7cVvYArq3fHGSD89HSgtN0VDdjV6bib7YddbcO6snzjchTiLfjeTqBjtArVQ==} - /@prisma/engines-version@5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36: - resolution: {integrity: sha512-qvnEflL0//lh44S/T9NcvTMxfyowNeUxTunPcDfKPjyJNrCNf2F1zQLcUv5UHAruECpX+zz21CzsC7V2xAeM7Q==} + /@prisma/engines-version@5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303: + resolution: {integrity: sha512-HkT2WbfmFZ9WUPyuJHhkiADxazHg8Y4gByrTSVeb3OikP6tjQ7txtSUGu9OBOBH0C13dPKN2qqH12xKtHu/Hiw==} - /@prisma/engines@5.21.1: - resolution: {integrity: sha512-hGVTldUkIkTwoV8//hmnAAiAchi4oMEKD3aW5H2RrnI50tTdwza7VQbTTAyN3OIHWlK5DVg6xV7X8N/9dtOydA==} + /@prisma/engines@5.16.1: + resolution: {integrity: sha512-KkyF3eIUtBIyp5A/rJHCtwQO18OjpGgx18PzjyGcJDY/+vNgaVyuVd+TgwBgeq6NLdd1XMwRCI+58vinHsAdfA==} requiresBuild: true dependencies: - '@prisma/debug': 5.21.1 - '@prisma/engines-version': 5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36 - '@prisma/fetch-engine': 5.21.1 - '@prisma/get-platform': 5.21.1 + '@prisma/debug': 5.16.1 + '@prisma/engines-version': 5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303 + '@prisma/fetch-engine': 5.16.1 + '@prisma/get-platform': 5.16.1 - /@prisma/extension-accelerate@1.0.0(@prisma/client@5.21.1): + /@prisma/extension-accelerate@1.0.0(@prisma/client@5.16.1): resolution: {integrity: sha512-5oSpPtKCMfTl/sSXaS/7vBsPqfm+eEVB6/5KPBJITFatDoFcmjx/PIC/T93mHLiHI98xKwosPN59NGXjDDhcFA==} engines: {node: '>=16'} peerDependencies: '@prisma/client': '>=4.16.1' dependencies: - '@prisma/client': 5.21.1(prisma@5.21.1) + '@prisma/client': 5.16.1(prisma@5.16.1) dev: false - /@prisma/fetch-engine@5.21.1: - resolution: {integrity: sha512-70S31vgpCGcp9J+mh/wHtLCkVezLUqe/fGWk3J3JWZIN7prdYSlr1C0niaWUyNK2VflLXYi8kMjAmSxUVq6WGQ==} + /@prisma/fetch-engine@5.16.1: + resolution: {integrity: sha512-oOkjaPU1lhcA/Rvr4GVfd1NLJBwExgNBE36Ueq7dr71kTMwy++a3U3oLd2ZwrV9dj9xoP6LjCcky799D9nEt4w==} dependencies: - '@prisma/debug': 5.21.1 - '@prisma/engines-version': 5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36 - '@prisma/get-platform': 5.21.1 + '@prisma/debug': 5.16.1 + '@prisma/engines-version': 5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303 + '@prisma/get-platform': 5.16.1 /@prisma/generator-helper@3.8.1: resolution: {integrity: sha512-3zSy+XTEjmjLj6NO+/YPN1Cu7or3xA11TOoOnLRJ9G4pTT67RJXjK0L9Xy5n+3I0Xlb7xrWCgo8MvQQLMWzxPA==} @@ -5937,10 +5937,10 @@ packages: cross-spawn: 7.0.3 dev: true - /@prisma/get-platform@5.21.1: - resolution: {integrity: sha512-sRxjL3Igst3ct+e8ya/x//cDXmpLbZQ5vfps2N4tWl4VGKQAmym77C/IG/psSMsQKszc8uFC/q1dgmKFLUgXZQ==} + /@prisma/get-platform@5.16.1: + resolution: {integrity: sha512-R4IKnWnMkR2nUAbU5gjrPehdQYUUd7RENFD2/D+xXTNhcqczp0N+WEGQ3ViyI3+6mtVcjjNIMdnUTNyu3GxIgA==} dependencies: - '@prisma/debug': 5.21.1 + '@prisma/debug': 5.16.1 /@prisma/instrumentation@5.19.1: resolution: {integrity: sha512-VLnzMQq7CWroL5AeaW0Py2huiNKeoMfCH3SUxstdzPrlWQi6UQ9UrfcbUkNHlVFqOMacqy8X/8YtE0kuKDpD9w==} @@ -20093,15 +20093,13 @@ packages: minimist: 1.2.8 dev: false - /prisma@5.21.1: - resolution: {integrity: sha512-PB+Iqzld/uQBPaaw2UVIk84kb0ITsLajzsxzsadxxl54eaU5Gyl2/L02ysivHxK89t7YrfQJm+Ggk37uvM70oQ==} + /prisma@5.16.1: + resolution: {integrity: sha512-Z1Uqodk44diztImxALgJJfNl2Uisl9xDRvqybMKEBYJLNKNhDfAHf+ZIJbZyYiBhLMbKU9cYGdDVG5IIXEnL2Q==} engines: {node: '>=16.13'} hasBin: true requiresBuild: true dependencies: - '@prisma/engines': 5.21.1 - optionalDependencies: - fsevents: 2.3.3 + '@prisma/engines': 5.16.1 /prismjs@1.27.0: resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} @@ -24052,7 +24050,7 @@ packages: readable-stream: 4.4.2 dev: false - /zod-prisma@0.5.4(prisma@5.21.1)(zod@3.23.8): + /zod-prisma@0.5.4(prisma@5.16.1)(zod@3.23.8): resolution: {integrity: sha512-5Ca4Qd1a1jy1T/NqCEpbr0c+EsbjJfJ/7euEHob3zDvtUK2rTuD1Rc/vfzH8q8PtaR2TZbysD88NHmrLwpv3Xg==} engines: {node: '>=14'} hasBin: true @@ -24066,7 +24064,7 @@ packages: dependencies: '@prisma/generator-helper': 3.8.1 parenthesis: 3.1.8 - prisma: 5.21.1 + prisma: 5.16.1 ts-morph: 13.0.3 zod: 3.23.8 dev: true From acf0eb37ddd66747376de4de2487ba74e65f6875 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 5 Nov 2024 14:09:16 +0000 Subject: [PATCH 42/61] build(release v1.13.1): See CHANGE_LOG.md --- CHANGE_LOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index a58cbc0f8..2e25e2815 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,3 +1,10 @@ +## [1.13.1](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.13.0...v1.13.1) (2024-11-05) + + +### Bug Fixes + +* revert prisma to 5.16.1 ([#336](https://github.com/oaknational/oak-ai-lesson-assistant/issues/336)) ([a593618](https://github.com/oaknational/oak-ai-lesson-assistant/commit/a593618b013607fbde75cfa98663d7298db7ff89)) + # [1.13.0](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.12.1...v1.13.0) (2024-10-31) From 9a12851471aeb5f36e7aebd9c1652d0c2a565966 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:16:51 +0100 Subject: [PATCH 43/61] fix: null render in sidebar provider (#337) --- .../Chat/sidebar-actions.stories.tsx | 19 +++++++++++++++---- apps/nextjs/src/lib/hooks/use-sidebar.tsx | 6 +----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx index 395fef328..496c40aae 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { within } from "@storybook/test"; import { SidebarActions } from "./sidebar-actions"; @@ -26,14 +27,24 @@ export const Default: Story = { }, }; +export const SharePending: Story = { + args: { + ...Default.args, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const deleteButton = canvas.getByRole("button", { name: "Share" }); + deleteButton.click(); + }, +}; + export const RemovePending: Story = { args: { ...Default.args, }, play: async ({ canvasElement }) => { - const deleteButton = canvasElement.querySelector("button:nth-child(2)"); - if (deleteButton instanceof HTMLElement) { - deleteButton.click(); - } + const canvas = within(canvasElement); + const deleteButton = canvas.getByRole("button", { name: "Delete" }); + deleteButton.click(); }, }; diff --git a/apps/nextjs/src/lib/hooks/use-sidebar.tsx b/apps/nextjs/src/lib/hooks/use-sidebar.tsx index 595e8ea6b..d573b85dc 100644 --- a/apps/nextjs/src/lib/hooks/use-sidebar.tsx +++ b/apps/nextjs/src/lib/hooks/use-sidebar.tsx @@ -27,7 +27,7 @@ interface SidebarProviderProps { } export function SidebarProvider({ children }: SidebarProviderProps) { - const [isSidebarOpen, setSidebarOpen] = React.useState(true); + const [isSidebarOpen, setSidebarOpen] = React.useState(false); const [isLoading, setLoading] = React.useState(true); React.useEffect(() => { @@ -46,10 +46,6 @@ export function SidebarProvider({ children }: SidebarProviderProps) { }); }; - if (isLoading) { - return null; - } - return ( Date: Wed, 6 Nov 2024 13:52:01 +0000 Subject: [PATCH 44/61] chore: delete old google drive export files cron job- AI-561 (#257) --- .../src/app/api/aila-download-all/route.ts | 2 +- .../nextjs/src/app/api/aila-download/route.ts | 1 + .../api/cron-jobs/expired-exports/route.ts | 140 ++++++++++++++++++ apps/nextjs/vercel.json | 8 +- packages/api/src/router/exports.ts | 1 + packages/logger/index.ts | 3 +- turbo.json | 3 +- 7 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 apps/nextjs/src/app/api/cron-jobs/expired-exports/route.ts diff --git a/apps/nextjs/src/app/api/aila-download-all/route.ts b/apps/nextjs/src/app/api/aila-download-all/route.ts index 1ed389af7..37d46e7f6 100644 --- a/apps/nextjs/src/app/api/aila-download-all/route.ts +++ b/apps/nextjs/src/app/api/aila-download-all/route.ts @@ -130,7 +130,7 @@ async function getHandler(req: Request): Promise { } const lessonExport = await prisma.lessonExport.findFirst({ - where: { gdriveFileId: fileId, userId }, + where: { gdriveFileId: fileId, userId, expiredAt: null }, }); if (!lessonExport) { diff --git a/apps/nextjs/src/app/api/aila-download/route.ts b/apps/nextjs/src/app/api/aila-download/route.ts index 52d4cf5b3..64c9010bc 100644 --- a/apps/nextjs/src/app/api/aila-download/route.ts +++ b/apps/nextjs/src/app/api/aila-download/route.ts @@ -117,6 +117,7 @@ async function getHandler(req: Request): Promise { where: { gdriveFileId: fileId, userId, + expiredAt: null, }, cacheStrategy: { ttl: 60 * 5, swr: 60 * 2 }, }); diff --git a/apps/nextjs/src/app/api/cron-jobs/expired-exports/route.ts b/apps/nextjs/src/app/api/cron-jobs/expired-exports/route.ts new file mode 100644 index 000000000..114d7d276 --- /dev/null +++ b/apps/nextjs/src/app/api/cron-jobs/expired-exports/route.ts @@ -0,0 +1,140 @@ +import { prisma } from "@oakai/db"; +import { googleDrive } from "@oakai/exports/src/gSuite/drive/client"; +import { aiLogger } from "@oakai/logger"; +import * as Sentry from "@sentry/node"; +import type { NextRequest } from "next/server"; +import { isTruthy } from "remeda"; + +const log = aiLogger("cron"); + +const requiredEnvVars = ["CRON_SECRET", "GOOGLE_DRIVE_OUTPUT_FOLDER_ID"]; + +requiredEnvVars.forEach((envVar) => { + if (!process.env[envVar]) { + throw new Error(`Environment variable ${envVar} is not set.`); + } +}); + +async function updateExpiredAt(fileIds: string[]) { + if (fileIds.length === 0) { + log.info("No file IDs to update."); + return; + } + + try { + const result = await prisma.lessonExport.updateMany({ + where: { + gdriveFileId: { + in: fileIds, + }, + }, + data: { + expiredAt: new Date(), + }, + }); + log.info(`Updated expiredAt for ${fileIds.length} files.`); + + if (result.count === fileIds.length) { + log.info("All files updated successfully."); + } else { + throw new Error( + `Expected to update ${fileIds.length} files, but only updated ${result.count}.`, + ); + } + } catch (error) { + log.error("Error updating expiredAt field in the database:", error); + throw error; + } +} + +async function deleteExpiredExports(fileIds: string[]) { + try { + for (const id of fileIds) { + await googleDrive.files.delete({ fileId: id }); + log.info("Deleted:", id); + } + } catch (error) { + log.error("Error deleting old files from folder:", error); + throw error; + } +} + +interface FetchExpiredExportsOptions { + folderId: string; + daysAgo: number; +} + +async function fetchExpiredExports({ + folderId, + daysAgo, +}: FetchExpiredExportsOptions) { + try { + const currentDate = new Date(); + const targetDate = new Date( + currentDate.setDate(currentDate.getDate() - daysAgo), + ).toISOString(); + + const query = `modifiedTime < '${targetDate}' and '${folderId}' in parents`; + + const res = await googleDrive.files.list({ + q: query, + fields: "files(id, name, modifiedTime, ownedByMe )", + pageSize: 1000, + }); + + const files = + res.data.files?.filter((file) => file.ownedByMe === true) || []; + + if (files.length === 0) { + log.info( + "No files found that are older than one month in the specified folder.", + ); + return null; + } + + log.info(`Found ${files.length} files older than one month in folder:`); + return files; + } catch (error) { + log.error("Error fetching old files from folder:", error); + return null; + } +} + +export async function GET(request: NextRequest) { + try { + const authHeader = request.headers.get("authorization"); + + const cronSecret = process.env.CRON_SECRET; + const folderId = process.env.GOOGLE_DRIVE_OUTPUT_FOLDER_ID; + + if (!cronSecret) { + log.error("Missing cron secret"); + return new Response("Missing cron secret", { status: 500 }); + } + if (!folderId) { + log.error("No folder ID provided."); + return new Response("No folder ID provided", { status: 500 }); + } + + if (authHeader !== `Bearer ${cronSecret}`) { + log.error("Authorization failed. Invalid token."); + return new Response("Unauthorized", { status: 401 }); + } + + const files = await fetchExpiredExports({ folderId, daysAgo: 14 }); + + if (!files || files.length === 0) { + return new Response("No expired files found", { status: 404 }); + } + + const validFileIds = files.map((file) => file.id).filter(isTruthy); + + await updateExpiredAt(validFileIds); + await deleteExpiredExports(validFileIds); + } catch (error) { + Sentry.captureException(error); + return new Response("Internal Server Error", { status: 500 }); + } + + return new Response(JSON.stringify({ success: true }), { status: 200 }); +} diff --git a/apps/nextjs/vercel.json b/apps/nextjs/vercel.json index 1c4945e35..8746c630a 100644 --- a/apps/nextjs/vercel.json +++ b/apps/nextjs/vercel.json @@ -8,5 +8,11 @@ "deploymentEnabled": { "release": false } - } + }, + "crons": [ + { + "path": "/api/cron-jobs/expired-exports", + "schedule": "0 3 * * *" + } + ] } diff --git a/packages/api/src/router/exports.ts b/packages/api/src/router/exports.ts index 8d1c53de4..24ef6a433 100644 --- a/packages/api/src/router/exports.ts +++ b/packages/api/src/router/exports.ts @@ -127,6 +127,7 @@ export async function ailaGetExportBySnapshotId({ where: { lessonSnapshotId: snapshotId, exportType, + expiredAt: null, }, orderBy: { createdAt: "desc", diff --git a/packages/logger/index.ts b/packages/logger/index.ts index 255319ba1..7336f9b7c 100644 --- a/packages/logger/index.ts +++ b/packages/logger/index.ts @@ -59,7 +59,8 @@ type ChildKey = | "transcripts" | "trpc" | "ui" - | "webhooks"; + | "webhooks" + | "cron"; const errorLogger = typeof window === "undefined" diff --git a/turbo.json b/turbo.json index 0cb26a988..45f42d4e1 100644 --- a/turbo.json +++ b/turbo.json @@ -133,6 +133,7 @@ "STRICT_CSP", "TELEMETRY_ENABLED", "UPSTASH_*", - "WOLFRAM_CLIENT_SECRET" + "WOLFRAM_CLIENT_SECRET", + "CRON_SECRET" ] } From b4f24d8884f4a9fe799ff7920a74cdc6847471d8 Mon Sep 17 00:00:00 2001 From: MG Date: Wed, 6 Nov 2024 15:27:12 +0000 Subject: [PATCH 45/61] docs: comment on lesson snapshots model (#347) --- packages/core/src/models/lessonSnapshots.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core/src/models/lessonSnapshots.ts b/packages/core/src/models/lessonSnapshots.ts index baf62b4d5..330344254 100644 --- a/packages/core/src/models/lessonSnapshots.ts +++ b/packages/core/src/models/lessonSnapshots.ts @@ -5,11 +5,8 @@ import type { } from "@oakai/db"; import crypto from "crypto"; -import type { - LooseLessonPlan} from "../../../aila/src/protocol/schema"; -import { - LessonPlanJsonSchema -} from "../../../aila/src/protocol/schema"; +import type { LooseLessonPlan } from "../../../aila/src/protocol/schema"; +import { LessonPlanJsonSchema } from "../../../aila/src/protocol/schema"; // #TODO this import is reaching out of the package because it would otherwise be a circular dependency import type { DeepNullable } from "../utils/DeepNullable"; import type { DeepPartial } from "../utils/DeepPartial"; @@ -31,6 +28,12 @@ function getSnapshotHash(snapshot: Snapshot) { return hash; } +/** + * Lesson snapshots are stored after each assistant message. + * They act as a snapshot of the lesson plan at that point in the chat. + * These snapshots are passed to the Moderation service and the Exports service + * for moderation and export downloads respectively. + */ export class LessonSnapshots { constructor(private readonly prisma: PrismaClientWithAccelerate) {} From 273cfdc668ca45def0b8a68dc08b7301974e1def Mon Sep 17 00:00:00 2001 From: Tom Wise <79859203+tomwisecodes@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:48:26 +0000 Subject: [PATCH 46/61] fix: design-changes-to-footer (#324) --- .../components/AppComponents/ScaleSpan.tsx | 6 ++ apps/nextjs/src/components/Footer.tsx | 91 +++++++++---------- apps/nextjs/src/data/menus.tsx | 4 +- apps/nextjs/src/styles/globals.css | 5 + 4 files changed, 57 insertions(+), 49 deletions(-) create mode 100644 apps/nextjs/src/components/AppComponents/ScaleSpan.tsx diff --git a/apps/nextjs/src/components/AppComponents/ScaleSpan.tsx b/apps/nextjs/src/components/AppComponents/ScaleSpan.tsx new file mode 100644 index 000000000..8bd29debb --- /dev/null +++ b/apps/nextjs/src/components/AppComponents/ScaleSpan.tsx @@ -0,0 +1,6 @@ +import { OakSpan } from "@oaknational/oak-components"; +import styled from "styled-components"; + +export const ScaleSpan = styled(OakSpan)<{ $scale: number }>` + transform: scale(${(props) => props.$scale}); +`; diff --git a/apps/nextjs/src/components/Footer.tsx b/apps/nextjs/src/components/Footer.tsx index 3533f3cd7..1d9a9637e 100644 --- a/apps/nextjs/src/components/Footer.tsx +++ b/apps/nextjs/src/components/Footer.tsx @@ -7,11 +7,11 @@ import { OakFlex, OakLink, OakMaxWidth, - OakSpan, useCookieConsent, OakP, OakUL, OakLI, + OakIcon, } from "@oaknational/oak-components"; import { aiTools } from "data/aiTools"; import { legalMenuItems, menuItems, socialMenuItems } from "data/menus"; @@ -22,7 +22,7 @@ import styled from "styled-components"; import loop from "@/assets/svg/loop.svg"; import useAnalytics from "@/lib/analytics/useAnalytics"; -import { Icon } from "./Icon"; +import { ScaleSpan } from "./AppComponents/ScaleSpan"; import { Logo } from "./Logo"; function ManageCookiesButton() { @@ -56,7 +56,7 @@ const Footer = () => { $alignItems={"start"} $width={["100%", "unset"]} > - + Menu @@ -97,7 +97,7 @@ const Footer = () => { $alignItems={"start"} $width={["100%", "unset"]} > - + AI Experiments Legal @@ -112,11 +112,7 @@ const Footer = () => { if (item.target) { return ( - + {item.title} @@ -134,43 +130,38 @@ const Footer = () => { - + - + - - - {socialMenuItems.map((item) => { - return ( - - - - ); - })} - - -

      - © Oak National Academy Limited, No 14174888 -

      -

      - 1 Scott Place, 2 Hardman Street, Manchester, M3 3AA -

      -
      + + {socialMenuItems.map((item) => { + return ( + + + + ); + })} + + + + + +

      + © Oak National Academy Limited, No 14174888 +

      +

      + 1 Scott Place, 2 Hardman Street, Manchester, M3 3AA +

      @@ -206,19 +197,18 @@ const StyledOakLink = styled(OakLink)` text-decoration: underline; } `; + const FooterButton = ({ href, onClick, disabled, target, - icon, children, }: { href?: string; onClick?: () => void; disabled?: boolean; target?: string; - icon?: OakIconName; children: React.ReactNode; }) => { const element = href ? Link : "button"; @@ -229,12 +219,21 @@ const FooterButton = ({ element={element} target={target} disabled={disabled} - iconName={icon} - isTrailingIcon={true} > - - {children} - + + {children} + {href && href.includes("http") && ( + + + + )} + ); }; diff --git a/apps/nextjs/src/data/menus.tsx b/apps/nextjs/src/data/menus.tsx index 786df5f08..76c859f28 100644 --- a/apps/nextjs/src/data/menus.tsx +++ b/apps/nextjs/src/data/menus.tsx @@ -1,7 +1,5 @@ -import type { IconName } from "@/components/Icon"; - interface SocialItem { - icon: IconName; + icon: "twitter" | "facebook" | "linkedin" | "instagram"; href: string; id: string; } diff --git a/apps/nextjs/src/styles/globals.css b/apps/nextjs/src/styles/globals.css index f7370123b..17300bd72 100644 --- a/apps/nextjs/src/styles/globals.css +++ b/apps/nextjs/src/styles/globals.css @@ -2,7 +2,12 @@ @tailwind components; @tailwind utilities; +:root { + --base-color: #22222; +} + .radix-themes { + color: var(--base-color); --default-font-family: Lexend, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", From 198d170a738ef5512ee52fb935b6dcf47f6453f3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 7 Nov 2024 14:29:25 +0000 Subject: [PATCH 47/61] build(release v1.14.0): See CHANGE_LOG.md --- CHANGE_LOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index 2e25e2815..3e5949ea9 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,3 +1,26 @@ +# [1.14.0](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.13.1...v1.14.0) (2024-11-07) + + +### Bug Fixes + +* add missing dependencies to lesson plan tracking context ([#307](https://github.com/oaknational/oak-ai-lesson-assistant/issues/307)) ([3758dfc](https://github.com/oaknational/oak-ai-lesson-assistant/commit/3758dfcda8a937ca363c0eb40014b01d18c93c23)) +* add missing prisma import ([#342](https://github.com/oaknational/oak-ai-lesson-assistant/issues/342)) ([a0ac1de](https://github.com/oaknational/oak-ai-lesson-assistant/commit/a0ac1de89dc6dda38ab64a02054e992072332fd5)) +* disable feature flagg polling in tests ([c44e1f1](https://github.com/oaknational/oak-ai-lesson-assistant/commit/c44e1f10e98dcda37367f2cd03b324078c7b910f)) +* null render in sidebar provider ([#337](https://github.com/oaknational/oak-ai-lesson-assistant/issues/337)) ([9a12851](https://github.com/oaknational/oak-ai-lesson-assistant/commit/9a12851471aeb5f36e7aebd9c1652d0c2a565966)) +* remaining linting fixes ([#272](https://github.com/oaknational/oak-ai-lesson-assistant/issues/272)) ([18a0f70](https://github.com/oaknational/oak-ai-lesson-assistant/commit/18a0f7061bc9d8bd72c4f6d44eec003acd878df1)) +* set up server side styled-components ([f221c24](https://github.com/oaknational/oak-ai-lesson-assistant/commit/f221c2442617031fa694708da953b4a5f1ea5e6c)) +* skip instrumentation when running turbopack - fix HMR ([4d6fd3b](https://github.com/oaknational/oak-ai-lesson-assistant/commit/4d6fd3b963710703b547cda5b2085bfc7f00f941)) + + +### Features + +* add rag schema (migration) ([#343](https://github.com/oaknational/oak-ai-lesson-assistant/issues/343)) ([638f43f](https://github.com/oaknational/oak-ai-lesson-assistant/commit/638f43fa0167f28974250f3da2d67ef95aaae57e)) +* allow us to configure the server port for local testing ([#308](https://github.com/oaknational/oak-ai-lesson-assistant/issues/308)) ([33bee19](https://github.com/oaknational/oak-ai-lesson-assistant/commit/33bee19e5e631428b648159452ef5af5dbec36bd)) +* bootstrap posthog feature flags with local evaluation ([15e8a67](https://github.com/oaknational/oak-ai-lesson-assistant/commit/15e8a67e9a1ce8e08baa5642c7fffbd2193d3b64)) +* sync featureFlagGroup from clerk to posthog ([46b4f13](https://github.com/oaknational/oak-ai-lesson-assistant/commit/46b4f13a1e887177035a9d195886f34647a87ac9)) +* update emails to use html ([#319](https://github.com/oaknational/oak-ai-lesson-assistant/issues/319)) ([a71e7bc](https://github.com/oaknational/oak-ai-lesson-assistant/commit/a71e7bc4b6416ef01b48de4c950805f8244e658b)) +* use featureFlagGroup claim for feature flags ([759534a](https://github.com/oaknational/oak-ai-lesson-assistant/commit/759534a8b8189c25d665b34db1cdf7f0b7a8f31e)) + ## [1.13.1](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.13.0...v1.13.1) (2024-11-05) From 80f50507ab370032a3ef6767bcfe5da0d8b6fe82 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:50:25 +0100 Subject: [PATCH 48/61] fix: allow requests without a cookie header (#352) --- apps/nextjs/src/lib/feature-flags/bootstrap.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/nextjs/src/lib/feature-flags/bootstrap.ts b/apps/nextjs/src/lib/feature-flags/bootstrap.ts index e916408b8..216e016bb 100644 --- a/apps/nextjs/src/lib/feature-flags/bootstrap.ts +++ b/apps/nextjs/src/lib/feature-flags/bootstrap.ts @@ -21,7 +21,9 @@ const log = aiLogger("feature-flags"); function getDistinctIdFromCookie(headers: ReadonlyHeaders) { const cookieHeader = headers.get("cookie"); - invariant(cookieHeader, "No cookie header"); + if (!cookieHeader) { + return null; + } const cookies = cookie.parse(cookieHeader) as Record; const phCookieKey = `ph_${process.env.NEXT_PUBLIC_POSTHOG_API_KEY}_posthog`; const phCookie = cookies[phCookieKey]; From 57bf43973036a735757b1fd018458975baec4531 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 7 Nov 2024 16:51:04 +0000 Subject: [PATCH 49/61] build(release v1.14.1): See CHANGE_LOG.md --- CHANGE_LOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index 3e5949ea9..c673ff1d6 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,3 +1,10 @@ +## [1.14.1](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.14.0...v1.14.1) (2024-11-07) + + +### Bug Fixes + +* allow requests without a cookie header ([#352](https://github.com/oaknational/oak-ai-lesson-assistant/issues/352)) ([80f5050](https://github.com/oaknational/oak-ai-lesson-assistant/commit/80f50507ab370032a3ef6767bcfe5da0d8b6fe82)) + # [1.14.0](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.13.1...v1.14.0) (2024-11-07) From 9af9b110bb54ed3d2f50162eb5ec92d956111e43 Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Thu, 7 Nov 2024 17:39:25 +0000 Subject: [PATCH 50/61] chore: reduce prompt step logging (#349) --- .../parts/interactingWithTheUser.ts | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/core/src/prompts/lesson-assistant/parts/interactingWithTheUser.ts b/packages/core/src/prompts/lesson-assistant/parts/interactingWithTheUser.ts index 9c73d7347..798efa717 100644 --- a/packages/core/src/prompts/lesson-assistant/parts/interactingWithTheUser.ts +++ b/packages/core/src/prompts/lesson-assistant/parts/interactingWithTheUser.ts @@ -36,7 +36,8 @@ These values are not all present, so ask the user for the missing values.`, hasRelevantLessons && !Object.keys(lessonPlan).includes("basedOn") ? { - title: "ASK THE USER IF THEY WANT TO BASE THEIR LESSON PLAN ON AN EXISTING LESSON", + title: + "ASK THE USER IF THEY WANT TO BASE THEIR LESSON PLAN ON AN EXISTING LESSON", content: `Ask if the user would like to adapt one of the Oak lessons as a starting point for their new lesson. Provide a list of lessons for the user as numbered options, with the title of each lesson. The user will then respond with the number of the lesson they would like to adapt. @@ -64,7 +65,8 @@ In some cases it is possible for the user to base their lesson on existing ones, } : { sections: ["learningOutcome", "learningCycles"] as LessonPlanKeys[], - title: "GENERATE SECTION GROUP [basedOn, learningOutcome, learningCycles]", + title: + "GENERATE SECTION GROUP [basedOn, learningOutcome, learningCycles]", content: `You need to generate three sections in one interaction with the user. Do these all in one interaction. * basedOn - store the reference to the basedOn lesson in the lesson plan unless it is already set. * learningOutcome - generate learning outcomes. @@ -86,14 +88,16 @@ Generate all of these sections together and respond to the user within this one "misconceptions", "keywords", ], - title: "GENERATE SECTION GROUP [priorKnowledge, keyLearningPoints, misconceptions, keywords]", + title: + "GENERATE SECTION GROUP [priorKnowledge, keyLearningPoints, misconceptions, keywords]", content: `Generate these four sections together in one single interaction. You should not ask the user for feedback after generating them one-by-one. Generate them all together in one response.`, }, { sections: ["starterQuiz", "cycle1", "cycle2", "cycle3", "exitQuiz"], - title: "GENERATE SECTION GROUP [starterQuiz, cycle1, cycle2, cycle3, exitQuiz]", + title: + "GENERATE SECTION GROUP [starterQuiz, cycle1, cycle2, cycle3, exitQuiz]", content: `Generate the bulk of the lesson. Generate all of these sections in one interaction. Your response should include the starter quiz, each of the three learning cycles, and the exit quiz all within a single response. Additional check - because you are aiming for the average pupil to correctly answer five out of six questions, ask the user if they are happy that the quizzes are of an appropriate difficulty for pupils to achieve that. @@ -161,11 +165,13 @@ export const interactingWithTheUser = ({ relevantLessonPlans, }: TemplateProps) => { const allSteps = lessonConstructionSteps(lessonPlan, relevantLessonPlans); - - const step = allSteps[0] - ? `${allSteps[0]?.title}\n\n${allSteps[0]?.content}` - : "FINAL STEP: Respond to the user and help them edit the lesson plan"; - log.info("Prompt: next lesson step", JSON.stringify(step, null, 2)); + const finalStep = { + title: "FINAL STEP", + content: "Respond to the user and help them edit the lesson plan", + }; + const nextStep = allSteps[0] ?? finalStep; + const step = `${nextStep.title}\n\n${nextStep.content}`; + log.info("Prompt: next lesson step", nextStep.title); const parts = [ `YOUR INSTRUCTIONS FOR INTERACTING WITH THE USER From 7a34686180040e6465bc4b898f45b4c28e2d6ff5 Mon Sep 17 00:00:00 2001 From: Adam Howard <91115+codeincontext@users.noreply.github.com> Date: Thu, 7 Nov 2024 18:58:13 +0100 Subject: [PATCH 51/61] test: set up chromatic config and get storybook building (#344) --- .github/workflows/chromatic.yml | 60 +++++++++++++++++++ apps/nextjs/.gitignore | 3 +- .../RadixThemeDecorator.tsx} | 2 +- apps/nextjs/.storybook/preview.tsx | 56 +++++++---------- apps/nextjs/chromatic.config.json | 6 ++ apps/nextjs/package.json | 3 +- .../Chat/Chat/ChatUserAccessCheck.stories.tsx | 26 -------- .../Chat/Chat/ChatUserAccessCheck.tsx | 33 ---------- .../Chat/chat-history.stories.tsx | 8 +++ .../Chat/sidebar-actions.stories.tsx | 4 ++ 10 files changed, 105 insertions(+), 96 deletions(-) create mode 100644 .github/workflows/chromatic.yml rename apps/nextjs/.storybook/{ThemeDecorator.tsx => decorators/RadixThemeDecorator.tsx} (68%) create mode 100644 apps/nextjs/chromatic.config.json delete mode 100644 apps/nextjs/src/components/AppComponents/Chat/Chat/ChatUserAccessCheck.stories.tsx delete mode 100644 apps/nextjs/src/components/AppComponents/Chat/Chat/ChatUserAccessCheck.tsx diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 000000000..663268423 --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,60 @@ +name: "Chromatic" + +on: push + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-chromatic + cancel-in-progress: true + +jobs: + changed-files: + runs-on: ubuntu-latest + name: changed-files + outputs: + all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }} + any_changed: ${{ steps.changed-files.outputs.any_changed }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v44 + with: + files: | + apps/nextjs/** + + chromatic: + name: Run Chromatic + needs: [changed-files] + if: ${{ needs.changed-files.outputs.any_changed == 'true' }} + + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 8 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + - name: Install dependencies + run: pnpm install + - name: Inject Doppler env vars + uses: dopplerhq/secrets-fetch-action@v1.2.0 + id: doppler + with: + doppler-token: ${{ secrets.DOPPLER_TOKEN }} + inject-env-vars: true + - name: Run Chromatic + uses: chromaui/action@latest + with: + workingDir: apps/nextjs + exitZeroOnChanges: true + exitOnceUploaded: true + onlyChanged: true diff --git a/apps/nextjs/.gitignore b/apps/nextjs/.gitignore index 8c2f3b8e2..415dc7351 100644 --- a/apps/nextjs/.gitignore +++ b/apps/nextjs/.gitignore @@ -9,6 +9,7 @@ tests-e2e/.auth .env.sentry-build-plugin *storybook.log +.chromatic.log # Playwright artifacts -playwright-report \ No newline at end of file +playwright-report diff --git a/apps/nextjs/.storybook/ThemeDecorator.tsx b/apps/nextjs/.storybook/decorators/RadixThemeDecorator.tsx similarity index 68% rename from apps/nextjs/.storybook/ThemeDecorator.tsx rename to apps/nextjs/.storybook/decorators/RadixThemeDecorator.tsx index de0d350f3..5f213faf5 100644 --- a/apps/nextjs/.storybook/ThemeDecorator.tsx +++ b/apps/nextjs/.storybook/decorators/RadixThemeDecorator.tsx @@ -3,7 +3,7 @@ import React from "react"; import { Theme } from "@radix-ui/themes"; import "@radix-ui/themes/styles.css"; -export const ThemeDecorator = (Story: React.ComponentType) => ( +export const RadixThemeDecorator = (Story: React.ComponentType) => ( diff --git a/apps/nextjs/.storybook/preview.tsx b/apps/nextjs/.storybook/preview.tsx index 887d667e3..ff270b2e2 100644 --- a/apps/nextjs/.storybook/preview.tsx +++ b/apps/nextjs/.storybook/preview.tsx @@ -9,18 +9,10 @@ import "@fontsource/lexend/900.css"; import { OakThemeProvider, oakDefaultTheme } from "@oaknational/oak-components"; import type { Preview, Decorator } from "@storybook/react"; -// ModerationProvider is coming in the main Chat.tsx refactor -//import { ModerationProvider } from "../src/components/AppComponents/Chat/Chat/ModerationProvider"; import { TooltipProvider } from "../src/components/AppComponents/Chat/ui/tooltip"; -import { DialogProvider } from "../src/components/AppComponents/DialogContext"; -import { CookieConsentProvider } from "../src/components/ContextProviders/CookieConsentProvider"; -import { DemoProvider } from "../src/components/ContextProviders/Demo"; -import LessonPlanTrackingProvider from "../src/lib/analytics/lessonPlanTrackingContext"; -import { SidebarProvider } from "../src/lib/hooks/use-sidebar"; import { AnalyticsProvider } from "../src/mocks/analytics/provider"; import { TRPCReactProvider } from "../src/utils/trpc"; -import { MockClerkProvider } from "./MockClerkProvider"; -import { ThemeDecorator } from "./ThemeDecorator"; +import { RadixThemeDecorator } from "./decorators/RadixThemeDecorator"; import "./preview.css"; const preview: Preview = { @@ -35,33 +27,29 @@ const preview: Preview = { tags: ["autodocs"], }; +// Providers not currently used +// - MockClerkProvider +// - CookieConsentProvider +// - DemoProvider +// - LessonPlanTrackingProvider +// - DialogProvider +// - OakThemeProvider +// - SidebarProvider +// - ChatModerationProvider + export const decorators: Decorator[] = [ - ThemeDecorator, + RadixThemeDecorator, (Story) => ( - - - {" "} - - - - - - - - - {/* */} - - {/* */} - - - - - - - {" "} - - - + <> + {/* TODO: Mock tRPC calls with MSW */} + + + + + + + + ), ]; diff --git a/apps/nextjs/chromatic.config.json b/apps/nextjs/chromatic.config.json new file mode 100644 index 000000000..418b9e3fd --- /dev/null +++ b/apps/nextjs/chromatic.config.json @@ -0,0 +1,6 @@ +{ + "onlyChanged": true, + "projectId": "Project:672908473f629875f0be294f", + "storybookBaseDir": "apps/nextjs", + "zip": true +} diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index e156833f1..e3206734c 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -23,7 +23,8 @@ "with-env": "dotenv -e ../../.env --", "aila": "tsx scripts/aila-cli.ts", "storybook": "dotenv -e ../../.env -- storybook dev -p 6006 --no-open", - "build-storybook": "dotenv -e ../../.env -- storybook build" + "build-storybook": "dotenv -e ../../.env -- storybook build", + "chromatic": "pnpm with-env pnpm dlx chromatic" }, "prettier": "@oakai/prettier-config", "dependencies": { diff --git a/apps/nextjs/src/components/AppComponents/Chat/Chat/ChatUserAccessCheck.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/Chat/ChatUserAccessCheck.stories.tsx deleted file mode 100644 index 849f8f2ed..000000000 --- a/apps/nextjs/src/components/AppComponents/Chat/Chat/ChatUserAccessCheck.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import ChatUserAccessCheck from "./ChatUserAccessCheck"; - -const meta: Meta = { - title: "Components/Chat/ChatUserAccessCheck", - component: ChatUserAccessCheck, - tags: ["autodocs"], -}; - -export default meta; -type Story = StoryObj; - -export const UserHasAccess: Story = { - args: { - userCanView: true, - children:
      Content that the user can view
      , - }, -}; - -export const UserDeniedAccess: Story = { - args: { - userCanView: false, - children:
      Content that should not be visible
      , - }, -}; diff --git a/apps/nextjs/src/components/AppComponents/Chat/Chat/ChatUserAccessCheck.tsx b/apps/nextjs/src/components/AppComponents/Chat/Chat/ChatUserAccessCheck.tsx deleted file mode 100644 index b8b889bf8..000000000 --- a/apps/nextjs/src/components/AppComponents/Chat/Chat/ChatUserAccessCheck.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react"; - -import { Flex } from "@radix-ui/themes"; - -export interface ChatUserAccessCheckProps { - userCanView: boolean; - children: React.ReactNode; -} - -const ChatUserAccessCheck: React.FC = ({ - userCanView, - children, -}) => { - if (!userCanView) { - return ( - -

      - Sorry, you do not have permission to view this page. -

      -
      - ); - } - - return <>{children}; -}; - -export default ChatUserAccessCheck; diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-history.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-history.stories.tsx index ee875272b..21c3e5120 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/chat-history.stories.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/chat-history.stories.tsx @@ -1,3 +1,4 @@ +import * as Dialog from "@radix-ui/react-dialog"; import type { Meta, StoryObj } from "@storybook/react"; import { ChatHistory } from "./chat-history"; @@ -9,6 +10,13 @@ const meta: Meta = { layout: "centered", }, tags: ["autodocs"], + decorators: [ + (Story) => ( + + + + ), + ], }; export default meta; diff --git a/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx index 496c40aae..d0310566d 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/sidebar-actions.stories.tsx @@ -27,6 +27,8 @@ export const Default: Story = { }, }; +// NOTE: The modal appears on a parent element which isn't captured by visual testing +// TODO: Test the modal directly export const SharePending: Story = { args: { ...Default.args, @@ -38,6 +40,8 @@ export const SharePending: Story = { }, }; +// NOTE: The modal appears on a parent element which isn't captured by visual testing +// TODO: Test the modal directly export const RemovePending: Story = { args: { ...Default.args, From dd5bf71a21421ac6e0beb60b4bab560cb159d877 Mon Sep 17 00:00:00 2001 From: Stef Lewandowski Date: Fri, 8 Nov 2024 16:35:08 +0000 Subject: [PATCH 52/61] fix: only categorise initial user input once (#348) --- apps/nextjs/src/app/api/chat/config.ts | 4 +- apps/nextjs/src/app/api/chat/route.test.ts | 4 +- packages/aila/src/core/Aila.test.ts | 95 ++++++++++++++++++++++ packages/aila/src/core/Aila.ts | 18 ++++ 4 files changed, 119 insertions(+), 2 deletions(-) diff --git a/apps/nextjs/src/app/api/chat/config.ts b/apps/nextjs/src/app/api/chat/config.ts index c3ba5b90d..4f32fe969 100644 --- a/apps/nextjs/src/app/api/chat/config.ts +++ b/apps/nextjs/src/app/api/chat/config.ts @@ -17,7 +17,7 @@ export const defaultConfig: Config = { prisma: globalPrisma, createAila: async (options) => { const webActionsPlugin = createWebActionsPlugin(globalPrisma); - return new Aila({ + const createdAila = new Aila({ ...options, plugins: [...(options.plugins || []), webActionsPlugin], prisma: options.prisma ?? globalPrisma, @@ -26,5 +26,7 @@ export const defaultConfig: Config = { userId: undefined, }, }); + await createdAila.initialise(); + return createdAila; }, }; diff --git a/apps/nextjs/src/app/api/chat/route.test.ts b/apps/nextjs/src/app/api/chat/route.test.ts index 1d976909c..74314022f 100644 --- a/apps/nextjs/src/app/api/chat/route.test.ts +++ b/apps/nextjs/src/app/api/chat/route.test.ts @@ -57,7 +57,9 @@ describe("Chat API Route", () => { chatCategoriser: mockChatCategoriser, }, }; - return new Aila(ailaConfig); + const ailaInstance = new Aila(ailaConfig); + await ailaInstance.initialise(); + return ailaInstance; }), // eslint-disable-next-line @typescript-eslint/no-explicit-any prisma: {} as any, diff --git a/packages/aila/src/core/Aila.test.ts b/packages/aila/src/core/Aila.test.ts index 12f725f3d..4f791bc7b 100644 --- a/packages/aila/src/core/Aila.test.ts +++ b/packages/aila/src/core/Aila.test.ts @@ -1,6 +1,7 @@ import type { Polly } from "@pollyjs/core"; import { setupPolly } from "../../tests/mocks/setupPolly"; +import type { AilaCategorisation } from "../features/categorisation"; import { MockCategoriser } from "../features/categorisation/categorisers/MockCategoriser"; import { Aila } from "./Aila"; import { AilaAuthenticationError } from "./AilaError"; @@ -76,6 +77,96 @@ describe("Aila", () => { expect(ailaInstance.lesson.plan.keyStage).toBe("key-stage-2"); }); + it("should use the categoriser to determine the lesson plan from user input if the lesson plan is not already set up", async () => { + const mockCategoriser = { + categorise: jest.fn().mockResolvedValue({ + keyStage: "key-stage-2", + subject: "history", + title: "Roman Britain", + topic: "The Roman Empire", + }), + }; + + const ailaInstance = new Aila({ + lessonPlan: {}, + chat: { + id: "123", + userId: "user123", + messages: [ + { + id: "1", + role: "user", + content: + "Create a lesson about Roman Britain for Key Stage 2 History", + }, + ], + }, + options: { + usePersistence: false, + useRag: false, + useAnalytics: false, + useModeration: false, + }, + plugins: [], + services: { + chatCategoriser: mockCategoriser as unknown as AilaCategorisation, + }, + }); + + await ailaInstance.initialise(); + + expect(mockCategoriser.categorise).toHaveBeenCalledTimes(1); + expect(ailaInstance.lesson.plan.title).toBe("Roman Britain"); + expect(ailaInstance.lesson.plan.subject).toBe("history"); + expect(ailaInstance.lesson.plan.keyStage).toBe("key-stage-2"); + }); + + it("should not use the categoriser to determine the lesson plan from user input if the lesson plan is already set up", async () => { + const mockCategoriser = { + categorise: jest.fn().mockResolvedValue({ + keyStage: "key-stage-2", + subject: "history", + title: "Roman Britain", + topic: "The Roman Empire", + }), + }; + const ailaInstance = new Aila({ + lessonPlan: { + title: "Roman Britain", + subject: "history", + keyStage: "key-stage-2", + }, + chat: { + id: "123", + userId: "user123", + messages: [ + { + id: "1", + role: "user", + content: + "Create a lesson about Roman Britain for Key Stage 2 History", + }, + ], + }, + options: { + usePersistence: false, + useRag: false, + useAnalytics: false, + useModeration: false, + }, + plugins: [], + services: { + chatCategoriser: mockCategoriser as unknown as AilaCategorisation, + }, + }); + + await ailaInstance.initialise(); + expect(mockCategoriser.categorise).toHaveBeenCalledTimes(0); + expect(ailaInstance.lesson.plan.title).toBe("Roman Britain"); + expect(ailaInstance.lesson.plan.subject).toBe("history"); + expect(ailaInstance.lesson.plan.keyStage).toBe("key-stage-2"); + }); + // Calling initialise method successfully initializes the Aila instance it("should successfully initialize the Aila instance when calling the initialise method, and by default not set the lesson plan to initial values", async () => { const ailaInstance = new Aila({ @@ -226,6 +317,8 @@ describe("Aila", () => { expect(ailaInstance.lesson.plan.subject).not.toBeDefined(); expect(ailaInstance.lesson.plan.keyStage).not.toBeDefined(); + await ailaInstance.initialise(); + await ailaInstance.generateSync({ input: "Glaciation", }); @@ -295,6 +388,8 @@ describe("Aila", () => { }, }); + await ailaInstance.initialise(); + await ailaInstance.generateSync({ input: "Change the title to 'This should be ignored by the mocked service'", diff --git a/packages/aila/src/core/Aila.ts b/packages/aila/src/core/Aila.ts index 886bc6089..da30dc549 100644 --- a/packages/aila/src/core/Aila.ts +++ b/packages/aila/src/core/Aila.ts @@ -43,6 +43,7 @@ import type { const log = aiLogger("aila"); export class Aila implements AilaServices { + private _initialised: boolean = false; // We have a separate flag for this because we have an async initialise method which cannot be called in the constructor private _analytics?: AilaAnalyticsFeature; private _chat: AilaChatService; private _errorReporter?: AilaErrorReportingFeature; @@ -123,8 +124,22 @@ export class Aila implements AilaServices { this._plugins = options.plugins; } + private checkInitialised() { + if (!this._initialised) { + log.warn( + "Aila instance has not been initialised. Please call the initialise method before using the instance.", + ); + throw new Error("Aila instance has not been initialised."); + } + } + // Initialization methods public async initialise() { + if (this._initialised) { + log.info("Aila - already initialised"); + return; + } + log.info("Aila - initialise"); this.checkUserIdPresentIfPersisting(); await this.loadChatIfPersisting(); const persistedLessonPlan = this._chat.persistedChat?.lessonPlan; @@ -132,6 +147,7 @@ export class Aila implements AilaServices { this._lesson.setPlan(persistedLessonPlan); } await this._lesson.setUpInitialLessonPlan(this._chat.messages); + this._initialised = true; } private initialiseOptions(options?: AilaOptions) { @@ -246,6 +262,7 @@ export class Aila implements AilaServices { // Generation methods public async generateSync(opts: AilaGenerateLessonPlanOptions) { + this.checkInitialised(); const stream = await this.generate(opts); const reader = stream.getReader(); @@ -273,6 +290,7 @@ export class Aila implements AilaServices { keyStage, topic, }: AilaGenerateLessonPlanOptions) { + this.checkInitialised(); if (this._isShutdown) { throw new AilaGenerationError( "This Aila instance has been shut down and cannot be reused.", From 4c346d0b858c57c48fb680dda776d139dc4c3374 Mon Sep 17 00:00:00 2001 From: Joe Baker Date: Mon, 11 Nov 2024 14:12:13 +0000 Subject: [PATCH 53/61] chore: add expired exports cron job to public routes (#358) --- apps/nextjs/src/middlewares/auth.middleware.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/nextjs/src/middlewares/auth.middleware.ts b/apps/nextjs/src/middlewares/auth.middleware.ts index 361fea1e9..7cd325dc5 100644 --- a/apps/nextjs/src/middlewares/auth.middleware.ts +++ b/apps/nextjs/src/middlewares/auth.middleware.ts @@ -23,6 +23,7 @@ const publicRoutes = [ "/aila/health", "/api/trpc/main/health.check", "/api/trpc/chat/chat.health.check", + "/api/cron-jobs/expired-exports", /** * The inngest route is protected using a signing key * @see https://www.inngest.com/docs/faq#my-app-s-serve-endpoint-requires-authentication-what-should-i-do From 4a9d4fb793ab9bd60240f86a3132b7c989bd62ee Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 12 Nov 2024 11:50:52 +0000 Subject: [PATCH 54/61] build(release v1.14.2): See CHANGE_LOG.md --- CHANGE_LOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index c673ff1d6..8dd6f82a7 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,3 +1,11 @@ +## [1.14.2](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.14.1...v1.14.2) (2024-11-12) + + +### Bug Fixes + +* design-changes-to-footer ([#324](https://github.com/oaknational/oak-ai-lesson-assistant/issues/324)) ([273cfdc](https://github.com/oaknational/oak-ai-lesson-assistant/commit/273cfdc668ca45def0b8a68dc08b7301974e1def)) +* only categorise initial user input once ([#348](https://github.com/oaknational/oak-ai-lesson-assistant/issues/348)) ([dd5bf71](https://github.com/oaknational/oak-ai-lesson-assistant/commit/dd5bf71a21421ac6e0beb60b4bab560cb159d877)) + ## [1.14.1](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.14.0...v1.14.1) (2024-11-07) From 854950d51524eb8d84a0ec9695c88b67f829fd8d Mon Sep 17 00:00:00 2001 From: Joe Baker Date: Tue, 12 Nov 2024 12:45:05 +0000 Subject: [PATCH 55/61] feat: prisma health check - AI-625 (#356) --- apps/nextjs/src/middlewares/auth.middleware.ts | 1 + packages/api/src/router/health.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/apps/nextjs/src/middlewares/auth.middleware.ts b/apps/nextjs/src/middlewares/auth.middleware.ts index 7cd325dc5..086bbd871 100644 --- a/apps/nextjs/src/middlewares/auth.middleware.ts +++ b/apps/nextjs/src/middlewares/auth.middleware.ts @@ -22,6 +22,7 @@ const publicRoutes = [ "/api/health", "/aila/health", "/api/trpc/main/health.check", + "/api/trpc/main/health.prismaCheck", "/api/trpc/chat/chat.health.check", "/api/cron-jobs/expired-exports", /** diff --git a/packages/api/src/router/health.ts b/packages/api/src/router/health.ts index a7d4f910b..14e585118 100644 --- a/packages/api/src/router/health.ts +++ b/packages/api/src/router/health.ts @@ -1,7 +1,24 @@ +import { aiLogger } from "@oakai/logger"; +import { TRPCError } from "@trpc/server"; + import { router, publicProcedure } from "../trpc"; +const log = aiLogger("db"); + export const healthRouter = router({ check: publicProcedure.query(() => { return "OK"; }), + prismaCheck: publicProcedure.query(async ({ ctx }) => { + try { + await ctx.prisma.prompt.count(); + return { status: "ok", message: "Prisma is connected" }; + } catch (error) { + log.error("Prisma health check failed", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Prisma connection failed", + }); + } + }), }); From d0fe2d015865b89ea2287993652a6f8111f0ae4a Mon Sep 17 00:00:00 2001 From: Joe Baker Date: Tue, 12 Nov 2024 17:32:29 +0000 Subject: [PATCH 56/61] feat: add additional materials button - AI-539 [migration] (#255) --- .../src/app/api/chat/errorHandling.test.ts | 2 +- .../action-button-wrapper.tsx | 106 +++++++++ .../drop-down-section/action-button.types.ts | 51 ++++ .../drop-down-section/action-drop-down.tsx | 116 ++++++++++ .../add-additional-materials-button.tsx | 41 ++++ .../Chat/drop-down-section/chat-section.tsx | 20 +- .../drop-down-form-wrapper.tsx | 2 +- .../Chat/drop-down-section/modify-button.tsx | 177 ++------------ apps/nextjs/tests-e2e/helpers/auth/index.ts | 1 + .../add-additional-materials.chunks.txt | 219 ++++++++++++++++++ .../add-additional-materials.formatted.json | 27 +++ .../add-additional-materials.moderation.json | 32 +++ .../modify-lesson-easier.chunks.txt | 84 +++++++ .../modify-lesson-easier.formatted.json | 27 +++ .../modify-lesson-easier.moderation.json | 32 +++ .../tests-e2e/tests/modifiy-lesson.test.ts | 101 ++++++++ packages/api/src/router/chatFeedback.ts | 27 ++- .../api/src/router/testSupport/prepareUser.ts | 8 + .../prompts/lesson-assistant/parts/body.ts | 27 ++- .../migration.sql | 16 ++ packages/db/prisma/schema.prisma | 5 + 21 files changed, 948 insertions(+), 173 deletions(-) create mode 100644 apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button-wrapper.tsx create mode 100644 apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button.types.ts create mode 100644 apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-drop-down.tsx create mode 100644 apps/nextjs/src/components/AppComponents/Chat/drop-down-section/add-additional-materials-button.tsx create mode 100644 apps/nextjs/tests-e2e/recordings/add-additional-materials.chunks.txt create mode 100644 apps/nextjs/tests-e2e/recordings/add-additional-materials.formatted.json create mode 100644 apps/nextjs/tests-e2e/recordings/add-additional-materials.moderation.json create mode 100644 apps/nextjs/tests-e2e/recordings/modify-lesson-easier.chunks.txt create mode 100644 apps/nextjs/tests-e2e/recordings/modify-lesson-easier.formatted.json create mode 100644 apps/nextjs/tests-e2e/recordings/modify-lesson-easier.moderation.json create mode 100644 apps/nextjs/tests-e2e/tests/modifiy-lesson.test.ts create mode 100644 packages/db/prisma/migrations/20241022135134_add_additional_material_modification/migration.sql diff --git a/apps/nextjs/src/app/api/chat/errorHandling.test.ts b/apps/nextjs/src/app/api/chat/errorHandling.test.ts index 33f618225..16ae3af32 100644 --- a/apps/nextjs/src/app/api/chat/errorHandling.test.ts +++ b/apps/nextjs/src/app/api/chat/errorHandling.test.ts @@ -95,7 +95,7 @@ describe("handleChatException", () => { type: "error", value: "Rate limit exceeded", message: - "**Unfortunately you’ve exceeded your fair usage limit for today.** Please come back in 1 hour. If you require a higher limit, please [make a request](https://forms.gle/tHsYMZJR367zydsG8).", + "**Unfortunately you’ve exceeded your fair usage limit for today.** Please come back in 1 hour. If you require a higher limit, please [make a request](https://share.hsforms.com/118hyngR-QSS0J7vZEVlRSgbvumd).", }); }); }); diff --git a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button-wrapper.tsx b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button-wrapper.tsx new file mode 100644 index 000000000..a2e602193 --- /dev/null +++ b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button-wrapper.tsx @@ -0,0 +1,106 @@ +import { useRef, useState } from "react"; + +import { getLastAssistantMessage } from "@oakai/aila/src/helpers/chat/getLastAssistantMessage"; +import { OakBox } from "@oaknational/oak-components"; +import type { AilaUserModificationAction } from "@prisma/client"; + +import { useLessonChat } from "@/components/ContextProviders/ChatProvider"; +import { trpc } from "@/utils/trpc"; + +import ActionButton from "./action-button"; +import type { + AdditionalMaterialOptions, + ModifyOptions, +} from "./action-button.types"; +import { ActionDropDown } from "./action-drop-down"; +import type { FeedbackOption } from "./drop-down-form-wrapper"; + +type ActionButtonWrapperProps = { + sectionTitle: string; + sectionPath: string; + sectionValue: Record | string | Array; + options: ModifyOptions | AdditionalMaterialOptions; + buttonText: string; + actionButtonLabel: string; + userSuggestionTitle: string; + tooltip: string; + generateMessage: ( + option: FeedbackOption, + userFeedbackText: string, + ) => string; +}; + +const ActionButtonWrapper = ({ + sectionTitle, + sectionPath, + sectionValue, + options, + actionButtonLabel, + tooltip, + buttonText, + userSuggestionTitle, + generateMessage, +}: ActionButtonWrapperProps) => { + const dropdownRef = useRef(null); + const [isOpen, setIsOpen] = useState(false); + const [userFeedbackText, setUserFeedbackText] = useState(""); + const [selectedRadio, setSelectedRadio] = + useState | null>(null); + + const chat = useLessonChat(); + const { append, id, messages } = chat; + const { mutateAsync } = trpc.chat.chatFeedback.modifySection.useMutation(); + + const lastAssistantMessage = getLastAssistantMessage(messages); + + const recordUserModifySectionContent = async () => { + if (selectedRadio && lastAssistantMessage) { + const payload = { + chatId: id, + messageId: lastAssistantMessage.id, + sectionPath, + sectionValue, + action: selectedRadio.enumValue, + actionOtherText: userFeedbackText || null, + }; + await mutateAsync(payload); + } + }; + + const handleSubmit = async () => { + if (!selectedRadio) return; + const message = generateMessage(selectedRadio, userFeedbackText); + await Promise.all([ + append({ content: message, role: "user" }), + recordUserModifySectionContent(), + ]); + setIsOpen(false); + }; + + return ( + + setIsOpen(!isOpen)} tooltip={tooltip}> + {actionButtonLabel} + + + {isOpen && ( + + )} + + ); +}; + +export default ActionButtonWrapper; diff --git a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button.types.ts b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button.types.ts new file mode 100644 index 000000000..9711e768b --- /dev/null +++ b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button.types.ts @@ -0,0 +1,51 @@ +export const additionalMaterialsModifyOptions = [ + { + label: "A homework task", + enumValue: "ADD_HOMEWORK_TASK", + chatMessage: "Add a homework task", + }, + { + label: "A narrative for my explanation", + enumValue: "ADD_NARRATIVE", + chatMessage: "Add a narrative for my explanation", + }, + { + label: "Additional practice questions", + enumValue: "ADD_PRACTICE_QUESTIONS", + chatMessage: "Add additional practice questions", + }, + { + label: "Practical instructions (if relevant)", + enumValue: "ADD_PRACTICAL_INSTRUCTIONS", + chatMessage: "Add practical instructions", + }, + { label: "Other", enumValue: "OTHER" }, +] as const; + +export type AdditionalMaterialOptions = typeof additionalMaterialsModifyOptions; + +export const modifyOptions = [ + { + label: "Make it easier", + enumValue: "MAKE_IT_EASIER", + chatMessage: "easier", + }, + { + label: "Make it harder", + enumValue: "MAKE_IT_HARDER", + chatMessage: "harder", + }, + { + label: "Shorten content", + enumValue: "SHORTEN_CONTENT", + chatMessage: "shorter", + }, + { + label: "Add more detail", + enumValue: "ADD_MORE_DETAIL", + chatMessage: "more detailed", + }, + { label: "Other", enumValue: "OTHER" }, +] as const; + +export type ModifyOptions = typeof modifyOptions; diff --git a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-drop-down.tsx b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-drop-down.tsx new file mode 100644 index 000000000..8bce2a066 --- /dev/null +++ b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-drop-down.tsx @@ -0,0 +1,116 @@ +import { Dispatch, RefObject, SetStateAction } from "react"; + +import { aiLogger } from "@oakai/logger"; +import { OakP, OakRadioGroup } from "@oaknational/oak-components"; +import { $Enums, AilaUserModificationAction } from "@prisma/client"; +import { TextArea } from "@radix-ui/themes"; + +import { + AdditionalMaterialOptions, + ModifyOptions, +} from "./action-button.types"; +import { DropDownFormWrapper, FeedbackOption } from "./drop-down-form-wrapper"; +import { SmallRadioButton } from "./small-radio-button"; + +const log = aiLogger("chat"); + +type DropDownProps = { + sectionTitle: string; + options: ModifyOptions | AdditionalMaterialOptions; + selectedRadio: FeedbackOption | null; + setSelectedRadio: Dispatch< + SetStateAction | null> + >; + isOpen: boolean; + setIsOpen: (open: boolean) => void; + setUserFeedbackText: (text: string) => void; + handleSubmit: ( + option: FeedbackOption, + ) => Promise; + buttonText: string; + userSuggestionTitle: string; + dropdownRef: RefObject; + id: string; +}; + +export const ActionDropDown = ({ + sectionTitle, + options, + selectedRadio, + setSelectedRadio, + isOpen, + setIsOpen, + setUserFeedbackText, + handleSubmit, + buttonText, + userSuggestionTitle, + dropdownRef, + id, +}: DropDownProps) => { + return ( + + + {options.map((option) => { + return ( + { + setSelectedRadio(option); + }} + /> + ); + })} + + {selectedRadio?.label === "Other" && ( + <> + {userSuggestionTitle} +