From c46d9fcd6b483adbf91988de3548fa5fb8daaea0 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Wed, 8 Nov 2023 12:04:22 +0100 Subject: [PATCH] :recycle: Introduce typebot v6 with events Closes #885 --- .../features/analytics/api/getTotalAnswers.ts | 60 +++++ .../analytics/api/getTotalVisitedEdges.ts | 55 +++++ .../helpers/computeTotalUsersAtBlock.ts | 60 +++++ .../helpers/getTotalAnswersAtBlock.ts | 20 ++ .../editor/providers/typebotActions/events.ts | 22 ++ .../features/events/start/StartEventNode.tsx | 11 + .../endpoints/BlockSourceEndpoint.tsx | 157 +++++++++++++ .../endpoints/EventSourceEndpoint.tsx | 138 ++++++++++++ .../nodes/event/EventFocusToolbar.tsx | 68 ++++++ .../components/nodes/event/EventNode.tsx | 208 ++++++++++++++++++ .../nodes/event/EventNodeContent.tsx | 14 ++ .../nodes/event/EventNodeContextMenu.tsx | 24 ++ .../graph/components/nodes/event/index.tsx | 1 + .../providers/EventsCoordinateProvider.tsx | 59 +++++ .../src/features/typebot/api/importTypebot.ts | 147 +++++++++++++ apps/viewer/public/site-preview.png | Bin 0 -> 178374 bytes .../bot-engine/queries/saveVisitedEdges.ts | 8 + packages/lib/getBlockById.ts | 24 ++ .../lib/migrations/migratePublicTypebot.ts | 15 ++ packages/lib/migrations/migrateTypebot.ts | 30 +++ .../migrations/migrateTypebotFromV5ToV6.ts | 125 +++++++++++ .../blocks/bubbles/audio/constants.ts | 5 + .../features/blocks/bubbles/audio/index.ts | 1 + .../features/blocks/bubbles/audio/schema.ts | 18 ++ .../features/blocks/bubbles/constants.ts | 7 + .../blocks/bubbles/embed/constants.ts | 5 + .../features/blocks/bubbles/embed/index.ts | 1 + .../features/blocks/bubbles/embed/schema.ts | 18 ++ .../blocks/bubbles/image/constants.ts | 7 + .../features/blocks/bubbles/image/index.ts | 1 + .../features/blocks/bubbles/image/schema.ts | 22 ++ .../schemas/features/blocks/bubbles/schema.ts | 21 ++ .../features/blocks/bubbles/text/constants.ts | 0 .../features/blocks/bubbles/text/index.ts | 1 + .../features/blocks/bubbles/text/schema.ts | 24 ++ .../blocks/bubbles/video/constants.ts | 9 + .../features/blocks/bubbles/video/schema.ts | 21 ++ .../blocks/inputs/choice/constants.ts | 9 + .../features/blocks/inputs/choice/index.ts | 1 + .../features/blocks/inputs/choice/schema.ts | 64 ++++++ .../features/blocks/inputs/date/constants.ts | 12 + .../features/blocks/inputs/date/index.ts | 1 + .../features/blocks/inputs/date/schema.ts | 29 +++ .../features/blocks/inputs/email/constants.ts | 11 + .../features/blocks/inputs/email/index.ts | 1 + .../features/blocks/inputs/email/schema.ts | 22 ++ .../features/blocks/inputs/file/constants.ts | 15 ++ .../features/blocks/inputs/file/index.ts | 1 + .../features/blocks/inputs/file/schema.ts | 50 +++++ .../blocks/inputs/number/constants.ts | 6 + .../features/blocks/inputs/number/index.ts | 1 + .../features/blocks/inputs/number/schema.ts | 24 ++ .../blocks/inputs/payment/constants.ts | 12 + .../features/blocks/inputs/payment/schema.ts | 87 ++++++++ .../features/blocks/inputs/phone/constants.ts | 11 + .../features/blocks/inputs/phone/index.ts | 1 + .../features/blocks/inputs/phone/schema.ts | 22 ++ .../blocks/inputs/pictureChoice/constants.ts | 12 + .../blocks/inputs/pictureChoice/index.ts | 1 + .../blocks/inputs/pictureChoice/schema.ts | 75 +++++++ .../blocks/inputs/rating/constants.ts | 12 + .../features/blocks/inputs/rating/index.ts | 1 + .../features/blocks/inputs/rating/schema.ts | 33 +++ .../schemas/features/blocks/inputs/schema.ts | 56 +++++ .../features/blocks/inputs/text/constants.ts | 7 + .../features/blocks/inputs/text/index.ts | 1 + .../features/blocks/inputs/text/schema.ts | 29 +++ .../features/blocks/inputs/url/constants.ts | 11 + .../features/blocks/inputs/url/index.ts | 1 + .../features/blocks/inputs/url/schema.ts | 21 ++ .../blocks/integrations/chatwoot/constants.ts | 8 + .../blocks/integrations/chatwoot/index.ts | 1 + .../blocks/integrations/chatwoot/schema.ts | 28 +++ .../features/blocks/integrations/constants.ts | 13 ++ .../integrations/googleAnalytics/constants.ts | 0 .../integrations/googleAnalytics/index.ts | 1 + .../integrations/googleAnalytics/schema.ts | 22 ++ .../integrations/googleSheets/constants.ts | 16 ++ .../integrations/googleSheets/schema.ts | 178 +++++++++++++++ .../blocks/integrations/makeCom/constants.ts | 0 .../blocks/integrations/makeCom/index.ts | 1 + .../blocks/integrations/makeCom/schema.ts | 23 ++ .../blocks/integrations/openai/constants.ts | 29 +++ .../blocks/integrations/openai/index.ts | 1 + .../blocks/integrations/openai/schema.ts | 126 +++++++++++ .../integrations/pabblyConnect/constants.ts | 0 .../integrations/pabblyConnect/index.ts | 1 + .../integrations/pabblyConnect/schema.ts | 23 ++ .../blocks/integrations/pixel/index.ts | 1 + .../blocks/integrations/pixel/schema.ts | 52 +++++ .../features/blocks/integrations/schema.ts | 58 +++++ .../integrations/sendEmail/constants.ts | 7 + .../blocks/integrations/sendEmail/index.ts | 1 + .../blocks/integrations/sendEmail/schema.ts | 43 ++++ .../blocks/integrations/webhook/constants.ts | 23 ++ .../blocks/integrations/webhook/schema.ts | 108 +++++++++ .../blocks/integrations/zapier/constants.ts | 0 .../blocks/integrations/zapier/index.ts | 1 + .../blocks/integrations/zapier/schema.ts | 25 +++ .../integrations/zemanticAi/constants.ts | 11 + .../blocks/integrations/zemanticAi/index.ts | 1 + .../blocks/integrations/zemanticAi/schema.ts | 57 +++++ .../features/blocks/logic/abTest/constants.ts | 5 + .../features/blocks/logic/abTest/index.ts | 1 + .../features/blocks/logic/abTest/schema.ts | 48 ++++ .../blocks/logic/condition/constants.ts | 25 +++ .../features/blocks/logic/condition/index.ts | 1 + .../features/blocks/logic/condition/schema.ts | 62 ++++++ .../features/blocks/logic/constants.ts | 10 + .../features/blocks/logic/jump/constants.ts | 0 .../features/blocks/logic/jump/index.ts | 1 + .../features/blocks/logic/jump/schema.ts | 17 ++ .../blocks/logic/redirect/constants.ts | 5 + .../features/blocks/logic/redirect/index.ts | 1 + .../features/blocks/logic/redirect/schema.ts | 17 ++ .../schemas/features/blocks/logic/schema.ts | 44 ++++ .../features/blocks/logic/script/constants.ts | 5 + .../features/blocks/logic/script/index.ts | 1 + .../features/blocks/logic/script/schema.ts | 18 ++ .../blocks/logic/setVariable/constants.ts | 25 +++ .../blocks/logic/setVariable/index.ts | 1 + .../blocks/logic/setVariable/schema.ts | 69 ++++++ .../blocks/logic/typebotLink/constants.ts | 5 + .../blocks/logic/typebotLink/index.ts | 1 + .../blocks/logic/typebotLink/schema.ts | 18 ++ .../features/blocks/logic/wait/constants.ts | 5 + .../features/blocks/logic/wait/index.ts | 1 + .../features/blocks/logic/wait/schema.ts | 17 ++ packages/schemas/features/blocks/schema.ts | 46 ++++ packages/schemas/features/blocks/shared.ts | 19 ++ packages/schemas/features/events/constants.ts | 3 + packages/schemas/features/events/index.ts | 1 + packages/schemas/features/events/shared.ts | 10 + .../schemas/features/events/start/index.ts | 1 + .../schemas/features/events/start/schema.ts | 7 + packages/schemas/features/items/constants.ts | 1 + packages/schemas/features/items/schema.ts | 30 +++ packages/schemas/features/items/shared.ts | 16 ++ packages/schemas/features/typebot/group.ts | 32 +++ .../features/typebot/settings/constants.ts | 22 ++ .../features/typebot/settings/index.ts | 1 + .../features/typebot/settings/schema.ts | 41 ++++ .../features/typebot/theme/constants.ts | 31 +++ .../schemas/features/typebot/theme/schema.ts | 63 ++++++ packages/schemas/features/typebot/types.ts | 5 + 145 files changed, 3638 insertions(+) create mode 100644 apps/builder/src/features/analytics/api/getTotalAnswers.ts create mode 100644 apps/builder/src/features/analytics/api/getTotalVisitedEdges.ts create mode 100644 apps/builder/src/features/analytics/helpers/computeTotalUsersAtBlock.ts create mode 100644 apps/builder/src/features/analytics/helpers/getTotalAnswersAtBlock.ts create mode 100644 apps/builder/src/features/editor/providers/typebotActions/events.ts create mode 100644 apps/builder/src/features/events/start/StartEventNode.tsx create mode 100644 apps/builder/src/features/graph/components/endpoints/BlockSourceEndpoint.tsx create mode 100644 apps/builder/src/features/graph/components/endpoints/EventSourceEndpoint.tsx create mode 100644 apps/builder/src/features/graph/components/nodes/event/EventFocusToolbar.tsx create mode 100644 apps/builder/src/features/graph/components/nodes/event/EventNode.tsx create mode 100644 apps/builder/src/features/graph/components/nodes/event/EventNodeContent.tsx create mode 100644 apps/builder/src/features/graph/components/nodes/event/EventNodeContextMenu.tsx create mode 100644 apps/builder/src/features/graph/components/nodes/event/index.tsx create mode 100644 apps/builder/src/features/graph/providers/EventsCoordinateProvider.tsx create mode 100644 apps/builder/src/features/typebot/api/importTypebot.ts create mode 100644 apps/viewer/public/site-preview.png create mode 100644 packages/bot-engine/queries/saveVisitedEdges.ts create mode 100644 packages/lib/getBlockById.ts create mode 100644 packages/lib/migrations/migratePublicTypebot.ts create mode 100644 packages/lib/migrations/migrateTypebot.ts create mode 100644 packages/lib/migrations/migrateTypebotFromV5ToV6.ts create mode 100644 packages/schemas/features/blocks/bubbles/audio/constants.ts create mode 100644 packages/schemas/features/blocks/bubbles/audio/index.ts create mode 100644 packages/schemas/features/blocks/bubbles/audio/schema.ts create mode 100644 packages/schemas/features/blocks/bubbles/constants.ts create mode 100644 packages/schemas/features/blocks/bubbles/embed/constants.ts create mode 100644 packages/schemas/features/blocks/bubbles/embed/index.ts create mode 100644 packages/schemas/features/blocks/bubbles/embed/schema.ts create mode 100644 packages/schemas/features/blocks/bubbles/image/constants.ts create mode 100644 packages/schemas/features/blocks/bubbles/image/index.ts create mode 100644 packages/schemas/features/blocks/bubbles/image/schema.ts create mode 100644 packages/schemas/features/blocks/bubbles/schema.ts create mode 100644 packages/schemas/features/blocks/bubbles/text/constants.ts create mode 100644 packages/schemas/features/blocks/bubbles/text/index.ts create mode 100644 packages/schemas/features/blocks/bubbles/text/schema.ts create mode 100644 packages/schemas/features/blocks/bubbles/video/constants.ts create mode 100644 packages/schemas/features/blocks/bubbles/video/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/choice/constants.ts create mode 100644 packages/schemas/features/blocks/inputs/choice/index.ts create mode 100644 packages/schemas/features/blocks/inputs/choice/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/date/constants.ts create mode 100644 packages/schemas/features/blocks/inputs/date/index.ts create mode 100644 packages/schemas/features/blocks/inputs/date/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/email/constants.ts create mode 100644 packages/schemas/features/blocks/inputs/email/index.ts create mode 100644 packages/schemas/features/blocks/inputs/email/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/file/constants.ts create mode 100644 packages/schemas/features/blocks/inputs/file/index.ts create mode 100644 packages/schemas/features/blocks/inputs/file/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/number/constants.ts create mode 100644 packages/schemas/features/blocks/inputs/number/index.ts create mode 100644 packages/schemas/features/blocks/inputs/number/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/payment/constants.ts create mode 100644 packages/schemas/features/blocks/inputs/payment/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/phone/constants.ts create mode 100644 packages/schemas/features/blocks/inputs/phone/index.ts create mode 100644 packages/schemas/features/blocks/inputs/phone/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/pictureChoice/constants.ts create mode 100644 packages/schemas/features/blocks/inputs/pictureChoice/index.ts create mode 100644 packages/schemas/features/blocks/inputs/pictureChoice/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/rating/constants.ts create mode 100644 packages/schemas/features/blocks/inputs/rating/index.ts create mode 100644 packages/schemas/features/blocks/inputs/rating/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/text/constants.ts create mode 100644 packages/schemas/features/blocks/inputs/text/index.ts create mode 100644 packages/schemas/features/blocks/inputs/text/schema.ts create mode 100644 packages/schemas/features/blocks/inputs/url/constants.ts create mode 100644 packages/schemas/features/blocks/inputs/url/index.ts create mode 100644 packages/schemas/features/blocks/inputs/url/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/chatwoot/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/chatwoot/index.ts create mode 100644 packages/schemas/features/blocks/integrations/chatwoot/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/googleAnalytics/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/googleAnalytics/index.ts create mode 100644 packages/schemas/features/blocks/integrations/googleAnalytics/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/googleSheets/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/googleSheets/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/makeCom/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/makeCom/index.ts create mode 100644 packages/schemas/features/blocks/integrations/makeCom/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/openai/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/openai/index.ts create mode 100644 packages/schemas/features/blocks/integrations/openai/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/pabblyConnect/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/pabblyConnect/index.ts create mode 100644 packages/schemas/features/blocks/integrations/pabblyConnect/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/pixel/index.ts create mode 100644 packages/schemas/features/blocks/integrations/pixel/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/sendEmail/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/sendEmail/index.ts create mode 100644 packages/schemas/features/blocks/integrations/sendEmail/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/webhook/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/webhook/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/zapier/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/zapier/index.ts create mode 100644 packages/schemas/features/blocks/integrations/zapier/schema.ts create mode 100644 packages/schemas/features/blocks/integrations/zemanticAi/constants.ts create mode 100644 packages/schemas/features/blocks/integrations/zemanticAi/index.ts create mode 100644 packages/schemas/features/blocks/integrations/zemanticAi/schema.ts create mode 100644 packages/schemas/features/blocks/logic/abTest/constants.ts create mode 100644 packages/schemas/features/blocks/logic/abTest/index.ts create mode 100644 packages/schemas/features/blocks/logic/abTest/schema.ts create mode 100644 packages/schemas/features/blocks/logic/condition/constants.ts create mode 100644 packages/schemas/features/blocks/logic/condition/index.ts create mode 100644 packages/schemas/features/blocks/logic/condition/schema.ts create mode 100644 packages/schemas/features/blocks/logic/constants.ts create mode 100644 packages/schemas/features/blocks/logic/jump/constants.ts create mode 100644 packages/schemas/features/blocks/logic/jump/index.ts create mode 100644 packages/schemas/features/blocks/logic/jump/schema.ts create mode 100644 packages/schemas/features/blocks/logic/redirect/constants.ts create mode 100644 packages/schemas/features/blocks/logic/redirect/index.ts create mode 100644 packages/schemas/features/blocks/logic/redirect/schema.ts create mode 100644 packages/schemas/features/blocks/logic/schema.ts create mode 100644 packages/schemas/features/blocks/logic/script/constants.ts create mode 100644 packages/schemas/features/blocks/logic/script/index.ts create mode 100644 packages/schemas/features/blocks/logic/script/schema.ts create mode 100644 packages/schemas/features/blocks/logic/setVariable/constants.ts create mode 100644 packages/schemas/features/blocks/logic/setVariable/index.ts create mode 100644 packages/schemas/features/blocks/logic/setVariable/schema.ts create mode 100644 packages/schemas/features/blocks/logic/typebotLink/constants.ts create mode 100644 packages/schemas/features/blocks/logic/typebotLink/index.ts create mode 100644 packages/schemas/features/blocks/logic/typebotLink/schema.ts create mode 100644 packages/schemas/features/blocks/logic/wait/constants.ts create mode 100644 packages/schemas/features/blocks/logic/wait/index.ts create mode 100644 packages/schemas/features/blocks/logic/wait/schema.ts create mode 100644 packages/schemas/features/blocks/schema.ts create mode 100644 packages/schemas/features/blocks/shared.ts create mode 100644 packages/schemas/features/events/constants.ts create mode 100644 packages/schemas/features/events/index.ts create mode 100644 packages/schemas/features/events/shared.ts create mode 100644 packages/schemas/features/events/start/index.ts create mode 100644 packages/schemas/features/events/start/schema.ts create mode 100644 packages/schemas/features/items/constants.ts create mode 100644 packages/schemas/features/items/schema.ts create mode 100644 packages/schemas/features/items/shared.ts create mode 100644 packages/schemas/features/typebot/group.ts create mode 100644 packages/schemas/features/typebot/settings/constants.ts create mode 100644 packages/schemas/features/typebot/settings/index.ts create mode 100644 packages/schemas/features/typebot/settings/schema.ts create mode 100644 packages/schemas/features/typebot/theme/constants.ts create mode 100644 packages/schemas/features/typebot/theme/schema.ts create mode 100644 packages/schemas/features/typebot/types.ts diff --git a/apps/builder/src/features/analytics/api/getTotalAnswers.ts b/apps/builder/src/features/analytics/api/getTotalAnswers.ts new file mode 100644 index 00000000000..e6e3c0200bd --- /dev/null +++ b/apps/builder/src/features/analytics/api/getTotalAnswers.ts @@ -0,0 +1,60 @@ +import prisma from '@typebot.io/lib/prisma' +import { authenticatedProcedure } from '@/helpers/server/trpc' +import { TRPCError } from '@trpc/server' +import { z } from 'zod' +import { canReadTypebots } from '@/helpers/databaseRules' +import { totalAnswersSchema } from '@typebot.io/schemas/features/analytics' +import { parseGroups } from '@typebot.io/schemas' +import { isInputBlock } from '@typebot.io/lib' + +export const getTotalAnswers = authenticatedProcedure + .meta({ + openapi: { + method: 'GET', + path: '/typebots/{typebotId}/analytics/totalAnswersInBlocks', + protect: true, + summary: 'List total answers in blocks', + tags: ['Analytics'], + }, + }) + .input( + z.object({ + typebotId: z.string(), + }) + ) + .output(z.object({ totalAnswers: z.array(totalAnswersSchema) })) + .query(async ({ input: { typebotId }, ctx: { user } }) => { + const typebot = await prisma.typebot.findFirst({ + where: canReadTypebots(typebotId, user), + select: { publishedTypebot: true }, + }) + if (!typebot?.publishedTypebot) + throw new TRPCError({ + code: 'NOT_FOUND', + message: 'Published typebot not found', + }) + + const totalAnswersPerBlock = await prisma.answer.groupBy({ + by: ['blockId'], + where: { + result: { + typebotId: typebot.publishedTypebot.typebotId, + }, + blockId: { + in: parseGroups(typebot.publishedTypebot.groups, { + typebotVersion: typebot.publishedTypebot.version, + }).flatMap((group) => + group.blocks.filter(isInputBlock).map((block) => block.id) + ), + }, + }, + _count: { _all: true }, + }) + + return { + totalAnswers: totalAnswersPerBlock.map((a) => ({ + blockId: a.blockId, + total: a._count._all, + })), + } + }) diff --git a/apps/builder/src/features/analytics/api/getTotalVisitedEdges.ts b/apps/builder/src/features/analytics/api/getTotalVisitedEdges.ts new file mode 100644 index 00000000000..9be16c0984e --- /dev/null +++ b/apps/builder/src/features/analytics/api/getTotalVisitedEdges.ts @@ -0,0 +1,55 @@ +import prisma from '@typebot.io/lib/prisma' +import { authenticatedProcedure } from '@/helpers/server/trpc' +import { TRPCError } from '@trpc/server' +import { z } from 'zod' +import { canReadTypebots } from '@/helpers/databaseRules' +import { totalVisitedEdgesSchema } from '@typebot.io/schemas' + +export const getTotalVisitedEdges = authenticatedProcedure + .meta({ + openapi: { + method: 'GET', + path: '/typebots/{typebotId}/analytics/totalVisitedEdges', + protect: true, + summary: 'List total edges used in results', + tags: ['Analytics'], + }, + }) + .input( + z.object({ + typebotId: z.string(), + }) + ) + .output( + z.object({ + totalVisitedEdges: z.array(totalVisitedEdgesSchema), + }) + ) + .query(async ({ input: { typebotId }, ctx: { user } }) => { + const typebot = await prisma.typebot.findFirst({ + where: canReadTypebots(typebotId, user), + select: { id: true }, + }) + if (!typebot?.id) + throw new TRPCError({ + code: 'NOT_FOUND', + message: 'Published typebot not found', + }) + + const edges = await prisma.visitedEdge.groupBy({ + by: ['edgeId'], + where: { + result: { + typebotId: typebot.id, + }, + }, + _count: { resultId: true }, + }) + + return { + totalVisitedEdges: edges.map((e) => ({ + edgeId: e.edgeId, + total: e._count.resultId, + })), + } + }) diff --git a/apps/builder/src/features/analytics/helpers/computeTotalUsersAtBlock.ts b/apps/builder/src/features/analytics/helpers/computeTotalUsersAtBlock.ts new file mode 100644 index 00000000000..e2c944c5f6c --- /dev/null +++ b/apps/builder/src/features/analytics/helpers/computeTotalUsersAtBlock.ts @@ -0,0 +1,60 @@ +import { isInputBlock, isNotDefined } from '@typebot.io/lib' +import { PublicTypebotV6 } from '@typebot.io/schemas' +import { + TotalAnswers, + TotalVisitedEdges, +} from '@typebot.io/schemas/features/analytics' + +export const computeTotalUsersAtBlock = ( + currentBlockId: string, + { + publishedTypebot, + totalVisitedEdges, + totalAnswers, + }: { + publishedTypebot: PublicTypebotV6 + totalVisitedEdges: TotalVisitedEdges[] + totalAnswers: TotalAnswers[] + } +): number => { + let totalUsers = 0 + const currentGroup = publishedTypebot.groups.find((group) => + group.blocks.find((block) => block.id === currentBlockId) + ) + if (!currentGroup) return 0 + const currentBlockIndex = currentGroup.blocks.findIndex( + (block) => block.id === currentBlockId + ) + const previousBlocks = currentGroup.blocks.slice(0, currentBlockIndex + 1) + for (const block of previousBlocks.reverse()) { + if (currentBlockId !== block.id && isInputBlock(block)) + return totalAnswers.find((t) => t.blockId === block.id)?.total ?? 0 + const incomingEdges = publishedTypebot.edges.filter( + (edge) => edge.to.blockId === block.id + ) + if (!incomingEdges.length) continue + totalUsers += incomingEdges.reduce( + (acc, incomingEdge) => + acc + + (totalVisitedEdges.find( + (totalEdge) => totalEdge.edgeId === incomingEdge.id + )?.total ?? 0), + 0 + ) + } + const edgesConnectedToGroup = publishedTypebot.edges.filter( + (edge) => + edge.to.groupId === currentGroup.id && isNotDefined(edge.to.blockId) + ) + + totalUsers += edgesConnectedToGroup.reduce( + (acc, connectedEdge) => + acc + + (totalVisitedEdges.find( + (totalEdge) => totalEdge.edgeId === connectedEdge.id + )?.total ?? 0), + 0 + ) + + return totalUsers +} diff --git a/apps/builder/src/features/analytics/helpers/getTotalAnswersAtBlock.ts b/apps/builder/src/features/analytics/helpers/getTotalAnswersAtBlock.ts new file mode 100644 index 00000000000..c17769e7079 --- /dev/null +++ b/apps/builder/src/features/analytics/helpers/getTotalAnswersAtBlock.ts @@ -0,0 +1,20 @@ +import { byId } from '@typebot.io/lib' +import { PublicTypebotV6 } from '@typebot.io/schemas' +import { TotalAnswers } from '@typebot.io/schemas/features/analytics' + +export const getTotalAnswersAtBlock = ( + currentBlockId: string, + { + publishedTypebot, + totalAnswers, + }: { + publishedTypebot: PublicTypebotV6 + totalAnswers: TotalAnswers[] + } +): number => { + const block = publishedTypebot.groups + .flatMap((g) => g.blocks) + .find(byId(currentBlockId)) + if (!block) throw new Error(`Block ${currentBlockId} not found`) + return totalAnswers.find((t) => t.blockId === block.id)?.total ?? 0 +} diff --git a/apps/builder/src/features/editor/providers/typebotActions/events.ts b/apps/builder/src/features/editor/providers/typebotActions/events.ts new file mode 100644 index 00000000000..1b271575e57 --- /dev/null +++ b/apps/builder/src/features/editor/providers/typebotActions/events.ts @@ -0,0 +1,22 @@ +import { produce } from 'immer' +import { TEvent } from '@typebot.io/schemas' +import { SetTypebot } from '../TypebotProvider' + +export type EventsActions = { + updateEvent: ( + eventIndex: number, + updates: Partial> + ) => void +} + +const eventsActions = (setTypebot: SetTypebot): EventsActions => ({ + updateEvent: (eventIndex: number, updates: Partial>) => + setTypebot((typebot) => + produce(typebot, (typebot) => { + const event = typebot.events[eventIndex] + typebot.events[eventIndex] = { ...event, ...updates } + }) + ), +}) + +export { eventsActions } diff --git a/apps/builder/src/features/events/start/StartEventNode.tsx b/apps/builder/src/features/events/start/StartEventNode.tsx new file mode 100644 index 00000000000..51cbfae9818 --- /dev/null +++ b/apps/builder/src/features/events/start/StartEventNode.tsx @@ -0,0 +1,11 @@ +import { FlagIcon } from '@/components/icons' +import { HStack, Text } from '@chakra-ui/react' + +export const StartEventNode = () => { + return ( + + + Start + + ) +} diff --git a/apps/builder/src/features/graph/components/endpoints/BlockSourceEndpoint.tsx b/apps/builder/src/features/graph/components/endpoints/BlockSourceEndpoint.tsx new file mode 100644 index 00000000000..1d912b8c2ef --- /dev/null +++ b/apps/builder/src/features/graph/components/endpoints/BlockSourceEndpoint.tsx @@ -0,0 +1,157 @@ +import { + BoxProps, + Flex, + useColorModeValue, + useEventListener, +} from '@chakra-ui/react' +import { BlockSource } from '@typebot.io/schemas' +import React, { + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react' +import { useEndpoints } from '../../providers/EndpointsProvider' +import { useGraph } from '../../providers/GraphProvider' +import { useGroupsCoordinates } from '../../providers/GroupsCoordinateProvider' + +const endpointHeight = 32 + +export const BlockSourceEndpoint = ({ + source, + groupId, + isHidden, + ...props +}: BoxProps & { + source: BlockSource + groupId?: string + isHidden?: boolean +}) => { + const id = source.itemId ?? source.blockId + const color = useColorModeValue('blue.200', 'blue.100') + const connectedColor = useColorModeValue('blue.300', 'blue.200') + const bg = useColorModeValue('gray.100', 'gray.700') + const { setConnectingIds, previewingEdge, graphPosition } = useGraph() + const { setSourceEndpointYOffset, deleteSourceEndpointYOffset } = + useEndpoints() + const { groupsCoordinates } = useGroupsCoordinates() + const ref = useRef(null) + const [groupHeight, setGroupHeight] = useState() + const [groupTransformProp, setGroupTransformProp] = useState() + + const endpointY = useMemo( + () => + ref.current + ? Number( + ( + (ref.current?.getBoundingClientRect().y + + (endpointHeight * graphPosition.scale) / 2 - + graphPosition.y) / + graphPosition.scale + ).toFixed(2) + ) + : undefined, + // We need to force recompute whenever the group height and position changes + // eslint-disable-next-line react-hooks/exhaustive-deps + [graphPosition.scale, graphPosition.y, groupHeight, groupTransformProp] + ) + + useLayoutEffect(() => { + const resizeObserver = new ResizeObserver((entries) => { + setGroupHeight(entries[0].contentRect.height) + }) + const groupElement = document.getElementById(`group-${groupId}`) + if (!groupElement) return + resizeObserver.observe(groupElement) + return () => { + resizeObserver.disconnect() + } + }, [groupId]) + + useLayoutEffect(() => { + const mutationObserver = new MutationObserver((entries) => { + setGroupTransformProp((entries[0].target as HTMLElement).style.transform) + }) + const groupElement = document.getElementById(`group-${groupId}`) + if (!groupElement) return + mutationObserver.observe(groupElement, { + attributes: true, + attributeFilter: ['style'], + }) + return () => { + mutationObserver.disconnect() + } + }, [groupId]) + + useEffect(() => { + if (!endpointY) return + setSourceEndpointYOffset?.({ + id, + y: endpointY, + }) + }, [setSourceEndpointYOffset, endpointY, id]) + + useEffect( + () => () => { + deleteSourceEndpointYOffset?.(id) + }, + [deleteSourceEndpointYOffset, id] + ) + + useEventListener( + 'pointerdown', + (e) => { + e.stopPropagation() + if (groupId) setConnectingIds({ source: { ...source, groupId } }) + }, + ref.current + ) + + useEventListener( + 'mousedown', + (e) => { + e.stopPropagation() + }, + ref.current + ) + + if (!groupsCoordinates) return <> + return ( + + + + + + ) +} diff --git a/apps/builder/src/features/graph/components/endpoints/EventSourceEndpoint.tsx b/apps/builder/src/features/graph/components/endpoints/EventSourceEndpoint.tsx new file mode 100644 index 00000000000..8a29fa8b006 --- /dev/null +++ b/apps/builder/src/features/graph/components/endpoints/EventSourceEndpoint.tsx @@ -0,0 +1,138 @@ +import { + BoxProps, + Flex, + useColorModeValue, + useEventListener, +} from '@chakra-ui/react' +import React, { + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react' +import { useEndpoints } from '../../providers/EndpointsProvider' +import { useGraph } from '../../providers/GraphProvider' +import { TEventSource } from '@typebot.io/schemas' +import { isNotDefined } from '@typebot.io/lib' + +const endpointHeight = 32 + +export const EventSourceEndpoint = ({ + source, + isHidden, + ...props +}: BoxProps & { + source: TEventSource + isHidden?: boolean +}) => { + const color = useColorModeValue('blue.200', 'blue.100') + const connectedColor = useColorModeValue('blue.300', 'blue.200') + const bg = useColorModeValue('gray.100', 'gray.700') + const { setConnectingIds, previewingEdge, graphPosition } = useGraph() + const { setSourceEndpointYOffset, deleteSourceEndpointYOffset } = + useEndpoints() + const [eventTransformProp, setEventTransformProp] = useState() + const ref = useRef(null) + + const endpointY = useMemo( + () => + ref.current + ? Number( + ( + (ref.current?.getBoundingClientRect().y + + (endpointHeight * graphPosition.scale) / 2 - + graphPosition.y) / + graphPosition.scale + ).toFixed(2) + ) + : undefined, + // We need to force recompute whenever the event node position changes + // eslint-disable-next-line react-hooks/exhaustive-deps + [graphPosition.scale, graphPosition.y, eventTransformProp] + ) + + useLayoutEffect(() => { + const mutationObserver = new MutationObserver((entries) => { + setEventTransformProp((entries[0].target as HTMLElement).style.transform) + }) + const groupElement = document.getElementById(`event-${source.eventId}`) + if (!groupElement) return + mutationObserver.observe(groupElement, { + attributes: true, + attributeFilter: ['style'], + }) + return () => { + mutationObserver.disconnect() + } + }, [source.eventId]) + + useEffect(() => { + if (isNotDefined(endpointY)) return + setSourceEndpointYOffset?.({ + id: source.eventId, + y: endpointY, + }) + }, [endpointY, setSourceEndpointYOffset, source.eventId]) + + useEffect( + () => () => { + deleteSourceEndpointYOffset?.(source.eventId) + }, + [deleteSourceEndpointYOffset, source.eventId] + ) + + useEventListener( + 'pointerdown', + (e) => { + e.stopPropagation() + setConnectingIds({ source }) + }, + ref.current + ) + + useEventListener( + 'mousedown', + (e) => { + e.stopPropagation() + }, + ref.current + ) + + return ( + + + + + + ) +} diff --git a/apps/builder/src/features/graph/components/nodes/event/EventFocusToolbar.tsx b/apps/builder/src/features/graph/components/nodes/event/EventFocusToolbar.tsx new file mode 100644 index 00000000000..105f06c3e85 --- /dev/null +++ b/apps/builder/src/features/graph/components/nodes/event/EventFocusToolbar.tsx @@ -0,0 +1,68 @@ +import { InfoIcon, PlayIcon, TrashIcon } from '@/components/icons' +import { + HStack, + IconButton, + Tooltip, + useClipboard, + useColorModeValue, +} from '@chakra-ui/react' + +type Props = { + eventId: string + onPlayClick: () => void + onDeleteClick?: () => void +} + +export const EventFocusToolbar = ({ + eventId, + onPlayClick, + onDeleteClick, +}: Props) => { + const { hasCopied, onCopy } = useClipboard(eventId) + + return ( + + } + borderRightWidth="1px" + borderRightRadius="none" + aria-label={'Preview bot from this group'} + variant="ghost" + onClick={onPlayClick} + size="sm" + /> + + } + borderRightWidth="1px" + borderRightRadius="none" + borderLeftRadius="none" + aria-label={'Show group info'} + variant="ghost" + size="sm" + onClick={onCopy} + /> + + {onDeleteClick ? ( + } + onClick={onDeleteClick} + variant="ghost" + size="sm" + /> + ) : null} + + ) +} diff --git a/apps/builder/src/features/graph/components/nodes/event/EventNode.tsx b/apps/builder/src/features/graph/components/nodes/event/EventNode.tsx new file mode 100644 index 00000000000..258f356d4dd --- /dev/null +++ b/apps/builder/src/features/graph/components/nodes/event/EventNode.tsx @@ -0,0 +1,208 @@ +import { SlideFade, Stack, useColorModeValue } from '@chakra-ui/react' +import React, { memo, useCallback, useEffect, useRef, useState } from 'react' +import { EventNodeContextMenu } from './EventNodeContextMenu' +import { useDebounce } from 'use-debounce' +import { ContextMenu } from '@/components/ContextMenu' +import { useDrag } from '@use-gesture/react' +import { EventFocusToolbar } from './EventFocusToolbar' +import { useOutsideClick } from '@/hooks/useOutsideClick' +import { + RightPanel, + useEditor, +} from '@/features/editor/providers/EditorProvider' +import { useGraph } from '@/features/graph/providers/GraphProvider' +import { useEventsCoordinates } from '@/features/graph/providers/EventsCoordinateProvider' +import { setMultipleRefs } from '@/helpers/setMultipleRefs' +import { Coordinates } from '@/features/graph/types' +import { TEvent } from '@typebot.io/schemas' +import { EventNodeContent } from './EventNodeContent' +import { EventSourceEndpoint } from '../../endpoints/EventSourceEndpoint' +import { eventWidth } from '@/features/graph/constants' +import { useTypebot } from '@/features/editor/providers/TypebotProvider' + +type Props = { + event: TEvent + eventIndex: number +} + +export const EventNode = ({ event, eventIndex }: Props) => { + const { updateEventCoordinates } = useEventsCoordinates() + + const handleEventDrag = useCallback( + (newCoord: Coordinates) => { + updateEventCoordinates(event.id, newCoord) + }, + [event.id, updateEventCoordinates] + ) + + return ( + + ) +} + +const NonMemoizedDraggableEventNode = ({ + event, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + eventIndex, + onEventDrag, +}: Props & { onEventDrag: (newCoord: Coordinates) => void }) => { + const elementBgColor = useColorModeValue('white', 'gray.900') + const previewingBorderColor = useColorModeValue('blue.400', 'blue.300') + const { previewingEdge, isReadOnly, graphPosition } = useGraph() + const { updateEvent } = useTypebot() + const { setRightPanel, setStartPreviewAtEvent } = useEditor() + + const [isMouseDown, setIsMouseDown] = useState(false) + const [currentCoordinates, setCurrentCoordinates] = useState( + event.graphCoordinates + ) + + const isPreviewing = previewingEdge + ? 'eventId' in previewingEdge.from + ? previewingEdge.from.eventId === event.id + : false + : false + + const eventRef = useRef(null) + const [debouncedEventPosition] = useDebounce(currentCoordinates, 100) + const [isFocused, setIsFocused] = useState(false) + + useOutsideClick({ + handler: () => setIsFocused(false), + ref: eventRef, + capture: true, + isEnabled: isFocused, + }) + + // When the event is moved from external action (e.g. undo/redo), update the current coordinates + useEffect(() => { + setCurrentCoordinates({ + x: event.graphCoordinates.x, + y: event.graphCoordinates.y, + }) + }, [event.graphCoordinates.x, event.graphCoordinates.y]) + + useEffect(() => { + if (!currentCoordinates || isReadOnly) return + if ( + currentCoordinates?.x === event.graphCoordinates.x && + currentCoordinates.y === event.graphCoordinates.y + ) + return + updateEvent(eventIndex, { graphCoordinates: currentCoordinates }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [debouncedEventPosition]) + + const startPreviewAtThisEvent = () => { + setStartPreviewAtEvent(event.id) + setRightPanel(RightPanel.PREVIEW) + } + + useDrag( + ({ first, last, offset: [offsetX, offsetY], event, target }) => { + event.stopPropagation() + if ( + (target as HTMLElement) + .closest('.prevent-event-drag') + ?.classList.contains('prevent-event-drag') + ) + return + + if (first) { + setIsFocused(true) + setIsMouseDown(true) + } + if (last) { + setIsMouseDown(false) + } + const newCoord = { + x: Number((offsetX / graphPosition.scale).toFixed(2)), + y: Number((offsetY / graphPosition.scale).toFixed(2)), + } + setCurrentCoordinates(newCoord) + onEventDrag(newCoord) + }, + { + target: eventRef, + pointer: { keys: false }, + from: () => [ + currentCoordinates.x * graphPosition.scale, + currentCoordinates.y * graphPosition.scale, + ], + } + ) + + return ( + + renderMenu={() => } + isDisabled={isReadOnly || event.type === 'start'} + > + {(ref, isContextMenuOpened) => ( + + + + {!isReadOnly && ( + + {} : undefined} + /> + + )} + + )} + + ) +} + +export const DraggableEventNode = memo(NonMemoizedDraggableEventNode) diff --git a/apps/builder/src/features/graph/components/nodes/event/EventNodeContent.tsx b/apps/builder/src/features/graph/components/nodes/event/EventNodeContent.tsx new file mode 100644 index 00000000000..bcc592eaf2d --- /dev/null +++ b/apps/builder/src/features/graph/components/nodes/event/EventNodeContent.tsx @@ -0,0 +1,14 @@ +import { StartEventNode } from '@/features/events/start/StartEventNode' +import { TEvent } from '@typebot.io/schemas' + +type Props = { + event: TEvent +} +export const EventNodeContent = ({ event }: Props) => { + switch (event.type) { + case 'start': + return + default: + return null + } +} diff --git a/apps/builder/src/features/graph/components/nodes/event/EventNodeContextMenu.tsx b/apps/builder/src/features/graph/components/nodes/event/EventNodeContextMenu.tsx new file mode 100644 index 00000000000..da7a234d3f3 --- /dev/null +++ b/apps/builder/src/features/graph/components/nodes/event/EventNodeContextMenu.tsx @@ -0,0 +1,24 @@ +import { MenuList, MenuItem } from '@chakra-ui/react' +import { CopyIcon, TrashIcon } from '@/components/icons' + +type Props = { + onDuplicateClick?: () => void + onDeleteClick?: () => void +} +export const EventNodeContextMenu = ({ + onDuplicateClick, + onDeleteClick, +}: Props) => ( + + {onDuplicateClick && ( + } onClick={onDuplicateClick}> + Duplicate + + )} + {onDeleteClick && ( + } onClick={onDeleteClick}> + Delete + + )} + +) diff --git a/apps/builder/src/features/graph/components/nodes/event/index.tsx b/apps/builder/src/features/graph/components/nodes/event/index.tsx new file mode 100644 index 00000000000..f9883a5ae59 --- /dev/null +++ b/apps/builder/src/features/graph/components/nodes/event/index.tsx @@ -0,0 +1 @@ +export { EventNode } from './EventNode' diff --git a/apps/builder/src/features/graph/providers/EventsCoordinateProvider.tsx b/apps/builder/src/features/graph/providers/EventsCoordinateProvider.tsx new file mode 100644 index 00000000000..5388925b9df --- /dev/null +++ b/apps/builder/src/features/graph/providers/EventsCoordinateProvider.tsx @@ -0,0 +1,59 @@ +import { + ReactNode, + useState, + useEffect, + useContext, + createContext, + useCallback, +} from 'react' +import { Coordinates, CoordinatesMap } from '../types' +import { TypebotV6 } from '@typebot.io/schemas' + +const eventsCoordinatesContext = createContext<{ + eventsCoordinates: CoordinatesMap + updateEventCoordinates: (groupId: string, newCoord: Coordinates) => void + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore +}>({}) + +export const EventsCoordinatesProvider = ({ + children, + events, +}: { + children: ReactNode + events: TypebotV6['events'][number][] + isReadOnly?: boolean +}) => { + const [eventsCoordinates, setEventsCoordinates] = useState({}) + + useEffect(() => { + setEventsCoordinates( + events.reduce( + (coords, group) => ({ + ...coords, + [group.id]: group.graphCoordinates, + }), + {} + ) + ) + }, [events]) + + const updateEventCoordinates = useCallback( + (groupId: string, newCoord: Coordinates) => + setEventsCoordinates((eventsCoordinates) => ({ + ...eventsCoordinates, + [groupId]: newCoord, + })), + [] + ) + + return ( + + {children} + + ) +} + +export const useEventsCoordinates = () => useContext(eventsCoordinatesContext) diff --git a/apps/builder/src/features/typebot/api/importTypebot.ts b/apps/builder/src/features/typebot/api/importTypebot.ts new file mode 100644 index 00000000000..88b51c0af78 --- /dev/null +++ b/apps/builder/src/features/typebot/api/importTypebot.ts @@ -0,0 +1,147 @@ +import prisma from '@typebot.io/lib/prisma' +import { authenticatedProcedure } from '@/helpers/server/trpc' +import { TRPCError } from '@trpc/server' +import { Plan, WorkspaceRole } from '@typebot.io/prisma' +import { + Typebot, + TypebotV6, + resultsTablePreferencesSchema, + typebotV5Schema, + typebotV6Schema, +} from '@typebot.io/schemas' +import { z } from 'zod' +import { getUserRoleInWorkspace } from '@/features/workspace/helpers/getUserRoleInWorkspace' +import { sanitizeGroups, sanitizeSettings } from '../helpers/sanitizers' +import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent' +import { preprocessTypebot } from '@typebot.io/schemas/features/typebot/helpers/preprocessTypebot' +import { migrateTypebot } from '@typebot.io/lib/migrations/migrateTypebot' + +const omittedProps = { + id: true, + whatsAppCredentialsId: true, + isClosed: true, + isArchived: true, + createdAt: true, + updatedAt: true, + customDomain: true, + workspaceId: true, + resultsTablePreferencesSchema: true, + selectedThemeTemplateId: true, +} as const + +const importingTypebotSchema = z.preprocess( + preprocessTypebot, + z.discriminatedUnion('version', [ + typebotV5Schema._def.schema.omit(omittedProps).extend({ + resultsTablePreferences: resultsTablePreferencesSchema.nullish(), + selectedThemeTemplateId: z.string().nullish(), + }), + typebotV6Schema.omit(omittedProps).extend({ + resultsTablePreferences: resultsTablePreferencesSchema.nullish(), + selectedThemeTemplateId: z.string().nullish(), + }), + ]) +) + +type ImportingTypebot = z.infer + +const migrateImportingTypebot = ( + typebot: ImportingTypebot +): Promise => { + const fullTypebot = { + ...typebot, + id: 'dummy id', + workspaceId: 'dummy workspace id', + resultsTablePreferences: typebot.resultsTablePreferences ?? null, + selectedThemeTemplateId: typebot.selectedThemeTemplateId ?? null, + createdAt: new Date(), + updatedAt: new Date(), + customDomain: null, + isClosed: false, + isArchived: false, + whatsAppCredentialsId: null, + } satisfies Typebot + return migrateTypebot(fullTypebot) +} + +export const importTypebot = authenticatedProcedure + .meta({ + openapi: { + method: 'POST', + path: '/typebots/import', + protect: true, + summary: 'Import a typebot', + tags: ['Typebot'], + }, + }) + .input( + z.object({ + workspaceId: z.string(), + typebot: importingTypebotSchema, + }) + ) + .output( + z.object({ + typebot: typebotV6Schema, + }) + ) + .mutation(async ({ input: { typebot, workspaceId }, ctx: { user } }) => { + const workspace = await prisma.workspace.findUnique({ + where: { id: workspaceId }, + select: { id: true, members: true, plan: true }, + }) + const userRole = getUserRoleInWorkspace(user.id, workspace?.members) + if ( + userRole === undefined || + userRole === WorkspaceRole.GUEST || + !workspace + ) + throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' }) + + const migratedTypebot = await migrateImportingTypebot(typebot) + + const newTypebot = await prisma.typebot.create({ + data: { + version: '6', + workspaceId, + name: migratedTypebot.name, + icon: migratedTypebot.icon, + selectedThemeTemplateId: migratedTypebot.selectedThemeTemplateId, + groups: (migratedTypebot.groups + ? await sanitizeGroups(workspaceId)(migratedTypebot.groups) + : []) as TypebotV6['groups'], + events: migratedTypebot.events ?? undefined, + theme: migratedTypebot.theme ? migratedTypebot.theme : {}, + settings: migratedTypebot.settings + ? sanitizeSettings(migratedTypebot.settings, workspace.plan, 'create') + : workspace.plan === Plan.FREE + ? { + general: { + isBrandingEnabled: true, + }, + } + : {}, + folderId: migratedTypebot.folderId, + variables: migratedTypebot.variables ?? [], + edges: migratedTypebot.edges ?? [], + resultsTablePreferences: + migratedTypebot.resultsTablePreferences ?? undefined, + } satisfies Partial, + }) + + const parsedNewTypebot = typebotV6Schema.parse(newTypebot) + + await sendTelemetryEvents([ + { + name: 'Typebot created', + workspaceId: parsedNewTypebot.workspaceId, + typebotId: parsedNewTypebot.id, + userId: user.id, + data: { + name: newTypebot.name, + }, + }, + ]) + + return { typebot: parsedNewTypebot } + }) diff --git a/apps/viewer/public/site-preview.png b/apps/viewer/public/site-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..895cdb3710904ce7377cbdfd9daba67e19ef850d GIT binary patch literal 178374 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

lFCNLb+IeY?N;2BBZ++mJW7O3aui`>~^$6zP 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

L=3BTmYtbs`J5qC*?=X=1smpDt73d* zNH)t1v=lMvX(JTzdW zPIoS!I7YYL_{a0z)$enh)BXiBfQnhJ8B+J3 zO6q;oEsg<;SQYK_*mQk%oh3vj&cl2la}8C`J0xB1{V7?;&ARr;7X~wL!j&Y$}rrDd}ue2X2!f8-tlqDEOI#}-oMU-CWv4EqwIkXJfAme-n1)JOEldyj#q*-dR1Iq>zVCEVMOyCMP)@aISN zqYU~4WuUy)=Lyd-8jLV~R?<%{T_eil#=tp!I!k3J@9##en3tr_nB&D|zfu_U>`mBw zrdR&J9M(ssQ78|NT|UovrJ35x;`Na}B-N%;K=jVps>WYKy`9$RG238%6Faa^Ohde% z=7Ye;D$AMK>maj$zhxh5Oz@Jvd8+4l?(W6^$?w=DEiuF_@fy9q2+@a{Fy5py1pFmQ zWmi;b6+A6dZEem-4 zgWJG~o$3@UpSlDSROv+jKI>#H^dC1cEgJ|C@B3-4L}1&$FifqsCER(-#+%b=mMolA zJ^x7$i$i{K1d+~jxcUXaBM%n+J7b~XgUOztGv+T2So_hJo3X8et zG@SPjc%-FM@m_o30sML-+rU*~nd?xKlER$Uwa8RLt17g?(iPwsCQLMU^+_4?e;Nq> zKQ$biL{*|yc$)?@Q)5;9jlT)qg5}ePF<+`B{(j%ziLI<)TQ%P+m_XN#|Eo~k+^J=Q zqgiXMppP@Nb!|4dw&*YTb`eoswxPPh<;c^q5@(^@LBheZ46rX7ax7-T zpr+>D4OJkAIpPE-7*g2~`xvD5VH zqo;acKSuk7C>ah|kN_jPqMSrIt|H{LQ#C>bzorv z0YqAJn848Mc7=>y$`97IFi}z8q)_#Jws~!GmSv^&=Vqj-U(dGGKY8tcw1uZZExlf4 zDByu8Stwre4P)(X_<+=U+Pr6Ux^^j=^7hMqMX@X-kS4KPWR5B0!XoslkyntYo1~t! z99YNiRrCs%e8v*2`Dq}+42xioXt*=vo#yVa9G8!tWVgd$2rO3WG7ddpwKQoDXVsR2#U_?EFmPSV3m3-)}_{24B2u8l{3Ga3Gpz{ z3E3t`6W4mgdPU_NOD~n~WXtpFBdAxW1atI@i^o>3dO#C%1aV0}LcI_ta7CKN)074F z2)Lq5{tCHlyn1|v=D{H7VN^gTVZgn{QB{mjr0LFV2O#NwngjB1+lfW==(5?GK59Hi zP3JDBaqb%R#C-XsA?g3L&Y*9Cqr(2K|7ql4V3Y=l#!WrHW!(Y3dTLqz-8?~jN?HhL zVn0qo!1pJI+@72(8QH#9O0!(_JXcZ0DY?v_O%Y)Oe@j*_+8;Iibg`9VEKuu)iN2^G z^WE9=VVuxUjEEYoBIw`qKD_cwh2%M!0%|zkhetF(S&6P?`7Ud5Tj8f+N9tNo9ho=% zLj+(ap>KUoB31l>3#fAeJlD7F51|UAock{cH~z#0o`%x(bi43HK}4-#*SV@T z>h9oU2X2B+1LjEidJqY?YZh+9L2oUufg;KerAcL{&-_a`NHnZwL&dBl9zYs9Ltn=> z7pzP@deR=lp9e1Ik#tYStq=ig3GWt+QK3w@E4FaE-1d^HsYo?+GnVpc@i)r|4pD7B zcM(mFXs+^rYyX-@U2XjG_%_YOqj#y5uBp}M#|_)7QskDBz%!e&fO-K10N?8Iw$5r8JiIbn2x}luBVs97CF=j8gJXgv(LQ zNP1~%Y8I4iP_p|E^#j86-nHWjshrd4D{X%wx_ZzwqqiYZaxLrfEXpd`b|q0;M&Eup z+BWABxda7vHg75_4u)~cnygi6?!n?n&n|MZ1QbgLKQ*CL&Qf*t#y+XV za#T45_`utiWCX_4cs+gnzuK)|-)Fpe;Wbn_$Cc+2;4|LparEne{S@HzUD>JT!1`FB3|_yS4( zn#`a_wbl9Klap{*_WS7h{JV#6sq{YkyQYVxX<1{JW@7)R_YPyr%xkuAjpR_dl!{vx zmQe{RxBt5W?*7}vv?($`V3IHGTpAIt<4hUC-Q4HgMuN-aQS2>?Uc>4^1*-7t=4^`$ z?dSA}97z@99AS*#oE#e>#XcW-JJZ+a`UpaOGAa>O$vu^57-Xd=<|(PA87Hv@XU>25w_ikB z3*g@4`J_WT=>>6d3!tm7n3oBMQb`w_QLtVd4|8$u!xl9*aygXmp|F}~QZ06VBrlNh zXbmau-13@B=z1oiDD+1;79b=_G?or^@tC=gCOUuyak{A|I#dOVLIu4&f5VDh^Ca*@~J3-cm3yq4!fOoUvdYAu}`ljtt) zNU3{)d!*E@kL1lQx+l4J-_^Qvm0wk?&obVXD#YY{WxU;92?|hYQ+0wKlMb9`EYM7m z4i2)z&WM~?*%P2n9_S^+`Kp9=X|vPDzYw3xZ^CE2KO`5P8Y$;D4AfAw{Ly_nOM~vF z(4B)2&i+Omf3WH;85_p1b$$_2);r6(KeQ8zKM}$7h0;2@I7h&9kk* zBPFjik7sSv^NuUWS5T?7etlET?HfRj{Ze}mbT5O`D1 z)C2z7nFsys=i~zn4Mvzn8%)AzAy)_&VIK zd7eok1;W4hCJ{jyKf;LfHfc3>q6)(MpE}ye08MP^O5<8?GTL-(ekGP|Z#%#N<(_@SQ8MC+zgq1{3az&fke5_UeR^ z(3$wKi$Gl;gCemHbZz5A@+`8Ca)QG|lTL}g=AxwAW+b9QQTMs!44~W2G^>)L%asJrto?(QR)%kGS)xMG zNXFNG41`=47bl|$y5TSaoonv?az~MF^`_Xx->v^@V{kdS2YwghmGjH-C=D6TM^0-! z%W3)fB&kWMXW`Qu+9SmuH-Srg&tjo)ndEpkmjR|nE5#xi|CqyBH&S*stTS??kh7<1 z^rn+@_EtS%V2*#V?V3ewfzhlx1fj?LyxBmrQ7!d6t7@3N`tM=Kugi3hT076Oh;*^y zWYBQ<#W(G)6|6oJubo=AQBBtKJ?c~q9cp?~w-aDA0O~xk*pS}srgtXomoaDc^}kQj6Q{Zway`GCJh$;ZyXCU8B`_S8jls9)z{qw~ zpi!8Spm?k+0+f>181WzXZZGjHo*nQuww$ZcTR1#Szj!wVNSN2uQP+=j zq;$>QvBS7NhZMg;R&a8b6F&+3q2Ow%U+4`YRnX<;dLAnUt<^EilqizRR6H>JhFfif z(l1mqB1XN4jQ^in4m8n0^e@qYK}r%b8#b#;#d_!�ji;xBg+c@_J3OQ7#d~jXg5K zm5LXa)kE}mXwm+AP)y?j?kkbq&h3X3G3Q?6!l+C>mNm5JMNM7s%>yO*@)g zjdNNS)B=0IYO!7Ie>ysFbHdAMMxs1|pduwvP`zVZ)%X)y=Kx@%YFlR%rVBuS5@U5PRTbRw@g9%Aw12H|A$2C?d z4&Nfg_R&E$s3DB3vbMtrH;7%HM?{pPzao8*fH`EnO3HLyqRQ1kJ92xbQoc%upQG4p ztL*%`>|6PUOv#0uQYER8@I0eO;*7L+=g8yb4IlKW_*E)dbeZ)McJpqf<1!_4@12${ zt9_*_@=u|%%h>Y!Rlf0eb_fADdHT~Fp|lSi)y=aqRb*C#s0ep5fVH&{Cy;PPJkc&q zcrU47F@+Rz(gcINu9u-vc(a|lIg9^}E?t$wdeLpcLc~N&Pp8+ z>fvh> zN^IoAzIzk8XC_C|Z)9?kuc=A6!3b3GDJEt?-6!qhZ9qp%)`995r{gV#7E<3G!Q6Aj^=I? zqP+pa#kH%e&H*0i3QHup6}@@tj_fU(r%qn`OHXUC=fe^PZvmH%D< zj3gpP&;mxJU%Pr2`W6Wy-$v@#aBn!QrZz#9 zE{vx=Vgtk@m%`TQYD%}WuRB`cF_f`*osWO0~n|YhuRQq}2eXB{Z7~A54{>q+t zjKM+Gs7k#-u+OopV3g3oI8h%|*U=2uO0S1CZ-x1^>yXqH#sV(HmSgBjoNiMf2Z>Xe zCIB4GO&*oy=`d>EB$$N+_O?zKIh7k_$teeHMEb4jUVsVo&F4b^oedJrM3ejkbD0r<5=lFb{ zPelGrqOP-E9u{-<4U73R`YFy({qbht`sp8^EED7WmAE8ydC}!g+J>7G)J{u}W-rIq zpw9;^3wXquYtOq``RP-x@uJ_SC>SEYyUCZ^|1>;<@ZyF@hh2p(uI)EIvL zRd1bRtz^5rdeGe2vbH|Sq6cBIT3{hxG4AYHZfR}Z$aL(K*16m%jBP=)OnWyxrhX`% zMhZ1GbrzvCvv$K!>B^qTW=!z&%;uGq69=DIU^DfZLnu32DeUSGP!aM~*wN(V`Z{Q% zp3qY1q{G$h)UDy&MYBJc^#{xH+vu#MqL9XP52(rMFt5~FrejC`7@3SupeYYF)~4G>oG=Wzp4ve*?KCsOZ7|5P}3&0pxg>g zzpwvoW{v%~nKcYsoXZMpUF|Rgm*@P0Mva#d)U)`D0aPg-C(k|5xnR~Ie)Dj$;gN-6 z-KaO`5KH;GIc6j74{ddsMgx<2f0|0{RP(p9@jjc80B|@3l*8m2tf^>5T?6BSZA~~_Z;jNki z%7J)8D)Ndz1v_r98IpIxP6^m<%09d1rgl^+<*v4@3C{(_`fk%rP+Lw2MSeSqc zy8k4AUcei%+f1I%K0mJv1%aTF7Y$hv6K&GLe=c}#GMhZP%)cd%O!hzCwk}Ph@>ya5 zYFn*cUx#@4j1Ql1OmXLg`d{k}6M!|VB(h}yO+-aEM6txKgFWE~$YkUZ@y($E+Cj|~ z-$K8jeCzXfy3*!1`RYxehETmSPFIoU1`C@JT81hx2dbh{HgiMBx}D!HhE@K09%-iJ z_S?TUq-E{%fYL_?wRljvVQ4`xb4J`w!+C=-Rd+Ar6D-MjTz%&1)-GC9YGj__v0SGb zAqvn1^T8yQUd)+E+Aw)XOF}x3_Puml@wQ(*ppWRE!b*G5IIWc0qcADmcG1KXHgSNf+$h_AW9yjzN7P$3#1$=DyJ)cB?(S~E-6gm~aEIUo zC%8*!v~hQr;2xaDY1}oq6a4ns_q_XjS-)V-Sv5!1cm|&wkMr51`$Dpkl_5k^drUh$ z^-3WszsC|W;X(g=O1*&na-Q(AX84;==w=MFuzy|YO-CnQ(e?C`KaD;-vH3m3xb*^b z7X8R(54=Ztc=Ynf33D>7aZzFxK>+#YXC)hmKV%Z=lz zI$Xg{wkp1T%kTj!~|#VfBjSkF8uwL`+ph$@j{pDPM7 z14veS?gps%S1P?n2DW@Cnb!5wDKxa5Jy z0HHHW_5_gt$_-({Z;%y0+Z5zWOKs^U%tn=(RIj%O8%O9h8W$yCA5y}hweDcUWn~a+ zC97TpF30%d{N|V5Q%(?(e{Usm>f*zf=&&$-Txmwoai!LaqaKm1U~ZehbiDqszSU*n z=Ptzt%|WiYdv}9rN7D8k5*4e1y7|3zy(fTp`5naBPI$^<=9U&=SU)q#oSACYZN51z zQXtD0_xUzLF_7bTm(){tmhKSI=_1kye&W@hy8)S*UX%WJ0?muZLO8wdFHFPhTzmFI zfBy-1o?H*SyZr3R#o6;hC{<89O`*j^M0m9)7HfS#eC-2=85k7 z;_&n9&N1@(UM4RbZ=?>V9S`{!s#b?S*yP9$K|!6v*bi#wS~)2s)o-2hUS?Wi`^0}R z@4Y|K=Y7L{bA5Af&2Y0j0Catg1o9m(&*H8ezX$Q^9qjLac93luW~$MzTO#=zVVAl5 zy|v5VGu~(SFMkL>{m4l9VC>OW{nj~TeelI7+eAajth1ZGBZ-KgC;Kp8)@#oC=|9U6Z%_g% z-?3YUgS+*SpgI&6Hqnq!9ViIBhN|SS#tiC`!)Dbt3f+zy8g*%PKGo=+!GOpVMVbnRb2xphOq;ceqDA z?rJRnW@(bWOAp$Jr24SgpYn31=s)>V!lDSXZBPNu2`tx(Hs8@@v&f-t*>d=--*n?y zfH6C{&_%vdAk0NQE(@r<&>Px>lI?&oN77fa$n$O2cyznCeMtaEf`($g3Lx%5(Q=(1 zS9&&Zo$osLDkde}J0vAzjkVQ_PkD{h*hVX7UM|tw!&O#Mih#1cUu*f&`jh)GD1y=k ztRU~C_Z`~B+1>K}Rqg4uCb`qlMqXca0Mn88_?7GDKW-lqfezo#Wg{uVb8}JJRwUT^ zT-(hCMK}5(tP22Fx5mSr@ZV%0&3Y=4mX@+M^9~qv zrzN8fyA|FHmo9w>IA$#G`t|hkpV!9~x;G>aq_xZUDa5q}#{FT9m6368Sr=R7!IGltl4^f%gCa|?S~r-f9D%n(jN$6Mn_I=(%sA* z=kc*eE5XB58Q2b@iBH^eYHbFqX@4n`6Yl8Py?FRk75?@RQMAWfwTMV&ZR<%r(f6yS zx5qa|QyAh)zVap~a;tW|sIB|Y#_R5_iF;>vk$!x*q>z>@?jzP&)71X0@xhO`Tx`T7 zw;Eb3T^a1Zzq;(>yY=fhv1QFpt&4?Ph!}@_kB!jlB0su%`2Z2Yi?Qqf<-@4|mk)!M z0=E4=OzW*h*Ds-wj<#4k!z3V)9Hv*Fv8#rs5G+L{P4h@(tYrd_4uMND1|o;jMtg)J z3*WyD4BE+uem}^>tdbAy53Qpq~=sU^d>zbFZW zPCnbw%wFlZPHPb{LS7AwZidiSG1>zx{n{~#rdlG}RGeRm3)t(2D1Fr8PfBD-(!?3A?h!jP$yydO5jLRn=8q3n7++YwXEJI=D>AzVB{mymG#&QSRLb^Bnb||{=;B83{CV6IaK^VOGs@0M zUb4xsrki%W>HGC*CTZ1L+Zy~SBw%X z9Lb6YRVP!%;QT6pfxC*tr<;!GeqMbRXE@xaIOvcF7tF&@4-?8-^u;;Gd+R$8^Y~IqWDG-2YGGCj3Bf4dA0Np9 zfDD|nIQHbZsc{gUg{e4XQzy(bj&jJoknFGE*XUvk7Bu?-R;YCa+*JP8nBgN4nhLGI zKDlxFL3x@Qg@hUAjO<9ZP$h$GB!mA(6WTnj^>Nq-i4-UlsVI({_#l&=394d}SpUuq z<=cVz`H}1#6AH!#c6XW$qZO4@jYr4fKY|Djh}yvfuURTK`#L`3Ryc)wE!h$cwr6>YA2AZ{ z+5ekdCfp0;i1w`_tBesTKoEv%M@MHUa@4r!;>i$7{7g|6-J-_=>-!Vc%SeM6##q6C zQ)!{1PWxd==zW z6c8Xun`lOs;fzl8z2Py>9)Ql~(X?qJzmVlL_cj*Aer3H5hby{gmfV*<}c!rLmnZqyEx$U^=_Ity+<1>GjO{ET6O~Y?kv47Wz zPfZ^;_{Tig&L7)9x9fTzzeDJQp!*e+2Z&8&iLb7G?c5feB-n6?;#pyo{hdR79fom+ zrw+nokKlkoAJRE`nsDfO?7qATB@c`|-8eU`v|~e49J}NT?WgmRxr8-<{1yyR;<+~tGmUtZQU;pD0o<6Wndb~3Hq=m6{$7Y3ffZ@{9Bx?0{O7lB?o)X zDbVNK(Uu^4W@6YAF>WGNEWrH2CD5kiY0?e{@bV)m$6bZ!QDk17OEy+&(c(X4yM_kB zy+6xKa$-q@SLR&EBGG1G`p+~m(G4EQh}s8je^U4FTN_)@Vzk?qFV*4tS)^ZpB2`!f z>-eq6BA4%qmPz}z)KJDX(kZ9VG*^K^y})*R7ou#Rl&=vmp?R`Yq$-k5R&s5cMD~Zm zB#qPXZgumk5JH{$=P0W2$XYAOrgEkFlpx{CAj2_(geZMJC-!+Kh2ThtNc^>BBT37c z&0VMEamZ9ry;b7^@m84vqA`*(`C72W332;gy^@fYl>KpXhRuy{8dC%-gMny@fm+)D zh%8eC-p}I{HAHZvdDaTMl9ea=rI{lhHn@{eIJ~$BOJ;0L)jbOmh?+a~n6_U9vu0I< z5Ptu;T`yE`lCD0YL6`H%f^}YR1AA1A<8cJ^`$aU>t8Dh~iR1KCyZ>pxA^l~++IKn+ z-=dUg2KcO$%6^j60ME3+V9#%Q=w6Q6D+(?>K7C7@A582&Zx~;CEIXVZ)w%-PZ1g^U>&qh;x2Nv;MfU-*Tdd_ygzDg^F9s{&Oi+< zvuByKAzahSw-G*5io2iJ-O z-bIo;5Bz*S6KgO;)u zJgkH`@8>h-*85cJ)8C`K`Ul!}i<-~a6LPG4@z?EI>+y@9#hh~9v7gOUDv?`e6=2iv!Wa2MFafj-cl+BdL!d+VhLIv55Z_4(wSV6Q@dBf1jX-U#Nw4s_2Zc>NIV( zVd1prKW$&puHL}Yd?LLVZ4IPRGz|a~%Fw1GWKpg~$I3ao-e1M|_{j=%Zf{H6O=+1C z1$oe)YWEO**I)j&<<{D1%~~Yu_Z0Lf`bs4B?_%rbY{+K0tDh!#nb#u&jkCI7?|5C_ z3Rfn$|r{NX~Rjun)CLKHp^P<8s)pg<;Ek&)vN6rXfLnUF7JC7^rNE0z=2!*Y42CC zq248=3cLK?X#5?~UN9e*HqcR}lsw;Ow$* zO)n!wU2Kv@CyWwH9>p8 zp)W=xuJf=M4Z_s)^wVP9&rrL}_lge!q5mxgKYt4n>e1(T%qf`24#<1*Gs)UoSKCw7 zVA9bj##=>ZxZ!=Mu)otQckiz8!YwX&$(F z5Kh12(e0!OUMWP9o7NU9ax)6GsQ<0#i=?3bNr_A^%2Kg(P0v*eX9Qt#1wn{;|7#*P{Q{HsCGgKLqZlPw?b`kdBo){&#j!<*%@JTov*Qt7nILnMnNV+wq+p>; z#KffxPV?GN?4LCKW?=ex}^(*@83KgK&gf`LaG5y=(SYkX)m*Wo`7Du_%?7LKiFD}7C-TTSobQ`2;EML4~x3k%w_tHKORBCqpjj!`BGX!GISFM{jN z#Z+UxuvZk47leA!10N*}2F!taw@daiv5mkm()v{{)0^JyL==H{`GdDQ|Iy1=uyYge z=;w*p(0{Ib$S1Ex?d&k28d7Lu3Xeqe>Y>oO=~z zgy-qIZBoNsILdGaONuz-@6z@xy6fTwa^lXC!p-sI2`4}f$2d}(<=wvh zk&B7izm3z<%+!1e+;lu#;g}|9&*#Y}5VubZo0t%vh1+J;#ZB!3CL#{3dEfDDN&B<# zA2Q>h4hvIJ?&S%eM0q*76h~Rm4UYsI)>o`c@cL+Sl4-(`YmZYb?fqeqxJY7-7RetO zw;!&|VG73}S14B@e=#HTkz=xe-1#v$cnBqCcb0Mb1=?kdNcTshYvg<@n=|DWq?{7x z)@cg-y3Km;n}|k&Vdh+MoR9s8C*l%@)T7OM=r$TVHLbZf$76<*S&H+LhUggK3iA*Z z>9MP$-RvC2rrOp}e-%Q51UAogTTm+ROjhP+@nDbmU;;Rvh*ZR^U$n}P49SE82L{o5 zrykB9GuY~)&T&tX$#N&_1*ttl|3Ur3pE1B>T{uts%hA9VFJ-b)*Z*{wU3M8b`Wi~! zaPcD>ggaw78ofSvgK1S+w5m8$U53fuPk8Vybap`k-eF6@{j1`fD=RGzo*dAZd!C*w z@8c485~a9Jh+9YtDh$zBhuMj?aBa>R)m{32hBvN&k0&hLYFo zpKw3*;@mrt9GUoZH?QjR{wiR=oOPZS8>{rZ>J2euF4figc>kCx5ON2$FGJiF4Qwd* zYapoV7rph##c7TsNWbO(;`|;#{&5w_5*c938R4qxF&S@F-6tCabmwU?b*Y}@#8rlu zw5@q_3~(VZjXiMhpkr5BcMn#(%`BAxmPtB)vIf$n!nN1+!r9TfDaU?FBsaR2>wR(l z$JhJzqCD?kiIEGb0{?Rs#5Cw(Pyh2)P+}TH;sWqnKFfxX1*9y6?W&+Rx*c-P=_^XF zswB*>akvn;*v2u{Gs0-pzt|hkj9CUHDng8kDsZ*!#u?){;I{>eK0Wqob&OJI7xYr1 zl%T~Bve3csKm>~s9E1qYGxQGc>Z@yb0W!Rzu@wnd0=Hw_Z`qN~kgG|_RhTIb_}3$er0i;=Viz6n+Z z1M-S0xt4&(W0I%f_V#Y;rj?uR6F^nfMD?5JAZjLWnw6EnS+oYCwy{y~p)=$i-1G59 zemho{*9iQL!wOeljRK(xZy)A7db@arhc}p9ELUTT#68u{u(K_v5TX?MAWZZ}9@O7l z%aPyaTm~fK*O^#0Lej;!8BgTqqfO@|<*n`?xQAzwEO0NHH|Hqc2Lv!MNk}pgJO;II zg=TI#G04;a={icmjK2^KxQmI@nTWq?HhqNPnMq4-wXOlTUgJ%Uem;|b*aW`u1;!gp zjP?D$r}EF@|JDa}ac5IX@xdaDQtALoYFzHLXGv~$T+w4K72@!!>?l5a(AIghlVnLg ztxMgc3d#-I%*N!Qxes!&N`lg%zo09EinSk=dnZR& z`7r712kJMH->?9EzLwkjO_qCvN8<}I36%+-3~pU%s5HMhZ1#EihD<170IWtR5?An@ zm@!ccZBnOP|LlvFXeAU=V`_bNy;%yW{+kM=V4(*6c^!2G2-run1hHca*?H;z6QMED z#+aDqM#cy~IT(#9i!e9K_~c$;hbT^zxhJb5jlsOFW`7eP=mSfdHW4r5PZC<~TERd& zp0zmMkA^a_uufyekvq&qSxji1aB9knlz||abDL`Ngx_|_V8DTYnEC5zXtnKW`mvka zf;fq!P`%m6F9B{_&Bn@eu-a^cW18q7Y=&m0BENFsJYpg;F$1!=8F&}v78ssB(9ep1 zLgQdOk}BUIZVf^%C2Z0;SxM7Q_CXaf&aQMZ~m%^ny-*Y*|up9(CAyi z-WtBC;PnWUcOMjUhj+lAK}Gn+bl`q##N^(vSg z%OpjzTunBsf4om7BdTutj$eLUliv#UemJ*vbUZu5^yd9GpI+P@YsIS{$$3?l`10*^M@DIRhVy}zGUGVuYepdex*Vh zp<3z!w`4RmUs%onPJY7898q{hnXqA!AIr3AQIv^dL_E!)srrG@j6VG|_1-3zgqHzoo6&l=(vM`TL=7eftRPb_ogSH#_lBt${s@BmWpXqdkKqr1QnQczK-p%LT><)2Pd;Y!^=q6s`<5Rx<^P{swIFPyE zW`myvRlUT|;u}#i!|ee)etJ42U^-)3>@E8gpB-p_P*bMr~Sk6dn5xlbKjG*YQZj0HFIo$8+WQK`fIqAD8@@#bWMwy%&bO6 z*}%z9n^X?d)hYkIrNA7q=TNcTZm&&RN!7nq|0@?gU(Wn*d3Xlg^y^2nAXpjG;Y4`y zs>|w6A_*$eo+m}en48;0>~|SSjwzMWBli1-jARjt=yb| zcwBlTX3}`r<5D48la`gv&W8#*!O$l71^LV$!Sd%8)cc!!nd4Uv!{|N|4@W7m82joe zz79{kPT(q~O~JDjN{`qPqiIPX4}Bu|Pwlf*QP42QU4EJwm;+3ViJYZeiQ#b>RpyF2 zATL9UT>*$q`sT>;5&U5G2{{8vt0ZnnW?qtWVvhvr*JDy2<4#+#PqD#FInwAuf?qfz zb5InO_NVL(MnP=3u7vZ$I2Pp@z)#HAJ6(4p_4QCJ$-1gt>$ zCf7+Nc6ngZNpm2@z{&y;!N$hOiTNCjpp=A$czndBW{2mJCqx5zlhm~g{k9Bm*@+!U zF0y+vwkGU*!={_KDLKV{WpFk6U7Ya9p@Nu#;ftHUnLLY~_ydf#E2|n)4c|$Zgu_mK z^^A_MAtP1SZGYr?*K_vT(exYzQ@8<>Myf)v#kgG@viv9L1}gc9Ki@_Kq=R@NW?k0l zr(<~qsY-o%Njpl6cm;EkEu#g^FHQMkaR?bS*Vc*LF=+GyXegGPCI`G<<1_2j5pR@U9W$EzI=7#KDv*!@&D-D z+DlIAQB?Uxrx*Q-R#)WvH)-TJ9Vkf^k~TiKlvja%sQ+`l3B$1x)=13p#&sPhLutof zkUHpFw*~!EI?ROqpOnvHNqecMDD2YP{h@NN554cCJx|QNvK6>ZxP%xV{}o3kPAyIU zM;)RMP@o;Ll~|dP{wFy__xSY5Tc}A#cE~;FtD+=WyANfvpB4l3a|tU>f_Jr`=^>RY z7sbA12hl0GoZ4XKSU*WBDf3=3I!QQ2Vj>1EGE%~uQt}hSldXP<#DQf!Ulc3 zf3aN5VWVLz+;X~^|BhO$8tvC8#C{s_I0Zg=L>(^uR45-cTrCH;aH9geO556(cB@K6 zQtduKgoJijLVSl6WXA!iJ9BDL<#%OR$aOGt?j)3VZ zmEC)bIZho8Uxx~XT1j#LfeP!`vSfInX6fayg^I>a0~JGf(lWAS>1#F3MgNXWC{yN0 zz6~zhpPSNE(tCCm-5=8-w!G4GF$qLgX&Oj|(O^cHtV(a1Y@t6Z!rr5UWzM(H_eyCk zsIZuY^b}i&nL-YiBx$5!YF+s@^1BS5@>W17duRG}`JK=#cts zt>r;=#xFif6Vl(7`_?5HX3XsLc61UBi<^fM5n2?9P;zA8c&frWP|DDvf>YD}o5+O6 z57va3Ytdui-^x)2AGgWNFyFtW-X|ep|L3R(O6xIup(ow{NTZNZS>oLPUHeJRT<*<3 z64K|41~oba3__@YruJq?V{G~63-Yxo$*bMS2Jt=8sT)N1aqvkn6iKJ%;d!X7&)1YCX>CA)HV;8EaVkiOdg302Q$uhDf zh)6|=`}~aCR4JL@+*bd0BNz|2E45(doYYJXZ_ENbt42xY1V0W3Qnia_7-?iK2QV+} z0Pbv9Gr2P|Jgu<4K*=%31R$$u(n6+TFJ9T>v&&+GWy-MryQU)632in5c7dV*8ZBN~ zf-fnY<^xu=I;+xfin`25hb5giSGCS&aff}0ZE2l<@gL#2yXFgdkS;x%71UrV_2t-K z*y7ypqaooG4OjtRsm(Po{uRR4-6Jho~Lo;IoCnlySc`_?7Br0dz;M_3NZ*D%VCq$W`ImUU6-+U zFOurr5`*yWJguA)YU07}uG2^l2p0N+4RoKs$z+s)Ttz9XLQB;6xU&oxt=xZ3l@F{K zS&>8 zAa|Eh4j)qL+Ti=&pSSi4<#ladpRCyMxOem&O^Y5qJVZb&#nZp3yN_*a23=4OeXMio z4h84C8#;YJzRTBFyV*XO-c}OixDy+JBUKxu3*&<$swpxU?d`q_C+EQ>BcHnU{SN<{)7}}_kKXeVAo_Al4YpEHVz}u%+o{F_YJr9!5eCoq z6Igm1@!&v-XQq(FTN}dDx)S0Of&5?AMrsb3Zc0M+&8cr@K27EX63!xZY41(6vY2fh z!bf~PPpI!he7*fcbh;w3Q)O3+|IvjM=-<4K|6e&^v{JMOge*!{#OV7MBmg4*G(e@0 z6z4{y9ik>7-jR9pW5Gu^?3db&W*nL8=g`^L;M|*)^Bb^W>*`#0!A{{ML221jQX5ep z?C0`*6l8Ay2#zAW%)t^&`-h|X3&tD{+ha06B_u{u zp=oHHLl}A!fCQ{QjMw@)Hj)8l&(i1mI7#udq5(z@=goPF> z!j%BB&ot1wC@#x}Jg1Y(TPGSyXcNg9k#gslj^=Pvj?}3*iX$wSv8FgP{!f~f=Om7M zS=E-9D5CnwHnYCu1Ql6I0t@U9-8&d&NG#5h-^s4K5$8Dhe(Oowi$% z8eTS^I*{F}qqOhFx*bZZ^I`AraL67 zq-3m1Tgofnwu*O_=f6i`tTt-I-u(Nkxw?}8Q7dh0iY^woW@AkqW|qQ!k3a=P3I*_@ zZT!7BW3^Q(szdPgKrCX<*|-nRdJCCjgfOp!L@kb~vKLW3HQkfcZeDkCt69P7;kzv-_D^){A+PVPbDL+o$Kn-JRktyZ~_-*SVBt|v=#m*bora3pkPl`>Z+6wgp9HktkMjAHi_9k ze-PTetvxDXD)lK(kZ7m%G)JnT!!=lRKO^KD$^K@-R(7=NG2RMp4DbL8HHzIR6 zmmjVMMDLF+pUf3l2e9gk4%TD$7Z%))su6u`gcZUsA~~{jt|(r{Xj$FM654U=mbG)5 z7+-DHz0=Jnm#qgrV?S@?*85*(RiPfG;|oJtAJC71;(7<{jnC}ZC6mf)K%dNLhPzFS z2BV$%7;v@0b9p^3$bCzuQ4e3O49LLUL3T5L8~vLE;Dra6ZlUX(KrPNzKOArT3Bnmj zroU{w+jhC-(zK<7telT@ZiszI4pujj5p~k;oE*mY9Ny$ZfwDKurtj~0y&`d02Uea4 zMN{|XleD=vv`P(zZ;C@(UyWoiy{eIjIEGwGZYQ7yY1k0$W!qk$pn{+jq$RWrkayyPCAcl7Rprlt0Q{7RUC4- zdWPpq-F^g4IrR>1T%yceOf`}~!VkDH+S)oS*-|Ga->pJxava=?Z3G(= ztj1=mt-lZJt^V63$DSeLgE(|vn^b)s|6+exV_p5uBLAxSA@Sq?9}g&e2>*9uqF5Sb zw@8Q26#R?gyQ2)RI0~^_3;WKYV9=gittPly`nAm}O`)>iMZ^5{ooj$p(8;MaXoFr< zE3Q)in+G`yOG^?*j54a-x)_2P&>9OwtgEi5jUh86E6E&urCd9xq_I`dA{xNSyoTP~SX51Gz;9l3}7W!ek;OR*Q{vFSl;F!Tja^j- zG3}ZiIy%T4xv%S_ZJ^F~?aa=X2|FM>wDPr6(BB+doY+}^@k(oP<@fM))=zdsaQy!0 zU)_%Z<>;3A(fzPily$5NVW**DXpzT;^||k zp?uHBvSvQ5I;;~?MA=1;@J)W99cHuo1^#Q@+O2zk6}z39FN4g($+=RW4-NeP_fMw} zu*>;e{h!A$dxZsSg_gw7m{J7=ZMgxbKxrDFOha}kj$KXT=0QvSoaEHUN7v(Zs|ZG( zw2Ocksff$T@%$^Def@7q6v{yiDk*ZaJs^A(44pKo=~ZS$0l^sy4YmQ8*CBXS9qaSY zpO5Fko-@1hc0l}VE)Aiih_UhqJy?ztp2O?%y$Ippeg1_^W={;DH-(9f9^JsOX6cv| zogP2|M%-+vvhoqBA$joQXujr9emDQnK2D#)HT|QGug5oYm{|yDx&bZUcQNz}xxXm}6!>70>NZK|^N=TZpGt!=E zj+8qXJ}L(Cc92mVYMG(JKc>m9S4qrYfG-^=#cKv9kqKGH4nI;liZ5~lZ473Qk9i6v z#(mpl=u0NyS5(g)mzo1jbEhCmsRj}FN4AOa^}jKpEuB*FW=sntn7N~em@0Y0n$@2M+D5jz8wT;~KAEB@|x05vE z5Jvls(9bs$$f$U_esdNe?l)^>hiiKO8W?+}G_Q*(arqYbZT1bdPlQU3s&~(mqxn4- z10U*8f_sF8;QYh{(KNlNif)~r%JB3R(iiQje%y)OY$A}mdAg3x(>o{n|6encIQ#!z zwR0Lq*^=SV9;##VU^vcgKF+ub>2`w0zZIM)mqpol@?6E0lyivFG?0}x#<3OGxZD;Q z*%nrh3|s2{fvm8QgqR7DgEoSN28>a?v)JmJR0BkHI622(rYe?Se4)mVLv=pjwKZy`w%B2G6TZsat54g0-l8TDOW>IASEW)V{(Z zt+1FhH2&EePDv&ewXz0ydcip6gRg%vVkX;5oiv0(XI| z&QNkV?c10=83z_@!TC~BUyU%SQ=F32g*`C5Yr#;7n*^wGI<&h$h5i0bH_d8@n0kkv zqhyR0Y@0;)3vtp@*4`|W`Id(kyN2#U3?DaP|fj?K46mQ4=s>Jpz8v4IVc z{v(3_3O6LG<``|myTP#iW&oJoPd6{*CrF$SVG}s7TjUOWeZ`%7^cI`uoz|$({My$oRHDVkUYk)m4mxM8gJc!>{N6d~mty;JH0e%c zvMD4(mo}JdR#G+(Spj-`i%&4LWmjX7n8X^KXCU=yiWY zZ2#h(7c4BL4n}-z_{ymfdiAJ-U`6r)UY%Y=jDX4?QIo`%rm?Pg_Q7qeWvK5Z(57oNwbc$5Ioi>*#-2uGkVsq z2ptYeV-c5XDv+iBijAgKqnB7Pjsh%x{o78%Kmuw|^p@jI1fr`fEgKb+-nuchN7?#t z*QBLN)r8DOqUtjUV#;8v)hfU+)6Uyqni9z$ms_7~Ln94H%V)ZTp;1@wU(PAk0BaR` z^hFJ8#?GQEgByg&s|Gtf{J%+r z_KK7|FV{=pHr^PQmCFlyi(oH;|Cev|FCLXDnk*trE?` z$Lty`Plac)=`^l(&9-Pz>Dvn82~r;S%=wp~D*1yW0MTQIa%fuOEF@l6H|a!G#2ff% zhep`~m{-0sJzXyHoqs5%lNM~+f#1*7*xV-zN1Bv=&tBs&x4DLG}U!I zL;Bo(PEKw#Yvp_I1K6C>22F|-4M@|z3$aa~|Cq3DI=H__-H3yv6MmVG_)976CHnps za)buSZ*GQA*1^g)2E-9!c6Vsj;4K?t=nVav-|K-pKgrKcVuYG=`75I!bF81La1LH) zZC@DF?!L0}R)ssq#7SlvjyUWYC3_-_?fO2P34A=Me!$)Huzs|Dbk=b?s|fu6ZAr4q z^T>ZS8AAAGybL5O#ZQY2<)0stt-=xaQ1ukJF854n{;Ynxc|?k$pRQQo;U9oweQLrC zbiNBK)0hj|y;@}Ka2Nmt(OsM;p1U1w( ze?-#=KN(Q&S5e0|AxegDx#qP2cK2^g2u1Ql^B<^x(vB$FDp;6y;jvmRgfxaQ8UqtP zXA^|?A=05L27?Qd&gwN_i51#3n27+wn_M|Z63!@AxR#am zjA25N_u|d4CvGN#ld0Y6VX-8vYP^TO5sVCqsf<2mT%W~Sy9(HS0@uq_xppJ(A`E9l zs%)HOo8U{U1yr1>TbJ$@fBBR?M|5sDq_wzv(lNIW!ppoK4Z0}PB4%`R@z){2a zw4jiPsq4u10*@kg1hR%_K6Vsw;*oZ`2OIj>&Y0!3a&WrUh&j2Bqo2`rv@Jc3B9iU{P=0zwDBH# zdG!CARv`bm&wsD-0;oxYO@{V@Ik{Q6x$RLrqA%|eJ-<#v$P#41FrXCb$JS2u!D}_! zm3ih}>FPm-S$~hQEPlf3BLB^p*dPJ}WXzjzT(6Rpw0)K(p5EaT{o%H`vl#h32GhF& zxLX)9`G!}4wz`!Rz(&8aJE4I&fpUbOfG97J2G+suSi!2g3VtW_TAQ?j&=7Gt(}YOK zt{HgXz+|b-X{k0OJzHser>tO0=5Q&%iYJ)MJSU4&VR68Dnk$%w8$k1ug=qGYoNh&6 zIcWbg9T07PlDm=S(OO%FUj}a0LW4R=v~Qo*LFpv7X0~vlLJhtvIq{p@tcH?Xr8HB? zL$!rLX-gL|WpGNLFL*elZ*pE5V(azn0c)h(-m*>vPD7F*3RBNl22a?b57;&sE{R&PD=( zG*HnPC53DY936Pdto?T_rVg-Pzq8!^FU}w&C~vs=Vo3P-cKw&M7AY1F^b3~dl<~!6 zIhOC!U%8*n;7#-cN+J&kkAr2{5PdDJ=GKCLleO_4H6km4aEFkr;-~h3tA$?R!Gs~9 z#+nqjPwJd;3i;kL8OK!#Ud+IMOyiaUbQpk$OC9d+bfxHsae(+eRg_9~LAzU2;%4p6 zDWFcD^f2UeAXux%lm#YMD$!`mBdd)!^j~%)W|!Yn#$RT`XR|#2DsV#yZRfZkB;2+5 zp47$$-RI^Q3I9m9xgXi#bfB?T?^3xV_~LF85%*ytw=n$NI{A|)4b+l}NZOrQ#L_HPZEO^fA?g3e)LBNwwQbuvSg;V>Ex5b81Ss6y z3GS{z65I(+74Git?(PuW-Q8cEbMM~o{;5{=vsxQ#t}(~x{p%5i%Go9R2M;o`nZ|?~ z??f8Zq(!JrxRgLg6~7xc)F&U6VQIEJqjDZv(Bdf^=&cM$Y4F&{^c7C5Pg311|Ee%k z2%N-rZ?F0H`N9A2Uvrj0#lPc43;x>(!iP5eDgJ9ss+> zp(N!WXn1)hT-zF>rv&d{UHmx{b3%KNDkAG%@7-4q#X2$$9z}7se2Tv}SE$nEny~J; zVFB!JeNglME1I)6vO~8@QL%9ZjDH}AV9Vn!s0eDw*rchI0k#h0xun*=4X z-MG*BO>I&x z9`2Bnc&2{_3V&Wt(Qg<>@_C|mpJ-<7Vmdh@}WKao?cp6Fr8sw9F^9L(-^SZn7VLZlSvqQsiD{S$p7qQu2R%&UPP1HO&QV0>bO z2TlZ>hHw9U8)cO~xlrj4uNg*uonfMUUo@lik@JF|ltX98Y`C)dn6#^F}{I#{bg^A(Dsy@@Miqf z3d|^PH89O{-O15c2QP?t1*l7_E-97H1g^EXHy>-)VW3 z;8fN?g1EWDMl_JshajCJeHfT!N7L|`TK7{zIORq`aU_@>+5qHb>z+JDK%&z7H*6qP z5L3bakCZqs{vGvStBLY?dI!O!VjC^%!afK;F}#Je#tg>tS9J`RYR*)941OM@{!xZR z%5l+zWzVv6HKTnTw1k?h1no{JqQa(2;d_f6I4=_-cej=jq5c&xybB zCWqACf7%CTw05He1(*|cAy3moL25li&?&0lN$C{MB{ZG#$085C43y~ikRm8QN6?b1 z7#RUQB%55`xp37aFM65L_u#q!!hu9P`SW_d`~XJ|$4Q)V7ftxE-kZ9G z>T;wiGokIAZ8K3ju`gN-!yw&wCpZq^(iJ%X`sW0=nyGBrNHe#4GU~9#X;DBr530p& z>`TNXZ6IkNz@!Ur`v)+mNHWW@A)EX_^RNULhlXqj;jCH2L~_~a?}l@&uU~!o$(AyN z#MV6LkynX$e+XFrL?ydRkzzSQybTK5EXk{ORj0k-H#Kd-r${RisZ2Z{F{5`sccUx7 zU-x}ssmC~XOW$)Fd@}nH=O0Wi+2L9h7sjz>*iTb326qfxWG{ve2G)exg8!cIy&<9F zq$8xds?As=f|66dN<(Y z=W-hvP|qzA>cA{EOFtmu%7d%w8{xMYg@NZauYY zlYO@)9buLq56}PC3M~9HRf!A#V{#-`0YiihQ;fiCnuwhJ0;!}_4CQuqYbMORD6J_T zu59d(#y+%BkWexQ0-|!At9%lNNd!@s3UbT9^lcMST_oN0MB4M1mJw+a*JE8++65vZ zo(^4sLWi_9V`ZJ19(HvSW$#lCcBBZbJ#!}tc)Ko9+M`TNXz@FvG& z2vWYVQ_z*I$Z72yr`ll zgZQi8!bKS7PQ?o>ivx-|YuauLzcDIN>J`d8}%Jaq`R;q z=nRTW_IQq|bq3 z7+2@=($MC17Qkc0EdG5v;)Y4?qH2Tnlf!nCjZt|FRb_dtit1j-xCx6#}So>4gk&B zn{+zF@{B%-a%Uq^EH9fXPL`j+@(3XzZU@btTU!gB9RgdN{eyM$y&5L~Z9m0yevzM= z_MzZ$g9xC&!?TPXo@$(t$n*J=z=^44SKjaK1W;$;1E|#8 zlWJ_}Akr)YbxjdL?a5A#PlsD*U(FB$s2j|6RQqj^vWzqYY3wj8x zD8FrI#Im6gI*nC}7nHc*VC{QEdKD!@7*!@%sm6_i=fS+YKMAk6z;PONz^}q>v7g1k zD!yt#*{V2P-qVyhMbR(C{>s%?T1CuQs3#r!t+15 z+wWl5jBQ1lSTMz@*-M+4gr{1V=c5y(iQAYZr&=*6q!q(S^EXR&9^G@~=x@tRqA-Vy zrX$1T4D&t5|{8gvie&EbPjUL^|wQ4aMPaTUE&{d z0h^I%JRWz$?cYZ?wjbUf{U*6S=83QBD}4e!ZaZK79?u&;l2XAu%(F3XP8G4Lg>Iib zlI|b*qmnA&RaaR9aZ(I5^li#Tw?D!|6~F z*3T=A$_YJO-*~WKiNJmm`?eb*Y_+HNTJyHoQ)4hOy}5qm<^&&XwYh$WCBED}YxH>D zg-7e|4MCn(ZeAhus9IAFzkaSVu3d9*brrV<+iaxEJrr^u+}uZfefr1(QwtF0mJA3^+w-oI^(y7rXvY+5%^EVibb zv{~3vJI^*RpA3XaayHy_6uEq7C;Rv*XVJwdI&0G`uuBy>Mig6y?0X1Pe-V5*s&;n) z1Od@&J{L&_7bT*jfxwD||E#g_i1BF_CIa5?RwT9&M<$F|=%zvD0&=H2#_K|gXo)4} zN@jY3BmdNco{ToBul1i8UiX&h^5{%>Yso3X_ee<`{5S4DJ|`85P70WSaWt$?nNn zg_F$?j~FI3%i&7UJ}vD>OPivJ=PQQi|K|L(_Fx_HXn0SJj(}Qszwb)Jj9#{2QBIO$ zbAVKDN28K0nGHm@ZV9Zn16t<;a_?Yra!Gob*}eEsSd_%Um~l(c^%7G2aVX7Awc^rR z3g}xsd2Shkt+!co4!1tFA(|r8>WuPyb)BUpJ@0QF2TlB!-i-W8)oFH^k-oO(Pa1Bm ziQohayI^%AHv1x0DmH6B85*$jP61e7^Ybpww{`I6{>MIVp}CiO&44Cm`T;xPkn~n; zyBmGFGb%?L&F8mTB01A)Z?xWiXOb=~K7tQyZts5_2#T*{CWQYWt z^286oMXsBcx!0JtHk|~;!tGUl0frG`jyh%O94z0MmsuOn^f>wS4hp@Kfbp3#i%A`7 z$?QVn#m0Ri;wVo~&{PrUe}kdK#4MlV$cy(ehzoukxt~*F(;Kkl~Gjh%-5E2 zc0tovM}e0YLV8@fGSnWkjFj*i9d&K4wmtKvY1Z{op*v#TeVv5r(8CADX44E+it6T}7l$_{$n%1)4$Jmj+$C7<+kEH#P6(?2Eb=`(e!jB}y zu-V~cA)$zVufRyA&C=$s>TiphM=OrFV2lInpl*%FV{+$GFM(|eGDGvH41iLP-%~Zu z=`e!nFES7Iw=kvs)NB=oU&0ZQn&i!af5bb@YLzK38C1Mw-E&?nod`KzJ%8Cyqg}AE zFXbS#Q`MPU9_(&jG2*7Kt&&tv_4syWT;RFg&{3ebfpQX_mC#~Z(i2S2wP2*r0I?Pj z)m6Z49lY(JF^9hr(fE|;_q4+A`yz0jl-QeHy=?Qisqq2xG1$1iRl4!W@xJ5t*m=|F zcbClH+OgkFZ&MIntkfRM{qiku=7&*~KQPNRpebs1+rQtRbKD?4TnwOgNWGN~Pw}ip zxdYepMJG)_gvNYKMZ{&)iMx%Zy3F{6u(6%2X77qG@gbwFSI6LqcJ}G}eIsng-F@rp zWxvdQE8)k9!8^>x+j*m>hbPQdTZgY)@}@&$ZYA9USg72(Mp&=TIU5M5qDQO*i@hA( zqVFeaV}#_PPR{WT4K49n@k>{h1Vfc(-=Ab0LQUkjf8@bw@TcMB&hS^jCmPyH0H{$P zrta zgP&LAM~B=0as7Sx{hx_CR1rB2y2vQ4A$LDi@;6;2xLdKK)FLS2-5mChEOi9d+Qa+D0!~3yw7f}BxLGdZ7ZCfvFX6EW zJc0*BJ4#h3b71Z;j=1zXTtNVvm9b?E;W%}?+iDu1bUTAGfT$1#Kq&>=OCwk9ZI7!Q zt`q^?ABuLX|A!Q+MGoW%AgJjmMumyBcYX}@GNHdyB1QuvD1HnYbyJ5z*Q<&Ko(nHR zAIli3P2reD!=e#R$CZYYE^XQZ&E*uOyBR#CG_#H|;Ok7#zQ8`nPNqaHmH{jHDs)*( zvY-3|{&b(BhD^z>8-ZW2V*)3cXwBR}#!M5%3BCn(h>iAdN)9sn$qQkuC)FEV6MQ1| zimpUva-$IZEN&+~ZpvUk2AwuWIan%zQ8rM-RWN;IEc@-suz5l&Pch^Pe}|{U9$xGB zZdrNnw^GxbMW8l5XXbZ%99YnuuIm>~9TMG?$pp_}Q~TAkize%zt9XR{sBR$x{0d;< zaNpAt3>Q;Hv*%sf-8oimmdBJ*Nmq4bb7C2)sA-fg;i;07l0)s5b>}nFWAevwBeziJ zu9vH+3M=hyDBOcC-fnXFvfeoc%xOcS`E5Nea`lof%3o1aRy9pU&KX6H{Fz;=&CNbh zQU|P&fIB@Reqgi5i%&+|3qGp~N2g;DE9=f2JRil@>}fA+=yK|+(!?%A*z_X-)DZvM$S zSF5+9t9Im>xz3RWcgO#s)KBo+iO4my@!Xqgp!Sf-wq*Ca&%RoDP^I@Hk>BG>=OEuj z+bCEvAE{00`_kx}ntF2ZS}645{C;2g0XksPum0Mr$kB-`6!Aysf5{;69g+Wy%CY4Ck6KO6@xa6vJ^6V0Ir3F;|9TgsaA-+QEOluZv~n$REZeMs(3a z=Xa`>7%xQ>h(rtDZgT12(9#5bDNybvALbooS2c^V(UjR6CpCgw69%L+ky@(^x}YgT zj6dvJhE5Uxdmt>(>tXq|l2p*RuN$M3qgmxF(2dX21^a zC@J;?$dmV$U_`5lUuY2};3;B^HYr{EL{An%Y-`1kJFuLacBfFap<8eXoH=GQEA&^!8&HjRWAE{Adb51+u zST?~QN{-dEh$taN;+QG^*8;XGCkCr64zvo5hnV_`SRA|o^gO(KVPi(fYJi3OLl$nz zf#)`8^bctj5P@^D?#-VA3?NWWWR`VO4x5}px4Z*CWk;tw3DLoc!FujMN2X^C; z@}scO{>r-Zsq(Q_=*{nH@net4^KIE2e1m2Tss+bWwoDB)^;A8iv`tgrQrE33PMg%t ziz0=xIx5j=Pcr-aHsv=4OKy2H+C&7fYh$^@x|#f;krX*VQlY6MI(3J=T}HHWrld); zC%Ww32?4Lfy3P=Y8xrfeD!}3N?+=}$2Cw~@wVi7sT~3-jr`T?deCKxdsGHZvXd+8? zoR&+eSFbmd9#2;>TK4vE)&cp21oR=q;p(8q?yBd#MMJh+tz5tEws5$GUs8ri1X7{e zOOR_A*nrOwjM(b4pA>Ga<~|SR7=FM^`UKWNr5U8%=yblkyc)b$esE=U+z9$TQ+*(2 z`n?+e$M>B+sIalU_Ru*1d(qZw@V59dg6w;X4tD4U!oo_~E&i+jjhX#-xKl#PUem#v z7@HO5)R_irNDIZh?s%rkqcEW+4;B5a*VAo{`_1jj#F$V-BNkZnD^Mq7oyp2n7r-`+ z-kfB*08TPyvlPCSb?l2o=tUR}Y8vBmrAK=dnCcI>B`5PS4fsob8;dtPbl21tk8e{F zTCJN>Op#}zJmO4JmX%wRJgN(Sm*iMSKxmejE4hoLPxTG&9QYbuy~HIc)B7+yI1oe> zEw)hVO`bWEMk_fY282sKxW zg=51kEOWGR;=eNt!yK>3wGXx^Mr6T&06G*+cA#%(N?QC^=ofPm9t7X$RU3cjt1Q>kH)0 zg`Mq@O&p9Ydq*!Cf+m-A+r-NgBWlAkeP7XgzM$^y-C(!1_y$qebxf6m7q;^A1liOp zZ3Fb~T&wNA;zIOd6FKWtx%`k1ZkaNAK)UopBZnJ4@Zv$*iR*-@TXYwlqn$6gexo0kuzp?puaPmnuVjKx)5z6! zuOnQ(K_LgpZ8r-l9nW_Ldra?(LI+GA&pP~m@1!9D@7Y@apP`m~{EsgLS85?&43cIL zGL1RAZe0^V`Yp#qx#SkY&9C+oRvf1+Zdl>#TW_tjhl=3zqw9%3CW(X-)%K7iBp>6x z=fq<%WvUe;a0p=%BX)tN)C@=l!Mp9rW&xubJ^DLX;tL{Ou$DX|rJYYmmGda<9*+_;ArG@LhqyJg-BzQ#Q{24@#(<9(&AvQ9%UI_Z zrnD-E*a35lmjX}i_|u(x4|baD4yxz9r2Vh29#5Jl^iLNrZdsL_kA@&wI<{4$+Sdn% z-d-(+WU5kLfwW$yE-xzf4L{Ao5JeZf;zrE=+Vy2#YfRBZTY>+WrOc7>lE*DWVD9;} zFK!QaxTCK%P}L!S^px-7h$!h=)vTAGC_8;3sli%mnI-qQz5n@&47R7(zy9TVy1#h+ z>UVYE*S+yNZ{Yaws?xIn?VqYr_buo4*LN8*E|V1g*kG&qux6~)Ewkj$JH4f_{5~&n zygVHhW$iK0S+#4jbK_%Xyyh7QH>$;^lnxDhk&rHtkXfz)8YC*<2os_}|lII^bL@ zhE!?F((ET6$2T66U(YYzITbcM!^JN*k1%1uKr8_&Rga6_2Veu6!urolK)Rn>*+3~Qze4^$s&A4%P8tOoHU510x`rI0l zUmRcV*wg!JTd)JFIa*9NL0prl)WBP(3#bLV6c5NjTyoY9A1`SEn4eT@xANA3IBenb|KYQI5HpSbBD9`;M|p9q8*d4LDAnyXMBR ztXmgsv^}~v2ix2jhM-}JV&m~@V?xyerOvTBtSw+m%@v3&3iWj;dITdK?D^;Q;Q+ag z;r(Ds?UuB%2CO-TXu}-QMYAP_EhA3r_;O;^S-1qzn%_$1vM=iay{^nQNATq9pv<~k z@YZ`m1jm6M%*z?=Ft&aX40Ij~M2kdas%^5NvYxh* zi*{8DGmq>KSIc;|8^8i2uCio&6urneW8j;FFMTQs{+1XF*qq z-t-F|cU;I<R4=e>@Arsnk)8*EfMub#`oMUa$8twq&4jz3vp64q` zmEn$b_4Jer!~UaKZrpIZThQV6JfTU2)c`y710UA?Vho<<4UFA?4!X6z6~3dN{;x>q zefi(_fK)Sa_MWAr*swHyoHy`K22DXM>!)UO<*XOU63k2X(_O_A6*^719Gnsa6-vo19#ME{a z9&7D6OWn|JcZjOZy9kDFJ;r7r2?Y3)IIbh!3z1^aw9lOxMNfal(SF2yO~U?$x0Av* ze5Gs>m)8sHp&<Y`{ow#{1GxOFii=dOd2bWyj4u`KVqp zX<3v}`s*Ts4!*Lmx6v~G&q+|W`}a2EShJXAFM~lCk?+*?%A8=r)90gaI-Jm`1M5LV zQTb=!)X^Nz^?QD^bGo&l_1VB$ha5QUg9Cog>jZ7O@tIE(a|Yc3vf)8F;;6&y>K76h z=>$EgHcBn;fd@M|LJ5bpXFE%x^sN7KPfh3ke8l7x zK!d+21-_x|Wx&_{duzKH-}sPOuaQ<$dxvT3^7U=HyXVK-gNN%4-iy(}-W|t^XYaCE zPdR!ysx>Hfjcjt*k2>1K(ka}62ys|Vq-@WX6Oyc1jdt)Yv}`84tAJJwGs0v5Q%!Ya zObSJ0n!w^woJsumkKpjj%`ME%4_LpUi?=P}EAS7a&$PNW6U$h8`*$*6bIqZSJ&W8h z)yl$1z>V{|&%yT}wrRGL^6>v^FaiIk?h%9F9sWemNr;K}1I6H%mD!(U#6&pgtd{jr zOAwljd4G+lRdY{y5#9-lsG$}o%mC))r0n8Z{%n!4sm9rJd|^Y)PAJ0(Cv1dSnPwA_ zA(1$f&i`e>xasMCw8MRp%zfQ`M*C1@IygI(9a)#SmW;GP-VoOrxEtlYzRME##qyN| zg<4)DkLr*2Wmve`yb>uA4XXIO;<)M`>LS=u9;}QL+s99x0nM9pZ9qa@x2r7mb_H2Y z96MZjM4oe81kUx=;62f-{mF315^COK2_N!3gfEZIw4Vb7W1_av-@z~|Rtf7sPuCmhyz!S=%8s*0bwUlx11@9W&MT5E~W zhV-RPiKJR-t5|2K)~j*a8bW0|$;FV$gIFh3(`&Yah3!KlodL9(>U4`$yY%DE1;*eboZTQAzNs=!a3w@??0*Y{yGf9eLCu%! z&)xfZ3sohOSCvwG0*f_j#msQI$n~foWG?tqm>Xn_lhPq_S5u??u6Sstjp+PNLp2o zPISo6G9Ts%(p*e5%>SuhXiCpqu{P*+GAY!FAj_eld{S_IXYU;#^t$tbWbm=i6{@UB zU+L$%{*AQF`#o*#jIbmKvTi*k{Q9yRGlp2`^KWg1kIILC%;3hZ&nmb7!wIya^+ZoC z%WV@8v&BINIH~S|^OKt+lh63cq{T;MV|U6me)N6!&I>5mWhco6&0S=^M6sX+$ic+Z zZY325NM<<)Cw*o+?g&p5TBKeYaahA_z}2!OtSX*0=6n+m_tgl9pKdY%uR~+g;Tvl&~-9V z6d32a6Tm}eI(K*N12ltS-DrlOu#Xf+DHfCJ;?rh@4>pSH@>PwEy%mlZN>X4giM6w& zZ=MoJh9CX4Uz%W&j?~oJ*yfC-(94#qA`;V`6^^9sZ%0V{S)s-&I1KBzRlk?{2Hs05 zVaUp&hqVV)_PI}fc{)rt>62Dd6(?9`pd2^@87~Vj?Z%NUYq^w!w9;Z!+iX}1R7{@< zt*>KN>n4}$`ulc>NBY%P>)LM-Xp2g!t_wqGmJwQ&j$Ou~8<$Mkn*oTi8p7m^GK$Q zp_Dh}t?1`-XpfpNeGHwYyjPpkI^LJ#23y@bRNSuQ716l~TH0qr>FZ?Fsc?jI(p^M?|e>)@w-I_3*9en+U?&sgq|Lqu}*Acs#<1k zjB?RuJ+m_uY^>EG3TY;Jz{#Px8vU3*=Corr-W&iN5+h2eYgNz$IV#VFdS~u)2a}q_ zx+MHV2shjGF9M~w=ud>y&Rg53k+zJI4z9latru;B&(9Q`GtNec9gJk+>Of3>-|Dc$ zU}ey?U`Oq_8+fshfFf(m-i|qVzza7wWtQScg0aKmHmWQ~=X1 zA6{NdH(uo!CzzayG0n+(_MUJLur<7&uc*Mc>L?v`S5szGbPT4pmX3TuZ!wy^%;%Wu z|K;I4n-~8pb0C*|m+K^6%!BlTaC8YT>re`lEKuypc1aQb6dq=lC{C^uk|6teK6*6O zL}ak&A+ddxTXvM1fC8cLq&|Zu?}W{F6zpQ z!O3*2`f4MRt5PvU$~~}@_(oH!;lx8UqVrNEsEnioB8PaqnKiO%OgNF1)r`nvcO;95 zWI=`!jEE-XElGR&J5MOFEh7P?nw(;F?Tz* zzB_A&aZZUvC;2}V_ZlL*Wm+*+QeH`Nv9+5vIELHU-SGV}T90Q&n*!-#NLJHpJB~bA z@T6rdm2F@7vmc#9qbyFWwa=Vz4@x?zm7$Ca3eZe(`mejHo%Aw z|5+uSXT@$QL(XO&k$PgQ%DmHj?(R$3tk)({(S*&3SUTN>&m(5XeWE?Wjivi6H? zt9Y%EuqU1Z?E}gk6D{dsyXp1_5@!3yU1w?8Hm`O9h0~Jg4_z-ZPZyNi^;v%8s*JF1 zma#{07i@Z2Ew=fibq`GQkb6Hw{`gbrd#SIz*;|CM>D{__v;*+|sps*&d{B0h!8-23 znxcWB@B1Dp^f79Gb@Belz{pgn5_S1cWB-4&n-mBtzf=In| z2>vCxPLw8A)~jvsS=z-`##iSSNQ5T=fT|bG^xlCFOgOozMym?cXtfr0zt1)TXa(t% zL0@NSxRuMIzt9n<`b&%SI*eK+d_|Y(sNy-?DI67U8ua-}3ogM;Lg6q-J;;9QSPqF~ zN%`2kWfzxEy7Y^XgRSB>UuV_O1_PNb$1~&0aHPk{6S=NKqk=@p9WI8a!vlY;ywU!s zDfr{qCV*}_+<+jqBUrsn6e?3wd#GukWYvP1z4HdZQ`$)+Qyfj8XFtLT#t^5_m=P7p z`?j561Va>)5fWKKZK)hJH}}nm;=+<%1gqv;BU}*@?05sK5L(h5gA95{-bjtZm$#*6>FvLGHjzxBWw4J6^%U$Tl5{tZ7c`^h-Dc>=6@Wm{?xvC5pbOPADUnrYXWMBo0l|X}bw_upXcAZ zYvRBkt4n~hZ_zI4&KfvZcCchgl9wENmn8E3x`2k)ze$Ushv^`)d0!^d4JHMAx z(qi}Y)!Q(#UsvYaQRd}Oze^z}PtPZXx51@`Etubz<&t-Rd<_Amd4FZlcY?pu`8U|WH>6SV;L(D zBs$Vu!7r}-*=o6ZNm(66h9@ZYhZHF6-@P{T0vPT|i_$hCu9Ren6wS(!9Ea&5H8#C? zoJCtI0tMv(l_Y$Sif`B1=2cAR5Co!-98LK4P?J#$A!M&pEaW7B($}?P!-2p9xne94 zc61FQ=|{<5NTlsco7q48*K?C%7iwODI;#Y6RC_-WaMhAB&xhw4BEjQg7S-LmH~_aj z%6b*6z43EdLCvj!!!OK*yX%Z~)sVQVX?O3=vK=7wxngC`#68m;;o%!LZ9L_N1AAsE zbpws)N^=k;I;_mxbfA@`_$q}V8b*iHD zvkAtbQ{GF>KLX)CYeJjW$>A16g({l;{$A6t&#L5D*VV#l7eZ3sFyZYs++voeBvy+3g_BS|`zVj>wa3D}c&)z(Rh6eIb-t353>u*ydcTJwvv(R;T zvf02Lyxcg-mq|KwdsYvn^IYi{w$6Z;@U125z0H6{H}+yN3sQoGAlf+Bjm&97qzNsc zUXpKSAMXN+c%WtN9{$FwtMA8yU#9!ICw|_l0okl2rd7J3JCjDiF9hSgpWFB&wt%>}DZ#)Zm z7j|A@tgdDzVMcJj{$W(kaqtku;?2@=2iPqxB~7i!DZeWqWpnR)N475KS&Y7T+j#H< z$MAiGxlW5?>$yyee|+Rd{QmEc+gJ=2QY`@rNFYm?6x6hLWSWi3G`Mb45yGEDYxudaBK_|Kyu zET>O|SVMJvRT&dSZrm&QPbqxqyk!Y+_%wy9vh2eC<|WGE+gL|UjhQ;S z*}e+dO`p7Qg4m#(AUy_P#|$qeJyfh~8GuvO?3kTGgPu@WEcsL(sN^(-4X-<%5$_LC8rhy?r9 z<$>TBMcWk=%>A|Kl@lH6#K8~^f*1f1(YdEb)1c1euAU~In30EvQ;Xo~C~`Awx$RSj zX2(ZofdsU*aFp=sGH4pWp(B_u7e16I$MEbU6axo7wknYQ*uZ>LO*p1?ulP#eqf+m! zLf>%D&fdC6*HD~ZsNYtXuTLDUZA{TQ&F1{cqW2k>U%(>Mh7{S^ z3;%fpW6NRAe?Ht7`n&%7^%)KiMgZrjC=^=3{ACojQW6EJ&cuhDAq@H3!9gwqH*I*Z zQ$uQj5fhDcw`JE;Id8m<1kd8WbO57actUkWNbZ<|M7$H{@I-y*)l=_bdF*^G0>vzj z7HpjoW5Si|;I>hkvu5;c?K3&=*CW=nRveDzG6FL^8X#{yuY`9Ew447@$IKtxSp#7Yqssz)1vIN0>B7LDB{x7n!hV%4ZY|{t7iu ziz>uey-b@4q&eUa?VZmO4ZE4zel&JX!61rFlq7vJ!5YRu1oYzy>fq(1X_*lF@BUmk zA&-mQ%P`Ba$`u>$7r)re_@$gz)!DMOzmIDxV0ul9FwQz0zfVwv3r;Pz8rJyx66`Sw zHx0P7o1M8oqzk^0PIv{Mv7%#ISxsmfYg&rqj!ppCc7N&iYgl zCMzmD`O}vgyen_kn}5E69@c$AX4mNcUXg5IlQ#dRsx^Y!f7y~x9EXY!{z0!2t|(2| z3P;SE%iX&?OI`s@g=iuZAj=`az%54~=KWz&F{GTCVt7q*L;Y*Kb5_Pno9Ii*NuPq> z``}}9rFR(DhFARlW;;cc1M`cg>nq757*!sNh zVhO}URDg;AqxLQSny-aua6cL_5@N7J-*tHzGssIS*44@Z;GZczTMdo|}ae+378?7qUP1j)K`plJr6N>Av<<8@ynybm37=~0?roKsFs*+xymWocYsF;vK z6~pdJUu)a6yS3eMRXc7lIvM%9APW=y*SKkBaiWE&dX@xh%DUJRV4gIgBpvjZtsIjv6c;NPI~zT%Y*w30yD-6@aq-qntm~@j>hiKnm#(o@ zU^?nxk?VD+aDCfLpSs*JKfOdzTXti&H@?^o{0XDi)-+1MoV?@)_{1xa1+l_%(5YmCzImJPCi(*HI)JHhmVosA#~$6p z`eoP)Kcd_bkN2jwa}X|*UFKB~lXoofM~{Ni0il`YfHqtOZw0Jy!rL8(ef!hr&R04x zg}J@gOMCh3L6VyZ+)3K|-q^~o+`qiRF8xoVg8jx&{!gcZN};SdBF2Tbz=+A<*(Hso z;9tZ587pYG63(U$84|X=W$6{kjzzXd9-xcfJ9?S%ny*cuG(HD^kjOIrjQlAV%Ji#g z!h1`kPv@&IeaTq5 z=ck4Jy4h78Ac++zE`zzMjfbjTWZcdw=A<`1m$6JbXiP+wNdqfVl7ZS(q>)lo1(v2A zEoRs-)qrgiXbxPhX*eKvd`nuig^~U}2Js1Sr+)R!td6>ULe^(6Z)8qK-x+mjK6+0z z?1VRMY97`%ffq8}AaAMENekwrf}2>ZiR zYNyR&Z(LtJT}GyV^~7mk$@!(wLAOiv=9!&pEKICnZzKm++35`JhTWEe`C>`@Wd^ZncIqddg{@Zfpp{>;z)hv(l zyOOSh=x8PO-lMC*RSbJleZ##i9*y_6ZEg>@m1jm8PZ&#qLkE{wiqy-KMFz)~*@m&E zj}Xt3FUusy_;Heix3xsn(7{Kmpi2gA=3`&#>tT#vMvTwtc$;ABPKnKUX!(w7Ubl4q z1mC*HO}y^e0|Al2?8<;IZJG7ulMA3|9qj=fy~KOmO~jAr^l6X$RRvp0DI?qsB_n`17tA+ zT=Ok$Fu)J*plE<0-qdv-=s?UTE~^9%WQ+S8LX?Th&B3DyvE^T4Y!-m7s)!^l^%qkK z?jewLkyU+{K$Mkbj&FLj$mF*NNZ^a}U~XX;B?S`J3LK3^aVS34RB!yU3T!B!3x5Yr zmsz2Ol%29kV(FY{Y;_#9NO>+f?(bkl7YVgYD_=3EnfTv+X1;s(IT@LEl9wLZf#{@K ziMNUwhX%Plnqnb|8u6UuvHO8K1xY2_+kGByGgf>Wf^5U&%D~UoCvCjczW@LMh`AEW z9LZs^q4`=JJ7Rk+;tNe= zpX$N5NnOTma5@ytw zYX3Jt{Wa9ot*FVJ5rK5=&U9qRuq{U!3f*uNFUty<#oJ(G00gr$a*)X5s3c z4BigezW`VgPekmI-%Akg$Ry^nuwI39S!VgSL@W)C#fFhUz+*Mq`|Z4IXF)TG@F4`y z;F_D%W2x87$M4|&0NUB{=^6P>@|W34=nrh!xh(fM{|29gUX$mff!|{5dGouuK(wt0 zr@IQf%<9gLt@ozuOAj0arpu0*a$`4|uloO6+#8{cpBMgH<^W}NL#H!+Hh38LEA{T; zVJ;^=Y#5%K$iDcUfL~G|Ow!m<6S9LHTBr=4in;{oUKvX2@ZF2kEvTFJEtf1(;L#8pXSxh@&JA67sJbYWW@I0{w{!89#l3iOcPs8zC{Bu7fa3022wL2sIKkcB zodPZH65QRrINiC|vz~js$NrK(ka^89=BZ}HG_wwH+xjbsW%mI^Zf~L}g)(SsnIok@ zUD@MBx6=ZPGRsZBYHel-RfH>JyMayTm_jrKi+w`mze;k%*7yQF$LP&!;OmQ60QpBU zd}t!${y4!T2+D^+p1mPW4H&cINO(O;+=|$hf=DNTt~?&0z{6kr7$>3kN*KMi{Ta0m zG4{BTADFe}1Z$h%q>cFvRa5)8;MzYo?f+(hx*Kg3R_U{BdKQE9fvDlHUSpQ~C`-a=G67o8a@eDFK z%Nh&=^aIq*TgL3D8t|d5D{6O9UbG+{$T5N z0=S8mcJlBa>|bMfI{LxccylKc9XyMKs8lUly$N-!9A}Yd54T`;x%&O=9ii)mcukN- zA6z~kby6}$$45DMPL~9yj3C8{%Zy&X^fV#2@VEHv9r#CxKZN;3oK)C0?+mzP%`XsA z7#5@0NQhDxs&4(+Pp>ea+`57exs%E=8zcAa&55V47EAxTpM1TYcH^x5pSP!2B&?UK zZVLd%TGp4g){}XZs)!bmQH#)yx-QVWy7A6fpY}1layA*DW-Rlgk}O2xeNIACuo28O zlJ|(++_;bi)lklL6yeq%4Q`Ebw-h};{~~1apqf}Jx#G%)* zE1dQNSUr@0>BQSI!L zPQ;uug1N3-8L|wM6Lfh06I*Avp}vnBMYJDM+-B7_3zir2BqIiO;oA^@^&Q=fL_Qz5 zy8Q`pBZTXlcj#S&>vdS>ydZF>c3fg_85U5_{W#{rw~>3l>FKk3bv1P5WVZI+&V6Sp zj`Tg|?Ok|MpY8`*!iYNcYCRb<94FS1JIGOU%t_NU&Gj;lOU%_HhR_-Kk1#!c`v*Q( zFY)Q+(3Fw8dG2b*Z>yr@f~y-1)g%F2$8<~wPte!S8#7ue}#7A4$Xtk`t88*QzFQ{`X~#dAe`{D{F99|O>MI%kveuSUnp5d z2@^r~4ncJ~#r@aVAHeIj;(Ur?N8|Ay+5O0)8kgZbNpiCzqPW2FkV)Tj?w7Cr2OARv zJ-2l42cgrO9`Xs#Bk`lCr{ zC#9osz}??a5W&veC}XiDsT3-@_#e^+`m)A_{IL5rx}3eguo< z;-Lu7!n{pY_(U28mNye#Fh?Fmx7bE()3(;cD16tb~T;bP6?aFSe_csO{hZ7Oh%Iqsh*Dt?G9>RV5h zy_=o|Fmk=S<$K@lTPdv|CYu>QYn@e2+q&^3?Kt*ep`|mM!7iIy8VO|YNns3~yAHtSqY<9yi4tHFHZGv3L%D%NAs<~MLRpS_Y{`|Vluc`dulfXZE=Ujq$Q zV|dxnlf&?fHSFTdv>>0;6Q2XwC*v`N=0Vhu%BVn>5i&g!#`VOy9JWKl+cUw7l%^+QRkzk8= zGO7~CV_fW~_x!9klE0m?o3F+hQ~1ovcHWW!%khH3a(+Mv@-TwhxQlrfvpRV3;-1I$ zFr~(Qe&UWc^W4zc^KNCx%S_VsvX|tfy^i3w%>>2}hp|j4Zaaqh9UMpi(XQLLcyYOw z+)Y_s&z%@$Bo~ZhK3O7mU*NNE*)0_n(QjPHqy!i`zyna(u`LKja5{wsMjlu z*TqX_oFF0mcCBxWo&J6>>G0Ub?hnrS8)CS*6h-jzRP)B=abNTJFepADGX4Nz6#c>E zlKvU5CNjmyqrvI*l;~{+ba$LR7?w=nxPJYz#5Q@a?ejeU8kW%ZKMVUmKeF@x#TglK zN}HmC4BiOW%}GA|c$X4^4wWav2)z$2MGtQaaHpMk{$@^SgvHZnAxeM9kup9pPeXE- z>l%Z}8-68-xF|4=@tsMXidbr~6btcO2!}>QHT7~A4?mfw8F`f}Bu}1N*+SFIo<-cIV%cmC8-y|)9Ns~z7iwG)kzyBnBvo9*s!p>D zYZ9;TlqnlY_ltY)w_Ij4@!sR73+HN|SkC96^z(~oKV^#xbMTnG3@jwE*Nc%G(`!wVm#w@ z?dosd&6W#e$4T{5Gy+vDXKs^T`huid&E5G44akq_^q;&9^YEys$KOjb-T)p3o;-KY z-+o?QLH|soslNI81B)J{;IIza*n5w(yO(Yot>3Kmk?l$PG_14HkO=b+#cH#xwn}$m zHaWw^+#}ylS&9T84Nt{FZ;?W3W&u2T2IHq%(&qXn@dH{+fdGkHVe)gs*>l6IP~LCu z#VI_NMOt$SAcQHlP>IY7gsLWeWU_h7!G`h!UO|Ty1^xpwYd%Xvtk|{Mb?`mE&-IPr z=BWQA=G6;>e?kw`2mjmmq|zeOi-w;T^?YLMD*Ns7dyA3|TtWnYY9MCu0`0+b0W1as z0n%sMFE&KH65P6lpkzISUcA^+qxW4a_cfS^0JMnH0fVF!ZnKGR{Qu4PKr2WjAPu?G=Y7#9SV$c3#7FA?0R)(73p0Fi9Wj1?sh z;ibryYT>_nTs(l4X@|F6>|0NeRw5JTDepHs<#i@l68h_n2vaR-n510UX52MCC_+|- z4)_5-V|3xB(<>{vx!K_Fz*7oSGtpT6ruwe=6oi%u?xmSZq-bH&h&5>C@EztR9?zNe z?mF5U_`C(JDx*>ek|BFGc@VNSSi@Lgz%T3UvaWpzM}}ti5Gz*oJujSU8xD3f_iXS< zv=Z*eP+*C0(xID&E*fid=jK>l_l$BVwi`dKkaVCqcuMYjOGb3amtM^>O_eMZ{6Es% zn%tVj)`Lo;9L$-#GpmfunMzY#{`w%_`^ed=@bFzzRXQ(A za`dMB^r#5fH4>{_*WZf2n+Xfs1l$0A>Ocf3BT4kTFhc2RvCyNQ*Dx=p)w z*1;!I8nTTp`k0G5WQrTDY6ZU(Y_rz+=(7-MCRN$e_I&HIm2Mpha54}l_DRTr2MrB* zb|m9jiL%5gHsPAlee>h4ey3>S{RD46DYj>G{)(`Z8HkIxENdy*7u-+Ok3nf<#h9+T zEOPF>!pQQS@d3&JvxK7B28Wy9<}Qo;b)+^h6qFoN*V{Vc2K3~rp+)S76e+cY0Jw-g zpoC#kCt9LsYcOsH41l+IN`VO|h)n7;>78a?0ewP7$3^JfNpR_!imsNf%Cpq8y?2n_ z>l_bGtRNv~z4O++EqM4PR)xs^Sp z_im~!m3|0p$K;EP{@l8!RYXEHq5+=_m;qycZ$sO*EO-xymT#aZ5>oS_X<$&IZ|jS% zWsqwH?*v8Rmmk(&p8g?%Mm`pk#pF&Lb=X#S}4D1&m9LN4Z72H zHt1Qv9$Y>Qd3hS~pD~$JnIOuTSbfC97s4$@;46s>`NT=H9C049E>v>%^V=!tYnVi4 zxDy|ym*VGHed~3XJk_N7(^h7*=ZX+(eg?qT%{Sv^X@)GTKndl2&mE3cwnUQ zR7A2&d>Akb%xI`)Xs&X#Bb!z#s^!ZQ7hM=GUamCVx6`wXQi#^Ds2Um9V}H0}_lswL zdttxaFY%8SW2)I`+j*ORzHQ#hZW=l0U?~=w?hn5UUO^=^ZlhgXJHj4gw%e&=_?1m+ zkCkUdR*^ZStGKVfZeJPS(*m0z=&*s7ABI^TBu%#=PBXyq`Q=kXX(`A2E?neKhwR5A zidT`I2k51*?0Y=hN@sg|yyO}?=;$mWay^!7U3fbmK zh|#R={j{wQ!JdrFNp+^w?S+d6{p<_+A=zsBp>2#Hup^gFU09cAR@gxf8#SH9rye_T zwxAHq^S(!iyz%D#*Qf7rRB7@1iG;)fm??O)9!w_v(oAh7gGBpE0a(7}<9jAL^dDh| z+Y9%ieM4)KxQ>-2(;dh|?{nMowu3e1h&|K&hRIplG(Kd__a1`o)C){s-A0sBg+I&hYQ%Y)>V~VroAMj-*|90 z5K_2hqq@)>+&7w(kU=$1OBND* z-gIEY+DqHvQv5ehFsirCR?t{6`7QY%6 zxJxQRY*Nj`BJxFBsV@|nX=fZu9Cm_6G}Ib`o{y}6;{qdD(Vkx_+4Gy zN9X-ynb6GB8F%h%92(DlUDdkiVnX3&=lg5kxO=4cgR*=D5S%fTXest0|Yf^AVEab5~)w2R+fiKR53$dJB^) zPDl)nJ8sXuhsHR^0s;{vHA~#(C-i=55EL5VN_+Z=8Hl{~H4T z9)DH5O?y*e`{5tuxYGC-R65oLe#P zFIv)e+HM)2!uWxEQ}ach0)-N~n38awpR`zHg7jpUkKw(K3dOi$t-Rz9MnD)jcvV_) zBqGZQ}5Be~?=o8-U1otIO(6-8KGZmF!=_j8pc$)EH~buSb4Q}c(ZU-_nyXM<4x#( zlSt+MWAz#|*IB|htlk)SA zu}lck{;M!^Fu4SY5A)4WJ)|4Vm=8YpJ8$n|{Ho!fJ_9Uc1paiS-I^PQB&z`fOHNSk7J>t>T^Z z+ql1)UE2M@C&>pn#`^!VRPz6_RE&r^PmR^!W0YJ#8lt4EIUGq?qhi7ytgl(;-&CJz8N$}e)@hJ%T9t4@<8 z8N+L0NA6Lri9(U+Q;y*6XledZ-U?}JUw70L3hsByrZubtq(t&e!@H4jld>9($L~}u zO{(5W+d{7NuWJZj@@;6G_m#t(HDyIEf5eB5)HsV7q4|oqj4$-hbTV=kbx}!JX(4h) z9l+fhVv+ErWyc*yCJmh&>hqJ0+R(wr12QBaby8X9WLGbwnrM^jPlS$5F?U-(5Dna* z+j#vcbXf)$S5ABhu?DA^Zfo!_XAW$up_wnMek~h~T{~kNo#*W^R|cad(x>T{t7*gw zQE|B{ZQnUqcZ2#)T~Tun%FXztXfceJIGEB{rink0bn%iKh+jH+4DM_Nbo7gQV0x_f zbVHX1VN#_yPv!2#dRCDRzk#!H-pL&CgU2%r_ZA~$%t72Paj1uN1)96#y(+%Gg98{1 z>@XgWU()zRq^u zBCjIYCaXKP60IPfXI&bR1T&e?6gqj|L{I*Gh`YM+AKd7Td)-Yvy1|zny=eJ}MvZGe zw_j{s%#O(R#fO__Ips4}I)TMS@A0d(@1Db8?g$egRA#2BW+R~qy4ufpIZf>zQ+}CKZs=*>Z`|7q_B7Tq zRudo-F6mFv+*wBtb?tt zDI}i`G4u z<^ork&Fw@IlADn`X~ofpp_K@&(!}BLQiBC`E81dj5z12zd`e-mkbauPzD{<)DE_k$ z-HLVjsm=B5@f|rEAC6`uglrUgrnoyYSoT2@``~gk>g4>k;33Zq!*z?=cw?hS4Fkz6*g;OWfajsb)Tp742|sv+pau_XOvtx<>v8Eu z!U!^F8yIR1`RZsJdWkWjc@d7Xw(pGAJ;`-;9{bXdrSy9gq@iK-m(15jv~ci5$WkQ|Z zI~dj|HS@50n(Yu>wCC5p`W&5NL3PX7ggZ$@M>%}gk`H-v5%n2MX9UG-eH!!{sB}r> zSvyB^j${o3)@zFpO5&isCgZim_Y6uWFbxxBj$-DI82 zWKQW8#EFwWWU$|M9rWDd`Mls40^NLsZjQ*9vvh@3tjx#YA4&^QGnWS<^F)!kIm}O3 zplm?(03=Y8Sk#S{4UHUV#OM5HGW%v=P`p1w{7HS)Z=d1vk*I=sF0!(~K}5&sBz?40 zEn!=mCLChrOGAyTvrZF}@u0Nm1uqXv8Wn51g4|%t?eE{h1n;F<8{4{5j@NMU=qpLj z6cR^&@AFaoE<*V_%>OF&;V9<~_NKF-iS{#A;h=Qc^zR`2 zDP0wenL%b?U7~S!5{xs&Fx|U&T4fL(hpZx&38OkaqfGf>ry?VED@s!C2$4y4<^v;` zJaQx#c5k87I4!7sMpR;fN`la{P*5`xt4XCXKYfxVzAT@MQT;KO9zge~A5ZBWmm8&O zRyaS=L(cS`Vb*RsOYQjG9s@~*teApi>5R+o>5@7nb3 zwVT6bGfEzprL8d+U4#~7u6bdcATjarS*ygM-IYJ^Q#Ik7*+HskF)-tCKa14!KYf>1 z#uE5M*ca?w!rNa7BU6SlZ3u15UeE^QyNxC-L{q6v>mK>eQQK0{a^YKLeM2ROJpT&5 zo3J4{`IcW5b7n>gndPX9?&jk&bUtZRH$7p&9`4dz$bPBLmvXe;z2gxSxo*$8=Ra-a zNCtzzOXJ+`Kt^ony{jnAgM@%OQWyw8bK$ejm?hU<-cb;kvo=Wy@vVXn2|E+3aCS8Z zT8wC9A7==&BBxKAS6BM?r%mHZ;jJD4{9# zuZqSn1b)EHL#P@#yW?JecwkuUxpLk;MzCL=R+JbCRv4#x7hUE4KJFhSXcKBjnUfj} z8SiAX1VJZ4Rlm*3J)sVVnilqyumgA^Q+jC|4)+B88v%B<9r^In<-MZep7@8z;%U5~ zwmaeOD9jeyY)ijw2EV`>pFI|zQ&S2WDd40?hYk~7JBjGEXp^_{#9}Gm`zXh|MX8}&kJFCunUqIx(JE+C@-J>kBAuh^R#wi@}HNjuZAyj-}zcDW=39$h-Z`0 z3ST6D{_eI0SY}UUbdyCe2EQ!5ry0P|YPnpRf};7?=!iWtTn2qVK5ToP5!z#;QV$`J zVNw~9(wV7{oE%HmCz5J-Dy%+Sf*N#%mzT(Ka;>gyj8yU`h>%F=4f=mEnG+-RjlBu5 zz-W{~!$*D|62__)s_|(5`8C^ly^ixx83@3|A71|PJqZRhS&hw{AQNVW!-SY2TN@7* zV8d@e+7@h6;UzQ`_C3U2-=W4Z@M+PVd=Ue%B8HQqOEEEr1A>VYHGeMlmC<{SA82WS z3oU-vTSQ71z5hHlAxNP?M}RdLSyY-|3>*yv!9ERkQM$rX!iKkmx2)v{@fq#U8C*m4=xPVcG; zBPDUqBnRoly68^kBx@GYAq#NT)EPUeU4Sg496acmMG&bp_cd?sJzbl6hk1UvnnKZ> zXsJqJtrevrLe>Dig>+~H0c^E|aF&3mNIlsFvMs<|LX$~|VS|nDSR>6t_*kT2%v)m= zspW~I20kry#6jD*aHi5DSTb7IvgOk+=P$7tdff&cYK&C539Q2KHk1i>@<&SEBA3^; z8=L1O8)iacyH6JI4FalYd9lridc=O5D438#>^ZsS^<`zd9gyR>-MTe4=@-?iHc93{ zrQ{=MbwlSJeTOAQ$>}#02G&4(;Q;>pF;C1$)TX{GC#V@Cbv29Jx&*g@h&M2ih>y_6 zL5cf0_INXKHIL+Lphe7$X6ju<|6HRs=wB4@0m&1qDR2So<&uaqOW-j*C zBO1y7BQ@JQ=pM23KOOwUvSQKLy^TGFFZ3wXK~#Y-=x%Nhm-Z-Eps>D?t}V&b$AhGO z^NVIcBY82pe@)b*hC+b!LT2XX-=l5(uQaDTGWzak%zA^C7Gs)38JYUw^x%vk<(AH; zg;2=Q5oqL{?1{Q!jYgiwJCZy6Yg%%I>DUL*I3QkymK9Se{gX?1K7BB1{5~uS3e^D@qr!Mfq4)O*5qR|p z_`4E@w2a7VJecJs!-dxXhxr72xfp<`V5|)X)S_(UI;ijossD1U#Vm_)_z}f`0$+qW zm9zq2l{J%z%kZmrZ}{*e+t!g9bjYshEFTNu2{;M%94<^0N=%(5j|A`?Ng3~xQgM2H ztj<$bj5z01vqdn`Kt-_GCC=ZGQpOFd(>k;-h*DLAkbkCLcvGNXrySUrv@v^w@i(Y+ zJ9XQA^;kdHdgHwxl>JuT=?|r1iHk8qtlRf(fNA>(F;L~rCeMR;+wG#qv=XNoANf&Z+p5TRclbVjlKMT&l;hKR+SN_vzE`b8*f~GU406 zc%+o6Rnt!~sq24ebl>q5s;gqDaz8id-p}&r9$q`~P{mH!i-g)wEyvv=``(U;=1?xO zq$)5ry`mAM(cGBz&pcQ=L=%~3X6@VsNlHOA7DDXu_SlrFwRPXh1m<1lXTSsT zNKGVlRi?fi>Lt>-mO&4~ct7_=m&~uIl$Jf_ktQ#{@rOYY!AKi7C^Uf%K#%pOi}g;E zPVrJ?ZS94B1eLp%-7l{CqTUl_2M3w>#I}%zGPeeOpoo3rPC)mVnGAMs1kC2Q+@Vb%_HZZ|uY^Ng zRY>%mtCUc+D_3@3JuzdfQos}Zr?eR?dQ{14(7yRVb7;q&B$ZMXFXjrfmFk%Lr(NH) z4-(EG*lKT#VrF1Y!*}9fVoI8`qW%Pz5yq2Y0gL5XRrLvQw#11-BI6hKNxHUDqrlXm zU2JP7AaY|RP?eO7@*A?*$lm8WO6K4s9uw*Xl#pEQQo5~W>^Ahn{_pjKAeu6`=8tWt z5!4#>L9}yixvDaK=)aJ580ry^Afb7Oa#b`aNWX)q@#g~-iYoy#yXADY8R6Wim2RjY zxj*p&%y=^1=yjB|vvMO+&XcvN%(@84kzqww_$ZHYPWkSg@G(XoYTs!W$VnbGNPrjm zKtC^EMv{eSW`7A^bWJXF|FN}O85!@qwDIqEC>j3HFw(fU1}+Xe@tC=U%$Ej0GAKJu z!91N{pDCtN4dD{Nx9#Tf-gd`Lx9uNj@|6q|^S%<3CGNgPu=4(p>t_2;in+h^;>ecM z&r)y-nt46RsoSry!)d1JCDv4~hCDs&HRq-k&dJf%?Ma0CPOUN!W`@Uy@nrEU$!Q*2 zX__x;Uz*pNvjw`m;y?^s!$qP5Q<%?{-n-3UPj}?-Ci`4^JpS&)?xy$z^bz2>59A z_3+0@ttPT?T{Z6ESk3Ez<0UleM^QvX>{=q>?!Q<1&oC^yckjNxlb4dvcq(lt)cKEo zUK++h291QntmKE?2lw7073MMCyd8yNJq7O7kn-do>7=x`W!DzjMD!6d#(f?18O%oI zfhkJtzOpHB3U?C_a!E8QG&{_xo@-C3zPy20fcmRW%28;Y zyC149v39(b+a6qLXF^%-=@H$b!60mo@1>9i9Lm89%EGL|tjFUvPPQY$wA4r|;BhvkR>yd^9P?KnNfrRV&Mk0j*S zt`kBosrj4cQYBIfLjKi>Y?z(Dli_*jmvuL1nF(T+Ode(W3j^&qVqpO`id!mdaWLYV zOPy1Gp|dmuvx7w1R??HpjBjGly2|Yulo3_m(Pa+$_IZdhIl0oaTqzAt0ehd>4fFj8 zTobb58qJ}c5`N)%S}NIS0?Y=_fpLA&JS}%Y-aW=8-3s$#*eel+^nyDjiv`@{X0ro0~qp7cPpH;WEw;3<7 zsmxT53t6SNFay0Y=(O35=Ma;frsQXSp$;yOY@Lgqulr^UdiX^_9Vj!grRXD>%!vTI z^~>?Y6B!-irV4vE*Ur7Z&1SmBwGNDqCE%J4TPo85D9_+5EnB>#`|3kSi=ar+qB;p0Mu?^$w8IG~2Yrn~E@GU_*|;vsc?5Rf7Uk?b{Sk!R zyiEfeo{o~IesbhwZ_<4^aQ1^;K|PGh;1)<6ZS=)(BS!^E;=*@M5m7|{r&MV^ z9=qIly27_`JVre0B&>2;Z917o>0te+6Up${phcP7G!34C$PBl35-4>w^V6nF)k{Uc zObAslFO8_^P+cf0ebO>Ixq~~mUFA|u6)$o-;fSS{K`AOfEps%`x?K=x8mYQ#G_X)HAI`??yJyHF+oG>U0I; z>>B`;=uxw0u-r_ zYw4DLQm_j zIAhb;OQ5jAw(!2+Sp4E)%u?+4mXnbvX&dm{sjYcW#bYM4ZP^-vQ~>~w28M7ZOJ)(c zb?+bB4Dy^Uv1=z4snHul^wZn|hVdIFT4`?t^)ns#$`b;TU;Z8|V1JI1%&LPq%^A&{ z^oKv6@DjSZ^xX6man@HB5fgLj^dE00Us6Jf)iV|5Sru9nRzq!R;a}BfTU}k}8b^~R z&=ogkq()mKw!vphS@;H4orKRmP(-Cp(q(d27<%@b>|}MryZ2#zJRUOo!IW4CquoTg zeiXmjP>vrHvvc*czhh&&%kVEgdG{ml_Bq`$iNfdsx~!>w{CTDlL#Och1e7fBShle{ zOZxO`U$BC7>4K?KwSzp$!+rMQ8$bVChUJi(^k+dbOc9aw23Fzh4KdU8@wvL)rLF)$ zRh%C^4wxd@ z>q@>G3M+%Go=i~v&3^gV&94%%SD@cj>KjMSb6pN4kGgAs#qvn+Q@Vvak>(OaXCM5P z4CQl@pOTTn+hwn)jIZ93;fB-ADTdz-zh6v^|0~H$Q_mmm$9?|pUMO3<1XI?-gYsW+ zCT#kDzXavm@q1za^O~v`|G&o(hHqyB{2M2o{*J3;*Bje8Us2c{SPUO_2gL8g-f;bH z2gM`&w-2UI;$HpyPc{lF$IX`qt7zyX4q9(^x8bGkgBwS6PKEkIL(F8rdFd>c0s>@F zN}RH`#l9v!Y;m#R10h2qOIyr*k9L$V%pZYdvqO!kN+Yy%AQigMC3-yUK%t*!T(KKM zZ}or=X{vaa5o!u9`48{#$PIqVYYtIo4T(;QOswz)52}K2s1h$pXdfPehiAamv-op7mbZXxC=fZ66MU8$eg#R&@^p zoE%(WBcPXt)`hw!y-SpZI7i037?@R8A+*7kfB{V9R^S;zQj4;IUm7gz*KM( zr?&j2?B3emw9NEQ$DuNEu*-1OkGow5`LrIqZDkJ*$|_-!gbl(CHqShx7{+;Gs!XEV zA-88NriQHPw}QTn^Jr?|i7Y~JZ7)!W2f5e}g$A&>8V;$_JiIoL9W|I(N3$PojC*qa z@s5@SV+3jP*ar0KY)01!5d!ajGnwl{$I4E?ea{`=h5uP9=z!lJ4QlMpBIB~(4TZx| zI_oWt^xCo_sp7BPtFVTyMu4wHy-2niv23_H=Ft?aqp=|VEj0nYt;mpECy^{G?8Yi* zZ{N{|m4nsun#K7Db#WYr+wmzZoz1JO04RL`5U`c1miyM9Zu-8$JKb_)ybGt-jj)-T z=j74gK4@K=M&QoOFv9=Gs@Idx?p@Y^^V|Giq02w{l5CIXkTImzuFLHh{)d^=o{`k< zmm3ji=WRb^;JI>_%fe&KzB6tPT|aNGoi@nH#}L>Ifa|6#WsZ)#a#-ga7w&$$bG%Ib z+n00jdcVHa%sg5BCw%00=YK_-6#vUsJ!KR_f_iS7V*D@u-VOEK=ZIWt`!!s7Ud986 zWfN~u=)NyLOco=(O>6&sFaAX158g0%WAs0M@*kmi{+zQrnv;{jdus@1EqJ3ss&O^c z)ao?2SU*z)j%4bmbF-dYSCwQK=&!j#*i^%V z5%3PUZ4(5vJ&ZX!eLQMp{cKX5d-r9Mo)FwM7i$&T#PEa9eo~)26jU=~^&lduM2(!6 zP5|O_*mx(8ek*D)9#J$W))?IMNXBG#UK58h-4`uxn<`56V$iY_`*3GER^>(M?5}gL zFbJ&lEFp8RM&A2GX2galV<#Lml+aO;FM_TGCajPqj0mH<+#EirnXx(M%x(M1Y1c`} z$^S1@wiz%BamMX)X{-4da|kcX7E0IZ2#tNzK5ymkIwQt8Vyfc18xtpJ&lok)j9!K2NLRMHgsbhvD_* zlY9C%cn6fV$%Qs7xAOhhTe5*@;SaH3I$~vFc>6GeR&Hho zG0lwd@kc!U@rQ%cB5TLE#h=B6Q z-)(KJH~zm13op-qFJ!yl9ICxz5Q#SkOoZPc1FL!la0cREv-PT&PTbX zpxl7O!PFY}lp4Dw?Wr&u{HSAFp*$G?_Wz`y)yt}mAta_lHb$Ipjbtp5xI6aiod!){ z$aX#+GdU1n;|MDuSr*3XHk?QKmntVzn4uKPiH+AD&k`}6IEX9hVfitnY)n9u;6Q<1 zuzb#EomD1(Om!GFA&Ns&g^5rM0te^Hz>3+06KgBE%`$u`NjA;%Gm@D)Sqa)X6cl6u znMy!9L++i12g{1Jta&58Nai9Jw{3=zsMh=G(y(YM@7D&r|2(#4wf=+p)X~vp-dS`; z32vNSA`1^}VRSh#hg}8GWO6Sa;?dCq$6CK{=B3Zv54?k)&Db@K^$XK6T62!8RQAHE zGIh9Z#y)REe)8&ThO=E+gd`6>CqtAn?MNIklV3Y6m$Xxg)QTfs^&?au5a0q_NkWf@ zwoH}nXfXcPZEYQTDKfYESz6)N5uSoT7YTES^)eN5BI<0tN_qVp+30zsJOgd;F-3m# zBT`g|0~wo2X)qFw(HfL^2EDF#biO<-c>Z0xUsSp;tp+3vfbV0QciHZo&S@_0syviV z6i!4jD&32ka_uP-O>~c7&STclbuC(#T<>1k(rQAf+|7jTZrx3>xBmPg;$Ps;gv8j3 zPD5hQ=`F^qlLECCFFvHZulqid4-R_Uoq)3R(znaW^5U3pJ-SO2;oS=K;55A)!F?|< zq3Ra{&E~z$rO=Q?e`syo3nbz0bXUo@N4caQS!@BZLs(r^LDitsGpGhCf`gI<;)4w< zBuZ?!g=+Ae3?}&#_%Rc#6Yb6V& zz0N`mf42C6B~(`ZQrC0P^ZV`iDd9h9JJ5Oa@2IKKh+{Y~{5yO967~0wqTks`3%E3cZMm}%{ zXzZZ>fLxK8>&nv+=|!p(<;(d&6?W@^T%3px97HH3kQ_DYS=wND3)piM2@jazU=`xe zN~xi@W(r{1nn4=O9CbE8emK2jxfHoqkfhx2KI|(V1L~R>A(js)AZ=0c*@RFOgx^>r za=I`<0Yz|yFnV!WtWc3@*MZt{X@>N!i@qm?S-~fF z*4G$jbA~CbN?}i8bK=m}StMVyGN9sIf{f;;DkZW3RO^sD?8XLFCZOpJCmz-oP&Da# zX|R1bS`$FULZ3_;iF$*Q>qujSL-TX3Lz*ygEU3VqrQ4wuU!m;LeQ0@+n)k#q|!$A$EMFLws0Q2)xPS5hLk0ny$Pe-Z41uJ-W)) zzVM6k=)PkkU-u>HfCh&yRL%@U-cNXmY<10wpcS0=UU{0G#W;lC8QJzWBb+D@hr$>8 z0#4@SCwh8qt=^ronhy^*2R<*eIoqZ= zKHL?d|9Z(Vi~b|1e$_4)eF;iMCXBoOc}^d`b6B<=2r2nLy+0fMP{jTeOo6{uTNL{@dN zrm@F=x3E_2!LaMsA*&R4ZXR#K<+4eL){wscS#Jvd!2OdLy;&oC*ZBOUDnwnbIiX`` z8pFiW0oC3LQt!rK%BHOtU?hJi!bo|Kw>0u`Z#3 zaLwbLOa0==(yH7WbcE*M5a+P#7Q>W)_AQoNeVY!Rj?>r9p_(x(FiNof+M?FM)#Xrs zzvyc#)iTrQn1i_bDESezp895lL-fE2xwq1EFvcRw8Vqy`n+P54tFwQ&lIvyCEJaXQ zbM0DbIQ?VABrne{AvXnANhF%!T$~t7Hwjzi+}?8ll_zA}ufMU88}`UT-|Tjncq@nY z{Z(7*M;1J`mCbvBMF`{3wLBisG=aO#JYj5v*#>J=>E!X4Cimj6P-ShWmeR-51KUqq zP9`lIuI}y_@vPO&^zdSpv(FLR_(Z$DOd;yRS7%Uep*zdT+ zKb`&Mbh1|8aCV_{@*CZTf4@XmjB?VjS`3DVWqZ4chwCWMIF#UGEXtjhsplI%w*CX>nfW2Pdy`y53;@eg1y*ztHHJYGvfcUt}PK17Iee@l4EdP1;q@ubDjuu zd{q6EDGHFsxNrBZkh#ol7(}SX)Ivei?yMakaxEFuL24}C8FQhApTT8jR9AC(gF0#4 zpEAi4WTN640|U!dX;Zf~=sOXafyz?ck2~Mp3t~rU`v&ibM0uxSNm73MCbn5Sy?hK=u=7|;xP;r_Dar#Er z4+XH|M|Du+Q--jmx+PIrQ{Z$0w5MB5p_b=i+f$>$#3y2gSRF~F-y5iBf?JZx|j~||v96ZAZpAvgnj4e%Qv4Mu8^Pi19?)sZvLZEiqUwK0Z zA|gd~iH>8+@$~{zi-}hUOzl6iT6MIz51sO8RkvGvWO*n(?|os@k?%NH6kaAAo~57% z-*wE-L3NlNb-a|$JdIUQmXT|*tKgwWRMUNbTBrM{FA3+%Lbj9n$O~d4k zG#|39EXVE^#owX?AK=sH(kE9;Frd_9bBt;I0c(o6I48+j$2$$n)#+);(P}qBWB26h zomK$A5yAjcalUqcis}2$YJV{Myj0#W`th>=2!eqvDKY5p-v1vq>i_E^Uq_zJA9`O8 zy_Y+px=DVZ`0se&W+my#_NNOBU6etv)xxxu)feUVwdL#H&^Mp9aW~<*KbO`+FV{n3 zZ!Qmb19pr=XeDp{-TY%+T($bk9unV@ceRvDd=EQ9H}SmWC%L_L4}mZ)kdkYl5Sy-L z#_d(1f2aGwik4V1`qRDY^OPs=7~CRCzB~dB{ZxMT62sW5mzyT0{qT_thvRFFs0>d& zT~a7*mMe#r7rxmdN(M3EP%)FtnL)B@kGRb%ku&qHyPemLpMh~ui}0LY-Uzd)gfo20 z_?J1gxAIUu%A zNify-`9~E*XO2_(UTj%tQ=Vtn@D8Tv#6qjq6vL zFVkpc*FN|by$EUAQ*j`=cbF-qPlnVLG9PEc5us2z zb9T+@`wIGFOm~dZc)oU~t_juI&`dW!w=+E3Hr_Vo+O@bt->B_y2iqzY1Hs8f2|BoL zhJ_E4(TS@Aj7-4=1pW-$$g5AK2Q{=3p|az!ufK9j;2pRJdvp0S9i{(z2MI6AZ?!LP zc?WYf(|yEs;fDUbVkQOiCcJM5FcdfpXpz{w3ETn|{XjT>5l9$6yeY-sFEMOkK^`4n zdtJYB8kb1vy-;R19J^H#u&Vy#CN+39&T8fFE)QKG(AQ_+91vlG=E-_`(|`U*^{rSP zKW1;wIsIzLJMrUf9ac4unY2L>DfX5GBSN1bAhf8%f28lpA3nwFUN6S6tN$HFZ97qu z|6@g8NB;8nynb=h`|ncjX8f+3LgnS%(2Lk7*DV&O=a+cNo9p)>7aZgQPgit1ul!Du zUTrWo=-qKkbgajvr#PENa?OKXo=(^z`aBf`GiKFHpVQaskCQ_A$9t3Z`T!`dM2Lx; zfhtV%cT!r_GSZ-mC>Y?|7K8u@3if(UkgfVH(3lZy1GMPkoUJugzOo ztD}wF__j;_SsH{%G_GyZdbpZC1Yx?5@ZtFi%PZRyXNDfOmM|CigP76@_)QY`W*_i~~v-QFW*7P|c@%NUcWUUDG4e?Yn>_aZ7HrPxkYfZu^Ff40iik=iSV**IJ2v({2bg#; z!kO)-ice&YN_`?e<7qG4pcnV~Qw!9RG@fVcAY2QbzK@HEj}pqPS~$>gGUPT33Yxm9 zmQSpPzwvSZ%q0=5bLFjk?u8n_)FI*D%LBoi9DY1 z{T$RaWXwu!KOPfcl-cy~USIP#vMWTnNxx$2OiXad9{TL#HAncM|0_bCtIcKQthp2F z?H_!W;u}gaGhv&I65pN!=xmqG)yzsQb1`q%6m3musB{QVXwf)u{hYEzMu7`1@|t=8sc=!*aJ zMMU_@9`Cz_q<*WhTnl+{_=I@;v@K2|!{|nb*zlG%*Kn}&dgqQ``mWW@l^kcNd-9M{ zadsi6#A#{G@~|iaVJ?lE|I*_5K0Hlfbs0lQ^mRkRIcv`%v%45=Njz7uAI65?&sx4z z`cOD6ldb!by;_FSKrlaoV>sE;@tmqE%C> z1yWy8{8+veKfio%v&vdpcv^FHzvj<;sBd3(%fOSfzW2xMH|V=H?3Yn6ROic<%fV0A zNk>u37nQ-m0ZBJsb#Kgq*MHI9ylc4;qUFMT?Emzeec^a>RQNJA^z!@RciY6J6OkxR zi4w0YJ_+oy?XXVYs^+6smBj>3K?7zB>_7m-osm5`zOcNuj9PY5l@`2YG(`ukPX3V- zk@%oV%kgP|HMg^|kiu{T+_AzwYL`h%C_+X`8c~qWL$9c>l1aaNkx{Puu+#g2W4*>o z3qc3LQ0bqCN#KOtJp?)Y$Tay&fyetFFTege(K0T7zT0oHb9+2@?E&)_wV9%$i0|24 zEiwHOZ#7i240`X_8EQ+1G}w_khE;r!^j18@100(^1I3H|vi#a0?e-;lV|Vn)QV+yF zA?}R_TLSx{?bK97P&-8*q-lxljHC2P1->JgwQS2%3Zn1RLX%_kCnUmAP5`^xA{25o zeK=c75kKk&Eol>Y4Ji}+nc%&@Fu&gQb92n8xkeGemw>rU>5ng}2x9gEu+h&2wfMWs z1UUWKG2iaU>Anp~l3*|*ry%f*UN{Q|ys-8BY*uR0XY{C1M0b+zec6ZsQt8?71|8m< zx8k{{>t62<_YMERjq9_Zj#pQo9p&_S{(~eQegA3YdxIHaD}Uzf)#h23LX8!>F+1BA zX8peg7(EH&BK`E3t45K1-{^b$$H~n&mOyon>e%`zyu=5y5|HrZt(cZCgesr)Ycy5Y zV-m8NsGx(TYhtRyPhAK8L&^80X3n??4*HaAbKXuT9S>pW(PvRQPp5II&$%?GEQc%A zw!K_r*-~7POnqhbI%<>K+M+pFBORmu+g4v0pq`Mqozg^vvR2Q=#;I^*!C*NZ!TFiTRs%J>0m zv1`t}%f{oTFp`<)uk{edJcMTQQsAEU%Vq!GuDy4`oi!{*rAa}Y3yy^art<0}T4Iv_ z{!a6MJe&VC@*fN#DvN&MK}GV}-j{YS7W=|}sK@5x&~>99dog?EdJF$ioLj=z??N$q zi;`B?f1V7y{PgL<=zrzjFXf&8srNoemzTZ zGyMM38vCC|k1zK^{~kpB+gQ8I^b1W6y?%G&aos(7sj1i+^e*)H*$wlp(B1tfDP+O4 z@@zSwKb8Z&vS01y2?xLYn|+;ecNYJH(Wlle;ugH)J}rzjGCW#eveNvOB9j{OiqZP_ z@i-z)hX(`QHSVA*p?xT6V$!^6Pza(?hG{lWyv9_%^?A&?>}VIxlk`0~&YL;2o`&PD z4Yz^uMH$Cdisjpez{$*=$)2`4dq9F!@34rOqT!fcmx*_53x0#b^q{R z^Y+j8D-5#T7px4!c}No&+8w-XLBx4&&wXYQt3Be?Pp(K2m?*;hJlW`g?wk_7kqe%x zlL~6}2GVRvBuAH9kyr9BLf%@_V$<=%*Rd^2myGWN_foZt#0y}cof&^Z+40YeoZBSj zef;P4C%8LXA%An@!ZVp!zCyKU=H-q3ujPy-4@R2L4xMe#_lM=*$#dVQ8(Dr63ch{D z`d;`cSD9*N3I4(8dygQmL4v-Tzs!7cb@O_5XFk1)vLYIQ^GhVQ>E-%DA10HNsBZZ& z_`Q8rz_rmeZ2^%R1&8wrjV;GG=jzX_Z^xBxpN4${H-1QFW5tr99Se6fG;^mqC zn!ft+O`U*h6LtZ4!PNk;MVdZ96iH3NYs1LL84atiI+bQ-U{4!xanWSl#l&YyK&*oG#ID&@g=|oT)%_Ps!ZcFafRH0-@1<*2yWUA^y@(YvbVh+3zO+js^n~uCO~tP z{@$kHZJu$oaUkWX$^RlHZ4yaogtmFYc`$H+uS=f4IrC4*LGDadAZ<*NNKn@*z=qX@&sjWeMBSh*PgO72Udcc^>swC*$~^NgqOKXIZZw@g3?u)mM%T1Pt?W~GORjQ zdP+kh7}$fpILg(33>vN6T8P@ZfC@ScZ)Y+N25tOZrd)MeI_`~Q$ZU&!h!_)VNE>K) zG5X!&aI&apXVL#=n*Y`5zMH526=0c}R&ezyXRu=I>aMjMP1*gDuBlxG2A;0_VY~kZ zukPO6xzF9?X~ao9yQH3V@Fw){8|GM9%Xjvf8Kw9(#iPNS&G*|iSE>&C-9;~?%!jkX z?E+T+ESN|SpY)NLJ>HUqr5G>2Uhu#@;~L`WXF%fR3PHeau-8 zukhE(|8w{rjQwxD2dw&eH00G5{}aW~(dCxouD@1)*Ahd29)8>mJq~^KCzsu2-D-94 zaKnkH^yAU!&A=1N&rh!YgeK`8ENxm{e7|80{fT=r`ss4-hV}YM&By*Z`s<{d(YrU@ z%gYz7qc_94C_|n$VVA;#8Br&X?s_M6I~>@Qcj(xW18gV3OZ`LS^^%HwShJv-z02!a zAN`?-w`$R=s-9P{lMiV7za^>hoa6mK#J%v%?qHG%Nf|ZxL-KbtH27I()dE_SKiSY5 zO0DF|z~p7l-2A|YGkp`LS@aOEnR^P~XrIjmk~qqq9RS}H+|G*3PH2QOVTu@4)gL7Y z#pFBmJnM_uK+;45|Ca58O?te6?-CP;8r;>JYAe6u^dDy-@%1(iHhT51n28@2W+6x& z%brpds+1rYF&gUNn0KZ6YM%Y&rq&`Qgh%TiLNO_r&WC%kVQ`!DD=fl~8P1xYLqNUbdrsR+-Q9C!-4s^0rS# zhX%WS?3z3+pTi8R+>EKX0Pwq@o_1>~_>>8W3uy-LbGqF+FGN&jxVN(WJ9@d~GjW4x zt#@C!5Hzt69narEhc3g?RSA=h>)z5QyQ^WYA=YxPD)+v9`_}Pi%dErWUrrm>Y)FXw z(cfzYwj|y*8T(Si^K>zT(k$&GeXC9DEva5&mn`ih&9JvmY42!i zKu@lC-X3W`{IroBO!x5K9a3r%YG|A&&&n*Zi%a|ip%4A(*>=;IMxQys?VM%y?E_eS zY}0PKqApup)ncZc-@#Dg*2nGBKP*q3IWf0*8bf8-BR}08DYh#wmXy^BI(=S0PSdEZ ziHL0A!;OIVk#WdM)$|udLVa`LoMdYQ<+}l z(~!b|FZpOKM?}uk?#E+A_ z=zM0}p|r}ZB~kG1U1Mjs>8cn*y&q^b1Wvrm(KtS{+DK`bI+FbSNB7Zh1wO#DbP$$E zmoK(H@L8oR?)AKpe?pXTkhuOcevN|KHft7DuHF?3*v!|jlbjt$)y)?WRTGX9in)l^ zpe3db;el^k?yYzbP_YpawEfk~o#$RJKzr@W9HnK?rc(oD=_A>X`A)Qf6*TawBcRwn z7zszjEETOaDF@GR5F?A1_k_YHy2qx+6{l?%TY|Ro;?#3IZBAL=EN4|w3d@~^Oi0KA z-6qpW;Khoqo2}~$+0ej~P?5rV%;VuJ4u-QQpQ)O2dJ@b=)_9c%FUDD8C+UTxcg{Z@ z$pl#HT~K#FHEwbW26YIGdibwNe_4%oN%RBYl%K_mqD0zH>0J`Xv1iZb^(^z7db6b) zFI)A-i?mFXRK5ppu~gL3DR<&dy6f)jA1UUf*#{*00&JcY4NPx}*avAlNS*+4s48n$ zflH<$fPHgDt^?(1`1i*^r0PAqw;=1>{amWMlZK46EW9Cya&A`R6Y93akPIgxC{x3>FRJwJQl+Q87%QZBAk$$W zvI0^HL<`@{n^g^dwUBA4ynK~myX7uKwP@NVFn6km82;g@G2R{1iQfwkAM0n9Lwiz| zLXYx6Dr|rn4G)#2R4&7|w!ku@J;YkHv<}oPm-KWrScb6dVPI@kJ>t;ywbMwYj`M_h zn*`%4^wY1(w&U&|nWrPVV362C{;?cMq)4qNj=NP~=kP3Y+hT7w_^_Pg;W zzeY299kS~FWNLH4(SbOChnD!6ge-PZ2QzB6ZSJoqr`|Aa?Y@UB`E_o9o6$%CKmc(P zQz?)b=EB9@*D+mJr`Qx-Y=2yiV7654%QJ&`ikCf`j(_BzJw@L>n2`}+)OnY0R zSBy>K{MspNpxAWbc@O2m{HFCygkprNDXKt^W~cs+^OZ`z{h$-BlZN<%vr`kK$%&yC z0q<7f=`|9|;XevU^vpA6a@jcQH4t))9vo!yT0d=WcKwj^;{Mz}Pd1}6;wIy0hzk7T z@l)Q&50z?b=4Au1kXuEnZG9CI91+yepPBcJq0yRV?Zqn!?8`%Q!#x*4my4d1YFRG} z&h20=TQ$ovti>yNefoLk@4CF2uK0BOkQd#PM)mb|ug``4O1;x*e`nGpXx_O(yd-^2 zFXb0_g!A&ieNy^X%GM!ws4%h#RHVSFe!pA``L2V=Bacjn$knywQLx%L)Etdir@fgi zq|l4_&5x7h0SJb}RKYG8n)bmCEe1S@+kdDlR|YvYN8U-o=-&X8^x#iXe0XD=CpaKn zTz2Tt13T9hP&-E#077a=q-(Ug>zh)_p06hx$}~8e@mqfwl@@_CF8Xi7dkfHI)J#hB6yAl&6h z_D&&hWK|R(6`<2ccXuoo;Pq?0zDY={*#iuSr#vmmxvxsYHeVKQu>K~y>*j=$>d}R^ zNr;1TkVQ5Al#`8Wu(20JR6&wdY6R%$l~Y|cFS~O}@7A>&gW3uGo)tmC@jh>!*9&kDi^XzMH7t5ZAY-{;(Urt&fF%d9Y%HLE zB2BH~t9zm5_*cGI9vqVHg&^qebhTw*U{LwAlX3Q>lU;m*l*6{nfSip1cX^};Phx5< za~a@GdpiWARTZ5I;eqwb@+H*K5Vuf@NJzu7N)c-r)lTs-l5Hv0+Tn2(;N1KKJSg8g zOp1n5eN~{Ypc4rKz|l=v2Tg{H#ml z7rF98?%20)&*bd^A>uFKhuL3y6Yj>#Dg%ASJ!Q#*g#~gO-<%4`C*^j5hSgmw=0OH3et=j%-zMMngv))H*N1}m?mbsDXZ$G@MAupq-gdczF62O>-)U`c&us0ZsrLj7?5Z0PvPS4!m2z_u=5^sP|S}k}Ba!Qy)^-JZ|uF-G4^)BXlIr>FqKKtAn z)D4I~HWsKp0xFslR=iGxBD~lAEM)K(#d@xWAI6-bBJmIeTgeb3v`1qVWEs#g7}IeZ zHz3LAS{DDbko7~o=T`^T=QJ4J__+w|L9V>I?a`dIgo<>IkY3%Fj4^gQy?ID+RhJ`r z5-U59KMt2(CV$i>S!V=x8&|A)>(EKYyIH2EWQatfP5x01r1M1e7?Y`BX@!OR2eG?o z{T(pCtKy{K(q3<)wQq}Q;7uab2U(U9SKD*(diNML%3Q4Ee3uenwE}*D*<{=Q;!I>u zlnLNQ`5V4fy+v>6fUpC{hGUaCh{>X2fQud=+co#K8e513hB`36&LmdoJ>8PSM1gj4 z2{1sZ{X2=EhlypiJfqGPRZdtWH)HPzlu**?7d3&S$k*lwRM3ke1nnt!v zb*-Hc#c4xhf&R0d-CdqoNRgvj>E=$O*u=sbh%1IWk*+{msPZNGSzvAvvREIdUxVn6 z17^!)*;1%QMl!K33>?ieW@?vDODI@Kt`#9i)F#2UdTQA9u{Nj9KKj>T3qhRk-|!!e zq>ER{UW|}bi-tSK>$k{^n4I`#S520j;vj6L&)-tr7k@7U z?BEfGAhpRfL0em3^Jj#UokP6@ueNwq(|l*MlNJzz@;3f4Y5J_;`@?cB1VnN!S8Bwx zf~UZ=d@U@&jCiMdO7AqO&mTQXX8Pt0x!c@RS+Q{KxcYtR2nn=Nk^RnH`~0G*?gjp0 zU~_+01*V;Gp&F5)OmBIOFdqBt>P+`*DV4$3rss=0tE5LzkTQSt;8BIXwno8NuuDkT zuyTHl_n+HJ0jyC}&gSvwJn5m}!(&w)EO{~?L$BWYRRUHPf#H8GyFG@)7J71jKQ@2H zmjb&3oBZh=ZL5m1f89JUFsNtj!}J0wa4OcjhDL zSmJnN&r}8@8A!K05I?r*g*LlY6#oQg<)jK;e=fQSp7aZGL(1{WfF zW+pcFBJgm16k5KRLereoRsA(bFPJrQY){XOfDq#=uA9>dR>z3<*K(xQe*_zyktr4F zuaL1rtUCJUI4>6ys9p>8k8}7#SZ1P_40|ixs|t>)j5d$^()VGCydK0jTXP_dOdP~B zXVOKE$~QwAz^{F0bEEj=yl)+&_P~G`PZ>peUKR{s;*19A$fcbC19ZIX(j@lurenbs z1*wF&@pv+p59>swJFY3JYAo0gEpL*Cnmn4`^>!%TkD-P-YujF>0W&6$fcV1{>E8#cTE6x3^qj}qNX#Kb zFd5RZWrh$3I)j=i!lZa~P!ZAUNRXrB&t1HulhsnklSS**qQb*XjS@aq6ArtR@ibxR z|#0qZnXHmbCiXw{c6W3AA{I2FUHYzd!%*$=;&BJW2* zAJXyFN;Rr7$Yc)Jz1)WHEd|vJL?ohqzhAbG%Tb4?<@T@hlj%el!O))_s8AT8H#Nt& zeJA>KeSoV(1+78lOj)mov3b`f*J~j$PrSLv+0SXkye5Sjpo%CB_RLIu^<%sIy%eWl z?Fq4|IMRmw%ir@SuP2O4cV@$54T_{v0Rja1X!AtlHFrct+Vj4;lg!}DM;Bd<3>*s% zK%WC9OT0qYvuJI#p9V{kLCm2+ugXqjjkWAVdMTQ4B!LJ7=A22XBI=G8^8&ouSdyiQ+%nR^>Rr=WnBVzTk49_Lo z*jgKGZ{+kCAgLj}qaSKq3au&%F-{%{)OGUmhDUIWB@+|DSHX?=47~@1!%(sUBv%u2XGo{kd&#(^Ye!?8M&}FZ!C5hp&$Bz=#ZCnW);r^P z8CjFE&z>_jr>tGnhYA6C$wS+xbP>mchHW0X0`nH6b49=JA5a4MhGUcMjG~55z zr_$tf$RJ*-&t0lYb_ZbQJJDLt>8?H?3+|1|>VjBZbeb7mYE z!dG)^80PLgDulx@;_VOle^PQF%YIVC-Sx)2nNJAUFmqCa_e{cxQ++;ffA|n-6a=|V z$)BERg$J$rOtl^xl6H|{&#)n;??6>@9+cTzSZ=c)Vn_4@8QA?X<<>J2Q9j&ML!iNk z7v(2euHcK=2mKZ*FCQVRA2%AP>xSrG#iY`j`kQCO*(&65=Hxrj>HO?`#P?+Nn+Gkj zNuT%)HnQcr#(Dt6a<9 zMQTWsbhKKvGtwsVEA}`*51aBuMt!yS0bfWf)arxPx{R&A6jO(h8Sqo>e53!M-woYD3v^t083QP=98nid!rT&)Dd-G~s8^Xi8&_pR(!fXSNpr;b3K%6i920v(+*_KS-Bbk;X}vNE0+=rb$s3hK&e z9URQ=A(Dn(-BNl^O4&z0Uete&Pb{H+D65jr>dU^qlxtxhxA8_In^#9kM0N*@Ur-SSPXYt*g;N9_jb7JkRpDmR)5 z_C~6IopX&9>(Do~jSFvfi$wi|u$(temtdnb<(LW|J#v{qnW^SYI>wJPG4VJz{W0;l zKN(c2XGhVi)(1WTTG@S&?=q94`I~6435Zx%o)exOIr&P z*Ide^!Rl-72UI^1e>l)HL=b(H&n!NC9Bk3d5NXfidnKIKpa*1h>ndRSkG~i}{ZIA3 z_o|K77iB0atK;cBFe9UohL`TBQb(ppw;Z4$Y@m%{Pw+!6Bp*u0k3MT-`Bkl?o-dD- z$r*_Wj!>h^*88&2K`gPPB3-Dd6(J~}*qB2@kYil}<;Nl5fz%JwzrX~ACtJ`PAEBtYgSIG`#mZ}}kY)FGP=V+cc2 z80xaonfeJxzu=4IjDW4~tP&<0eW^Uki4J^t)J#&D^fb)Q$p%|GJThT4LszAhBua{GDP6P^vZO8$Y(~u5*M; z;*$xQx|ngb!oD{`7Ky0_5o2j%y+6k6>FiqZ>?om3G3FY1AfVEPu|SR}E+? zQY%uQK7pHkZbn@rJp2SROayWW0AnkljX)pU?u zd0}R;x2bJ>g5-BGY5Z8aQhhJuNDJH&8{fmIU98VhJrlE*VXEH)wpe&k9GBKg-T;dz2@$^il&K2+kSSJ+6oSHG9VxKn} zoH3fKQU+t8XFnp|)|Z#@Acq4{dhwJ=M^gs6O5<3$6Ru-mnqdD4kJO%CqZ}|mt$mSm zctYeY3myQ7*cOXe-O#iHrxjp)9IC9;`5WSac|#11uXWlfOw>w?zt;zl4aiLRM)mH9 zH1{iWC-L)V)qAfsX_iwD#3zzZGCvSBk^=MR$A&}E6&x;!9*I?p-M@8S@d9@yvQB_e2^N^9G;HM=_#Z6q*&ckdo{ zS$osnmeDbbPpnm}l66QUb*5+=Yl{}v8+a2NokMDsewd`NKV9`=fS=_vDuX;z{rBbdu6J#Glh`C$ecDC}%&U zjFqPX@tLl#kEO@>7|Rcs*+`)MxFQU1g)kifN7lXK`U(;NrvW5}hHX`i2L1@U0Ta`_ z{x{Q+;Mj2_#;Z@Fbud3VnndtD`qbZmbkzn37sbR3upmI7AGJ2tsPY}~(D?+Uin`pe zY=$4uG01o6FK6wr?Xwxoedq;!SpaW)S}p>8BE_~3Hd2>BB-q3Jgquu-=lD4(Jnkii zr$grXNDZxLRGwn``QpccA0Nh6B2o$dZ*H|I+KC&Xv>BlDk~P;iY8;tbSEn>rs2$nM zOioD{x~xi|RexT%!zVEhA_E+@fU%fU0I>aVM)uzGN;nOL2>*~J?;des@8QL(H9;s6@tOBLrb1oYNo-KP0t zWdxVEr+R||p#th)pZNSX+Q<$zIp7(~sVo!MkT<(|QKg(FCAzel)Aw9PHilPGOK*KT z(S#ZAC~sLA^SXbYWt-#lZP{3skzpbXc&9@C*`Oh*vWP_8TK?$Zb{Sr~>SwtIPF0;J z!r{-z4cLJ-vdPT~v$>FGmEE;B(X6YEcfa=JK%cbLjoaU@RpV2qMZ>_h67W{B=m9eQ zN=E0#m=w4T&OyR67LURe9Od=fq0%v$?sNHrGUly5Wt`ShN#5}f1DehFQcoc|c!cDpyEk1S zCX93Hx3xSHo%-U?kdf>_xz_q2;?(%rszN!tm4g@s?+seLRj*U^`>Snl=}e=|>+kZy z@0BUHTc)W4T;aVd+R|T(NKXCmKeOG}lPN)_t*q=exPe`PxM9`+6x@WKcp^I*uMUJz zRSsafK6q)796>b={SR)T2R&LJo#v}xSQbm6p%h}2BU_{&MBW(UbW2Z=~WNdZDE>)WohDi)up z;T?{QbfTp(;#c9p?~KK6$s1EMRH=lbtG)Bv2R!`47=VdM8K}yY{H07pz+#(ivupR&G#AG( zl+j2uHZ$bPyU#H8H??EC(zp}cPB50UG}Yct)xnlENnDSpE*9#Kvq_`GeaKx%*&=F1 zO5^GEOhWz*PX8^das?S_8J4GB% zgM51o9W;O0gGwLGDMmv=Uk+A)i4e5n@J!GTYV#gUh7-X@@I%x0bmSi;3fj6hH&4{& zE9Kdql6%L0g8;(<&=5dQv&smXNGgp}KYhV*4g!_Y9hRdNn2GqtjA`oHt4MNRDu`pL zp_pUHe}qUbv>@4m=XFQy!VfRMT;KmsR4iSg`5%V?SO_}X{gN%*^r@`^a*ySH0$|^_ z`Ju!yNZLNSkw+ikHt;Y~%34pTvCY3&**VwG_&s_?xO9NVlF~|AB9q=j3TGejT_RZrpOfzXoay-| z|Nao;E1?;Hv&6t(k#o{GAM0GwyS2P2TAy&!<8r~q*@_D#2B_zW#IEEoFHK=s?}!Gb z83q*yVBZCXWM09}(I8N!>nU2`lG@{JF7Ym)}T#68N^YFEgf zl>p+6>@ivnwgMeI!undMuZ<8REw3WD3d^_X^`e~ps_3}dTg9HtrJq6c?>4bEFh@eH zYc5mTpXByQe!K-`s8$*5V0DtVpS8+JA90IMA9cqR8PP1f4{D{VoclAhiopk}n?^Fh znSSvZ5$gd8qzl7&o^jTD>dBZI)btiPegSJ&ctq&OF315YqqWd@egUF62Q4PrGB9FF zC7Cmp-vOiqf9pHvJurRUoJc<5AnD*IA23%m?G$nQZz}~x2~Z%5fW%6dQ-7mf<3ea~ z!@2KyXhMz5u}jm+O{Hp0`h4dc&>+?J>7r>D@tK!nE|N^niOD{)vyJ)4a=mh2xOSUp z?_0zp1dC~K2z?%VE2|Y~5*|mJ0_Lek#uQ{MJl_alOK}Gw9kjF(>gsN1J$f`{tE~_- zF2p-*+BokTJc9?x?CYo%j_2n##rS9yDfj>EB%A*g$+e3Gdx{yn<=1ucW|EGgw^`>f zq(Qr;mL18}#7RW7K7F|+Vs6D3Oe=cL7kUCFStsx}8+bakuGZ zg@u8J!fZ#vut5hI%BhNV1cY6ztzy()DjXPVtCfkF@cwz>{8@`FaCJVAD!C;QC$NdiZsLel@KFe2EsTg1?w{t!6dF#tLIYV$G$gG3}GVL!Tsd9#tN3w_;W&q(jq|W|dEvFv?nhc^^qYeG%*b7vq zW$zHbR~#Im&yIwMKczvZf}`#(e=)u*ol~b$a)$@v%!VKEn~|j@v`6k0t=216jg*?- zmKtIQkitl^b4H;-ti=1kIoUK)Lv7PN_xfGi4$tnGFYTU3EuwVFpOu61m41lKSn5fcc|8QwJrStaJ!Qg>7?)46p{sNVBn&i5LNbzc2MQPj_~7t+;OvRoB8EG15h~ zD%(FO630UC(djVWjyJFq>cbAE7aaT0b-^N;(a5ZvvHCJWluuVZ(boqFajCVmnYTfX zTK0MI3f(lzBFS${Yz|1yEt&<0#V-H|J=Z|g6EZKvLzlut` z)v1!mSo|?Ic42xM**DzgE@z2`b2q zav3j8^zKF*_eb zQ$?@>l>+c+f~Ig~fU)#_-Mw!?L;>mOKPm~LYEgZRP7?jDi9S;I2+i*lCp4@xc7k-S za*Q511Kv{XnPYh&S$%{hyl;XgtKdO6ZaRjYVL8<+8EeSVUX5n1X)Rj&`^A;Ch2~+x zMt8lN^HaAZvIXZHX#6{ADu8+MkIyI%!lvx3eHi<=5}uDF|op314GP z_X5^$XqXv8-V4j}80znQXh8rzC5uBExl|tlDU|H?KnT6EhI<%;Ce!4oRRLyNb`cH_ zV}7X+AzdWeh62H#pzc;kpYmh1m%1(zO41@B9APY{{$5lz{>V(3|% z&yVh4L0uM39;-B7e;V-Jxpf@A!t-;hkMxUL*`WovL_6Jro{^zt{8@$5$}Hn(J&`X(+ot?ORyTc53Ga`s}j8F zN|xTJ)R*36{vsdqk4Whai2bU`7pGLL=_@cPCn`%3N`Gf;+q@lmrXY<^nBUBBkTfc) z{TX|{Bbou`(MLV~c$z41n7?rGv-0E!0;;z9Ev{;sssf59u34i7E6I4fTpFVd|9WPx z<*2{AS%4TKU;d%)V=DB3*$#l1Iq*Sd&2JNG>`X{`e+Iy?`Qb|HqR4+@*5rJzW3)(W zU1Xg5-)-N(R-5qMsA>dvwO*wU4L1j)e(M`3)>fHC6+zI0oIvJv5YkbLg@#F+);WcX z_~D3H{nRbu7x*esdx>c$2ISt6Iln-is0ty0%};ow)w}1cQy5bbBJeVvX9LKYa1l8z?ko+XheF#9 z{Hh4evgt}L56P-(CSC-Bg54+@!x78hZE8@7%n)~`yqb>46X^>~H%)VUhNlI2rLR>qcy<6yJp7 z2l+=$tG=C7xXKw?dJ%Aay()4wm?`^DxK8pRBCKiu%)c)aIb&KcKvp9#KU_0^4B^;a zwp>-%$76BDDJm+aw#VKA-aT36iwaZ!{{DbfaK+p)99eJR+kc)~g-P9{FE(stt|)!3 zq{brffFp_h=y2aIO{%7smkO0z8T{!uFDfYJT zfbTC3yP8wGUH^TQlY4I;_{sI3z3!s#JBrf$^}gr9Zd2W^KkDm;bj?GJcyaSqewVW@ zkLLoS_7a!z{`OtX@Bx?h8>isPSM4EWeF6U#&qqIga_Td)n{OI;U+xJP9c@T@HJdko z9Mm2_Tek1kvd|Orudul(2BYrI@FqW`HCI8{vt+6nvpc(*A?jha-qO-_>Nmfbd(j$` zk36LlPwL%Hli6-@9ZT}lb`2t*QgPJ&)4o3K`wmXm($H0?@G)d|^*7JKXtyZbz9(_H zne@}u`o}oinknD*JwJJ#&C$dsj-oH-?p z2IM`_tI`*%iJx@R$Yg8x?L9l%rS7HABiVk@B^B8IQ?`!p-4P=Znm@&d9&W{~xBOdN zIyet#>GO%Y@Ahx_V*j_3aP?D#)MFzHZ4wd;W#t{nreDWWw+%!4U)CkxG++M# zg@1mPkFAaWzQOL_5VnF z@1Ul-b`MnL1r!ieK%|SPAXSR=q9P(7AVqqS4$`F)AQm7D5^*$0ZV4l zxA&yH@o`V9{#(THbu!t-BRyhB5%W7~SuLVo=sG%=bgG+s>T%t+Iy=AgINU7#dmk{e z^7SkK z;SE<=nLxo7@#Jmx$^dYNTs{##bF=zHOzGIW=P5!_P=P&=O!|SRQc4uiu=wZ%)Gwg% zwfgyqbwBHrpaJ(TN!UeWQvmlzAEOZeU z!Klh@pLRyfi%9oU&<2$pO1{iy{KrnZ0^enq`md?h-F=Qczt;$mm1(z6%IxvQM^3N{3=QMcDoPv!wh3k?-IcfjHw4LI*(eo1QbzQQWq(RL)q4y{UUEu(Qc+{Wl8hY z3ifU;q~R~K`*+tmF=MhE9dbsMH(@l3GV!Gia;~xO;^DjS`7Y_f7W!OPUh%=Tz47oL zyt;h;F+(Oe-vw{voRs;l}3 zFHxzNa$bGbGSzO>?16`##S)g6^81@9`d3Z6=)R1p6Rs@3 zO%j(~UO^5KKP6JECSXRdrqK;49j4gh@?SfcT4=9 zL%U3k91S}IVw2Od%xA6g^tfHMS>ukffwV+AjX%ayrovk6Tcvv8^OOXZ%>9@is&5M4=BGG5la-98&UMVtKYPDjNfz|KXzf$;vg@RMTbgNC)SyU_>4+`?)_jbupz z@1hBJo>w$-v)2E!c)BOUguR?wpQ>G zm@M7(v}#)7quRbzI^Bk-tG3QZ=g#5mAb?sCqrMLH z+l)S*XE0G^p_dVM6$T%EsI?e)t}ZFCJeNy9C|Tbdac}(#BGo1!m!G7&%ksp$ZdAR+e5yz*ah1on|l#CHDkIm(WRnEv}Ylrsh3gg=rC ze7PJ-WV`=-mdI9wfXM&;c1U4CL5xC?Y=LdB$rrClmiee5}R~tt|qGWw9AI-LqJ7~w@%{<4z_TKqoXy4?skQL zIT!GhP?9e-qW-# zzdzdY_N4m58xdwQ$~|AcwNfSNV#CxbbBETYBx&i)D~IBjKnf?-TC~*qEUw%moTXnaJ++VZ zG@@{Y*|ZrRG+xZ^b+OhlNF01y{Eo31qA_0))>7YvY@l>F>vA~JZ~peCjs{RJH*OP7 z{+_(o(CPj1rV4|$q7Q1Du4(Ko<}D|%6xQziQdZby_L(2~Wvu4&>$iZHUF&i7y-D3i zS12x7+UP3PyKX5IFY4@TOh)X7$S?k|Z4p;VQEkb%vOF9Ee)o6vjfs&V<7Bz+JHs!6 zR?;K)(02n-uOKx3&b%6>eHf`~zwfEF8G{d{g7x!_#a2vtG$~Xnwvy8pPxA-PG6kuH zM?D~?c9%G!IR3-9US-d+Y8aKvsp;vXyg%=a)lgD$4XNt;8CSA5F8NkVW;QBa|L|Tl zf4(p5Ui5ptFIgjR&UV4n$kWWZ{yYv)9ZeFjdyaoO-2wys0Clg!EKkLFx4C5o)udA5 zcTaO7#8}ZbPoU`dfL{(gR5qES8k}Y5nnQ+>p6&i(=e4Hr(kyA`3lwy8!Ep3sG)gIR zx@|)9Qbo`e^2~qb;UZFIVn+hMyhwhG({O@)Czs}4iidk5N2?l+gnnG0Gp<4g&s87t zoimk1Q!j`S$mpDx;R~&a&Ma%3W=vmWd!7RY%dL0*UFc((=zz^GAv`WbGvq0S9D4q0 zNEZuEHpL8VEf^r{mswQ)6vc)|7Vu7|spnj;!7lZftrt$qn`fu%ysO$zMns|3n7gSl z=79S7D%8 zn@CYZaEtVz2-Nwggj;51H)tSX1<@kZVd;Qw50C)BuZj&<#M{3bL}qbSop!sug~H4n zHeZ%5jPypBd$nYhzbNAl+zpe%!!xck*Iqvrg)I8D=TAbzj>q}W7A`F=2bI?Nzdr6x zHk$8cm}ri8x=4!VINMJpmQ9o>WAGBha4U1 zZqU)op|IoUpdeUHf1xwXMdO%3Amiv90sHIaoFp_zv}}Kzy#||`xR&<)zMNzClZ$Q# z#7d&yy420C!{o|jmIzhp6w+2jp+u1CrAqhIcB`7Q_>-4*G$ho8+HIJz&raqO&04RD zt`?TBl1aERy~bTJnqYof89E}9ZNtZf`F<3je z#lrFz8L(mqjogvggjXIqpNl{)!eit+Hpl)(lR$Fzs?T7qZjCQcxH^J;K1;$0_Fe&W zKM2rO|Is4knY^#2nPJu9?P}~nhnS#YipNaayYCWl53DX@TQh^@GkKkmZ0ZC>iDL@v z0^*Uoh%w6c%dI-31_yJ%p3}~wQrS*qz?0t3r85z|S~16-Cn=mBHLrb^K94xBk85vr zwPB6FDZJWEc#rY-7jY107&35NXNeG<$M2?~JRE~HWCHH+GE@2>vR&Lat3k;xXWPap zotGlrUCiC{$rq&0=76LsR0KC@~;eqD&&aKpJ;?^>3H!NXpx;neaI$}S`eZ{ z%?_k!y*_1s=)`o8d84rLm+nR~jV5XJbf42sMz3s%=LZkLjHfcSPggeiH|~fGcaQ6j zGQyhqaR(g<@6>}ywP)%WW$T`*%fw`lxAONitiY%z7sqd_&700Al2rL8cKZz+lJH_T zhwm9bI2Buv!CW^T#@rC$ZSOB}2^n;{Uw3Vbf#uh!V35QSG0^$V@)Dv@!4F^OgQORB zvF?aRO2e|uTVot9k1e&o4KkH2EE~)oP`gSmtYgI*Y#uI9&puPaNO1M7m>Wi_a9JPp z3_LfHN%SkVlaqapShm0I{${YeUFX3UikYg@Q25Z-s@JkYAV!{D2#9U_Qy$wvRsnYA z-J1xyd4E{4-u^(OppEjRA(3PIKp7TZo}a9X$o20Nh7o|O-}sMNU%rCo&`->=~X8kmzF zy(e^Nug$IN{K|3QiY#TGmP|$RdIPm=_rT-)?E-3$7GJ4!c9ZqFCTpsa{ztNY15uW1 zBEFI<+xP#`U+LC;l=IDfX#W~exBA#o*dh~EIz5J+MRQ2iK^|Rni}*~cPK@RNfr3P7 z0VSWKO)GkHH6|~CzRCR^vtTrsiz=W6v$%vLygP=scCrU8_zuZ_HDQ*3v$~qrM>fin zM4OUVsYCkYs6-NXrwr@TFGiY(JjtK_3hHs3dlp*}$={HW7SyK|#*$5IJG^)3(O{8n z7{tn&vqc^F<(G8Z8eCDPN3B zX!7ko_q2Ps_MBK5RjUrdU*a&beDgeZ;q=w^$}5W-CX@_kY<$p*d#9NB#VBucP=53I zkj(eYgnz_jZ>(EB5No4cq){~`A)%=%N_bjAC+pksb46hz_oW}hkPQ~dy2SI~`VzCx zsgWc+=ZdoZVi|1F$I%mqniKg|4k^RF)}W)JV)$C04|hd`Us?Wki~g@47pl+?x!Dr| zzH{Y{bg7DjlaR+iQ1PYiCT<wH7qOdcP~$792cZsFM9FH44E2>u7a^I ze?$qypf78*DOE*)W~}I!?a`=y&AT?G1Az<;xtVj?O3XB3oP|eM4*Q=dKJXZS!SX%_ ze$Iggm-D^$X-X7SqE2Ba_)0fh!Ef&i5D6A3v0!QZ)r}Y~Bx6?H~$_)2&8mC+p+{h{# zZQm$7^vIOR#A`F;yu7~dmdupIv+++ZmDGs-uD5g!{^3YrnxN;ODgbiam5UrvkN#^T zSZP-IpDDevn5q|S|F+v3R%wI9=S;RWnO#H|pL@Zv`kMM}6)1xw{y}@@q5Z7Mpn^{D zR@{r=HLv32-n4bth;tVzlyUdE$ga)n@cc1ZK^w>Ur-e9?n^AAKzSC1Twa?NaoHpDf ze~xQD<=qfEIFQ-`EDxVn}^2ECPJSaXBOGB&IE$8eX&}5C0tC}$v=1l}UN!Vmc4LqHMk@v)oaP_f-n0zcmEEwua2U2+XCQ>Vu4GJ;g<+BB%`5T?+D*K& z?5hE+@i(nHvOY((PLXksri0g6Ma3c0S__bg=0Gg9RHRZB;KV%6^rL_k&t_v;@gi8R`-kH_6jhW7y*g`jB8TU__mk))j zQby=OuYH4IFD)bJiZ)DEN0e!r?LO6n++e)%%F5|OfiHUxhuyBKMQ04D+l>&C(O- zGR(G6B>rrIH-aMn`2y&v<5MvO?PSgN)A#EWSDCh@57y@UclKro&wHddk}37TygLYI ze?-(>7kpo&sNvqvdu^!U+1%r<%psVy;5YV$)Wc6}pbWTRkPrS(PXWM;0D&WeeRUft z+PZklUm2Bu)v-e|8_+hliPxs4)`UBKC-bg|yj6Xhx-4))EMwNkhh8i<$%03eOgKcG zI;Qc0ww;X<(8#M!y-6Noo2CQ%S}%ktQHlO`&H;IrG^SC^vL|~LAjzLtj7XhZAR!Wm z#KlGJwaKdQ^%}EDb0!;6v|DnmlF_By2j==r4ujR9dKr_Q0nz!VEfw(Pm)Oya``(V? zqJOYD**E0+Z%O$rwjByRd^K&eH@e@T3gzRFJt8=Gj- z%{*8Kd7b7yuVB0Xnc(}Nh|K1LpYH2C7=NNi)ed&mmHj$Lho%b8zcJdfFSl^mqQ4q@ z-}O7@dAN7W^fkBFNCXMgh^CFZ>uRi4hL{^JMsCbzCu>8l#|1QB;uduy*OrQ7d7$W* z0g#egYJfi(o5=8YXFeH*{{xXL3F<{3p`BM);frCTQSr?1H<&HOC3@cLay{@;ybPid zOXgST%eKs;46bzT^YWnXd=2iGkq5k4=ZG2_=q5-tv z<3fT(AVGJuxNQ-)M1O{9&tCyj?aha}Y{v4Le*w8w0E8Sow&k}uT1Eqi?B@&sF4$B4 z*9u!;VoZ}fPhbn-mVL!*pPoBEX!(oBE%@2f%-lcrdR(?qP;*?RXWSPY&PHB_-D={+ z!ZVHEyE9eij1-_Mj09G8%>v22OHISJ!1VcPzVzCluL1zZvi~2%WJ)%7>1*Pn05U9z z?A&XSaa8vU6i9k=*er@zkU@ckJG2M z@zpA$&)La~d!ipXR+c3r*oQjHD7p<0i}r>k**=(fZ3)6JrpyMfAVPCa&q;~m#VR`K z!R|kZonUEQh(Q2n^(YfzF|ur#yh#1Laa~l%ijAW2VVm>9;TC{MKc~iArG3ou0f2bc zIOzZ;RC#m_p-taFG=LpZhDJ+!LqFOV-%qTgQc-s;7vu9S4jHphTcn8hCkG^clm;;0 z{elPwXNUdt*;JYDV)^55mfF8!3g#{SPx#GN)oka3JySI1P+ALwUGZ3kOS1DR+R++y zs5SvpcXM%7hK?ueKS9tY2%jJ#2JnlB(X*1|pCHKXVf_WMGb*;0^J22+1_oYuP-9GG z2~Wn$XO@cB%-6@$sxXbHt@`3wEmw@?J#YCL^YMnX$Pc7trYDwx7{$qJN~F#T;bi6dxM4iPy9?ks_%iv%#{-d3N*Q*O|Jv1&Wc(Wt*1A*}dnd03Fyv8Pg71aiYu4nCP)q0%wUxvxihnUM1hZy_QYyM0WF^F1=P}b%oP^@`A!TCX?_R{iue1<) z1{|4YYDN6uR_pOTcsKRyjDMFTKwQ$P)9(6v@F|`3N(;fG7?B}+> z6Hd_sB_&d11DE3Z1VHG>i6BAs;f=MZ<2rF^S51pQDu&~iRF6-do3)F&p}BXcB%ROtsLiI{3|fEqMO4*=?=Pt#8`jYJ9vpOscBT2-~PF-@a|L z7}h4Tpg!d&iN`Z509s3a4x$kH!G-<0;veEJ+r5(8e@7Dlz+(W+zNASJaS6<3w_R&! zqZV0bp~#?Dys?~XJ8mG#WwUUQq*9zDbZ7b0yVkCHPA=UCB#Mbne;5r^y!To@FQP)m z*vsed^2m%zMD^=FY+t{ss`nDW6t_U0@+JLOuMKG%LIjCYvGZ(U@sitt_HICuWXZF z_0CH&0IFD2=k9aK-V}xfu<8IS>U{o)O!t;5V_kRJAq~xcJ_Arc@tG_J#C%*g84v?5 zb9>+CDThK@g}R@$kJz2EHm9&^@Lo3aJ+IyHK)#`@(@w;nZ{p<`6nRHBt@1u>Q%%wr zY3zMY>U&!>MBVZ>Vm+;yYeV&^!30{gEX|j}_CQY2Z1mNNT}MBjf>(VTG>BVV)Yyod z!6E17hrSc=8KvMaEIYg5a520D`_JyjzkRb%6QZ*h6$m6?fdt_zoma9|_5TiGad)1S z^sxx&vg>;X@fEohh>E6sL1Bt&u92x1SnRX+r^w+;R3k7VOb(m`(!B)7>qs9u^E&Pw zEWy+I(rV8xH^_O`8chRhfMS5MNT~O(ZIf@i)DJV@z$cz~ig*=k$ac-FQ(D!p*Pc@8 z)GCikbQclb7H8|VE%F8lpRE~9Or-ZRB}C&cz-1zze&ACr06MbAx5=&W4LZM*G}6D< zet878a2|*`hFEF5>+q#_?YE|iV7kP~6~VOo&v^U4@+iwpIFz#Mw`xGtVTIGR$o2Z# z{rAn?Nd}BI&H*AG&L3LiU$iWJnfG?!Cm`9)U==R`z#2g?d!8wG8KLm9p9XwDf=A1@ z^r{Nh#}$ zU6a)iYLxm}IZnct1m52R#XPx@Q;!~$*00$TvKfS)bKvNs*sEK~Kijdze5qM~Q0B`Y z5YWU;YV_T4T}hnqiBi!2sv8TsgEo3>(}O$ABD=Hy)CByxE?^dRl&DD&)`kM+)bU2k z<0Zg{pIdBt<6J@8;>GY*aFnzWN@B>Sl>%bCebPuWR~w>P_&XSyxe<+LT*rEfRy z@+)sr>qcoWP?Z^Ird!qtq*t2>mo&q+!H;Jq39;@&Caq!ULHJ%HVwr%$EoRlMqx6iU zVjpV$HjSamvBVOFd%pp*l?OABBH4it@1JVIGhlb-M4&-aRvmI{CRO>!S~&ZhSkah< zJc%V$v<&J#gQ?GXmDZm>FY>bc2HjTHZ*gX27I#j&7{?ZHpb0QtpN5fouGXV_krPY~ z{6^oYV&Ia8evu0c3#@I|O0vec?oZrkH&AU_P}I<%QF&Jr)e5voie0{6Zcm#k?()<@ z&CE<(f!0B zS)en_G%9LqW%Z8(UT-*8L-b+N7czKZ=IZ+ zJd5$MXzA)=;?Dgx{yGAf&Q_9BRu-*tM*uN)f6&(t1oAG7(WYREygOh+QgRtshE5Ue zh@2c!x7ICQ<$t_+)v z+m&$5FvP#V%q)hAFY`~9Ml{6QR6PO{L`?||;;x2-usbqF(_)IwL2DcUD z3v||(r#rNuok|<`q|flD__tLoH(@i~-&b)OJ;jOUZP z%P#nJgajT-N*k|l*q-vB=(q)ZDxOxmER*4nQr-hE$whqBciW>_z#n4Zy@bL+ih?2z2(Vz8}LDP@X>!Yu8&&IgqAEkID$vTM1HykUi`VNvZ82@~! zXWCga83>dZ`HM_Q4a%UO2jV6dJCWV25X@5A%DR#Bdg|htRFR&%8cTOt;0Yu2aLdf9 zC*}^ob;wynpkS5FKz2(DyF*rEQL04LriYZi0}Vzn@{c(mQYE^Zckc|zDqIF(!^)1H zwEl2?lmhaj9#!+92?)@Z*sWfh^M$igUA{arFAIUN1zy~T0xu6;G9#>s6dNC3^bNdI zq8koj_e~1!7PLCrnf5`9sGz3`yMKniR~4wIgY!+A5d!eYOu0d1H~Y|NE*E;kQe%G- zj3$gY*VZqG>5a~gwMRSSIoMCJ^7k#fs1o?4BUJ^PA}T6`zB|vit(DcizoV-0$e}YB zAV+a>)yC##?)cVzj|QH$mTUyl>RBWc+!yZV!%`)!n7c9SAx06TX8 zI;L|Mm?)f|R#~04E!w3fKL0EO-k;g>Hj-H!4JI6w831b66dBf+EEI6pd%=^OxROsc z_gs9@f=k8LyEC@mUOGs?LwhUh0HK{IHc-o-e_F7Q$;9oji8;;u^yM9d*R4e7=jYqz z^W=)E`EC{D`g&_Rlvu6kSwITqKOwlR4 zzU#>k=6qHz1C`RK=EYNz!U<=8%7bvdCe-*>gRaZs#zPIB+t|Jq?0t9m zAS^~zAj8hcu|~=mwEMtus{CD_KmcQnp;}?tZ%;R93V2x|z!IJ3mEK(Kc{cW$fn1#9 z*|l~+wpPgHvF9~pkP;#`{Y#&f*lgDO4bnW?36;{sBr7u@zuHos!=lZl|g6*zCFO)@Gg96fu^VIT&v0asKl= z?Zoshc0BB~j_~f(pkkSYDDC@z&Z$6M?Hku4+`{3S=t$Gv^lGWgdfr=T|MA0SuaRJy zASe$IHhs}j#VkJ2_{}Bpc@NI9e6`to&)ulirFTWI^Gm&x-oxH=@!#%oet+(qc$D&! zM>_4PNy51hKsmez{-za``mu`GJYOj?!|})yj!DkC8Uw@4&N|I*Dboppj{A;(kmG|~ z0@2LIs2J66I)&W1-iDWiswE0P^2p`V0oFL&1$Sow+a*q`Na5i90HgwIvOGqu{npK6 z`52&${z^M%`&_`#d_-jA=-60aTQKGEp&5v)3^dQ2^q?wQcUDMPstwSVog2WQiurlk z-4BQW1meve<)GDA-Xk$bec&7&j>8FP^FfIfy6?`c)~{Z@g|PdD+EgjiDf+LZn%QU`TH;FK>T49p)HIa z?zVi13&q=V#@on}uqXRGT`HcajmrXX^1jO-iPBb8w-S|k0g!#O=NU$9eprq?wbZUY z(<9*s%~eUhB>BWdQ*?yLlJ4s-=w6h03wiGu1FM%Su8D|<+6P>M6>21&@8lRKnyNKWLyaWRE}(4ii*PTgeR&8F)S#Q7%rMW z^rKVc&fOR2|2YHo1zvT&+~T%fQu~TBTrB0by6a#?ZBG_vcz%9B8jLgX-$Q4vHEcJx z*G!r!fWh)*{%b|Pxb2098X6b@y^Y4D_msz7;w^*e-V3dlxYB;GdgJ2&_Ira`j_s+8 z`26|m$!Wf3(Kz7n11fA}=Dx6uOfv&*qqJMSN!(5Q5-+BiW?mKhnA_9lW;xt#An}TZ z|T}3M_*_*VSiRams;xx zi_JXaN^?KkD#J7Nshe~iY>pkbG2kOp16@}Jy*&h7`as_%UUTQxVV4tK4+YOG81J5Z z6zj>zU|aSvs`Fa@$R!kT;sHpHK)l``$ZIE`vo->Nl%Ev-?rFiGRtZw0-PPlca=y0i0OC_@)!>WQPCpC3U0kX@(`Jo%H#`ke7B@v*qR_ zfSM!jv$c~UeW+~(j_emH0`4OHkG!FSRJz!NVZ^k(w&RTG54b#mG!VZuWYFw?uVtUo zWl0usY!Y+Ze7x`_UFTqr%EgT>z~(bl*bFc=pT!Gm7EMSXplBo;Pkc};gP;w1ecvo_&#w?E4!;lf3jZ4ff8BpBF!T zFsjF9hu5F*ySzF^!Bt1IF(4$%&uA_Z_PQexv8PTCne# zbG;KKMt5zD6*W7o6PqI@o7Yte$G5H?Z{PZDU^~zsi9zw*J0Iy)uXmW=m*<8*~_hOni@l1noYw<~YGA^D>hy&9%(*R{V z)KEIBNFU!_f1kbYhJLl()pT_T__Pf!x!qqiRcBATh}-k@kV^kJs}U3Gi-x=72>zL1 z+~m-HHbYAi9gaj`4iDd~Ig~A1vJ=ShsCIB`ZldYa4EWr> z{3KBk5(5zHzP7nCAXLMjl*zXNVI@0$pU{o$7W~BXH_vnQ;kOcyKK)8D@2Qrvc4Ysl z@xN^c)~9VZa$n&R-fttEKd;Af8zF%_q~z&p?}C}Fyblcyhf51MlC*P@z#WLRRqW4( zKr?-ol@C0;t6X8(ABPor)qqe%D*cEW=z*mZa$kQAOw)3s76{nPl{sFGo%2W2)E`c; zYZvH963);cv$?C`@u&NheWqPG(WR>p`4bKRW_YvCXsgy=^M-nTD09e#a0EKW2nN0r z#Gf*Rt|Z-7#>SiZGMD+S6&?8y)(6x3h!NWQK&me+STNf#l1rD|xXzUWjD>(Z;Bo{5 zJmAf;_y$ZJ#vOOr8n$6(&zHM6I-7+vP6|l~;-*m1) zAsKtlz^Q%|vWbpYQI5G!+{c!Qv~g*EK7>)kCcDOF>(@OZtkPDiKBNcY{VpCo-+M=2(8<1#jC^SJLoQbpdC z^%LN)ezhlB`n~%>bCTV|!&kcO06u#j^b*KaD=8rtcK31F+&9?R@=VLL%O?A_3L=1r zL0FTZ=Ij&BQ%?_@XfUoHsj}|7 z4g?$Lfz=cXuWCuNWWr$P#r(jP-!pf^XP;=+Q~b`N+-2Focs~y1j3s7r)~KJpU3*94 z@5Ku~Tx;!l`O0@=6L@6}%9PlqlK;@2v}j_@e^<%!Yce^V59v(RY`tbK4NgTKjQ?&A zS_mdNBU6$E1vfQerxyav3Ru}|cLfPMH|_mDo$T#R9?i~-t(`;NvR&hhcAcE}M&^PE zw_JSt1oun5aq9vKNw$cU(wWtafZ3sD8jw^7$o;uDrm1IPeId}rB^qwEH@Wb~rJeed zb^03o0%)qC@M%|^_eX|kPEgZ<*e&keaFCQcI?R%_)ub3sxy_?5<4)T&E=E&q_Ap1A zv?iQaN?(F&{gC011!Y;0FRs#f3M`dS|30tB*x98vmyYT(HIjxM2g>lZxon%^dA%%P zH~vPB7o$3B=xj!R>VS!fm-k}oj?>;Q=B|6o-@GMx2vO$*yg9ja$p!i?H9FAJ2c;)A z^x4O^vv}bue${#s86vWm#RNCT3^V$#f=ves-%U$H^qCgkD2w++X#hp9l01OJx<6_U+C+0;a|c zUTbzn66Zy`*y4V83x5XEbHnwH?W;p>_O%*>^ZMh4;I7Ma*m`lnJL@28|A>hNUFVMq z3WgVfErh1`{B8mw--^?|=tlM5%zj`gQU|2ryAZhG1YJ2Z(wwPyvtqnw+H?_W(b>jz zJU5;R4`5h)s8MrSePp)ec%>+R@1@V3qYQVL>cT}f^Ht>E%ndgh;HeD-2nfKe$fLE) z73)7}z9Z%6V7ehtw>{!P5MTelwA|?1LuiMzXB>;7XaT3DLkxvi56#J}zCr=C9whMx zt%y+@2Eu7c{Bi3~zg?M5oJEBxj*(+W1qqZ48RcQ3s@Ns4StsEXwkF}QQCf3+h<1VD z5(A^_waJhI7S=~%HiCHiyrUhQ1ThEqDOr8l_g>WFoBV*)4|M`~@c*;jhOz>hojTpF|=j(VEk+wEK*0TCO7wU;$V(cALD;->Z9CC~LGX?>x8 z7I-JaB`#)*_qU34+xea6eAu7LhD2ieUrp$==eLMFF51QT^=3>&E!OQT83!Dtf03z` z_Z0>o<0kJH>u`7PiM%9{A&_JZ0BJn=C`@M>n`xUE#8;G>U2G*eSjU@Rbw*(=9$-{ z=7TL>V{T*y_A&U58F-tguhZ5-0 zsL=u@G8;Egm02Cd13Rez0i^oBw%U-lxb)Ve`udpJ-K>V!et0E=i1oQL;y`*6=#2rA z?3#PeE|q_u?2$?DAE6ijs7Ap(;~V*F;@eBQc$+oGwX*}^^EyED4#ipZ z*uS$}|2vz$zQa?erP6aLj#yu{aQd-qw`aSYxCk#L8^BwRx;I2!7N=!bvi4OOxI>n0 z%aNR(V)5@)5>={Y*AdsV1yDi5wa20Dj%S`er?2^lJq*A}@Qyy_4w6`{Y#ZD1sho?Q zb!so~R_F`mHb8VFDjnG^A_*qMeJxKD3%yvbktigbW<0A0$_3&bgmr0F zsiF)+n5p01n@_=;wj)I>6W9`62bU>qGxW{Zb=)616Q`G>--o>N3`oWxc4pvmX-iR0VU1*t8u?&5_M`G~w@7L)@9}jzMVG|&)nlPDiP~^+ zv$=uF;7gyU39*cbrXU~$bNfe21(p`OL>zK+rSa z8*2Cu-z*yk4htr0<%$@kk`0S+1j91E>z1S$xk$&Mo0--o_5K z=W%JOhP~+V0h&9~eYi(9Se9(j!grV{hP(ek+OL;QTtn!O=TvRz zDL9T20FwMGh1?{?{G18V{=T2UKsI|IL3)2XO=_diVd1#Y6o0^Qn!Hf!jjre-;d9_0 zl#IFkIrbLN4+WPgUPwZt^R)n#&E(|$JmY!i=j;|G_vN{yvFk543J~-1WW*823J$=? z3t~3h6yREb&xy}nqkj3e#>>5x9c)8shJ)QGEl!f+?$y970@YSv+~8(^Dt<2KtgEQ3 zi=ct8(;9T0xm{yDA zS){SB@j8@P^P907?D)}}>Ce@DfXefsXJg1Q$ZWL;_$NcZ#Hc-~Mlp?3{BNl~RUnci z@VHma5_olEG~3+wR+D?Y75KvzCE|A==mwVSWTlF@lZS>axv#cEV|$C{%c(Q;6Jytg za3_tp?P{ngCBTUmL^a28G~He#0yJJKn7=xONw{eb1JwhIlMtVSWS}yE8YtX)?b;th zf9?BD)CIEcG!{?Qw zpEPL4j;1O0ML}cY>H{Mp`wtPD04y?P4s^O|T^vGk^Nt|!w~i}DwPHT|_62Q#@cL1B z_LoW3>8;)*YM4J`r>D5gT5?-ONILONVLspV=no5LTCS+Ji=!;Om(I1U_|Xj%mcM3D`FF9kKr^`p+c*hwuS?!Z(4c z)Oy6nxR=|g02gw%ay!%h?nRMU)xqNP_Z?n$l;P&@W*8?<2N>u(aj*T?xBz7*FFStao=!~ z0=oWW=O9YNRRc@!IRBe4T9tF?pD7UmhHr|f%H;4FjL@UD()7Ty8Ola17Iehf@y2|y z(pb0bPgxL9*8TDKe(iHJ9*{_WQ%*2DW^FI>+NyW3hN!P^TESXo3l@bsQXHvs=^V)3 z7#0wJ4Hrk$o=Wwf-XuQQ?KZJVHdMLi&Ct@uZSq`D(zpwDqcI;1Jkga6e=N;8htr2> zw7A?o$Bvu0&zk^bK_=BMCAwuSo^6~$mfaQo2M5uzYTE_T?DoTsa(Q%#8(%&bBf<;34r6sfWG-p-j2B)H zble&rW;;V6f~NAlX8vxxf`+CFiSfojArPdwJifVM-6;|CW*lf=M@So;_0_b$Izn~P zQHitE%VN3wG)e+PW)V;t`2`ip=J471gL+#*_%CgBsg2WHK@xs^DXeaLqde$5B^`@k z&-)TK^$28KrhGh*CLtUS`07hYFRuxHi>1$SS)8$YHKpFL|7VpCe|u*8_c}jleCUux;&k5vty)uPckt_%Y~BgblPOV8DKq<4=TU-o&Q-HUPKK29Q?`?|InsgPNylpnq^5-`{0fQ^7?7X#NB)ULK z`LX9;6x_b83?19GF;Gv*%NqE0O}j+Lt1((p^OKF_xXOE>*};cRKv@cFHg`(hWe5IU zuQ1O9U_QUBL#A{(T`pa`iZD{vfz>_*$gT|=#;!?DNG<;ye(k+KI5svbt7(mKOjlP) zfYV}XV;pCXI=(N5s&jQpidUF5&Z#Fra7A}-yVYsfr@+#33`?HRCCUi?MEgTdZPPh- zzus1UtdmgySJYf6o2x}JGK+a@|?>%vAkzz1#vdgx7FOfZWQot(W`NxBWhUe_cVEebmt$aS$&yC zK9PdTNs)7^DRH4`-z8$qjRH(kRElrBp8ALYF}^2ye4(6wjz-y{vwEQi%z}9L89AZQ zS*pi)@HRwLP@y#7XI9=JQw(-k*GUrI8!Hn%>-?fW#dgBI+XUl2LOEoOhfdQjnl(C; z)Gh?lFr;~3ud-ExgC-aIccw)Ee8aqN!aU9WdHPJZub@OakWo-Kc9}nkobwzSVq{|M z1Zgzsu8`WMcYWkysa2Q~b>5yZcWyZL8kF1?O-O^<&0@$Q^)T{Uw-b9+1Fv3XAm8Bz zpQkBz#@&7Ks@i6AnO4&BSS#js9ydkcS!`p$OJ3;yd-uD}4(NoEGc1=kI-An;N7}TA zkJTEj%n?@m;K-v2xE4qpxE@;z<{6gkctCaKj&5TJ{%aj96~H`T-86S8%Jrf1OP}L4 z%C|n|Z)VPPi)@Q67jvpx3`)8)sxoB0)#A}vLTgldp-U)J^M**u=@~ zFXhNodCBTQm+$=Q4e(iNjhBI<+yoj|gZq`ON%J&5%XXK<*ZD#Ye7T|oK5HIK zC|JnPcoZ9_i_BG1QSqX(dq!8T%@lMea9*@ke*oR6X5#AqsBEEV>NDXiP)mmcVD<%+c7{Qtd1CG2>};gg{;x5Iidj+zjWUC7{hXeNO>=@X9+_TjS{1dH z&kyTlfW9R~!(A~=^6&+6whaB?;_268dtaq>Ea`%+J>t1Q%9_4(K-q(h^mNXIBBB zK7^Nj;DuiwIEcIUIeCwa&394?NiwCBC!KSy;r(sgzhGdw#x5N_rpKwNoN)>Y>gv>b)Q=3Z%)wQEvqYt~06x#I&v zF&9RQ`4BbQ)2hJsbLo6J`T319d-B%XOZROv>mz_0_rx`uIyd3i*uZZ*k44c`m!nbG zSOXLFWpp#n7|L5?_@q>v6yy^u)-hNMyFSA8!rWSVk* zqz6>`=CtRaOu=Jswb-?cX6J!TF5Qn2QjWYgf%y(VM40+-LLr}p0ntF}I{M2Q4`Sae zcY!oqaPao?YmjhmHP)4{%BY%U-Kyak&`lj5Du!c8(Hon4*&~jyCR-IV8iu9~;}btn zn`h&;KOux2y{bLrI>DGeD4=^v~h_Rz$DFYecf$$kLo`0r8%arZX^I7){MX|z z7=fAifbytHmKWD4Q%e<#cW8De*%qg|51OD7qju!z6CRXs+U(dpJn|3`mI}GBK9H_R z$2j3~&#o&9-MU$t2{S%<+x7}*$C!V=9o-uq@T z?Q|%1Bb%6=VeiPv^XRYT@GMG3F==wmEeKqUk{KD$TLrM2M#!^2`alm)rGri)zxU%^KD>_$h z>;pmLuO27Fa|T8?Yq|Vto8ix`~lBe z-?jEyd+ohHd++yp@Av!let-7+@+r3jzY}sd&rwu`@WC6y?V=6S8TVrR!!0GZj@_!? zy!Dyx<5!7yZ3@t(86B;24!dN#AxR7I)=42+@{H>jdYn{@E7g^AFu=-#p_89zpolMK zr-?&9|Ixczk7O+N&`N*8fq)SH9{~ZXrFfOWaJ&ybO^w63^Pr!C>B0EXKDqnOVn@Hc zOEwC7CWzJ5(6LgTXP+w^8ToKiDdT}p(gy0?$SxHXpsAU6>y33^buA4{`(MWW%+@fm zDN8L;^4rzgy;T?IIFps3ZbIIpp`t75J8DPkCgm7h@UWUkK5s{MC^92**<-G>)j!dz z?82UW-pl41bhz$y_(JP?n$C@7^@^a~Gt`nkKx{tkJ4^Bp8hJEV$1+QAE?VBNWTyYl zSw&?x5E|CfiX6CYSz-s%rz1}yzPcTG7f2_N&>u5p zsJiiZlRnqahQ(`z#XIgflYV?(#4h5BZHrpt%GdK7olv^+H>{rTe1T_kVSBIdrh;$H zl;g?dT+Nzx_Yi!zXW`X^Cb;mM(F{Us*S;s)UD8iW`fr<_8cG$tI?=VOZQqmsd|bk) z4!<-w6gLy^!N{fKF=BfR5pIS@L{=kXUQEy2$+o22lI}|#r`y0NhQ`iK38Bq~Rql~M3w?)8CLg`hy)iXdeWl2;#VwKo zTaX2}xVcv`ZwIqlX#EvfZW=G?q&uRwgG3a4?$3x_5=@qqmAd*AF+1)UW+}_gOTbj> z^uy{j!uJN#Qw&ERJ&s(_L|Ld1BU3+r{mmiG8La)x5V42d$;cqg1 z>S7npfL%H8l$K%yW5<7jPN)9l{eEXla_ZrL)IdECvi;_S)fK&{GCwL=?$HCVNjz%3 zt=6Su?Y+xM9Bwp4*md?ElLU8GA*EOg57?DhaZ~K@V=c(x@D%8pky8CXf`qt<&ItWa z&O;F=haxN+6B#B_&Q2lmOA=PkF|*OdN4?mspabZ=8_K9Zn4GdHPF^olOBVbnWfD^1 zK(H%MPf<}(2HlOUg$+Xz5w;veR5$isclXcV*T<~paOz)bQ>4FPu@pHhfcoJqjwffZ z^G^|5V7H^qoX2d+$?Fpg$UsZxtKKsF*fS0>VTxMPWFz`CxbeN)OEif&l^cbUy#xRw zxTnQL+AY_N{V_0cz%Is6!oGtXd9$DK@uT8yR` zeU3N*rZ2B7i0v&cpNe^4lqFba%57(=7-OQ%xJFNoGbIpl1_kOLq2s^^t5j8J(fAOB+Xh3lRizEzBk`_3n)>4a>C)t z%u@VjVoJo{{G4y*${L_cYFq~;hk~foWJ|m1XT<^TKNO#q)tcKSW*fMWf_Yah#@0l$ zw6yD{aES?P$j6&H>9OFE*ig#B^Szg)uUsCtFD95$sitEI-=^lC+aOKz_m9HjgROn5 z^u;T7J)TgglXC{J6inX4#D@c?QjB5ra^m-pG-3k6wZd4m1WqfQQ?}3q>+;9YoR8k2 zp);b9!JvK#4r`dhuCffD$WLj5_>rA$PtpJ1Xs_CD05a zVDdpdtx)@8Ro`wOGQu9$A$I~&>Wcfa=dY!97*h_|rJ3hAthik)v(2{}OEG{T`E$G8 zv6!hb^>ua#@`*TcnZ2l@Cw7WfVH3B4DwlWTL^5X`+JsTvnbu$w%Gek0Ha%YJY;JG) zG}6}!j7CW64>I50JOoMQ&mFLjw}vsw7~nMN8UKyt5A1|_6Q9Hwhgy?TgShWY1pBpB z3!CWKWqbJ)SlpSl(^NjoO&y2cSytm-t1STriB}qccCJ5`pF6~vu49nL8KfVvYm=wz zJKo46WxgKew)q%2zs7#?pC+5+?`~Y%6yuCB>t_=aFW;H^=P?oZ#-R10=hn%#hr7;d z>CQXTBYjW801;_Cdd=OM3}6KinwJiG=rXpxF*9x`c|lq%vVyFhSJAQESYWhbizJg5 z+DQK8+)Y&4YJN}gy|1-6a_*)?rVGZsPTKfbv$3q2mUj=QLnsG3ruD1O<(!dq-z5O3 z7{MH%LS&!4JH$EqB8Ge-X+%!L|`qsJ7$b9`nJbJV% zz&3Fh$xiO!9)KhpLS#};VEG@%%fX{`flb=CtJgJo-`dzMnT-H!UQ+rh%a?A+tAPg) z%mW{!y6*IM_bJdiz{maBDmsoBb12N-KLP_}v%mNpg8kOC^kk$VF5F|)+;zx|HJ(1xV0r_yoQFk z*#MmoH)NB*53Z_)nK#=I%q&V1AD2-_YTR}Ej0#g&=ok6^VChoF-H3hg;1|%>qqVX8 zcq;~Zq%R*#t7f@h^pJ?qy2K}7wnf1W&o>d*M_uq<)4hw`_KV;ARN9&6iO{Le2(ac% zN{R2RYN1IU6@1kO`LY;cHMfGLCSo@@;LxG@BgmE(^8v%yl(y0U(nufR@cXL$5X);ZURV z%b$TwkT4`ju&}gTQn)sPM9=W-=9W{m4jgFZw_uXKGwmgv{n8nTR8|+Txj;!7(hL>B z(2K*hl=pG)huC<45k8&!9d|Zf}i%rSWNGP!Sau$FxMX4vVEy>6OFbrq0z^thtpHM%__qr;@2u_O!H=(dP*>x3el< zY-p$pfOH%fEn2DIAS#*dYwM?*n&N!C{iwi$GGDn=H$7nz2!KVZ9=!dUH6N0)cIJW7 zPH;&tW=sddhLn<1L-nWc{3L}AV^raQ*3VwbC0s0=V;WkBNlPh0o&H+GFns1C)nY9z zV{`JQX|@{Z?L5i(slWAkPqIF}ExlbgVNx)<2cg;ZUBTt4(BjbY=F@z4*7FA&8FrAsBt8(n}?T z_t>Q5X)CqE|9L^`3l5zw@WrjAKFG)bQe2lhB=EG3eDRQ`s1v6eJT&zng8ObFbDb>u z+}R&ysa^7PpYQU%XGSZP90W=D(-5M$F(f<|DE=`D8klYYavG21kN{scc5-bxH@w(W z-O-6Fz5{q;Ok8ku^myY-;f3{>BWKUv^GRk%9O|8XLV;pDa#~H!E0<6^Xef_p6O*tk zFggprykrmviCOu)enghvI>+Q;l`2EV;A zg&gBP34*mrbs^ScmQ|5?hxQ`z^r#;73MM1+!a;D37;0C7ZXj9(nFWP-6Ax%CpAk|PLLpp(K|r@JWSu= z*VOmWNiHLQ&v`Ay?1h0ErN6IEx(a$%D+3v-dzYnRn8zD5$dD%TpLX>=3XmbW5Z zu+op^(O1^l0R13c&21XdXW4|NvdtfyzKtx%J_cqp+7>OFy5BCEIYYlOdxR9;E%MSN z2Zk{xu@@+Z0H`+x>RZIz)KZ8D2z*D73y(BPyOn+ETr zvJ#qF7vR@W?03%`DeyAYUqRkO06~XG5f87v@99}n~vTb)x0pTk}Gv;!3FzTFs<4mUe~WhPOmO}RvLIdbdG=h{3H z>pB6Jvo}~F9X`3|OrZPGUxXWqR`hDb!Ct>=ibr`_pOk3`m;L2g2@!o?$*S#ptdjax z;&!k}AziZPEeKS9xxx)d{<+6&?8xQ$moWxCIet$CZ~A`qc@vtuI|!HoMopKWQq;Bf z2Q10s^c^VvUdLQ4_KN!b#O1sDtBmtb`T8ykW}Z zyU7!vw|VJ1>TFz(D~~%J?GH|R|96Ed_Ig&=FeqI@BiE;&2s(EC4iO&Xz5O)Fyt-!MXUx*4Ct^MCCx5ub-b=^$=modiTHRLGj=C4PuS^ z3qm#rtSl_@rPAbLwQcO}P-sY6nuCRf#rZ}Qs^yp8LD4qXe&0S)Q2E#JZOc5pxVSi= zjw}ohh}gkB3J$-xR@4LTzIL{gcJ_n*oeJr-u`Nnlgl85x1)6Y1=9kUw>`JlNBFG-s zHbp0j2)T3b`|V zKW@}jLnGg*q@eKx0MUvhe~ABb`(9)F1|U$h!0#pqo&}tIM4koY zVl{H|)}bvi33q=F05a&s2!LV#KKvhU$RTQB4;boZAyEKRj>J29#|Phx53vo34FPr_ zb2IbfCT7+q$1Sd!+1OfI+n%s8GBdL^GuvAo7W=<$h>Qvj3r+sNZwOu+q;K*4yyL}l J^=AVA{4XyPh-?4= literal 0 HcmV?d00001 diff --git a/packages/bot-engine/queries/saveVisitedEdges.ts b/packages/bot-engine/queries/saveVisitedEdges.ts new file mode 100644 index 00000000000..ff90f16b9b1 --- /dev/null +++ b/packages/bot-engine/queries/saveVisitedEdges.ts @@ -0,0 +1,8 @@ +import prisma from '@typebot.io/lib/prisma' +import { VisitedEdge } from '@typebot.io/prisma' + +export const saveVisitedEdges = (visitedEdges: VisitedEdge[]) => + prisma.visitedEdge.createMany({ + data: visitedEdges, + skipDuplicates: true, + }) diff --git a/packages/lib/getBlockById.ts b/packages/lib/getBlockById.ts new file mode 100644 index 00000000000..a361952aa9b --- /dev/null +++ b/packages/lib/getBlockById.ts @@ -0,0 +1,24 @@ +import { Block, Group } from '@typebot.io/schemas' + +export const getBlockById = ( + blockId: string, + groups: Group[] +): { block: Block; group: Group; blockIndex: number; groupIndex: number } => { + for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) { + for ( + let blockIndex = 0; + blockIndex < groups[groupIndex].blocks.length; + blockIndex++ + ) { + if (groups[groupIndex].blocks[blockIndex].id === blockId) { + return { + block: groups[groupIndex].blocks[blockIndex], + group: groups[groupIndex], + blockIndex, + groupIndex, + } + } + } + } + throw new Error(`Block with id ${blockId} was not found`) +} diff --git a/packages/lib/migrations/migratePublicTypebot.ts b/packages/lib/migrations/migratePublicTypebot.ts new file mode 100644 index 00000000000..5c52693ca29 --- /dev/null +++ b/packages/lib/migrations/migratePublicTypebot.ts @@ -0,0 +1,15 @@ +import { PublicTypebot, PublicTypebotV6 } from '@typebot.io/schemas' +import { migrateTypebotFromV3ToV4 } from './migrateTypebotFromV3ToV4' +import { migrateTypebotFromV5ToV6 } from './migrateTypebotFromV5ToV6' + +export const migrateTypebot = async ( + typebot: PublicTypebot +): Promise => { + if (typebot.version === '6') return typebot + let migratedTypebot: any = typebot + if (migratedTypebot.version === '3') + migratedTypebot = await migrateTypebotFromV3ToV4(typebot) + if (migratedTypebot.version === '4' || migratedTypebot.version === '5') + migratedTypebot = migrateTypebotFromV5ToV6(migratedTypebot) + return migratedTypebot +} diff --git a/packages/lib/migrations/migrateTypebot.ts b/packages/lib/migrations/migrateTypebot.ts new file mode 100644 index 00000000000..d05012b433b --- /dev/null +++ b/packages/lib/migrations/migrateTypebot.ts @@ -0,0 +1,30 @@ +import { + PublicTypebot, + PublicTypebotV6, + Typebot, + TypebotV6, +} from '@typebot.io/schemas' +import { migrateTypebotFromV3ToV4 } from './migrateTypebotFromV3ToV4' +import { migrateTypebotFromV5ToV6 } from './migrateTypebotFromV5ToV6' + +export const migrateTypebot = async (typebot: Typebot): Promise => { + if (typebot.version === '6') return typebot + let migratedTypebot: any = typebot + if (migratedTypebot.version === '3') + migratedTypebot = await migrateTypebotFromV3ToV4(typebot) + if (migratedTypebot.version === '4' || migratedTypebot.version === '5') + migratedTypebot = migrateTypebotFromV5ToV6(migratedTypebot) + return migratedTypebot +} + +export const migratePublicTypebot = async ( + typebot: PublicTypebot +): Promise => { + if (typebot.version === '6') return typebot + let migratedTypebot: any = typebot + if (migratedTypebot.version === '3') + migratedTypebot = await migrateTypebotFromV3ToV4(typebot) + if (migratedTypebot.version === '4' || migratedTypebot.version === '5') + migratedTypebot = migrateTypebotFromV5ToV6(migratedTypebot) + return migratedTypebot +} diff --git a/packages/lib/migrations/migrateTypebotFromV5ToV6.ts b/packages/lib/migrations/migrateTypebotFromV5ToV6.ts new file mode 100644 index 00000000000..7733b1acbcb --- /dev/null +++ b/packages/lib/migrations/migrateTypebotFromV5ToV6.ts @@ -0,0 +1,125 @@ +import { + BlockV5, + BlockV6, + GoogleSheetsBlockV5, + GoogleSheetsBlockV6, + PublicTypebotV5, + PublicTypebotV6, + TypebotV5, + TypebotV6, +} from '@typebot.io/schemas' +import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants' +import { GoogleSheetsAction } from '@typebot.io/schemas/features/blocks/integrations/googleSheets/constants' +import { ComparisonOperators } from '@typebot.io/schemas/features/blocks/logic/condition/constants' +import { createId } from '@paralleldrive/cuid2' +import { EventType } from '@typebot.io/schemas/features/events/constants' +import { byId } from '../utils' + +export const migrateTypebotFromV5ToV6 = async ( + typebot: TypebotV5 | PublicTypebotV5 +): Promise => { + const startGroup = typebot.groups.find((group) => + group.blocks.some((b) => b.type === 'start') + ) + + if (!startGroup) throw new Error('Start group not found') + + const startBlock = startGroup?.blocks.find((b) => b.type === 'start') + + if (!startBlock) throw new Error('Start block not found') + + const startOutgoingEdge = typebot.edges.find(byId(startBlock.outgoingEdgeId)) + + return { + ...typebot, + groups: migrateGroups( + typebot.groups.filter((g) => g.blocks.some((b) => b.type !== 'start')) + ), + version: '6', + events: [ + { + id: startGroup.id, + type: EventType.START, + graphCoordinates: startGroup.graphCoordinates, + outgoingEdgeId: startBlock.outgoingEdgeId, + }, + ], + edges: startOutgoingEdge + ? [ + { + ...startOutgoingEdge, + from: { + eventId: startGroup.id, + }, + }, + ...typebot.edges.filter((e) => e.id !== startOutgoingEdge.id), + ] + : typebot.edges, + } +} + +const migrateGroups = (groups: TypebotV5['groups']): TypebotV6['groups'] => + groups.map((group) => ({ + ...group, + blocks: migrateBlocksFromV1ToV2(group.blocks), + })) + +const migrateBlocksFromV1ToV2 = ( + blocks: TypebotV5['groups'][0]['blocks'] +): BlockV6[] => + ( + blocks.filter((block) => block.type !== 'start') as Exclude< + BlockV5, + { type: 'start' } + >[] + ).map((block) => { + if (block.type === IntegrationBlockType.GOOGLE_SHEETS) { + return { + ...block, + options: migrateGoogleSheetsOptions(block.options), + } + } + return block + }) + +const migrateGoogleSheetsOptions = ( + options: GoogleSheetsBlockV5['options'] +): GoogleSheetsBlockV6['options'] => { + if (!options) return + if (options.action === GoogleSheetsAction.GET) { + if (options.filter || !options.referenceCell) return options + return { + ...options, + filter: { + comparisons: [ + { + id: createId(), + column: options.referenceCell?.column, + comparisonOperator: ComparisonOperators.EQUAL, + value: options.referenceCell?.value, + }, + ], + }, + } + } + if (options.action === GoogleSheetsAction.INSERT_ROW) { + return options + } + if (options.action === GoogleSheetsAction.UPDATE_ROW) { + if (options.filter || !options.referenceCell) return options + return { + ...options, + filter: { + comparisons: [ + { + id: createId(), + column: options.referenceCell?.column, + comparisonOperator: ComparisonOperators.EQUAL, + value: options.referenceCell?.value, + }, + ], + }, + } + } + return options +} diff --git a/packages/schemas/features/blocks/bubbles/audio/constants.ts b/packages/schemas/features/blocks/bubbles/audio/constants.ts new file mode 100644 index 00000000000..79759230bda --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/audio/constants.ts @@ -0,0 +1,5 @@ +import { AudioBubbleBlock } from './schema' + +export const defaultAudioBubbleContent = { + isAutoplayEnabled: true, +} as const satisfies AudioBubbleBlock['content'] diff --git a/packages/schemas/features/blocks/bubbles/audio/index.ts b/packages/schemas/features/blocks/bubbles/audio/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/audio/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/bubbles/audio/schema.ts b/packages/schemas/features/blocks/bubbles/audio/schema.ts new file mode 100644 index 00000000000..f76ccfc2459 --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/audio/schema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' +import { blockBaseSchema } from '../../shared' +import { BubbleBlockType } from '../constants' + +export const audioBubbleContentSchema = z.object({ + url: z.string().optional(), + isAutoplayEnabled: z.boolean().optional(), +}) + +export const audioBubbleBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([BubbleBlockType.AUDIO]), + content: audioBubbleContentSchema.optional(), + }) +) + +export type AudioBubbleBlock = z.infer +export type AudioBubbleContent = z.infer diff --git a/packages/schemas/features/blocks/bubbles/constants.ts b/packages/schemas/features/blocks/bubbles/constants.ts new file mode 100644 index 00000000000..d7c6ae872a0 --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/constants.ts @@ -0,0 +1,7 @@ +export enum BubbleBlockType { + TEXT = 'text', + IMAGE = 'image', + VIDEO = 'video', + EMBED = 'embed', + AUDIO = 'audio', +} diff --git a/packages/schemas/features/blocks/bubbles/embed/constants.ts b/packages/schemas/features/blocks/bubbles/embed/constants.ts new file mode 100644 index 00000000000..165bbf5c48a --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/embed/constants.ts @@ -0,0 +1,5 @@ +import { EmbedBubbleBlock } from './schema' + +export const defaultEmbedBubbleContent = { + height: 400, +} as const satisfies EmbedBubbleBlock['content'] diff --git a/packages/schemas/features/blocks/bubbles/embed/index.ts b/packages/schemas/features/blocks/bubbles/embed/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/embed/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/bubbles/embed/schema.ts b/packages/schemas/features/blocks/bubbles/embed/schema.ts new file mode 100644 index 00000000000..d32b07c1cbe --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/embed/schema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' +import { variableStringSchema } from '../../../utils' +import { blockBaseSchema } from '../../shared' +import { BubbleBlockType } from '../constants' + +export const embedBubbleContentSchema = z.object({ + url: z.string().optional(), + height: z.number().or(variableStringSchema).optional(), +}) + +export const embedBubbleBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([BubbleBlockType.EMBED]), + content: embedBubbleContentSchema.optional(), + }) +) + +export type EmbedBubbleBlock = z.infer diff --git a/packages/schemas/features/blocks/bubbles/image/constants.ts b/packages/schemas/features/blocks/bubbles/image/constants.ts new file mode 100644 index 00000000000..92dd96606bf --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/image/constants.ts @@ -0,0 +1,7 @@ +import { ImageBubbleBlock } from './schema' + +export const defaultImageBubbleContent = { + clickLink: { + alt: 'Bubble image', + }, +} as const satisfies ImageBubbleBlock['content'] diff --git a/packages/schemas/features/blocks/bubbles/image/index.ts b/packages/schemas/features/blocks/bubbles/image/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/image/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/bubbles/image/schema.ts b/packages/schemas/features/blocks/bubbles/image/schema.ts new file mode 100644 index 00000000000..9ed08edc26d --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/image/schema.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' +import { BubbleBlockType } from '../constants' +import { blockBaseSchema } from '../../shared' + +export const imageBubbleContentSchema = z.object({ + url: z.string().optional(), + clickLink: z + .object({ + url: z.string().optional(), + alt: z.string().optional(), + }) + .optional(), +}) + +export const imageBubbleBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([BubbleBlockType.IMAGE]), + content: imageBubbleContentSchema.optional(), + }) +) + +export type ImageBubbleBlock = z.infer diff --git a/packages/schemas/features/blocks/bubbles/schema.ts b/packages/schemas/features/blocks/bubbles/schema.ts new file mode 100644 index 00000000000..d5dd81883fa --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/schema.ts @@ -0,0 +1,21 @@ +import { z } from 'zod' +import { audioBubbleBlockSchema } from './audio' +import { embedBubbleBlockSchema } from './embed' +import { imageBubbleBlockSchema } from './image' +import { textBubbleBlockSchema } from './text' +import { videoBubbleBlockSchema } from './video' + +export const bubbleBlockSchemas = [ + textBubbleBlockSchema, + imageBubbleBlockSchema, + videoBubbleBlockSchema, + embedBubbleBlockSchema, + audioBubbleBlockSchema, +] as const + +export const bubbleBlockSchema = z.discriminatedUnion('type', [ + ...bubbleBlockSchemas, +]) + +export type BubbleBlock = z.infer +export type BubbleBlockContent = BubbleBlock['content'] diff --git a/packages/schemas/features/blocks/bubbles/text/constants.ts b/packages/schemas/features/blocks/bubbles/text/constants.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/schemas/features/blocks/bubbles/text/index.ts b/packages/schemas/features/blocks/bubbles/text/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/text/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/bubbles/text/schema.ts b/packages/schemas/features/blocks/bubbles/text/schema.ts new file mode 100644 index 00000000000..7d7019c56bf --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/text/schema.ts @@ -0,0 +1,24 @@ +import { z } from 'zod' +import type { TElement } from '@udecode/plate-common' +import { blockBaseSchema } from '../../shared' +import { BubbleBlockType } from '../constants' + +export const textBubbleContentSchema = z.object({ + html: z.string().optional(), + richText: z.array(z.any()).optional(), + plainText: z.string().optional(), +}) + +export const textBubbleBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([BubbleBlockType.TEXT]), + content: textBubbleContentSchema.optional(), + }) +) + +export type TextBubbleBlock = Omit< + z.infer, + 'content' +> & { + content?: { richText?: TElement[]; html?: string; plainText?: string } +} diff --git a/packages/schemas/features/blocks/bubbles/video/constants.ts b/packages/schemas/features/blocks/bubbles/video/constants.ts new file mode 100644 index 00000000000..38fae127d35 --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/video/constants.ts @@ -0,0 +1,9 @@ +export enum VideoBubbleContentType { + URL = 'url', + YOUTUBE = 'youtube', + VIMEO = 'vimeo', +} + +export const defaultVideoBubbleContent = { + height: 400, +} as const diff --git a/packages/schemas/features/blocks/bubbles/video/schema.ts b/packages/schemas/features/blocks/bubbles/video/schema.ts new file mode 100644 index 00000000000..f4316ec23ef --- /dev/null +++ b/packages/schemas/features/blocks/bubbles/video/schema.ts @@ -0,0 +1,21 @@ +import { z } from 'zod' +import { blockBaseSchema } from '../../shared' +import { VideoBubbleContentType } from './constants' +import { variableStringSchema } from '../../../utils' +import { BubbleBlockType } from '../constants' + +export const videoBubbleContentSchema = z.object({ + url: z.string().optional(), + id: z.string().optional(), + type: z.nativeEnum(VideoBubbleContentType).optional(), + height: z.number().or(variableStringSchema).optional(), +}) + +export const videoBubbleBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([BubbleBlockType.VIDEO]), + content: videoBubbleContentSchema.optional(), + }) +) + +export type VideoBubbleBlock = z.infer diff --git a/packages/schemas/features/blocks/inputs/choice/constants.ts b/packages/schemas/features/blocks/inputs/choice/constants.ts new file mode 100644 index 00000000000..683fdd4f760 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/choice/constants.ts @@ -0,0 +1,9 @@ +import { defaultButtonLabel } from '../constants' +import { ChoiceInputBlock } from './schema' + +export const defaultChoiceInputOptions = { + buttonLabel: defaultButtonLabel, + searchInputPlaceholder: 'Filter the options...', + isMultipleChoice: false, + isSearchable: false, +} as const satisfies ChoiceInputBlock['options'] diff --git a/packages/schemas/features/blocks/inputs/choice/index.ts b/packages/schemas/features/blocks/inputs/choice/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/choice/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/inputs/choice/schema.ts b/packages/schemas/features/blocks/inputs/choice/schema.ts new file mode 100644 index 00000000000..f6532e20127 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/choice/schema.ts @@ -0,0 +1,64 @@ +import { z } from 'zod' +import { InputBlockType } from '../constants' +import { itemBaseSchemas } from '../../../items/shared' +import { optionBaseSchema, blockBaseSchema } from '../../shared' +import { conditionSchema } from '../../logic' + +export const choiceInputOptionsSchema = optionBaseSchema.merge( + z.object({ + isMultipleChoice: z.boolean().optional(), + buttonLabel: z.string().optional(), + dynamicVariableId: z.string().optional(), + isSearchable: z.boolean().optional(), + searchInputPlaceholder: z.string().optional(), + }) +) + +export const buttonItemSchemas = { + v5: itemBaseSchemas.v5.extend({ + content: z.string().optional(), + displayCondition: z + .object({ + isEnabled: z.boolean().optional(), + condition: conditionSchema.optional(), + }) + .optional(), + }), + v6: itemBaseSchemas.v6.extend({ + content: z.string().optional(), + displayCondition: z + .object({ + isEnabled: z.boolean().optional(), + condition: conditionSchema.optional(), + }) + .optional(), + }), +} + +export const buttonItemSchema = z.union([ + buttonItemSchemas.v5, + buttonItemSchemas.v6, +]) + +export const buttonsInputV5Schema = blockBaseSchema.merge( + z.object({ + type: z.enum([InputBlockType.CHOICE]), + items: z.array(buttonItemSchemas.v5), + options: choiceInputOptionsSchema.optional(), + }) +) + +export const buttonsInputSchemas = { + v5: buttonsInputV5Schema, + v6: buttonsInputV5Schema.extend({ + items: z.array(buttonItemSchemas.v6), + }), +} as const + +export const buttonsInputSchema = z.union([ + buttonsInputSchemas.v5, + buttonsInputSchemas.v6, +]) + +export type ButtonItem = z.infer +export type ChoiceInputBlock = z.infer diff --git a/packages/schemas/features/blocks/inputs/date/constants.ts b/packages/schemas/features/blocks/inputs/date/constants.ts new file mode 100644 index 00000000000..04267606302 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/date/constants.ts @@ -0,0 +1,12 @@ +import { defaultButtonLabel } from '../constants' +import { DateInputBlock } from './schema' + +export const defaultDateInputOptions = { + hasTime: false, + isRange: false, + labels: { button: defaultButtonLabel, from: 'From:', to: 'To:' }, + format: 'dd/MM/yyyy', + formatWithTime: 'dd/MM/yyyy HH:mm', +} as const satisfies DateInputBlock['options'] & { + formatWithTime: string +} diff --git a/packages/schemas/features/blocks/inputs/date/index.ts b/packages/schemas/features/blocks/inputs/date/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/date/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/inputs/date/schema.ts b/packages/schemas/features/blocks/inputs/date/schema.ts new file mode 100644 index 00000000000..f08af256173 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/date/schema.ts @@ -0,0 +1,29 @@ +import { z } from 'zod' +import { optionBaseSchema, blockBaseSchema } from '../../shared' +import { InputBlockType } from '../constants' + +export const dateInputOptionsSchema = optionBaseSchema.merge( + z.object({ + labels: z + .object({ + button: z.string().optional(), + from: z.string().optional(), + to: z.string().optional(), + }) + .optional(), + hasTime: z.boolean().optional(), + isRange: z.boolean().optional(), + format: z.string().optional(), + min: z.string().optional(), + max: z.string().optional(), + }) +) + +export const dateInputSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([InputBlockType.DATE]), + options: dateInputOptionsSchema.optional(), + }) +) + +export type DateInputBlock = z.infer diff --git a/packages/schemas/features/blocks/inputs/email/constants.ts b/packages/schemas/features/blocks/inputs/email/constants.ts new file mode 100644 index 00000000000..411c3c4c6ed --- /dev/null +++ b/packages/schemas/features/blocks/inputs/email/constants.ts @@ -0,0 +1,11 @@ +import { defaultButtonLabel } from '../constants' +import { EmailInputBlock } from './schema' + +export const defaultEmailInputOptions = { + labels: { + button: defaultButtonLabel, + placeholder: 'Type your email...', + }, + retryMessageContent: + "This email doesn't seem to be valid. Can you type it again?", +} as const satisfies EmailInputBlock['options'] diff --git a/packages/schemas/features/blocks/inputs/email/index.ts b/packages/schemas/features/blocks/inputs/email/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/email/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/inputs/email/schema.ts b/packages/schemas/features/blocks/inputs/email/schema.ts new file mode 100644 index 00000000000..da65017260d --- /dev/null +++ b/packages/schemas/features/blocks/inputs/email/schema.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' +import { optionBaseSchema, blockBaseSchema } from '../../shared' +import { InputBlockType } from '../constants' +import { textInputOptionsBaseSchema } from '../text' + +export const emailInputOptionsSchema = optionBaseSchema + .merge(textInputOptionsBaseSchema) + .merge( + z.object({ + retryMessageContent: z.string().optional(), + }) + ) + +export const emailInputSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([InputBlockType.EMAIL]), + options: emailInputOptionsSchema.optional(), + }) +) + +export type EmailInputBlock = z.infer +export type EmailInputOptions = z.infer diff --git a/packages/schemas/features/blocks/inputs/file/constants.ts b/packages/schemas/features/blocks/inputs/file/constants.ts new file mode 100644 index 00000000000..53e60c4a60e --- /dev/null +++ b/packages/schemas/features/blocks/inputs/file/constants.ts @@ -0,0 +1,15 @@ +import { FileInputBlock } from './schema' + +export const defaultFileInputOptions = { + isRequired: true, + isMultipleAllowed: false, + labels: { + placeholder: ` + Click to upload + or drag and drop
+ (size limit: 10MB)`, + button: 'Upload', + clear: 'Clear', + skip: 'Skip', + }, +} as const satisfies FileInputBlock['options'] diff --git a/packages/schemas/features/blocks/inputs/file/index.ts b/packages/schemas/features/blocks/inputs/file/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/file/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/inputs/file/schema.ts b/packages/schemas/features/blocks/inputs/file/schema.ts new file mode 100644 index 00000000000..1bedfa1b70c --- /dev/null +++ b/packages/schemas/features/blocks/inputs/file/schema.ts @@ -0,0 +1,50 @@ +import { z } from 'zod' +import { optionBaseSchema, blockBaseSchema } from '../../shared' +import { InputBlockType } from '../constants' + +const fileInputOptionsV5Schema = optionBaseSchema.merge( + z.object({ + isRequired: z.boolean().optional(), + isMultipleAllowed: z.boolean().optional(), + labels: z + .object({ + placeholder: z.string().optional(), + button: z.string().optional(), + clear: z.string().optional(), + skip: z.string().optional(), + }) + .optional(), + sizeLimit: z.number().optional(), + }) +) + +const fileInputOptionsSchemas = { + v5: fileInputOptionsV5Schema, + v6: fileInputOptionsV5Schema.omit({ + sizeLimit: true, + }), +} as const + +const fileInputBlockV5Schema = blockBaseSchema.merge( + z.object({ + type: z.literal(InputBlockType.FILE), + options: fileInputOptionsSchemas.v5.optional(), + }) +) + +export const fileInputBlockSchemas = { + v5: fileInputBlockV5Schema, + v6: fileInputBlockV5Schema.merge( + z.object({ + options: fileInputOptionsSchemas.v6.optional(), + }) + ), +} + +const fileInputBlockSchema = z.union([ + fileInputBlockSchemas.v5, + fileInputBlockSchemas.v6, +]) + +export type FileInputBlock = z.infer +export type FileInputBlockV6 = z.infer diff --git a/packages/schemas/features/blocks/inputs/number/constants.ts b/packages/schemas/features/blocks/inputs/number/constants.ts new file mode 100644 index 00000000000..1f7e3d6d22c --- /dev/null +++ b/packages/schemas/features/blocks/inputs/number/constants.ts @@ -0,0 +1,6 @@ +import { defaultButtonLabel } from '../constants' +import { NumberInputBlock } from './schema' + +export const defaultNumberInputOptions = { + labels: { button: defaultButtonLabel, placeholder: 'Type a number...' }, +} as const satisfies NumberInputBlock['options'] diff --git a/packages/schemas/features/blocks/inputs/number/index.ts b/packages/schemas/features/blocks/inputs/number/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/number/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/inputs/number/schema.ts b/packages/schemas/features/blocks/inputs/number/schema.ts new file mode 100644 index 00000000000..1eb7e63ef40 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/number/schema.ts @@ -0,0 +1,24 @@ +import { z } from 'zod' +import { variableStringSchema } from '../../../utils' +import { optionBaseSchema, blockBaseSchema } from '../../shared' +import { InputBlockType } from '../constants' +import { textInputOptionsBaseSchema } from '../text' + +export const numberInputOptionsSchema = optionBaseSchema + .merge(textInputOptionsBaseSchema) + .merge( + z.object({ + min: z.number().or(variableStringSchema).optional(), + max: z.number().or(variableStringSchema).optional(), + step: z.number().or(variableStringSchema).optional(), + }) + ) + +export const numberInputSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([InputBlockType.NUMBER]), + options: numberInputOptionsSchema.optional(), + }) +) + +export type NumberInputBlock = z.infer diff --git a/packages/schemas/features/blocks/inputs/payment/constants.ts b/packages/schemas/features/blocks/inputs/payment/constants.ts new file mode 100644 index 00000000000..ea854890015 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/payment/constants.ts @@ -0,0 +1,12 @@ +import { PaymentInputBlock } from './schema' + +export enum PaymentProvider { + STRIPE = 'Stripe', +} + +export const defaultPaymentInputOptions = { + provider: PaymentProvider.STRIPE, + labels: { button: 'Pay', success: 'Success' }, + retryMessageContent: 'Payment failed. Please, try again.', + currency: 'USD', +} as const satisfies PaymentInputBlock['options'] diff --git a/packages/schemas/features/blocks/inputs/payment/schema.ts b/packages/schemas/features/blocks/inputs/payment/schema.ts new file mode 100644 index 00000000000..0705829dbc3 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/payment/schema.ts @@ -0,0 +1,87 @@ +import { z } from 'zod' +import { + optionBaseSchema, + blockBaseSchema, + credentialsBaseSchema, +} from '../../shared' +import { PaymentProvider } from './constants' +import { InputBlockType } from '../constants' + +export type CreditCardDetails = { + number: string + exp_month: string + exp_year: string + cvc: string +} + +const addressSchema = z.object({ + country: z.string().optional(), + line1: z.string().optional(), + line2: z.string().optional(), + state: z.string().optional(), + city: z.string().optional(), + postalCode: z.string().optional(), +}) + +export const paymentInputOptionsSchema = optionBaseSchema.merge( + z.object({ + provider: z.nativeEnum(PaymentProvider).optional(), + labels: z + .object({ + button: z.string().optional(), + success: z.string().optional(), + }) + .optional(), + additionalInformation: z + .object({ + description: z.string().optional(), + name: z.string().optional(), + email: z.string().optional(), + phoneNumber: z.string().optional(), + address: addressSchema.optional(), + }) + .optional(), + credentialsId: z.string().optional(), + currency: z.string().optional(), + amount: z.string().optional(), + retryMessageContent: z.string().optional(), + }) +) + +export const paymentInputRuntimeOptionsSchema = z.object({ + paymentIntentSecret: z.string(), + amountLabel: z.string(), + publicKey: z.string(), +}) + +export const paymentInputSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([InputBlockType.PAYMENT]), + options: paymentInputOptionsSchema.optional(), + }) +) + +export const stripeCredentialsSchema = z + .object({ + type: z.literal('stripe'), + data: z.object({ + live: z.object({ + secretKey: z.string(), + publicKey: z.string(), + }), + test: z.object({ + secretKey: z.string().optional(), + publicKey: z.string().optional(), + }), + }), + }) + .merge(credentialsBaseSchema) + +export type PaymentInputBlock = z.infer +export type PaymentInputRuntimeOptions = z.infer< + typeof paymentInputRuntimeOptionsSchema +> +export type StripeCredentials = z.infer +export type PaymentAddress = NonNullable< + NonNullable['additionalInformation'] +>['address'] diff --git a/packages/schemas/features/blocks/inputs/phone/constants.ts b/packages/schemas/features/blocks/inputs/phone/constants.ts new file mode 100644 index 00000000000..2ae92de3978 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/phone/constants.ts @@ -0,0 +1,11 @@ +import { defaultButtonLabel } from '../constants' +import { PhoneNumberInputBlock } from './schema' + +export const defaultPhoneInputOptions = { + labels: { + button: defaultButtonLabel, + placeholder: 'Type your phone number...', + }, + retryMessageContent: + "This phone number doesn't seem to be valid. Can you type it again?", +} as const satisfies PhoneNumberInputBlock['options'] diff --git a/packages/schemas/features/blocks/inputs/phone/index.ts b/packages/schemas/features/blocks/inputs/phone/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/phone/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/inputs/phone/schema.ts b/packages/schemas/features/blocks/inputs/phone/schema.ts new file mode 100644 index 00000000000..0f0bc48c72c --- /dev/null +++ b/packages/schemas/features/blocks/inputs/phone/schema.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' +import { optionBaseSchema, blockBaseSchema } from '../../shared' +import { InputBlockType } from '../constants' +import { textInputOptionsBaseSchema } from '../text' + +export const phoneNumberInputOptionsSchema = optionBaseSchema + .merge(textInputOptionsBaseSchema) + .merge( + z.object({ + retryMessageContent: z.string().optional(), + defaultCountryCode: z.string().optional(), + }) + ) + +export const phoneNumberInputBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([InputBlockType.PHONE]), + options: phoneNumberInputOptionsSchema.optional(), + }) +) + +export type PhoneNumberInputBlock = z.infer diff --git a/packages/schemas/features/blocks/inputs/pictureChoice/constants.ts b/packages/schemas/features/blocks/inputs/pictureChoice/constants.ts new file mode 100644 index 00000000000..f4aa6eeaf50 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/pictureChoice/constants.ts @@ -0,0 +1,12 @@ +import { defaultButtonLabel } from '../constants' +import { PictureChoiceBlock } from './schema' + +export const defaultPictureChoiceOptions = { + buttonLabel: defaultButtonLabel, + searchInputPlaceholder: 'Filter the options...', + isMultipleChoice: false, + isSearchable: false, + dynamicItems: { + isEnabled: false, + }, +} as const satisfies PictureChoiceBlock['options'] diff --git a/packages/schemas/features/blocks/inputs/pictureChoice/index.ts b/packages/schemas/features/blocks/inputs/pictureChoice/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/pictureChoice/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/inputs/pictureChoice/schema.ts b/packages/schemas/features/blocks/inputs/pictureChoice/schema.ts new file mode 100644 index 00000000000..fbe2fe65d0a --- /dev/null +++ b/packages/schemas/features/blocks/inputs/pictureChoice/schema.ts @@ -0,0 +1,75 @@ +import { z } from 'zod' +import { optionBaseSchema, blockBaseSchema } from '../../shared' +import { conditionSchema } from '../../logic' +import { InputBlockType } from '../constants' +import { itemBaseSchemas } from '../../../items/shared' + +export const pictureChoiceOptionsSchema = optionBaseSchema.merge( + z.object({ + isMultipleChoice: z.boolean().optional(), + isSearchable: z.boolean().optional(), + buttonLabel: z.string().optional(), + searchInputPlaceholder: z.string().optional(), + dynamicItems: z + .object({ + isEnabled: z.boolean().optional(), + titlesVariableId: z.string().optional(), + descriptionsVariableId: z.string().optional(), + pictureSrcsVariableId: z.string().optional(), + }) + .optional(), + }) +) + +export const pictureChoiceItemSchemas = { + v5: itemBaseSchemas.v5.extend({ + pictureSrc: z.string().optional(), + title: z.string().optional(), + description: z.string().optional(), + displayCondition: z + .object({ + isEnabled: z.boolean().optional(), + condition: conditionSchema.optional(), + }) + .optional(), + }), + v6: itemBaseSchemas.v6.extend({ + pictureSrc: z.string().optional(), + title: z.string().optional(), + description: z.string().optional(), + displayCondition: z + .object({ + isEnabled: z.boolean().optional(), + condition: conditionSchema.optional(), + }) + .optional(), + }), +} + +export const pictureChoiceItemSchema = z.union([ + pictureChoiceItemSchemas.v5, + pictureChoiceItemSchemas.v6, +]) + +export const pictureChoiceBlockV5Schema = blockBaseSchema.merge( + z.object({ + type: z.enum([InputBlockType.PICTURE_CHOICE]), + items: z.array(pictureChoiceItemSchemas.v5), + options: pictureChoiceOptionsSchema.optional(), + }) +) + +export const pictureChoiceBlockSchemas = { + v5: pictureChoiceBlockV5Schema, + v6: pictureChoiceBlockV5Schema.extend({ + items: z.array(pictureChoiceItemSchemas.v6), + }), +} as const + +export const pictureChoiceBlockSchema = z.union([ + pictureChoiceBlockSchemas.v5, + pictureChoiceBlockSchemas.v6, +]) + +export type PictureChoiceItem = z.infer +export type PictureChoiceBlock = z.infer diff --git a/packages/schemas/features/blocks/inputs/rating/constants.ts b/packages/schemas/features/blocks/inputs/rating/constants.ts new file mode 100644 index 00000000000..01ab6af2d78 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/rating/constants.ts @@ -0,0 +1,12 @@ +import { defaultButtonLabel } from '../constants' +import { RatingInputBlock } from '../rating' + +export const defaultRatingInputOptions = { + buttonType: 'Numbers', + length: 10, + labels: { + button: defaultButtonLabel, + }, + customIcon: { isEnabled: false }, + isOneClickSubmitEnabled: false, +} as const satisfies RatingInputBlock['options'] diff --git a/packages/schemas/features/blocks/inputs/rating/index.ts b/packages/schemas/features/blocks/inputs/rating/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/rating/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/inputs/rating/schema.ts b/packages/schemas/features/blocks/inputs/rating/schema.ts new file mode 100644 index 00000000000..41eb69aba30 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/rating/schema.ts @@ -0,0 +1,33 @@ +import { z } from 'zod' +import { optionBaseSchema, blockBaseSchema } from '../../shared' +import { InputBlockType } from '../constants' + +export const ratingInputOptionsSchema = optionBaseSchema.merge( + z.object({ + buttonType: z.literal('Icons').or(z.literal('Numbers')).optional(), + length: z.number().optional(), + labels: z + .object({ + left: z.string().optional(), + right: z.string().optional(), + button: z.string().optional(), + }) + .optional(), + customIcon: z + .object({ + isEnabled: z.boolean().optional(), + svg: z.string().optional(), + }) + .optional(), + isOneClickSubmitEnabled: z.boolean().optional(), + }) +) + +export const ratingInputBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.literal(InputBlockType.RATING), + options: ratingInputOptionsSchema.optional(), + }) +) + +export type RatingInputBlock = z.infer diff --git a/packages/schemas/features/blocks/inputs/schema.ts b/packages/schemas/features/blocks/inputs/schema.ts new file mode 100644 index 00000000000..1dd96c873d7 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/schema.ts @@ -0,0 +1,56 @@ +import { z } from 'zod' +import { buttonsInputSchemas } from './choice' +import { dateInputSchema } from './date' +import { emailInputSchema } from './email' +import { numberInputSchema } from './number' +import { paymentInputSchema } from './payment' +import { phoneNumberInputBlockSchema } from './phone' +import { + pictureChoiceBlockSchema, + pictureChoiceBlockSchemas, +} from './pictureChoice' +import { ratingInputBlockSchema } from './rating' +import { textInputSchema } from './text' +import { urlInputSchema } from './url' +import { fileInputBlockSchemas } from './file' + +export const inputBlockSchemas = { + v5: [ + textInputSchema, + buttonsInputSchemas.v5, + emailInputSchema, + numberInputSchema, + urlInputSchema, + phoneNumberInputBlockSchema, + dateInputSchema, + paymentInputSchema, + ratingInputBlockSchema, + fileInputBlockSchemas.v5, + pictureChoiceBlockSchemas.v5, + ], + v6: [ + textInputSchema, + buttonsInputSchemas.v6, + emailInputSchema, + numberInputSchema, + urlInputSchema, + phoneNumberInputBlockSchema, + dateInputSchema, + paymentInputSchema, + ratingInputBlockSchema, + fileInputBlockSchemas.v6, + pictureChoiceBlockSchemas.v6, + ], +} as const + +const inputBlockV5Schema = z.discriminatedUnion('type', [ + ...inputBlockSchemas.v5, +]) + +const inputBlockV6Schema = z.discriminatedUnion('type', [ + ...inputBlockSchemas.v6, +]) + +const inputBlockSchema = z.union([inputBlockV5Schema, inputBlockV6Schema]) + +export type InputBlock = z.infer diff --git a/packages/schemas/features/blocks/inputs/text/constants.ts b/packages/schemas/features/blocks/inputs/text/constants.ts new file mode 100644 index 00000000000..d8449d0486d --- /dev/null +++ b/packages/schemas/features/blocks/inputs/text/constants.ts @@ -0,0 +1,7 @@ +import { defaultButtonLabel } from '../constants' +import { TextInputBlock } from './schema' + +export const defaultTextInputOptions = { + isLong: false, + labels: { button: defaultButtonLabel, placeholder: 'Type your answer...' }, +} as const satisfies TextInputBlock['options'] diff --git a/packages/schemas/features/blocks/inputs/text/index.ts b/packages/schemas/features/blocks/inputs/text/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/text/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/inputs/text/schema.ts b/packages/schemas/features/blocks/inputs/text/schema.ts new file mode 100644 index 00000000000..fc69cec8509 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/text/schema.ts @@ -0,0 +1,29 @@ +import { z } from 'zod' +import { optionBaseSchema, blockBaseSchema } from '../../shared' +import { InputBlockType } from '../constants' + +export const textInputOptionsBaseSchema = z.object({ + labels: z + .object({ + placeholder: z.string().optional(), + button: z.string().optional(), + }) + .optional(), +}) + +export const textInputOptionsSchema = textInputOptionsBaseSchema + .merge(optionBaseSchema) + .merge( + z.object({ + isLong: z.boolean().optional(), + }) + ) + +export const textInputSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([InputBlockType.TEXT]), + options: textInputOptionsSchema.optional(), + }) +) + +export type TextInputBlock = z.infer diff --git a/packages/schemas/features/blocks/inputs/url/constants.ts b/packages/schemas/features/blocks/inputs/url/constants.ts new file mode 100644 index 00000000000..b351b194e63 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/url/constants.ts @@ -0,0 +1,11 @@ +import { defaultButtonLabel } from '../constants' +import { UrlInputBlock } from './schema' + +export const defaultUrlInputOptions = { + labels: { + button: defaultButtonLabel, + placeholder: 'Type a URL...', + }, + retryMessageContent: + "This URL doesn't seem to be valid. Can you type it again?", +} as const satisfies UrlInputBlock['options'] diff --git a/packages/schemas/features/blocks/inputs/url/index.ts b/packages/schemas/features/blocks/inputs/url/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/inputs/url/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/inputs/url/schema.ts b/packages/schemas/features/blocks/inputs/url/schema.ts new file mode 100644 index 00000000000..af9a0f6922c --- /dev/null +++ b/packages/schemas/features/blocks/inputs/url/schema.ts @@ -0,0 +1,21 @@ +import { z } from 'zod' +import { optionBaseSchema, blockBaseSchema } from '../../shared' +import { InputBlockType } from '../constants' +import { textInputOptionsBaseSchema } from '../text' + +export const urlInputOptionsSchema = optionBaseSchema + .merge(textInputOptionsBaseSchema) + .merge( + z.object({ + retryMessageContent: z.string().optional(), + }) + ) + +export const urlInputSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([InputBlockType.URL]), + options: urlInputOptionsSchema.optional(), + }) +) + +export type UrlInputBlock = z.infer diff --git a/packages/schemas/features/blocks/integrations/chatwoot/constants.ts b/packages/schemas/features/blocks/integrations/chatwoot/constants.ts new file mode 100644 index 00000000000..e822dfe46af --- /dev/null +++ b/packages/schemas/features/blocks/integrations/chatwoot/constants.ts @@ -0,0 +1,8 @@ +import { ChatwootBlock } from './schema' + +export const chatwootTasks = ['Show widget', 'Close widget'] as const + +export const defaultChatwootOptions = { + task: 'Show widget', + baseUrl: 'https://app.chatwoot.com', +} as const satisfies ChatwootBlock['options'] diff --git a/packages/schemas/features/blocks/integrations/chatwoot/index.ts b/packages/schemas/features/blocks/integrations/chatwoot/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/chatwoot/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/integrations/chatwoot/schema.ts b/packages/schemas/features/blocks/integrations/chatwoot/schema.ts new file mode 100644 index 00000000000..cd73c9cbdef --- /dev/null +++ b/packages/schemas/features/blocks/integrations/chatwoot/schema.ts @@ -0,0 +1,28 @@ +import { z } from 'zod' +import { chatwootTasks } from './constants' +import { blockBaseSchema } from '../../shared' +import { IntegrationBlockType } from '../constants' + +export const chatwootOptionsSchema = z.object({ + task: z.enum(chatwootTasks).optional(), + baseUrl: z.string().optional(), + websiteToken: z.string().optional(), + user: z + .object({ + id: z.string().optional(), + email: z.string().optional(), + name: z.string().optional(), + avatarUrl: z.string().optional(), + phoneNumber: z.string().optional(), + }) + .optional(), +}) + +export const chatwootBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([IntegrationBlockType.CHATWOOT]), + options: chatwootOptionsSchema.optional(), + }) +) + +export type ChatwootBlock = z.infer diff --git a/packages/schemas/features/blocks/integrations/constants.ts b/packages/schemas/features/blocks/integrations/constants.ts new file mode 100644 index 00000000000..c5567de42b5 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/constants.ts @@ -0,0 +1,13 @@ +export enum IntegrationBlockType { + GOOGLE_SHEETS = 'Google Sheets', + OPEN_AI = 'OpenAI', + GOOGLE_ANALYTICS = 'Google Analytics', + WEBHOOK = 'Webhook', + EMAIL = 'Email', + ZAPIER = 'Zapier', + MAKE_COM = 'Make.com', + PABBLY_CONNECT = 'Pabbly', + CHATWOOT = 'Chatwoot', + PIXEL = 'Pixel', + ZEMANTIC_AI = 'Zemantic AI', +} diff --git a/packages/schemas/features/blocks/integrations/googleAnalytics/constants.ts b/packages/schemas/features/blocks/integrations/googleAnalytics/constants.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/schemas/features/blocks/integrations/googleAnalytics/index.ts b/packages/schemas/features/blocks/integrations/googleAnalytics/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/googleAnalytics/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/integrations/googleAnalytics/schema.ts b/packages/schemas/features/blocks/integrations/googleAnalytics/schema.ts new file mode 100644 index 00000000000..3d4ede8e852 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/googleAnalytics/schema.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' +import { variableStringSchema } from '../../../utils' +import { blockBaseSchema } from '../../shared' +import { IntegrationBlockType } from '../constants' + +export const googleAnalyticsOptionsSchema = z.object({ + trackingId: z.string().optional(), + category: z.string().optional(), + action: z.string().optional(), + label: z.string().optional(), + value: z.number().or(variableStringSchema).optional(), + sendTo: z.string().optional(), +}) + +export const googleAnalyticsBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([IntegrationBlockType.GOOGLE_ANALYTICS]), + options: googleAnalyticsOptionsSchema.optional(), + }) +) + +export type GoogleAnalyticsBlock = z.infer diff --git a/packages/schemas/features/blocks/integrations/googleSheets/constants.ts b/packages/schemas/features/blocks/integrations/googleSheets/constants.ts new file mode 100644 index 00000000000..64f61b62328 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/googleSheets/constants.ts @@ -0,0 +1,16 @@ +export enum GoogleSheetsAction { + GET = 'Get data from sheet', + INSERT_ROW = 'Insert a row', + UPDATE_ROW = 'Update a row', +} + +export const totalRowsToExtractOptions = [ + 'All', + 'First', + 'Last', + 'Random', +] as const + +export const defaultGoogleSheetsOptions = { + totalRowsToExtract: 'All', +} as const diff --git a/packages/schemas/features/blocks/integrations/googleSheets/schema.ts b/packages/schemas/features/blocks/integrations/googleSheets/schema.ts new file mode 100644 index 00000000000..51484e1c6cb --- /dev/null +++ b/packages/schemas/features/blocks/integrations/googleSheets/schema.ts @@ -0,0 +1,178 @@ +import { z } from 'zod' +import { IntegrationBlockType } from '../constants' +import { GoogleSheetsAction, totalRowsToExtractOptions } from './constants' +import { blockBaseSchema, credentialsBaseSchema } from '../../shared' +import { + ComparisonOperators, + LogicalOperator, +} from '../../logic/condition/constants' + +const cellSchema = z.object({ + column: z.string().optional(), + value: z.string().optional(), + id: z.string(), +}) + +const extractingCellSchema = z.object({ + column: z.string().optional(), + id: z.string(), + variableId: z.string().optional(), +}) + +const googleSheetsOptionsBaseSchema = z.object({ + credentialsId: z.string().optional(), + sheetId: z.string().optional(), + spreadsheetId: z.string().optional(), +}) + +const rowsFilterComparisonSchema = z.object({ + id: z.string(), + column: z.string().optional(), + comparisonOperator: z.nativeEnum(ComparisonOperators).optional(), + value: z.string().optional(), +}) + +const initialGoogleSheetsOptionsSchema = googleSheetsOptionsBaseSchema.merge( + z.object({ + action: z.undefined(), + }) +) + +const googleSheetsGetOptionsV5Schema = googleSheetsOptionsBaseSchema.merge( + z.object({ + action: z.enum([GoogleSheetsAction.GET]), + referenceCell: cellSchema.optional().optional(), + filter: z + .object({ + comparisons: z.array(rowsFilterComparisonSchema).optional(), + logicalOperator: z.nativeEnum(LogicalOperator).optional(), + }) + .optional(), + cellsToExtract: z.array(extractingCellSchema).optional(), + totalRowsToExtract: z.enum(totalRowsToExtractOptions).optional(), + }) +) + +const googleSheetsGetOptionsSchemas = { + v5: googleSheetsGetOptionsV5Schema, + v6: googleSheetsGetOptionsV5Schema.omit({ + referenceCell: true, + }), +} + +const googleSheetsGetOptionsSchema = z.union([ + googleSheetsGetOptionsSchemas.v5, + googleSheetsGetOptionsSchemas.v6, +]) + +const googleSheetsInsertRowOptionsSchema = googleSheetsOptionsBaseSchema.merge( + z.object({ + action: z.enum([GoogleSheetsAction.INSERT_ROW]), + cellsToInsert: z.array(cellSchema).optional(), + }) +) + +const googleSheetsUpdateRowOptionsV5Schema = + googleSheetsOptionsBaseSchema.merge( + z.object({ + action: z.enum([GoogleSheetsAction.UPDATE_ROW]), + cellsToUpsert: z.array(cellSchema).optional(), + referenceCell: cellSchema.optional(), + filter: z + .object({ + comparisons: z.array(rowsFilterComparisonSchema).optional(), + logicalOperator: z.nativeEnum(LogicalOperator).optional(), + }) + .optional(), + }) + ) + +const googleSheetsUpdateRowOptionsSchemas = { + v5: googleSheetsUpdateRowOptionsV5Schema, + v6: googleSheetsUpdateRowOptionsV5Schema.omit({ + referenceCell: true, + }), +} + +const googleSheetsUpdateRowOptionsSchema = z.union([ + googleSheetsUpdateRowOptionsSchemas.v5, + googleSheetsUpdateRowOptionsSchemas.v6, +]) + +export const googleSheetsOptionsSchemas = { + v5: z.discriminatedUnion('action', [ + googleSheetsGetOptionsSchemas.v5, + googleSheetsInsertRowOptionsSchema, + googleSheetsUpdateRowOptionsSchemas.v5, + initialGoogleSheetsOptionsSchema, + ]), + v6: z.discriminatedUnion('action', [ + googleSheetsGetOptionsSchemas.v6, + googleSheetsInsertRowOptionsSchema, + googleSheetsUpdateRowOptionsSchemas.v6, + initialGoogleSheetsOptionsSchema, + ]), +} + +export const googleSheetsBlockV5Schema = blockBaseSchema.merge( + z.object({ + type: z.enum([IntegrationBlockType.GOOGLE_SHEETS]), + options: googleSheetsOptionsSchemas.v5.optional(), + }) +) + +export const googleSheetsBlockSchemas = { + v5: googleSheetsBlockV5Schema, + v6: googleSheetsBlockV5Schema.merge( + z.object({ + options: googleSheetsOptionsSchemas.v6.optional(), + }) + ), +} + +export const googleSheetsBlockSchema = z.union([ + googleSheetsBlockSchemas.v5, + googleSheetsBlockSchemas.v6, +]) + +export const googleSheetsCredentialsSchema = z + .object({ + type: z.literal('google sheets'), + data: z.object({ + refresh_token: z.string().nullish(), + expiry_date: z.number().nullish(), + access_token: z.string().nullish(), + token_type: z.string().nullish(), + id_token: z.string().nullish(), + scope: z.string().optional(), + }), + }) + .merge(credentialsBaseSchema) + +export type GoogleSheetsBlock = z.infer +export type GoogleSheetsBlockV5 = z.infer +export type GoogleSheetsBlockV6 = z.infer +export type GoogleSheetsOptionsBase = z.infer< + typeof googleSheetsOptionsBaseSchema +> +export type GoogleSheetsGetOptions = z.infer< + typeof googleSheetsGetOptionsSchema +> +export type GoogleSheetsGetOptionsV6 = z.infer< + typeof googleSheetsGetOptionsSchemas.v6 +> +export type GoogleSheetsInsertRowOptions = z.infer< + typeof googleSheetsInsertRowOptionsSchema +> +export type GoogleSheetsUpdateRowOptions = z.infer< + typeof googleSheetsUpdateRowOptionsSchema +> +export type GoogleSheetsUpdateRowOptionsV6 = z.infer< + typeof googleSheetsUpdateRowOptionsSchemas.v6 +> +export type Cell = z.infer +export type ExtractingCell = z.infer +export type RowsFilterComparison = z.infer +export type GoogleSheetsCredentials = z.infer< + typeof googleSheetsCredentialsSchema +> diff --git a/packages/schemas/features/blocks/integrations/makeCom/constants.ts b/packages/schemas/features/blocks/integrations/makeCom/constants.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/schemas/features/blocks/integrations/makeCom/index.ts b/packages/schemas/features/blocks/integrations/makeCom/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/makeCom/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/integrations/makeCom/schema.ts b/packages/schemas/features/blocks/integrations/makeCom/schema.ts new file mode 100644 index 00000000000..82196dbff34 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/makeCom/schema.ts @@ -0,0 +1,23 @@ +import { z } from 'zod' +import { IntegrationBlockType } from '../constants' +import { webhookBlockSchemas } from '../webhook' + +export const makeComBlockSchemas = { + v5: webhookBlockSchemas.v5.merge( + z.object({ + type: z.enum([IntegrationBlockType.MAKE_COM]), + }) + ), + v6: webhookBlockSchemas.v6.merge( + z.object({ + type: z.enum([IntegrationBlockType.MAKE_COM]), + }) + ), +} as const + +const makeComBlockSchema = z.union([ + makeComBlockSchemas.v5, + makeComBlockSchemas.v6, +]) + +export type MakeComBlock = z.infer diff --git a/packages/schemas/features/blocks/integrations/openai/constants.ts b/packages/schemas/features/blocks/integrations/openai/constants.ts new file mode 100644 index 00000000000..7559d1e1091 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/openai/constants.ts @@ -0,0 +1,29 @@ +export const openAITasks = ['Create chat completion', 'Create image'] as const + +export const chatCompletionMessageRoles = [ + 'system', + 'user', + 'assistant', +] as const + +export const chatCompletionMessageCustomRoles = [ + 'Messages sequence ✨', + 'Dialogue', +] as const + +export const deprecatedRoles = ['Messages sequence ✨'] as const + +export const chatCompletionResponseValues = [ + 'Message content', + 'Total tokens', +] as const + +export const defaultOpenAIOptions = { + baseUrl: 'https://api.openai.com/v1', + task: 'Create chat completion', + model: 'gpt-3.5-turbo', +} as const + +export const defaultOpenAIResponseMappingItem = { + valueToExtract: 'Message content', +} as const diff --git a/packages/schemas/features/blocks/integrations/openai/index.ts b/packages/schemas/features/blocks/integrations/openai/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/openai/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/integrations/openai/schema.ts b/packages/schemas/features/blocks/integrations/openai/schema.ts new file mode 100644 index 00000000000..42d2bb1ece7 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/openai/schema.ts @@ -0,0 +1,126 @@ +import { z } from 'zod' +import { + chatCompletionMessageCustomRoles, + chatCompletionMessageRoles, + chatCompletionResponseValues, + openAITasks, +} from './constants' +import { variableStringSchema } from '../../../utils' +import { blockBaseSchema, credentialsBaseSchema } from '../../shared' +import { IntegrationBlockType } from '../constants' + +const openAIBaseOptionsSchema = z.object({ + credentialsId: z.string().optional(), + baseUrl: z.string().optional(), + apiVersion: z.string().optional(), +}) + +const initialOptionsSchema = z + .object({ + task: z.undefined(), + }) + .merge(openAIBaseOptionsSchema) + +export const nativeMessageSchema = z.object({ + id: z.string(), + role: z.enum(chatCompletionMessageRoles).optional(), + content: z.string().optional(), + name: z.string().optional(), +}) + +const messageSequenceItemSchema = z.object({ + id: z.string(), + role: z.literal('Messages sequence ✨'), + content: z + .object({ + assistantMessagesVariableId: z.string().optional(), + userMessagesVariableId: z.string().optional(), + }) + .optional(), +}) + +const dialogueItemSchema = z.object({ + id: z.string(), + role: z.literal('Dialogue'), + dialogueVariableId: z.string().optional(), + startsBy: z.enum(['user', 'assistant']).optional(), +}) + +const chatCompletionOptionsSchema = z + .object({ + task: z.literal(openAITasks[0]), + model: z.string().optional(), + messages: z + .array( + z.union([ + nativeMessageSchema, + messageSequenceItemSchema, + dialogueItemSchema, + ]) + ) + .optional(), + advancedSettings: z + .object({ + temperature: z.number().or(variableStringSchema).optional(), + }) + .optional(), + responseMapping: z + .array( + z.object({ + id: z.string(), + valueToExtract: z.preprocess( + (val) => (!val ? 'Message content' : val), + z.enum(chatCompletionResponseValues) + ), + variableId: z.string().optional(), + }) + ) + .optional(), + }) + .merge(openAIBaseOptionsSchema) + +const createImageOptionsSchema = z + .object({ + task: z.literal(openAITasks[1]), + prompt: z.string().optional(), + advancedOptions: z.object({ + size: z.enum(['256x256', '512x512', '1024x1024']).optional(), + }), + responseMapping: z.array( + z.object({ + id: z.string(), + valueToExtract: z.enum(['Image URL']), + variableId: z.string().optional(), + }) + ), + }) + .merge(openAIBaseOptionsSchema) + +export const openAIBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([IntegrationBlockType.OPEN_AI]), + options: z + .discriminatedUnion('task', [ + initialOptionsSchema, + chatCompletionOptionsSchema, + createImageOptionsSchema, + ]) + .optional(), + }) +) + +export const openAICredentialsSchema = z + .object({ + type: z.literal('openai'), + data: z.object({ + apiKey: z.string(), + }), + }) + .merge(credentialsBaseSchema) + +export type OpenAICredentials = z.infer +export type OpenAIBlock = z.infer +export type ChatCompletionOpenAIOptions = z.infer< + typeof chatCompletionOptionsSchema +> +export type CreateImageOpenAIOptions = z.infer diff --git a/packages/schemas/features/blocks/integrations/pabblyConnect/constants.ts b/packages/schemas/features/blocks/integrations/pabblyConnect/constants.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/schemas/features/blocks/integrations/pabblyConnect/index.ts b/packages/schemas/features/blocks/integrations/pabblyConnect/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/pabblyConnect/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/integrations/pabblyConnect/schema.ts b/packages/schemas/features/blocks/integrations/pabblyConnect/schema.ts new file mode 100644 index 00000000000..b429e594ecc --- /dev/null +++ b/packages/schemas/features/blocks/integrations/pabblyConnect/schema.ts @@ -0,0 +1,23 @@ +import { z } from 'zod' +import { IntegrationBlockType } from '../constants' +import { webhookBlockSchemas } from '../webhook' + +export const pabblyConnectBlockSchemas = { + v5: webhookBlockSchemas.v5.merge( + z.object({ + type: z.enum([IntegrationBlockType.PABBLY_CONNECT]), + }) + ), + v6: webhookBlockSchemas.v6.merge( + z.object({ + type: z.enum([IntegrationBlockType.PABBLY_CONNECT]), + }) + ), +} as const + +const pabblyConnectBlockSchema = z.union([ + pabblyConnectBlockSchemas.v5, + pabblyConnectBlockSchemas.v6, +]) + +export type PabblyConnectBlock = z.infer diff --git a/packages/schemas/features/blocks/integrations/pixel/index.ts b/packages/schemas/features/blocks/integrations/pixel/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/pixel/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/integrations/pixel/schema.ts b/packages/schemas/features/blocks/integrations/pixel/schema.ts new file mode 100644 index 00000000000..94f1230598e --- /dev/null +++ b/packages/schemas/features/blocks/integrations/pixel/schema.ts @@ -0,0 +1,52 @@ +import { z } from 'zod' +import { pixelEventTypes } from './constants' +import { blockBaseSchema } from '../../shared' +import { IntegrationBlockType } from '../constants' + +const basePixelOptionSchema = z.object({ + pixelId: z.string().optional(), + isInitSkip: z.boolean().optional(), + params: z + .array( + z.object({ + id: z.string(), + key: z.string().optional(), + value: z.any().optional(), + }) + ) + .optional(), +}) + +const initialPixelOptionSchema = basePixelOptionSchema.merge( + z.object({ + eventType: z.undefined(), + }) +) + +const standardPixelEventOptionSchema = basePixelOptionSchema.merge( + z.object({ + eventType: z.enum(pixelEventTypes), + }) +) + +const customPixelOptionSchema = basePixelOptionSchema.merge( + z.object({ + eventType: z.enum(['Custom']), + name: z.string().optional(), + }) +) + +export const pixelOptionsSchema = z.discriminatedUnion('eventType', [ + initialPixelOptionSchema, + standardPixelEventOptionSchema, + customPixelOptionSchema, +]) + +export const pixelBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([IntegrationBlockType.PIXEL]), + options: pixelOptionsSchema.optional(), + }) +) + +export type PixelBlock = z.infer diff --git a/packages/schemas/features/blocks/integrations/schema.ts b/packages/schemas/features/blocks/integrations/schema.ts new file mode 100644 index 00000000000..34fdd90663b --- /dev/null +++ b/packages/schemas/features/blocks/integrations/schema.ts @@ -0,0 +1,58 @@ +import { z } from 'zod' +import { chatwootBlockSchema } from './chatwoot' +import { googleAnalyticsBlockSchema } from './googleAnalytics' +import { googleSheetsBlockSchemas } from './googleSheets' +import { openAIBlockSchema } from './openai' +import { pixelBlockSchema } from './pixel/schema' +import { sendEmailBlockSchema } from './sendEmail' +import { zemanticAiBlockSchema } from './zemanticAi' +import { zapierBlockSchemas } from './zapier' +import { webhookBlockSchemas } from './webhook' +import { makeComBlockSchemas } from './makeCom' +import { pabblyConnectBlockSchemas } from './pabblyConnect' + +export const integrationBlockSchemas = { + v5: [ + chatwootBlockSchema, + googleAnalyticsBlockSchema, + googleSheetsBlockSchemas.v5, + makeComBlockSchemas.v5, + openAIBlockSchema, + pabblyConnectBlockSchemas.v5, + sendEmailBlockSchema, + webhookBlockSchemas.v5, + zapierBlockSchemas.v5, + pixelBlockSchema, + zemanticAiBlockSchema, + ], + v6: [ + chatwootBlockSchema, + googleAnalyticsBlockSchema, + googleSheetsBlockSchemas.v6, + makeComBlockSchemas.v6, + openAIBlockSchema, + pabblyConnectBlockSchemas.v6, + sendEmailBlockSchema, + webhookBlockSchemas.v6, + zapierBlockSchemas.v6, + pixelBlockSchema, + zemanticAiBlockSchema, + ], +} as const + +const integrationBlockV5Schema = z.discriminatedUnion('type', [ + ...integrationBlockSchemas.v5, +]) + +const integrationBlockV6Schema = z.discriminatedUnion('type', [ + ...integrationBlockSchemas.v6, +]) + +const integrationBlockSchema = z.union([ + integrationBlockV5Schema, + integrationBlockV6Schema, +]) + +export type IntegrationBlock = z.infer +export type IntegrationBlockV5 = z.infer +export type IntegrationBlockV6 = z.infer diff --git a/packages/schemas/features/blocks/integrations/sendEmail/constants.ts b/packages/schemas/features/blocks/integrations/sendEmail/constants.ts new file mode 100644 index 00000000000..b46eb064284 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/sendEmail/constants.ts @@ -0,0 +1,7 @@ +import { SendEmailBlock } from './schema' + +export const defaultSendEmailOptions = { + credentialsId: 'default', + isCustomBody: false, + isBodyCode: false, +} as const satisfies SendEmailBlock['options'] diff --git a/packages/schemas/features/blocks/integrations/sendEmail/index.ts b/packages/schemas/features/blocks/integrations/sendEmail/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/sendEmail/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/integrations/sendEmail/schema.ts b/packages/schemas/features/blocks/integrations/sendEmail/schema.ts new file mode 100644 index 00000000000..57ba9ea53b0 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/sendEmail/schema.ts @@ -0,0 +1,43 @@ +import { z } from 'zod' +import { blockBaseSchema, credentialsBaseSchema } from '../../shared' +import { IntegrationBlockType } from '../constants' + +export const sendEmailOptionsSchema = z.object({ + credentialsId: z.string().optional(), + isCustomBody: z.boolean().optional(), + isBodyCode: z.boolean().optional(), + recipients: z.array(z.string()).optional(), + subject: z.string().optional(), + body: z.string().optional(), + replyTo: z.string().optional(), + cc: z.array(z.string()).optional(), + bcc: z.array(z.string()).optional(), + attachmentsVariableId: z.string().optional(), +}) + +export const sendEmailBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([IntegrationBlockType.EMAIL]), + options: sendEmailOptionsSchema.optional(), + }) +) + +export const smtpCredentialsSchema = z + .object({ + type: z.literal('smtp'), + data: z.object({ + host: z.string().optional(), + username: z.string().optional(), + password: z.string().optional(), + isTlsEnabled: z.boolean().optional(), + port: z.number(), + from: z.object({ + email: z.string().optional(), + name: z.string().optional(), + }), + }), + }) + .merge(credentialsBaseSchema) + +export type SendEmailBlock = z.infer +export type SmtpCredentials = z.infer diff --git a/packages/schemas/features/blocks/integrations/webhook/constants.ts b/packages/schemas/features/blocks/integrations/webhook/constants.ts new file mode 100644 index 00000000000..fe1dbc4d371 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/webhook/constants.ts @@ -0,0 +1,23 @@ +import { WebhookBlockV6 } from './schema' + +export enum HttpMethod { + POST = 'POST', + GET = 'GET', + PUT = 'PUT', + DELETE = 'DELETE', + PATCH = 'PATCH', + HEAD = 'HEAD', + CONNECT = 'CONNECT', + OPTIONS = 'OPTIONS', + TRACE = 'TRACE', +} + +export const defaultWebhookAttributes = { + method: HttpMethod.POST, +} as const + +export const defaultWebhookBlockOptions = { + isAdvancedConfig: false, + isCustomBody: false, + isExecutedOnClient: false, +} as const satisfies WebhookBlockV6['options'] diff --git a/packages/schemas/features/blocks/integrations/webhook/schema.ts b/packages/schemas/features/blocks/integrations/webhook/schema.ts new file mode 100644 index 00000000000..40ddb59a95a --- /dev/null +++ b/packages/schemas/features/blocks/integrations/webhook/schema.ts @@ -0,0 +1,108 @@ +import { z } from 'zod' +import { blockBaseSchema } from '../../shared' +import { IntegrationBlockType } from '../constants' +import { HttpMethod } from './constants' + +const variableForTestSchema = z.object({ + id: z.string(), + variableId: z.string().optional(), + value: z.string().optional(), +}) + +const responseVariableMappingSchema = z.object({ + id: z.string(), + variableId: z.string().optional(), + bodyPath: z.string().optional(), +}) + +const keyValueSchema = z.object({ + id: z.string(), + key: z.string().optional(), + value: z.string().optional(), +}) + +export const webhookV5Schema = z.object({ + id: z.string(), + queryParams: keyValueSchema.array().optional(), + headers: keyValueSchema.array().optional(), + method: z.nativeEnum(HttpMethod).optional(), + url: z.string().optional(), + body: z.string().optional(), +}) + +const webhookSchemas = { + v5: webhookV5Schema, + v6: webhookV5Schema.omit({ + id: true, + }), +} + +const webhookSchema = z.union([webhookSchemas.v5, webhookSchemas.v6]) + +export const webhookOptionsV5Schema = z.object({ + variablesForTest: z.array(variableForTestSchema).optional(), + responseVariableMapping: z.array(responseVariableMappingSchema).optional(), + isAdvancedConfig: z.boolean().optional(), + isCustomBody: z.boolean().optional(), + isExecutedOnClient: z.boolean().optional(), + webhook: webhookSchemas.v5.optional(), +}) + +const webhookOptionsSchemas = { + v5: webhookOptionsV5Schema, + v6: webhookOptionsV5Schema.merge( + z.object({ + webhook: webhookSchemas.v6.optional(), + }) + ), +} + +const webhookBlockV5Schema = blockBaseSchema.merge( + z.object({ + type: z.enum([IntegrationBlockType.WEBHOOK]), + options: webhookOptionsSchemas.v5.optional(), + webhookId: z.string().optional(), + }) +) + +export const webhookBlockSchemas = { + v5: webhookBlockV5Schema, + v6: webhookBlockV5Schema + .omit({ + webhookId: true, + }) + .merge( + z.object({ + options: webhookOptionsSchemas.v6.optional(), + }) + ), +} + +const webhookBlockSchema = z.union([ + webhookBlockSchemas.v5, + webhookBlockSchemas.v6, +]) + +export const executableWebhookSchema = z.object({ + url: z.string(), + headers: z.record(z.string()).optional(), + body: z.unknown().optional(), + method: z.nativeEnum(HttpMethod).optional(), +}) + +export type KeyValue = { id: string; key?: string; value?: string } + +export type WebhookResponse = { + statusCode: number + data?: unknown +} + +export type ExecutableWebhook = z.infer + +export type Webhook = z.infer +export type WebhookBlock = z.infer +export type WebhookBlockV6 = z.infer +export type ResponseVariableMapping = z.infer< + typeof responseVariableMappingSchema +> +export type VariableForTest = z.infer diff --git a/packages/schemas/features/blocks/integrations/zapier/constants.ts b/packages/schemas/features/blocks/integrations/zapier/constants.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/schemas/features/blocks/integrations/zapier/index.ts b/packages/schemas/features/blocks/integrations/zapier/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/zapier/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/integrations/zapier/schema.ts b/packages/schemas/features/blocks/integrations/zapier/schema.ts new file mode 100644 index 00000000000..71e633603e3 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/zapier/schema.ts @@ -0,0 +1,25 @@ +import { z } from 'zod' +import { IntegrationBlockType } from '../constants' +import { webhookBlockSchemas } from '../webhook' + +export const zapierBlockSchemas = { + v5: webhookBlockSchemas.v5.merge( + z.object({ + type: z.enum([IntegrationBlockType.ZAPIER]), + }) + ), + v6: webhookBlockSchemas.v6.merge( + z.object({ + type: z.enum([IntegrationBlockType.ZAPIER]), + }) + ), +} as const + +const zapierBlockSchema = z.union([ + zapierBlockSchemas.v5, + zapierBlockSchemas.v6, +]) + +export type ZapierBlock = z.infer +export type ZapierBlockV5 = z.infer +export type ZapierBlockV6 = z.infer diff --git a/packages/schemas/features/blocks/integrations/zemanticAi/constants.ts b/packages/schemas/features/blocks/integrations/zemanticAi/constants.ts new file mode 100644 index 00000000000..21af70408e9 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/zemanticAi/constants.ts @@ -0,0 +1,11 @@ +import { ZemanticAiBlock } from './schema' + +export const searchResponseValues = ['Summary', 'Results'] as const + +export const defaultZemanticAiOptions = { + maxResults: 3, +} as const satisfies ZemanticAiBlock['options'] + +export const defaultZemanticAiResponseMappingItem = { + valueToExtract: 'Summary', +} as const diff --git a/packages/schemas/features/blocks/integrations/zemanticAi/index.ts b/packages/schemas/features/blocks/integrations/zemanticAi/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/integrations/zemanticAi/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/integrations/zemanticAi/schema.ts b/packages/schemas/features/blocks/integrations/zemanticAi/schema.ts new file mode 100644 index 00000000000..fd59212506a --- /dev/null +++ b/packages/schemas/features/blocks/integrations/zemanticAi/schema.ts @@ -0,0 +1,57 @@ +import { z } from 'zod' +import { blockBaseSchema, credentialsBaseSchema } from '../../shared' +import { IntegrationBlockType } from '../constants' +import { searchResponseValues } from './constants' + +export const zemanticAiOptionsSchema = z.object({ + credentialsId: z.string().optional(), + projectId: z.string().optional(), + systemPrompt: z.string().optional(), + prompt: z.string().optional(), + query: z.string().optional(), + maxResults: z.number().int().optional(), + responseMapping: z + .array( + z.object({ + id: z.string(), + valueToExtract: z.preprocess( + (val) => (!val ? 'Summary' : val), + z.enum(searchResponseValues) + ), + variableId: z.string().optional(), + }) + ) + .optional(), +}) + +export const zemanticAiBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([IntegrationBlockType.ZEMANTIC_AI]), + blockId: z.string().optional(), + options: zemanticAiOptionsSchema.optional(), + }) +) + +export const zemanticAiCredentialsSchema = z + .object({ + type: z.literal('zemanticAi'), + data: z.object({ + apiKey: z.string(), + }), + }) + .merge(credentialsBaseSchema) + +export const zemanticSearchResponseSchema = z.object({ + results: z.array( + z.object({ + documentId: z.string(), + text: z.string(), + score: z.number(), + }) + ), + summary: z.string(), +}) + +export type ZemanticAiResponse = z.infer +export type ZemanticAiCredentials = z.infer +export type ZemanticAiBlock = z.infer diff --git a/packages/schemas/features/blocks/logic/abTest/constants.ts b/packages/schemas/features/blocks/logic/abTest/constants.ts new file mode 100644 index 00000000000..56c7c7b77cb --- /dev/null +++ b/packages/schemas/features/blocks/logic/abTest/constants.ts @@ -0,0 +1,5 @@ +import { AbTestBlock } from './schema' + +export const defaultAbTestOptions = { + aPercent: 50, +} as const satisfies AbTestBlock['options'] diff --git a/packages/schemas/features/blocks/logic/abTest/index.ts b/packages/schemas/features/blocks/logic/abTest/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/logic/abTest/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/logic/abTest/schema.ts b/packages/schemas/features/blocks/logic/abTest/schema.ts new file mode 100644 index 00000000000..11c46124d5f --- /dev/null +++ b/packages/schemas/features/blocks/logic/abTest/schema.ts @@ -0,0 +1,48 @@ +import { z } from 'zod' +import { blockBaseSchema } from '../../shared' +import { LogicBlockType } from '../constants' +import { itemBaseSchemas } from '../../../items/shared' + +export const aItemSchemas = { + v5: itemBaseSchemas.v5.extend({ + path: z.literal('a'), + }), + v6: itemBaseSchemas.v6.extend({ + path: z.literal('a'), + }), +} + +export const bItemSchemas = { + v5: itemBaseSchemas.v5.extend({ + path: z.literal('b'), + }), + v6: itemBaseSchemas.v6.extend({ + path: z.literal('b'), + }), +} + +const abTestBlockV5Schema = blockBaseSchema.merge( + z.object({ + type: z.enum([LogicBlockType.AB_TEST]), + items: z.tuple([aItemSchemas.v5, bItemSchemas.v5]), + options: z + .object({ + aPercent: z.number().min(0).max(100).optional(), + }) + .optional(), + }) +) + +export const abTestBlockSchemas = { + v5: abTestBlockV5Schema, + v6: abTestBlockV5Schema.extend({ + items: z.tuple([aItemSchemas.v6, bItemSchemas.v6]), + }), +} as const + +export const abTestBlockSchema = z.union([ + abTestBlockSchemas.v5, + abTestBlockSchemas.v6, +]) + +export type AbTestBlock = z.infer diff --git a/packages/schemas/features/blocks/logic/condition/constants.ts b/packages/schemas/features/blocks/logic/condition/constants.ts new file mode 100644 index 00000000000..67358324966 --- /dev/null +++ b/packages/schemas/features/blocks/logic/condition/constants.ts @@ -0,0 +1,25 @@ +import { ConditionBlock } from './schema' + +export enum LogicalOperator { + OR = 'OR', + AND = 'AND', +} + +export enum ComparisonOperators { + EQUAL = 'Equal to', + NOT_EQUAL = 'Not equal', + CONTAINS = 'Contains', + NOT_CONTAINS = 'Does not contain', + GREATER = 'Greater than', + LESS = 'Less than', + IS_SET = 'Is set', + IS_EMPTY = 'Is empty', + STARTS_WITH = 'Starts with', + ENDS_WITH = 'Ends with', + MATCHES_REGEX = 'Matches regex', + NOT_MATCH_REGEX = 'Does not match regex', +} + +export const defaultConditionItemContent = { + logicalOperator: LogicalOperator.AND, +} as const satisfies ConditionBlock['items'][number]['content'] diff --git a/packages/schemas/features/blocks/logic/condition/index.ts b/packages/schemas/features/blocks/logic/condition/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/logic/condition/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/logic/condition/schema.ts b/packages/schemas/features/blocks/logic/condition/schema.ts new file mode 100644 index 00000000000..df715e10bd4 --- /dev/null +++ b/packages/schemas/features/blocks/logic/condition/schema.ts @@ -0,0 +1,62 @@ +import { z } from 'zod' +import { blockBaseSchema } from '../../shared' +import { LogicBlockType } from '../constants' +import { ComparisonOperators, LogicalOperator } from './constants' +import { itemBaseSchemas } from '../../../items/shared' + +const comparisonSchema = z.object({ + id: z.string(), + variableId: z.string().optional(), + comparisonOperator: z.nativeEnum(ComparisonOperators).optional(), + value: z.string().optional(), +}) + +export const conditionSchema = z.object({ + logicalOperator: z.nativeEnum(LogicalOperator).optional(), + comparisons: z.array(comparisonSchema).optional(), +}) + +export const conditionItemSchemas = { + v5: itemBaseSchemas.v5.merge( + z.object({ + content: conditionSchema.optional(), + }) + ), + v6: itemBaseSchemas.v6.merge( + z.object({ + content: conditionSchema.optional(), + }) + ), +} + +export const conditionItemSchema = z.union([ + conditionItemSchemas.v5, + conditionItemSchemas.v6, +]) + +export const conditionBlockSchemas = { + v5: blockBaseSchema.merge( + z.object({ + type: z.enum([LogicBlockType.CONDITION]), + items: z.array(conditionItemSchemas.v5), + options: z.undefined(), + }) + ), + v6: blockBaseSchema.merge( + z.object({ + type: z.enum([LogicBlockType.CONDITION]), + items: z.array(conditionItemSchemas.v6), + options: z.undefined(), + }) + ), +} + +export const conditionBlockSchema = z.union([ + conditionBlockSchemas.v5, + conditionBlockSchemas.v6, +]) + +export type ConditionItem = z.infer +export type Comparison = z.infer +export type ConditionBlock = z.infer +export type Condition = z.infer diff --git a/packages/schemas/features/blocks/logic/constants.ts b/packages/schemas/features/blocks/logic/constants.ts new file mode 100644 index 00000000000..12fd493af72 --- /dev/null +++ b/packages/schemas/features/blocks/logic/constants.ts @@ -0,0 +1,10 @@ +export enum LogicBlockType { + SET_VARIABLE = 'Set variable', + CONDITION = 'Condition', + REDIRECT = 'Redirect', + SCRIPT = 'Code', + TYPEBOT_LINK = 'Typebot link', + WAIT = 'Wait', + JUMP = 'Jump', + AB_TEST = 'AB test', +} diff --git a/packages/schemas/features/blocks/logic/jump/constants.ts b/packages/schemas/features/blocks/logic/jump/constants.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/schemas/features/blocks/logic/jump/index.ts b/packages/schemas/features/blocks/logic/jump/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/logic/jump/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/logic/jump/schema.ts b/packages/schemas/features/blocks/logic/jump/schema.ts new file mode 100644 index 00000000000..e42b65a9b47 --- /dev/null +++ b/packages/schemas/features/blocks/logic/jump/schema.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' +import { blockBaseSchema } from '../../shared' +import { LogicBlockType } from '../constants' + +export const jumpOptionsSchema = z.object({ + groupId: z.string().optional(), + blockId: z.string().optional(), +}) + +export const jumpBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([LogicBlockType.JUMP]), + options: jumpOptionsSchema.optional(), + }) +) + +export type JumpBlock = z.infer diff --git a/packages/schemas/features/blocks/logic/redirect/constants.ts b/packages/schemas/features/blocks/logic/redirect/constants.ts new file mode 100644 index 00000000000..b7cd5fba69e --- /dev/null +++ b/packages/schemas/features/blocks/logic/redirect/constants.ts @@ -0,0 +1,5 @@ +import { RedirectBlock } from './schema' + +export const defaultRedirectOptions = { + isNewTab: false, +} as const satisfies RedirectBlock['options'] diff --git a/packages/schemas/features/blocks/logic/redirect/index.ts b/packages/schemas/features/blocks/logic/redirect/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/logic/redirect/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/logic/redirect/schema.ts b/packages/schemas/features/blocks/logic/redirect/schema.ts new file mode 100644 index 00000000000..b47e025c339 --- /dev/null +++ b/packages/schemas/features/blocks/logic/redirect/schema.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' +import { blockBaseSchema } from '../../shared' +import { LogicBlockType } from '../constants' + +export const redirectOptionsSchema = z.object({ + url: z.string().optional(), + isNewTab: z.boolean().optional(), +}) + +export const redirectBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([LogicBlockType.REDIRECT]), + options: redirectOptionsSchema.optional(), + }) +) + +export type RedirectBlock = z.infer diff --git a/packages/schemas/features/blocks/logic/schema.ts b/packages/schemas/features/blocks/logic/schema.ts new file mode 100644 index 00000000000..aec6ee53ad4 --- /dev/null +++ b/packages/schemas/features/blocks/logic/schema.ts @@ -0,0 +1,44 @@ +import { z } from 'zod' +import { conditionBlockSchemas } from './condition' +import { jumpBlockSchema } from './jump' +import { redirectBlockSchema } from './redirect' +import { scriptBlockSchema } from './script' +import { setVariableBlockSchema } from './setVariable' +import { typebotLinkBlockSchema } from './typebotLink' +import { waitBlockSchema } from './wait' +import { abTestBlockSchemas } from './abTest' + +export const logicBlockSchemas = { + v5: [ + scriptBlockSchema, + conditionBlockSchemas.v5, + redirectBlockSchema, + setVariableBlockSchema, + typebotLinkBlockSchema, + waitBlockSchema, + jumpBlockSchema, + abTestBlockSchemas.v5, + ], + v6: [ + scriptBlockSchema, + conditionBlockSchemas.v6, + redirectBlockSchema, + setVariableBlockSchema, + typebotLinkBlockSchema, + waitBlockSchema, + jumpBlockSchema, + abTestBlockSchemas.v6, + ], +} as const + +const logicBlockV5Schema = z.discriminatedUnion('type', [ + ...logicBlockSchemas.v5, +]) + +const logicBlockV6Schema = z.discriminatedUnion('type', [ + ...logicBlockSchemas.v6, +]) + +const logicBlockSchema = z.union([logicBlockV5Schema, logicBlockV6Schema]) + +export type LogicBlock = z.infer diff --git a/packages/schemas/features/blocks/logic/script/constants.ts b/packages/schemas/features/blocks/logic/script/constants.ts new file mode 100644 index 00000000000..b75ba8e7dbd --- /dev/null +++ b/packages/schemas/features/blocks/logic/script/constants.ts @@ -0,0 +1,5 @@ +import { ScriptBlock } from './schema' + +export const defaultScriptOptions = { + name: 'Script', +} as const satisfies ScriptBlock['options'] diff --git a/packages/schemas/features/blocks/logic/script/index.ts b/packages/schemas/features/blocks/logic/script/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/logic/script/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/logic/script/schema.ts b/packages/schemas/features/blocks/logic/script/schema.ts new file mode 100644 index 00000000000..1b6a09f5560 --- /dev/null +++ b/packages/schemas/features/blocks/logic/script/schema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' +import { blockBaseSchema } from '../../shared' +import { LogicBlockType } from '../constants' + +export const scriptOptionsSchema = z.object({ + name: z.string().optional(), + content: z.string().optional(), + shouldExecuteInParentContext: z.boolean().optional(), +}) + +export const scriptBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([LogicBlockType.SCRIPT]), + options: scriptOptionsSchema.optional(), + }) +) + +export type ScriptBlock = z.infer diff --git a/packages/schemas/features/blocks/logic/setVariable/constants.ts b/packages/schemas/features/blocks/logic/setVariable/constants.ts new file mode 100644 index 00000000000..3d2d6e1f1e4 --- /dev/null +++ b/packages/schemas/features/blocks/logic/setVariable/constants.ts @@ -0,0 +1,25 @@ +import { SetVariableBlock } from './schema' + +export const valueTypes = [ + 'Custom', + 'Empty', + 'Append value(s)', + 'Environment name', + 'User ID', + 'Now', + 'Today', + 'Yesterday', + 'Tomorrow', + 'Random ID', + 'Moment of the day', + 'Map item with same index', + 'Phone number', + 'Contact name', +] as const + +export const hiddenTypes = ['Today'] as const + +export const defaultSetVariableOptions = { + type: 'Custom', + isExecutedOnClient: false, +} as const satisfies SetVariableBlock['options'] diff --git a/packages/schemas/features/blocks/logic/setVariable/index.ts b/packages/schemas/features/blocks/logic/setVariable/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/logic/setVariable/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/logic/setVariable/schema.ts b/packages/schemas/features/blocks/logic/setVariable/schema.ts new file mode 100644 index 00000000000..6c2b2be24e5 --- /dev/null +++ b/packages/schemas/features/blocks/logic/setVariable/schema.ts @@ -0,0 +1,69 @@ +import { z } from 'zod' +import { blockBaseSchema } from '../../shared' +import { LogicBlockType } from '../constants' + +const baseOptions = z.object({ + variableId: z.string().optional(), + isExecutedOnClient: z.boolean().optional(), +}) + +const basicSetVariableOptionsSchema = baseOptions.extend({ + type: z.enum([ + 'Today', + 'Now', + 'Yesterday', + 'Tomorrow', + 'Moment of the day', + 'Empty', + 'Environment name', + 'User ID', + 'Random ID', + 'Phone number', + 'Contact name', + ]), +}) + +const initialSetVariableOptionsSchema = baseOptions.extend({ + type: z.undefined(), + expressionToEvaluate: z.string().optional(), + isCode: z.boolean().optional(), +}) + +const customSetVariableOptionsSchema = baseOptions.extend({ + type: z.literal('Custom'), + expressionToEvaluate: z.string().optional(), + isCode: z.boolean().optional(), +}) + +const mapListItemsOptionsSchema = baseOptions.extend({ + type: z.literal('Map item with same index'), + mapListItemParams: z + .object({ + baseItemVariableId: z.string().optional(), + baseListVariableId: z.string().optional(), + targetListVariableId: z.string().optional(), + }) + .optional(), +}) + +const appendItemToListOptionsSchema = baseOptions.extend({ + type: z.literal('Append value(s)'), + item: z.string().optional(), +}) + +export const setVariableOptionsSchema = z.discriminatedUnion('type', [ + initialSetVariableOptionsSchema, + basicSetVariableOptionsSchema, + customSetVariableOptionsSchema, + mapListItemsOptionsSchema, + appendItemToListOptionsSchema, +]) + +export const setVariableBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([LogicBlockType.SET_VARIABLE]), + options: setVariableOptionsSchema.optional(), + }) +) + +export type SetVariableBlock = z.infer diff --git a/packages/schemas/features/blocks/logic/typebotLink/constants.ts b/packages/schemas/features/blocks/logic/typebotLink/constants.ts new file mode 100644 index 00000000000..011f99cad86 --- /dev/null +++ b/packages/schemas/features/blocks/logic/typebotLink/constants.ts @@ -0,0 +1,5 @@ +import { TypebotLinkBlock } from './schema' + +export const defaultTypebotLinkOptions = { + mergeResults: false, +} as const satisfies TypebotLinkBlock['options'] diff --git a/packages/schemas/features/blocks/logic/typebotLink/index.ts b/packages/schemas/features/blocks/logic/typebotLink/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/logic/typebotLink/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/logic/typebotLink/schema.ts b/packages/schemas/features/blocks/logic/typebotLink/schema.ts new file mode 100644 index 00000000000..dc0aa4f0141 --- /dev/null +++ b/packages/schemas/features/blocks/logic/typebotLink/schema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' +import { blockBaseSchema } from '../../shared' +import { LogicBlockType } from '../constants' + +export const typebotLinkOptionsSchema = z.object({ + typebotId: z.string().optional(), + groupId: z.string().optional(), + mergeResults: z.boolean().optional(), +}) + +export const typebotLinkBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([LogicBlockType.TYPEBOT_LINK]), + options: typebotLinkOptionsSchema.optional(), + }) +) + +export type TypebotLinkBlock = z.infer diff --git a/packages/schemas/features/blocks/logic/wait/constants.ts b/packages/schemas/features/blocks/logic/wait/constants.ts new file mode 100644 index 00000000000..35fef6e2f16 --- /dev/null +++ b/packages/schemas/features/blocks/logic/wait/constants.ts @@ -0,0 +1,5 @@ +import { WaitBlock } from './schema' + +export const defaultWaitOptions = { + shouldPause: false, +} as const satisfies WaitBlock['options'] diff --git a/packages/schemas/features/blocks/logic/wait/index.ts b/packages/schemas/features/blocks/logic/wait/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/blocks/logic/wait/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/blocks/logic/wait/schema.ts b/packages/schemas/features/blocks/logic/wait/schema.ts new file mode 100644 index 00000000000..9dd7b58b66c --- /dev/null +++ b/packages/schemas/features/blocks/logic/wait/schema.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' +import { blockBaseSchema } from '../../shared' +import { LogicBlockType } from '../constants' + +export const waitOptionsSchema = z.object({ + secondsToWaitFor: z.string().optional(), + shouldPause: z.boolean().optional(), +}) + +export const waitBlockSchema = blockBaseSchema.merge( + z.object({ + type: z.enum([LogicBlockType.WAIT]), + options: waitOptionsSchema.optional(), + }) +) + +export type WaitBlock = z.infer diff --git a/packages/schemas/features/blocks/schema.ts b/packages/schemas/features/blocks/schema.ts new file mode 100644 index 00000000000..a56bd6db308 --- /dev/null +++ b/packages/schemas/features/blocks/schema.ts @@ -0,0 +1,46 @@ +import { z } from 'zod' +import { blockBaseSchema } from './shared' +import { startBlockSchema } from './start/schemas' +import { Item, ItemV6 } from '../items/schema' +import { bubbleBlockSchemas } from './bubbles/schema' +import { LogicBlock, logicBlockSchemas } from './logic/schema' +import { InputBlock, inputBlockSchemas } from './inputs/schema' +import { IntegrationBlock, integrationBlockSchemas } from './integrations' + +export type BlockWithOptions = Extract + +export type BlockWithOptionsType = BlockWithOptions['type'] + +export type BlockBase = z.infer + +export type BlockIndices = { + groupIndex: number + blockIndex: number +} + +export const blockSchemaV5 = z.discriminatedUnion('type', [ + startBlockSchema, + ...bubbleBlockSchemas, + ...inputBlockSchemas.v5, + ...logicBlockSchemas.v5, + ...integrationBlockSchemas.v5, +]) +export type BlockV5 = z.infer + +export const blockSchemaV6 = z.discriminatedUnion('type', [ + ...bubbleBlockSchemas, + ...inputBlockSchemas.v6, + ...logicBlockSchemas.v6, + ...integrationBlockSchemas.v6, +]) +export type BlockV6 = z.infer + +const blockSchema = blockSchemaV5.or(blockSchemaV6) +export type Block = z.infer + +export type BlockOptions = + | InputBlock['options'] + | LogicBlock['options'] + | IntegrationBlock['options'] + +export type BlockWithItems = Extract diff --git a/packages/schemas/features/blocks/shared.ts b/packages/schemas/features/blocks/shared.ts new file mode 100644 index 00000000000..5aa7439bf02 --- /dev/null +++ b/packages/schemas/features/blocks/shared.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' +import { Credentials as CredentialsFromPrisma } from '@typebot.io/prisma' + +export const blockBaseSchema = z.object({ + id: z.string(), + outgoingEdgeId: z.string().optional(), +}) + +export const optionBaseSchema = z.object({ + variableId: z.string().optional(), +}) + +export const credentialsBaseSchema = z.object({ + id: z.string(), + createdAt: z.date(), + workspaceId: z.string(), + name: z.string(), + iv: z.string(), +}) satisfies z.ZodType> diff --git a/packages/schemas/features/events/constants.ts b/packages/schemas/features/events/constants.ts new file mode 100644 index 00000000000..783551e0a63 --- /dev/null +++ b/packages/schemas/features/events/constants.ts @@ -0,0 +1,3 @@ +export enum EventType { + START = 'start', +} diff --git a/packages/schemas/features/events/index.ts b/packages/schemas/features/events/index.ts new file mode 100644 index 00000000000..be77bd3d89d --- /dev/null +++ b/packages/schemas/features/events/index.ts @@ -0,0 +1 @@ +export * from './start' diff --git a/packages/schemas/features/events/shared.ts b/packages/schemas/features/events/shared.ts new file mode 100644 index 00000000000..ba7b1ebea28 --- /dev/null +++ b/packages/schemas/features/events/shared.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +export const eventBaseSchema = z.object({ + id: z.string(), + outgoingEdgeId: z.string().optional(), + graphCoordinates: z.object({ + x: z.number(), + y: z.number(), + }), +}) diff --git a/packages/schemas/features/events/start/index.ts b/packages/schemas/features/events/start/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/events/start/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/events/start/schema.ts b/packages/schemas/features/events/start/schema.ts new file mode 100644 index 00000000000..92481e26830 --- /dev/null +++ b/packages/schemas/features/events/start/schema.ts @@ -0,0 +1,7 @@ +import { z } from 'zod' +import { eventBaseSchema } from '../shared' +import { EventType } from '../constants' + +export const startEventSchema = eventBaseSchema.extend({ + type: z.literal(EventType.START), +}) diff --git a/packages/schemas/features/items/constants.ts b/packages/schemas/features/items/constants.ts new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/packages/schemas/features/items/constants.ts @@ -0,0 +1 @@ + diff --git a/packages/schemas/features/items/schema.ts b/packages/schemas/features/items/schema.ts new file mode 100644 index 00000000000..2923e3628b4 --- /dev/null +++ b/packages/schemas/features/items/schema.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' +import { + buttonItemSchemas, + conditionItemSchemas, + pictureChoiceItemSchemas, + aItemSchemas, + bItemSchemas, +} from '../blocks' + +const itemSchemas = { + v5: z.union([ + buttonItemSchemas.v5, + conditionItemSchemas.v5, + pictureChoiceItemSchemas.v5, + aItemSchemas.v5, + bItemSchemas.v5, + ]), + v6: z.union([ + buttonItemSchemas.v6, + conditionItemSchemas.v6, + pictureChoiceItemSchemas.v6, + aItemSchemas.v6, + bItemSchemas.v6, + ]), +} as const + +const itemSchema = z.union([itemSchemas.v5, itemSchemas.v6]) +export type Item = z.infer +export type ItemV5 = z.infer +export type ItemV6 = z.infer diff --git a/packages/schemas/features/items/shared.ts b/packages/schemas/features/items/shared.ts new file mode 100644 index 00000000000..6820a818a34 --- /dev/null +++ b/packages/schemas/features/items/shared.ts @@ -0,0 +1,16 @@ +import { z } from 'zod' + +const itemBaseV5Schema = z.object({ + id: z.string(), + blockId: z.string().optional(), + outgoingEdgeId: z.string().optional(), +}) + +export const itemBaseSchemas = { + v5: itemBaseV5Schema, + v6: itemBaseV5Schema.omit({ blockId: true }), +} + +export const itemBaseSchema = z.union([itemBaseSchemas.v5, itemBaseSchemas.v6]) + +export type ItemBase = z.infer diff --git a/packages/schemas/features/typebot/group.ts b/packages/schemas/features/typebot/group.ts new file mode 100644 index 00000000000..f75d67327b7 --- /dev/null +++ b/packages/schemas/features/typebot/group.ts @@ -0,0 +1,32 @@ +import { blockSchemaV5, blockSchemaV6 } from '../blocks' +import { z } from 'zod' + +export const groupV5Schema = z.object({ + id: z.string(), + title: z.string(), + graphCoordinates: z.object({ + x: z.number(), + y: z.number(), + }), + blocks: z.array(blockSchemaV5), +}) +type GroupV5 = z.infer + +export const groupV6Schema = groupV5Schema.extend({ + blocks: z.array(blockSchemaV6), +}) +export type GroupV6 = z.infer + +export const parseGroups = ( + groups: unknown, + { typebotVersion }: { typebotVersion: T } +): T extends '6' ? GroupV6[] : GroupV5[] => { + if (typebotVersion === '6') { + return z.array(groupV6Schema).parse(groups) as T extends '6' + ? GroupV6[] + : GroupV5[] + } + return z.array(groupV5Schema).parse(groups) as T extends '6' + ? GroupV6[] + : GroupV5[] +} diff --git a/packages/schemas/features/typebot/settings/constants.ts b/packages/schemas/features/typebot/settings/constants.ts new file mode 100644 index 00000000000..9f55293ad51 --- /dev/null +++ b/packages/schemas/features/typebot/settings/constants.ts @@ -0,0 +1,22 @@ +export const defaultSettings = { + general: { + isInputPrefillEnabled: false, + isHideQueryParamsEnabled: true, + isNewResultOnRefreshEnabled: true, + rememberUser: { + isEnabled: false, + storage: 'session', + }, + isBrandingEnabled: false, + isTypingEmulationEnabled: true, + }, + typingEmulation: { enabled: true, speed: 300, maxDelay: 1.5 }, + metadata: { + description: + 'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.', + favIconUrl: (viewerBaseUrl: string) => viewerBaseUrl + '/favicon.png', + imageUrl: (viewerBaseUrl: string) => viewerBaseUrl + '/site-preview.png', + }, +} as const + +export const rememberUserStorages = ['session', 'local'] as const diff --git a/packages/schemas/features/typebot/settings/index.ts b/packages/schemas/features/typebot/settings/index.ts new file mode 100644 index 00000000000..cb7cdd48bd0 --- /dev/null +++ b/packages/schemas/features/typebot/settings/index.ts @@ -0,0 +1 @@ +export * from './schema' diff --git a/packages/schemas/features/typebot/settings/schema.ts b/packages/schemas/features/typebot/settings/schema.ts new file mode 100644 index 00000000000..d9cab601cd2 --- /dev/null +++ b/packages/schemas/features/typebot/settings/schema.ts @@ -0,0 +1,41 @@ +import { z } from 'zod' +import { rememberUserStorages } from './constants' +import { whatsAppSettingsSchema } from '../../whatsapp' + +const generalSettings = z.object({ + isBrandingEnabled: z.boolean().optional(), + isTypingEmulationEnabled: z.boolean().optional(), + isInputPrefillEnabled: z.boolean().optional(), + isHideQueryParamsEnabled: z.boolean().optional(), + isNewResultOnRefreshEnabled: z.boolean().optional(), + rememberUser: z + .object({ + isEnabled: z.boolean().optional(), + storage: z.enum(rememberUserStorages).optional(), + }) + .optional(), +}) + +const typingEmulation = z.object({ + enabled: z.boolean().optional(), + speed: z.number().optional(), + maxDelay: z.number().optional(), +}) + +const metadataSchema = z.object({ + title: z.string().optional(), + description: z.string().optional(), + imageUrl: z.string().optional(), + favIconUrl: z.string().optional(), + customHeadCode: z.string().optional(), + googleTagManagerId: z.string().optional(), +}) + +export const settingsSchema = z.object({ + general: generalSettings.optional(), + typingEmulation: typingEmulation.optional(), + metadata: metadataSchema.optional(), + whatsApp: whatsAppSettingsSchema.optional(), +}) + +export type Settings = z.infer diff --git a/packages/schemas/features/typebot/theme/constants.ts b/packages/schemas/features/typebot/theme/constants.ts new file mode 100644 index 00000000000..f597fceeffa --- /dev/null +++ b/packages/schemas/features/typebot/theme/constants.ts @@ -0,0 +1,31 @@ +import { Theme } from './schema' + +export enum BackgroundType { + COLOR = 'Color', + IMAGE = 'Image', + NONE = 'None', +} + +export const defaultTheme = { + chat: { + roundness: 'medium', + hostBubbles: { backgroundColor: '#F7F8FF', color: '#303235' }, + guestBubbles: { backgroundColor: '#FF8E21', color: '#FFFFFF' }, + buttons: { backgroundColor: '#0042DA', color: '#FFFFFF' }, + inputs: { + backgroundColor: '#FFFFFF', + color: '#303235', + placeholderColor: '#9095A0', + }, + hostAvatar: { + isEnabled: true, + }, + guestAvatar: { + isEnabled: false, + }, + }, + general: { + font: 'Open Sans', + background: { type: BackgroundType.COLOR, content: '#ffffff' }, + }, +} as const satisfies Theme diff --git a/packages/schemas/features/typebot/theme/schema.ts b/packages/schemas/features/typebot/theme/schema.ts new file mode 100644 index 00000000000..e4c1f362b49 --- /dev/null +++ b/packages/schemas/features/typebot/theme/schema.ts @@ -0,0 +1,63 @@ +import { ThemeTemplate as ThemeTemplatePrisma } from '@typebot.io/prisma' +import { z } from 'zod' +import { BackgroundType } from './constants' + +const avatarPropsSchema = z.object({ + isEnabled: z.boolean().optional(), + url: z.string().optional(), +}) + +const containerColorsSchema = z.object({ + backgroundColor: z.string().optional(), + color: z.string().optional(), +}) + +const inputColorsSchema = containerColorsSchema.merge( + z.object({ + placeholderColor: z.string().optional(), + }) +) + +export const chatThemeSchema = z.object({ + hostAvatar: avatarPropsSchema.optional(), + guestAvatar: avatarPropsSchema.optional(), + hostBubbles: containerColorsSchema.optional(), + guestBubbles: containerColorsSchema.optional(), + buttons: containerColorsSchema.optional(), + inputs: inputColorsSchema.optional(), + roundness: z.enum(['none', 'medium', 'large']).optional(), +}) + +const backgroundSchema = z.object({ + type: z.nativeEnum(BackgroundType).optional(), + content: z.string().optional().optional(), +}) + +const generalThemeSchema = z.object({ + font: z.string().optional(), + background: backgroundSchema.optional(), +}) + +export const themeSchema = z.object({ + general: generalThemeSchema.optional(), + chat: chatThemeSchema.optional(), + customCss: z.string().optional(), +}) + +export const themeTemplateSchema = z.object({ + id: z.string(), + name: z.string(), + theme: themeSchema, + workspaceId: z.string(), + createdAt: z.date(), + updatedAt: z.date(), +}) satisfies z.ZodType + +export type Theme = z.infer +export type ChatTheme = z.infer +export type AvatarProps = z.infer +export type GeneralTheme = z.infer +export type Background = z.infer +export type ContainerColors = z.infer +export type InputColors = z.infer +export type ThemeTemplate = z.infer diff --git a/packages/schemas/features/typebot/types.ts b/packages/schemas/features/typebot/types.ts new file mode 100644 index 00000000000..62178f69748 --- /dev/null +++ b/packages/schemas/features/typebot/types.ts @@ -0,0 +1,5 @@ +import { Typebot, TypebotV6 } from './typebot' + +export type Group = Typebot['groups'][number] + +export type TEvent = TypebotV6['events'][number]