diff --git a/apps/builder/src/components/TableList.tsx b/apps/builder/src/components/TableList.tsx index 7f1a8f8d6..a12716346 100644 --- a/apps/builder/src/components/TableList.tsx +++ b/apps/builder/src/components/TableList.tsx @@ -15,25 +15,23 @@ const defaultItem = { id: createId(), } -type ItemWithId = T & { id: string } - export type TableListItemProps = { item: T onItemChange: (item: T) => void } -type Props = { - initialItems?: ItemWithId[] +type Props = { + initialItems?: T[] isOrdered?: boolean addLabel?: string newItemDefaultProps?: Partial hasDefaultItem?: boolean ComponentBetweenItems?: (props: unknown) => JSX.Element - onItemsChange: (items: ItemWithId[]) => void + onItemsChange: (items: T[]) => void children: (props: TableListItemProps) => JSX.Element } -export const TableList = ({ +export const TableList = ({ initialItems, isOrdered, addLabel = 'Add', @@ -45,7 +43,7 @@ export const TableList = ({ }: Props) => { const [items, setItems] = useState( addIdsIfMissing(initialItems) ?? - (hasDefaultItem ? ([defaultItem] as ItemWithId[]) : []) + (hasDefaultItem ? ([defaultItem] as T[]) : []) ) const [showDeleteIndex, setShowDeleteIndex] = useState(null) @@ -56,14 +54,14 @@ export const TableList = ({ const createItem = () => { const id = createId() - const newItem = { id, ...newItemDefaultProps } as ItemWithId + const newItem = { id, ...newItemDefaultProps } as T setItems([...items, newItem]) onItemsChange([...items, newItem]) } const insertItem = (itemIndex: number) => () => { const id = createId() - const newItem = { id } as ItemWithId + const newItem = { id } as T const newItems = [...items] newItems.splice(itemIndex + 1, 0, newItem) setItems(newItems) @@ -96,7 +94,7 @@ export const TableList = ({ return ( {items.map((item, itemIndex) => ( - + {itemIndex !== 0 && ComponentBetweenItems && ( )} @@ -185,7 +183,7 @@ export const TableList = ({ ) } -const addIdsIfMissing = (items?: T[]): ItemWithId[] | undefined => +const addIdsIfMissing = (items?: T[]): T[] | undefined => items?.map((item) => ({ id: createId(), ...item, diff --git a/apps/builder/src/features/blocks/bubbles/embed/components/EmbedBubbleContent.tsx b/apps/builder/src/features/blocks/bubbles/embed/components/EmbedBubbleContent.tsx index 66f1499ee..6c1b28826 100644 --- a/apps/builder/src/features/blocks/bubbles/embed/components/EmbedBubbleContent.tsx +++ b/apps/builder/src/features/blocks/bubbles/embed/components/EmbedBubbleContent.tsx @@ -1,14 +1,29 @@ import { useTranslate } from '@tolgee/react' -import { Text } from '@chakra-ui/react' +import { Stack, Text } from '@chakra-ui/react' import { EmbedBubbleBlock } from '@typebot.io/schemas' +import { SetVariableLabel } from '@/components/SetVariableLabel' +import { useTypebot } from '@/features/editor/providers/TypebotProvider' type Props = { block: EmbedBubbleBlock } export const EmbedBubbleContent = ({ block }: Props) => { + const { typebot } = useTypebot() const { t } = useTranslate() if (!block.content?.url) return {t('clickToEdit')} - return {t('editor.blocks.bubbles.embed.node.show.text')} + return ( + + {t('editor.blocks.bubbles.embed.node.show.text')} + {typebot && + block.content.waitForEvent?.isEnabled && + block.content.waitForEvent.saveDataInVariableId && ( + + )} + + ) } diff --git a/apps/builder/src/features/blocks/bubbles/embed/components/EmbedUploadContent.tsx b/apps/builder/src/features/blocks/bubbles/embed/components/EmbedUploadContent.tsx index 7bd29fd9e..b2d9da477 100644 --- a/apps/builder/src/features/blocks/bubbles/embed/components/EmbedUploadContent.tsx +++ b/apps/builder/src/features/blocks/bubbles/embed/components/EmbedUploadContent.tsx @@ -1,9 +1,11 @@ import { TextInput, NumberInput } from '@/components/inputs' import { Stack, Text } from '@chakra-ui/react' -import { EmbedBubbleBlock } from '@typebot.io/schemas' +import { EmbedBubbleBlock, Variable } from '@typebot.io/schemas' import { sanitizeUrl } from '@typebot.io/lib' import { useTranslate } from '@tolgee/react' import { defaultEmbedBubbleContent } from '@typebot.io/schemas/features/blocks/bubbles/embed/constants' +import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings' +import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' type Props = { content: EmbedBubbleBlock['content'] @@ -23,6 +25,24 @@ export const EmbedUploadContent = ({ content, onSubmit }: Props) => { height?: NonNullable['height'] ) => height && onSubmit({ ...content, height }) + const updateWaitEventName = (name: string) => + onSubmit({ ...content, waitForEvent: { ...content?.waitForEvent, name } }) + + const updateWaitForEventEnabled = (isEnabled: boolean) => + onSubmit({ + ...content, + waitForEvent: { ...content?.waitForEvent, isEnabled }, + }) + + const updateSaveDataInVariableId = (variable?: Pick) => + onSubmit({ + ...content, + waitForEvent: { + ...content?.waitForEvent, + saveDataInVariableId: variable?.id, + }, + }) + return ( @@ -43,8 +63,25 @@ export const EmbedUploadContent = ({ content, onSubmit }: Props) => { defaultValue={content?.height ?? defaultEmbedBubbleContent.height} onValueChange={handleHeightChange} suffix={t('editor.blocks.bubbles.embed.settings.numberInput.unit')} - width="150px" + direction="row" /> + + + + ) } diff --git a/apps/docs/editor/blocks/bubbles/embed.mdx b/apps/docs/editor/blocks/bubbles/embed.mdx index 2be3668fe..26c9bab42 100644 --- a/apps/docs/editor/blocks/bubbles/embed.mdx +++ b/apps/docs/editor/blocks/bubbles/embed.mdx @@ -35,3 +35,24 @@ The Embed bubble block allows you to display a website or an iframe to your user For this, you'll need to select the pdf file you want to embed. Right click > Preview > More actions > Open in a new window. Now click More actions > Embed item. Copy the embed code and paste it in the Embed bubble block configuration. + +## Wait for event + +Enable this if you are the owner of the website you want to embed and would like to continue the bot flow only when an event from the embed is sent to the bot. This event dispatch needs to be executed in the embed website. Here is an example: + +```js +window.parent.postMessage( + { name: 'My event', data: 'Custom data passed to the typebot variable' }, + '*' +) +``` + +You can choose the name of the event, it needs to match what you've set in the Embed bubble block configuration. + + + Embed bubble + diff --git a/apps/docs/images/blocks/bubbles/embed-wait.jpg b/apps/docs/images/blocks/bubbles/embed-wait.jpg new file mode 100644 index 000000000..bc81f5046 Binary files /dev/null and b/apps/docs/images/blocks/bubbles/embed-wait.jpg differ diff --git a/apps/viewer/src/features/chat/api/legacy/sendMessageV1.ts b/apps/viewer/src/features/chat/api/legacy/sendMessageV1.ts index 34af6f4f0..b7fe7895a 100644 --- a/apps/viewer/src/features/chat/api/legacy/sendMessageV1.ts +++ b/apps/viewer/src/features/chat/api/legacy/sendMessageV1.ts @@ -11,6 +11,7 @@ import { restartSession } from '@typebot.io/bot-engine/queries/restartSession' import { continueBotFlow } from '@typebot.io/bot-engine/continueBotFlow' import { parseDynamicTheme } from '@typebot.io/bot-engine/parseDynamicTheme' import { isDefined } from '@typebot.io/lib/utils' +import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants' export const sendMessageV1 = publicProcedure .meta({ @@ -136,8 +137,11 @@ export const sendMessageV1 = publicProcedure logs: allLogs, clientSideActions, visitedEdges, - hasCustomEmbedBubble: messages.some( - (message) => message.type === 'custom-embed' + hasEmbedBubbleWithWaitEvent: messages.some( + (message) => + message.type === 'custom-embed' || + (message.type === BubbleBlockType.EMBED && + message.content.waitForEvent?.isEnabled) ), setVariableHistory, }) @@ -199,8 +203,11 @@ export const sendMessageV1 = publicProcedure logs: allLogs, clientSideActions, visitedEdges, - hasCustomEmbedBubble: messages.some( - (message) => message.type === 'custom-embed' + hasEmbedBubbleWithWaitEvent: messages.some( + (message) => + message.type === 'custom-embed' || + (message.type === BubbleBlockType.EMBED && + message.content.waitForEvent?.isEnabled) ), setVariableHistory, }) diff --git a/apps/viewer/src/features/chat/api/legacy/sendMessageV2.ts b/apps/viewer/src/features/chat/api/legacy/sendMessageV2.ts index ddf4c7a79..982890630 100644 --- a/apps/viewer/src/features/chat/api/legacy/sendMessageV2.ts +++ b/apps/viewer/src/features/chat/api/legacy/sendMessageV2.ts @@ -11,6 +11,7 @@ import { chatReplySchema, sendMessageInputSchema, } from '@typebot.io/schemas/features/chat/legacy/schema' +import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants' export const sendMessageV2 = publicProcedure .meta({ @@ -136,8 +137,11 @@ export const sendMessageV2 = publicProcedure logs: allLogs, clientSideActions, visitedEdges, - hasCustomEmbedBubble: messages.some( - (message) => message.type === 'custom-embed' + hasEmbedBubbleWithWaitEvent: messages.some( + (message) => + message.type === 'custom-embed' || + (message.type === BubbleBlockType.EMBED && + message.content.waitForEvent?.isEnabled) ), setVariableHistory, }) @@ -198,8 +202,11 @@ export const sendMessageV2 = publicProcedure logs: allLogs, clientSideActions, visitedEdges, - hasCustomEmbedBubble: messages.some( - (message) => message.type === 'custom-embed' + hasEmbedBubbleWithWaitEvent: messages.some( + (message) => + message.type === 'custom-embed' || + (message.type === BubbleBlockType.EMBED && + message.content.waitForEvent?.isEnabled) ), setVariableHistory, }) diff --git a/packages/bot-engine/apiHandlers/continueChat.ts b/packages/bot-engine/apiHandlers/continueChat.ts index 6d3f1d5ce..0ae019c7b 100644 --- a/packages/bot-engine/apiHandlers/continueChat.ts +++ b/packages/bot-engine/apiHandlers/continueChat.ts @@ -1,11 +1,12 @@ import { TRPCError } from '@trpc/server' -import { isDefined, isNotDefined } from '@typebot.io/lib/utils' +import { isDefined, isNotDefined, isNotEmpty } from '@typebot.io/lib/utils' import { getSession } from '../queries/getSession' import { continueBotFlow } from '../continueBotFlow' import { filterPotentiallySensitiveLogs } from '../logs/filterPotentiallySensitiveLogs' import { parseDynamicTheme } from '../parseDynamicTheme' import { saveStateToDatabase } from '../saveStateToDatabase' import { computeCurrentProgress } from '../computeCurrentProgress' +import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants' type Props = { origin: string | undefined @@ -77,8 +78,11 @@ export const continueChat = async ({ clientSideActions, visitedEdges, setVariableHistory, - hasCustomEmbedBubble: messages.some( - (message) => message.type === 'custom-embed' + hasEmbedBubbleWithWaitEvent: messages.some( + (message) => + message.type === 'custom-embed' || + (message.type === BubbleBlockType.EMBED && + message.content.waitForEvent?.isEnabled) ), }) diff --git a/packages/bot-engine/apiHandlers/startChat.ts b/packages/bot-engine/apiHandlers/startChat.ts index 5776f417e..b351aabd7 100644 --- a/packages/bot-engine/apiHandlers/startChat.ts +++ b/packages/bot-engine/apiHandlers/startChat.ts @@ -1,8 +1,10 @@ +import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants' import { computeCurrentProgress } from '../computeCurrentProgress' import { filterPotentiallySensitiveLogs } from '../logs/filterPotentiallySensitiveLogs' import { restartSession } from '../queries/restartSession' import { saveStateToDatabase } from '../saveStateToDatabase' import { startSession } from '../startSession' +import { isNotEmpty } from '@typebot.io/lib' type Props = { origin: string | undefined @@ -74,8 +76,11 @@ export const startChat = async ({ clientSideActions, visitedEdges, setVariableHistory, - hasCustomEmbedBubble: messages.some( - (message) => message.type === 'custom-embed' + hasEmbedBubbleWithWaitEvent: messages.some( + (message) => + message.type === 'custom-embed' || + (message.type === BubbleBlockType.EMBED && + message.content.waitForEvent?.isEnabled) ), }) diff --git a/packages/bot-engine/apiHandlers/startChatPreview.ts b/packages/bot-engine/apiHandlers/startChatPreview.ts index e2f488ee6..542bd1415 100644 --- a/packages/bot-engine/apiHandlers/startChatPreview.ts +++ b/packages/bot-engine/apiHandlers/startChatPreview.ts @@ -3,6 +3,7 @@ import { restartSession } from '../queries/restartSession' import { saveStateToDatabase } from '../saveStateToDatabase' import { startSession } from '../startSession' import { computeCurrentProgress } from '../computeCurrentProgress' +import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants' type Props = { message?: string @@ -69,8 +70,11 @@ export const startChatPreview = async ({ clientSideActions, visitedEdges, setVariableHistory, - hasCustomEmbedBubble: messages.some( - (message) => message.type === 'custom-embed' + hasEmbedBubbleWithWaitEvent: messages.some( + (message) => + message.type === 'custom-embed' || + (message.type === BubbleBlockType.EMBED && + message.content.waitForEvent?.isEnabled) ), initialSessionId: sessionId, }) diff --git a/packages/bot-engine/continueBotFlow.ts b/packages/bot-engine/continueBotFlow.ts index 4d4742b99..f26181fe4 100644 --- a/packages/bot-engine/continueBotFlow.ts +++ b/packages/bot-engine/continueBotFlow.ts @@ -145,6 +145,13 @@ export const continueBotFlow = async ( } } } + } else if ( + block.type === BubbleBlockType.EMBED && + block.content?.waitForEvent?.saveDataInVariableId + ) { + variableToUpdate = state.typebotsQueue[0].typebot.variables.find( + (v) => v.id === block.content?.waitForEvent?.saveDataInVariableId + ) } if (variableToUpdate) { diff --git a/packages/bot-engine/executeGroup.ts b/packages/bot-engine/executeGroup.ts index 1861803ba..2cd9349f4 100644 --- a/packages/bot-engine/executeGroup.ts +++ b/packages/bot-engine/executeGroup.ts @@ -6,7 +6,7 @@ import { SessionState, SetVariableHistoryItem, } from '@typebot.io/schemas' -import { isNotEmpty } from '@typebot.io/lib' +import { isEmpty, isNotEmpty } from '@typebot.io/lib' import { isBubbleBlock, isInputBlock, @@ -32,6 +32,7 @@ import { BubbleBlockWithDefinedContent, parseBubbleBlock, } from './parseBubbleBlock' +import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants' type ContextProps = { version: 1 | 2 @@ -95,14 +96,30 @@ export const executeGroup = async ( if (isBubbleBlock(block)) { if (!block.content || (firstBubbleWasStreamed && index === 0)) continue - messages.push( - parseBubbleBlock(block as BubbleBlockWithDefinedContent, { - version, - variables: newSessionState.typebotsQueue[0].typebot.variables, - typebotVersion: newSessionState.typebotsQueue[0].typebot.version, - textBubbleContentFormat, - }) - ) + const message = parseBubbleBlock(block as BubbleBlockWithDefinedContent, { + version, + variables: newSessionState.typebotsQueue[0].typebot.variables, + typebotVersion: newSessionState.typebotsQueue[0].typebot.version, + textBubbleContentFormat, + }) + messages.push(message) + if ( + message.type === BubbleBlockType.EMBED && + message.content.waitForEvent?.isEnabled + ) { + return { + messages, + newSessionState: { + ...newSessionState, + currentBlockId: block.id, + }, + clientSideActions, + logs, + visitedEdges, + setVariableHistory, + } + } + lastBubbleBlockId = block.id continue } diff --git a/packages/bot-engine/saveStateToDatabase.ts b/packages/bot-engine/saveStateToDatabase.ts index 0241deddc..4231ee98d 100644 --- a/packages/bot-engine/saveStateToDatabase.ts +++ b/packages/bot-engine/saveStateToDatabase.ts @@ -17,7 +17,7 @@ type Props = { clientSideActions: ContinueChatResponse['clientSideActions'] visitedEdges: VisitedEdge[] setVariableHistory: SetVariableHistoryItem[] - hasCustomEmbedBubble?: boolean + hasEmbedBubbleWithWaitEvent?: boolean initialSessionId?: string } @@ -28,7 +28,7 @@ export const saveStateToDatabase = async ({ clientSideActions, visitedEdges, setVariableHistory, - hasCustomEmbedBubble, + hasEmbedBubbleWithWaitEvent, initialSessionId, }: Props) => { const containsSetVariableClientSideAction = clientSideActions?.some( @@ -36,7 +36,9 @@ export const saveStateToDatabase = async ({ ) const isCompleted = Boolean( - !input && !containsSetVariableClientSideAction && !hasCustomEmbedBubble + !input && + !containsSetVariableClientSideAction && + !hasEmbedBubbleWithWaitEvent ) const queries: Prisma.PrismaPromise[] = [] diff --git a/packages/embeds/js/package.json b/packages/embeds/js/package.json index e7468c3fa..fff491368 100644 --- a/packages/embeds/js/package.json +++ b/packages/embeds/js/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/js", - "version": "0.2.88", + "version": "0.2.89", "description": "Javascript library to display typebots on your website", "type": "module", "main": "dist/index.js", diff --git a/packages/embeds/js/src/components/bubbles/HostBubble.tsx b/packages/embeds/js/src/components/bubbles/HostBubble.tsx index 99056677d..6c883843d 100644 --- a/packages/embeds/js/src/components/bubbles/HostBubble.tsx +++ b/packages/embeds/js/src/components/bubbles/HostBubble.tsx @@ -51,6 +51,7 @@ export const HostBubble = (props: Props) => ( diff --git a/packages/embeds/js/src/features/blocks/bubbles/embed/components/EmbedBubble.tsx b/packages/embeds/js/src/features/blocks/bubbles/embed/components/EmbedBubble.tsx index 861d7968d..84a241bae 100644 --- a/packages/embeds/js/src/features/blocks/bubbles/embed/components/EmbedBubble.tsx +++ b/packages/embeds/js/src/features/blocks/bubbles/embed/components/EmbedBubble.tsx @@ -4,10 +4,12 @@ import { createSignal, onCleanup, onMount } from 'solid-js' import { clsx } from 'clsx' import { EmbedBubbleBlock } from '@typebot.io/schemas' import { defaultEmbedBubbleContent } from '@typebot.io/schemas/features/blocks/bubbles/embed/constants' +import { isNotEmpty } from '@typebot.io/lib/utils' type Props = { content: EmbedBubbleBlock['content'] onTransitionEnd?: (ref?: HTMLDivElement) => void + onCompleted?: (data?: string) => void } let typingTimeout: NodeJS.Timeout @@ -20,9 +22,29 @@ export const EmbedBubble = (props: Props) => { props.onTransitionEnd ? true : false ) + const handleMessage = ( + event: MessageEvent<{ name?: string; data?: string }> + ) => { + if ( + props.content?.waitForEvent?.isEnabled && + isNotEmpty(event.data.name) && + event.data.name === props.content?.waitForEvent.name + ) { + props.onCompleted?.( + props.content.waitForEvent.saveDataInVariableId && event.data.data + ? event.data.data + : undefined + ) + window.removeEventListener('message', handleMessage) + } + } + onMount(() => { typingTimeout = setTimeout(() => { setIsTyping(false) + if (props.content?.waitForEvent?.isEnabled) { + window.addEventListener('message', handleMessage) + } setTimeout(() => { props.onTransitionEnd?.(ref) }, showAnimationDuration) @@ -31,6 +53,7 @@ export const EmbedBubble = (props: Props) => { onCleanup(() => { if (typingTimeout) clearTimeout(typingTimeout) + window.removeEventListener('message', handleMessage) }) return ( diff --git a/packages/embeds/nextjs/package.json b/packages/embeds/nextjs/package.json index 935d2f802..226d259c8 100644 --- a/packages/embeds/nextjs/package.json +++ b/packages/embeds/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/nextjs", - "version": "0.2.88", + "version": "0.2.89", "description": "Convenient library to display typebots on your Next.js website", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/embeds/react/package.json b/packages/embeds/react/package.json index f2d3e9f4d..77350456d 100644 --- a/packages/embeds/react/package.json +++ b/packages/embeds/react/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/react", - "version": "0.2.88", + "version": "0.2.89", "description": "Convenient library to display typebots on your React app", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/schemas/features/blocks/bubbles/embed/schema.ts b/packages/schemas/features/blocks/bubbles/embed/schema.ts index a58d92f2b..834704535 100644 --- a/packages/schemas/features/blocks/bubbles/embed/schema.ts +++ b/packages/schemas/features/blocks/bubbles/embed/schema.ts @@ -6,6 +6,13 @@ import { BubbleBlockType } from '../constants' export const embedBubbleContentSchema = z.object({ url: z.string().optional(), height: z.number().or(variableStringSchema).optional(), + waitForEvent: z + .object({ + isEnabled: z.boolean().optional(), + name: z.string().optional(), + saveDataInVariableId: z.string().optional(), + }) + .optional(), }) export const embedBubbleBlockSchema = blockBaseSchema.merge( diff --git a/packages/schemas/features/chat/schema.ts b/packages/schemas/features/chat/schema.ts index 828fb3299..5000698ab 100644 --- a/packages/schemas/features/chat/schema.ts +++ b/packages/schemas/features/chat/schema.ts @@ -16,7 +16,6 @@ import { import { logSchema } from '../result' import { settingsSchema, themeSchema } from '../typebot' import { - textBubbleContentSchema, imageBubbleContentSchema, videoBubbleContentSchema, audioBubbleContentSchema,