diff --git a/apps/builder/src/features/graph/components/Nodes/BlockNode/BlockNode.tsx b/apps/builder/src/features/graph/components/Nodes/BlockNode/BlockNode.tsx
index 4d237c19c..424dbd20e 100644
--- a/apps/builder/src/features/graph/components/Nodes/BlockNode/BlockNode.tsx
+++ b/apps/builder/src/features/graph/components/Nodes/BlockNode/BlockNode.tsx
@@ -63,6 +63,7 @@ export const BlockNode = ({
setOpenedBlockId,
setFocusedGroupId,
previewingEdge,
+ isReadOnly,
} = useGraph()
const { mouseOverBlock, setMouseOverBlock } = useBlockDnd()
const { typebot, updateBlock } = useTypebot()
@@ -111,6 +112,7 @@ export const BlockNode = ({
}
const handleMouseEnter = () => {
+ if (isReadOnly) return
if (mouseOverBlock?.id !== block.id)
setMouseOverBlock({ id: block.id, ref: blockRef })
if (connectingIds)
diff --git a/packages/bot-engine/src/components/ChatGroup/ChatBlock/bubbles/HostBubble.tsx b/packages/bot-engine/src/components/ChatGroup/ChatBlock/bubbles/HostBubble.tsx
index 5b06ad033..6cb82296e 100644
--- a/packages/bot-engine/src/components/ChatGroup/ChatBlock/bubbles/HostBubble.tsx
+++ b/packages/bot-engine/src/components/ChatGroup/ChatBlock/bubbles/HostBubble.tsx
@@ -4,7 +4,6 @@ import { ImageBubble } from '@/features/blocks/bubbles/image'
import { TextBubble } from '@/features/blocks/bubbles/textBubble'
import { VideoBubble } from '@/features/blocks/bubbles/video'
import { BubbleBlock, BubbleBlockType } from 'models'
-import React from 'react'
type Props = {
block: BubbleBlock
diff --git a/packages/bot-engine/src/components/ChatGroup/ChatGroup.tsx b/packages/bot-engine/src/components/ChatGroup/ChatGroup.tsx
index 92069c9e7..a0b18e987 100644
--- a/packages/bot-engine/src/components/ChatGroup/ChatGroup.tsx
+++ b/packages/bot-engine/src/components/ChatGroup/ChatGroup.tsx
@@ -307,7 +307,7 @@ const ChatChunks = ({
unmountOnExit
in={isDefined(input)}
>
- {input && (
+ {input ? (
+ ) : (
+
diff --git a/packages/bot-engine/src/features/blocks/bubbles/image/components/ImageBubble.tsx b/packages/bot-engine/src/features/blocks/bubbles/image/components/ImageBubble.tsx
index 12b59fe33..8f63ea3ac 100644
--- a/packages/bot-engine/src/features/blocks/bubbles/image/components/ImageBubble.tsx
+++ b/packages/bot-engine/src/features/blocks/bubbles/image/components/ImageBubble.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useMemo, useRef, useState } from 'react'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTypebot } from '@/providers/TypebotProvider'
import { ImageBubbleBlock } from 'models'
import { TypingBubble } from '@/components/TypingBubble'
@@ -24,31 +24,37 @@ export const ImageBubble = ({ block, onTransitionEnd }: Props) => {
[block.content?.url, typebot.variables]
)
- useEffect(() => {
- if (!isTyping || isLoading) return
- showContentAfterMediaLoad()
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isLoading])
-
- const showContentAfterMediaLoad = () => {
- if (!image.current) return
- const timeout = setTimeout(() => {
- setIsTyping(false)
- onTypingEnd()
- }, mediaLoadingFallbackTimeout)
- image.current.onload = () => {
- clearTimeout(timeout)
- setIsTyping(false)
- onTypingEnd()
- }
- }
-
- const onTypingEnd = () => {
+ const onTypingEnd = useCallback(() => {
setIsTyping(false)
setTimeout(() => {
onTransitionEnd()
}, showAnimationDuration)
- }
+ }, [onTransitionEnd])
+
+ useEffect(() => {
+ if (!isTyping || isLoading) return
+
+ const timeout = setTimeout(() => {
+ setIsTyping(false)
+ onTypingEnd()
+ }, mediaLoadingFallbackTimeout)
+
+ return () => {
+ clearTimeout(timeout)
+ }
+ }, [isLoading, isTyping, onTypingEnd])
+
+ useEffect(() => {
+ const currentImage = image.current
+ if (!currentImage || isLoading || !isTyping) return
+ currentImage.onload = () => {
+ setIsTyping(false)
+ onTypingEnd()
+ }
+ return () => {
+ currentImage.onload = null
+ }
+ }, [isLoading, isTyping, onTypingEnd])
return (
diff --git a/packages/bot-engine/src/features/blocks/bubbles/textBubble/components/TextBubble.tsx b/packages/bot-engine/src/features/blocks/bubbles/textBubble/components/TextBubble.tsx
index 2d8d01973..77e0403c6 100644
--- a/packages/bot-engine/src/features/blocks/bubbles/textBubble/components/TextBubble.tsx
+++ b/packages/bot-engine/src/features/blocks/bubbles/textBubble/components/TextBubble.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useRef, useState } from 'react'
+import { useCallback, useEffect, useRef, useState } from 'react'
import { useTypebot } from '@/providers/TypebotProvider'
import { BubbleBlockType, TextBubbleBlock } from 'models'
import { computeTypingDuration } from '../utils/computeTypingDuration'
@@ -27,24 +27,34 @@ export const TextBubble = ({ block, onTransitionEnd }: Props) => {
parseVariables(typebot.variables)(block.content.html)
)
- useEffect(() => {
- if (!isTyping || isLoading) return
- const typingTimeout = computeTypingDuration(
- block.content.plainText,
- typebot.settings?.typingEmulation ?? defaultTypingEmulation
- )
- setTimeout(() => {
- onTypingEnd()
- }, typingTimeout)
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isLoading])
-
- const onTypingEnd = () => {
+ const onTypingEnd = useCallback(() => {
setIsTyping(false)
setTimeout(() => {
onTransitionEnd()
}, showAnimationDuration)
- }
+ }, [onTransitionEnd])
+
+ useEffect(() => {
+ if (!isTyping || isLoading) return
+
+ const typingTimeout = computeTypingDuration(
+ block.content.plainText,
+ typebot.settings?.typingEmulation ?? defaultTypingEmulation
+ )
+ const timeout = setTimeout(() => {
+ onTypingEnd()
+ }, typingTimeout)
+
+ return () => {
+ clearTimeout(timeout)
+ }
+ }, [
+ block.content.plainText,
+ isLoading,
+ isTyping,
+ onTypingEnd,
+ typebot.settings?.typingEmulation,
+ ])
return (
diff --git a/packages/bot-engine/src/features/blocks/bubbles/video/components/VideoBubble.tsx b/packages/bot-engine/src/features/blocks/bubbles/video/components/VideoBubble.tsx
index 46bbf1af7..a401cf224 100644
--- a/packages/bot-engine/src/features/blocks/bubbles/video/components/VideoBubble.tsx
+++ b/packages/bot-engine/src/features/blocks/bubbles/video/components/VideoBubble.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useMemo, useRef, useState } from 'react'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTypebot } from '@/providers/TypebotProvider'
import {
Variable,
@@ -23,25 +23,24 @@ export const VideoBubble = ({ block, onTransitionEnd }: Props) => {
const messageContainer = useRef
(null)
const [isTyping, setIsTyping] = useState(true)
- useEffect(() => {
- if (!isTyping || isLoading) return
- showContentAfterMediaLoad()
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isLoading])
-
- const showContentAfterMediaLoad = () => {
- setTimeout(() => {
- setIsTyping(false)
- onTypingEnd()
- }, 1000)
- }
-
- const onTypingEnd = () => {
+ const onTypingEnd = useCallback(() => {
setIsTyping(false)
setTimeout(() => {
onTransitionEnd()
}, showAnimationDuration)
- }
+ }, [onTransitionEnd])
+
+ useEffect(() => {
+ if (!isTyping || isLoading) return
+ const timeout = setTimeout(() => {
+ setIsTyping(false)
+ onTypingEnd()
+ }, 1000)
+
+ return () => {
+ clearTimeout(timeout)
+ }
+ }, [isLoading, isTyping, onTypingEnd])
return (