✨ (settings) Add delay between bubbles option and typing disabling on first message
This commit is contained in:
@ -84,7 +84,7 @@ export const SettingsSideMenu = () => {
|
|||||||
<AccordionButton py={6}>
|
<AccordionButton py={6}>
|
||||||
<HStack flex="1" pl={2}>
|
<HStack flex="1" pl={2}>
|
||||||
<ChatIcon />
|
<ChatIcon />
|
||||||
<Heading fontSize="lg">Typing emulation</Heading>
|
<Heading fontSize="lg">Typing</Heading>
|
||||||
</HStack>
|
</HStack>
|
||||||
<AccordionIcon />
|
<AccordionIcon />
|
||||||
</AccordionButton>
|
</AccordionButton>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { Stack } from '@chakra-ui/react'
|
import { HStack, Stack, Text } from '@chakra-ui/react'
|
||||||
import { Settings } from '@typebot.io/schemas'
|
import { Settings } from '@typebot.io/schemas'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { isDefined } from '@typebot.io/lib'
|
|
||||||
import { NumberInput } from '@/components/inputs'
|
import { NumberInput } from '@/components/inputs'
|
||||||
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
||||||
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
||||||
|
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
|
||||||
|
import { isDefined } from '@typebot.io/lib'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
typingEmulation: Settings['typingEmulation']
|
typingEmulation: Settings['typingEmulation']
|
||||||
@ -18,51 +19,92 @@ export const TypingEmulationForm = ({ typingEmulation, onUpdate }: Props) => {
|
|||||||
enabled,
|
enabled,
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleSpeedChange = (speed?: number) =>
|
const updateSpeed = (speed?: number) =>
|
||||||
isDefined(speed) && onUpdate({ ...typingEmulation, speed })
|
onUpdate({ ...typingEmulation, speed })
|
||||||
|
|
||||||
const handleMaxDelayChange = (maxDelay?: number) =>
|
const updateMaxDelay = (maxDelay?: number) =>
|
||||||
isDefined(maxDelay) && onUpdate({ ...typingEmulation, maxDelay })
|
onUpdate({
|
||||||
|
...typingEmulation,
|
||||||
|
maxDelay: isDefined(maxDelay)
|
||||||
|
? Math.max(Math.min(maxDelay, 5), 0)
|
||||||
|
: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
const isEnabled =
|
const updateIsDisabledOnFirstMessage = (isDisabledOnFirstMessage: boolean) =>
|
||||||
typingEmulation?.enabled ?? defaultSettings.typingEmulation.enabled
|
onUpdate({
|
||||||
|
...typingEmulation,
|
||||||
|
isDisabledOnFirstMessage,
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateDelayBetweenBubbles = (delayBetweenBubbles?: number) =>
|
||||||
|
onUpdate({ ...typingEmulation, delayBetweenBubbles })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={6}>
|
<Stack spacing={6}>
|
||||||
<SwitchWithLabel
|
<SwitchWithRelatedSettings
|
||||||
label={'Typing emulation'}
|
label={'Typing emulation'}
|
||||||
initialValue={isEnabled}
|
initialValue={
|
||||||
|
typingEmulation?.enabled ?? defaultSettings.typingEmulation.enabled
|
||||||
|
}
|
||||||
onCheckChange={updateIsEnabled}
|
onCheckChange={updateIsEnabled}
|
||||||
/>
|
>
|
||||||
{isEnabled && (
|
<NumberInput
|
||||||
<Stack pl={10}>
|
label="Words per minutes:"
|
||||||
|
data-testid="speed"
|
||||||
|
defaultValue={
|
||||||
|
typingEmulation?.speed ?? defaultSettings.typingEmulation.speed
|
||||||
|
}
|
||||||
|
onValueChange={updateSpeed}
|
||||||
|
withVariableButton={false}
|
||||||
|
maxW="100px"
|
||||||
|
step={30}
|
||||||
|
direction="row"
|
||||||
|
/>
|
||||||
|
<HStack>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label="Words per minutes:"
|
label="Max delay:"
|
||||||
data-testid="speed"
|
|
||||||
defaultValue={
|
|
||||||
typingEmulation?.speed ?? defaultSettings.typingEmulation.speed
|
|
||||||
}
|
|
||||||
onValueChange={handleSpeedChange}
|
|
||||||
withVariableButton={false}
|
|
||||||
maxW="100px"
|
|
||||||
step={30}
|
|
||||||
direction="row"
|
|
||||||
/>
|
|
||||||
<NumberInput
|
|
||||||
label="Max delay (in seconds):"
|
|
||||||
data-testid="max-delay"
|
data-testid="max-delay"
|
||||||
defaultValue={
|
defaultValue={
|
||||||
typingEmulation?.maxDelay ??
|
typingEmulation?.maxDelay ??
|
||||||
defaultSettings.typingEmulation.maxDelay
|
defaultSettings.typingEmulation.maxDelay
|
||||||
}
|
}
|
||||||
onValueChange={handleMaxDelayChange}
|
onValueChange={updateMaxDelay}
|
||||||
withVariableButton={false}
|
withVariableButton={false}
|
||||||
maxW="100px"
|
maxW="100px"
|
||||||
step={0.1}
|
step={0.1}
|
||||||
direction="row"
|
direction="row"
|
||||||
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
<Text>seconds</Text>
|
||||||
)}
|
</HStack>
|
||||||
|
|
||||||
|
<SwitchWithLabel
|
||||||
|
label={'Disable on first message'}
|
||||||
|
moreInfoContent="When checked, typing emulation will be disabled for the first message sent by the bot."
|
||||||
|
onCheckChange={updateIsDisabledOnFirstMessage}
|
||||||
|
initialValue={
|
||||||
|
typingEmulation?.isDisabledOnFirstMessage ??
|
||||||
|
defaultSettings.typingEmulation.isDisabledOnFirstMessage
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SwitchWithRelatedSettings>
|
||||||
|
<HStack>
|
||||||
|
<NumberInput
|
||||||
|
label="Delay between messages:"
|
||||||
|
defaultValue={
|
||||||
|
typingEmulation?.delayBetweenBubbles ??
|
||||||
|
defaultSettings.typingEmulation.delayBetweenBubbles
|
||||||
|
}
|
||||||
|
withVariableButton={false}
|
||||||
|
onValueChange={updateDelayBetweenBubbles}
|
||||||
|
direction="row"
|
||||||
|
maxW={'100px'}
|
||||||
|
min={0}
|
||||||
|
max={5}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
<Text>seconds</Text>
|
||||||
|
</HStack>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -128,6 +128,7 @@ export const startWhatsAppPreview = authenticatedProcedure
|
|||||||
messages,
|
messages,
|
||||||
input,
|
input,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
|
isFirstChatChunk: true,
|
||||||
credentials: {
|
credentials: {
|
||||||
phoneNumberId: env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID,
|
phoneNumberId: env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID,
|
||||||
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
|
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
|
||||||
|
@ -27386,6 +27386,14 @@
|
|||||||
},
|
},
|
||||||
"maxDelay": {
|
"maxDelay": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
},
|
||||||
|
"delayBetweenBubbles": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 5
|
||||||
|
},
|
||||||
|
"isDisabledOnFirstMessage": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -7623,6 +7623,14 @@
|
|||||||
},
|
},
|
||||||
"maxDelay": {
|
"maxDelay": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
},
|
||||||
|
"delayBetweenBubbles": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 5
|
||||||
|
},
|
||||||
|
"isDisabledOnFirstMessage": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -28,6 +28,9 @@ You can customize this typing speed in the settings:
|
|||||||
|
|
||||||
The goal of a typebot is not to pretend that the bot is a real human. So we suggest not setting the typing speed too low.
|
The goal of a typebot is not to pretend that the bot is a real human. So we suggest not setting the typing speed too low.
|
||||||
|
|
||||||
|
The `Disable on first message` allows you to disable the typing emulation on the first message. This is useful if you want to lower the first message display time since the site can take some time to load first.
|
||||||
|
|
||||||
|
The `Delay between messages` by default is 0 and you can increase it up to 5 seconds if you want to add a delay between **every** messages sent by the typebot.
|
||||||
Sometimes you want to pause the bot for a few seconds between one message and another, regardless of the typing speed. You can achieve this by adding a Code block with the following content:
|
Sometimes you want to pause the bot for a few seconds between one message and another, regardless of the typing speed. You can achieve this by adding a Code block with the following content:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
@ -93,10 +93,12 @@ export const resumeWhatsAppFlow = async ({
|
|||||||
visitedEdges,
|
visitedEdges,
|
||||||
} = resumeResponse
|
} = resumeResponse
|
||||||
|
|
||||||
|
const isFirstChatChunk = (!session || isSessionExpired) ?? false
|
||||||
await sendChatReplyToWhatsApp({
|
await sendChatReplyToWhatsApp({
|
||||||
to: receivedMessage.from,
|
to: receivedMessage.from,
|
||||||
messages,
|
messages,
|
||||||
input,
|
input,
|
||||||
|
isFirstChatChunk,
|
||||||
typingEmulation: newSessionState.typingEmulation,
|
typingEmulation: newSessionState.typingEmulation,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
credentials,
|
credentials,
|
||||||
|
@ -16,12 +16,14 @@ import { isNotDefined } from '@typebot.io/lib/utils'
|
|||||||
import { computeTypingDuration } from '../computeTypingDuration'
|
import { computeTypingDuration } from '../computeTypingDuration'
|
||||||
import { continueBotFlow } from '../continueBotFlow'
|
import { continueBotFlow } from '../continueBotFlow'
|
||||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||||
|
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
||||||
|
|
||||||
// Media can take some time to be delivered. This make sure we don't send a message before the media is delivered.
|
// Media can take some time to be delivered. This make sure we don't send a message before the media is delivered.
|
||||||
const messageAfterMediaTimeout = 5000
|
const messageAfterMediaTimeout = 5000
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
to: string
|
to: string
|
||||||
|
isFirstChatChunk: boolean
|
||||||
typingEmulation: SessionState['typingEmulation']
|
typingEmulation: SessionState['typingEmulation']
|
||||||
credentials: WhatsAppCredentials['data']
|
credentials: WhatsAppCredentials['data']
|
||||||
state: SessionState
|
state: SessionState
|
||||||
@ -30,6 +32,7 @@ type Props = {
|
|||||||
export const sendChatReplyToWhatsApp = async ({
|
export const sendChatReplyToWhatsApp = async ({
|
||||||
to,
|
to,
|
||||||
typingEmulation,
|
typingEmulation,
|
||||||
|
isFirstChatChunk,
|
||||||
messages,
|
messages,
|
||||||
input,
|
input,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
@ -57,6 +60,7 @@ export const sendChatReplyToWhatsApp = async ({
|
|||||||
to,
|
to,
|
||||||
messages,
|
messages,
|
||||||
input,
|
input,
|
||||||
|
isFirstChatChunk: false,
|
||||||
typingEmulation: newSessionState.typingEmulation,
|
typingEmulation: newSessionState.typingEmulation,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
credentials,
|
credentials,
|
||||||
@ -64,19 +68,40 @@ export const sendChatReplyToWhatsApp = async ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let i = -1
|
||||||
for (const message of messagesBeforeInput) {
|
for (const message of messagesBeforeInput) {
|
||||||
|
i += 1
|
||||||
|
if (
|
||||||
|
i > 0 &&
|
||||||
|
(typingEmulation?.delayBetweenBubbles ??
|
||||||
|
defaultSettings.typingEmulation.delayBetweenBubbles) > 0
|
||||||
|
) {
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
setTimeout(
|
||||||
|
resolve,
|
||||||
|
(typingEmulation?.delayBetweenBubbles ??
|
||||||
|
defaultSettings.typingEmulation.delayBetweenBubbles) * 1000
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
const whatsAppMessage = convertMessageToWhatsAppMessage(message)
|
const whatsAppMessage = convertMessageToWhatsAppMessage(message)
|
||||||
if (isNotDefined(whatsAppMessage)) continue
|
if (isNotDefined(whatsAppMessage)) continue
|
||||||
const lastSentMessageIsMedia = ['audio', 'video', 'image'].includes(
|
const lastSentMessageIsMedia = ['audio', 'video', 'image'].includes(
|
||||||
sentMessages.at(-1)?.type ?? ''
|
sentMessages.at(-1)?.type ?? ''
|
||||||
)
|
)
|
||||||
|
|
||||||
const typingDuration = lastSentMessageIsMedia
|
const typingDuration = lastSentMessageIsMedia
|
||||||
? messageAfterMediaTimeout
|
? messageAfterMediaTimeout
|
||||||
|
: isFirstChatChunk &&
|
||||||
|
i === 0 &&
|
||||||
|
(typingEmulation?.isDisabledOnFirstMessage ??
|
||||||
|
defaultSettings.typingEmulation.isDisabledOnFirstMessage)
|
||||||
|
? 0
|
||||||
: getTypingDuration({
|
: getTypingDuration({
|
||||||
message: whatsAppMessage,
|
message: whatsAppMessage,
|
||||||
typingEmulation,
|
typingEmulation,
|
||||||
})
|
})
|
||||||
if (typingDuration)
|
if ((typingDuration ?? 0) > 0)
|
||||||
await new Promise((resolve) => setTimeout(resolve, typingDuration))
|
await new Promise((resolve) => setTimeout(resolve, typingDuration))
|
||||||
try {
|
try {
|
||||||
await sendWhatsAppMessage({
|
await sendWhatsAppMessage({
|
||||||
@ -101,6 +126,7 @@ export const sendChatReplyToWhatsApp = async ({
|
|||||||
to,
|
to,
|
||||||
messages,
|
messages,
|
||||||
input,
|
input,
|
||||||
|
isFirstChatChunk: false,
|
||||||
typingEmulation: newSessionState.typingEmulation,
|
typingEmulation: newSessionState.typingEmulation,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
credentials,
|
credentials,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/js",
|
"name": "@typebot.io/js",
|
||||||
"version": "0.2.35",
|
"version": "0.2.36",
|
||||||
"description": "Javascript library to display typebots on your website",
|
"description": "Javascript library to display typebots on your website",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
@ -40,6 +40,20 @@ export const ChatChunk = (props: Props) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const displayNextMessage = async (bubbleOffsetTop?: number) => {
|
const displayNextMessage = async (bubbleOffsetTop?: number) => {
|
||||||
|
if (
|
||||||
|
(props.settings.typingEmulation?.delayBetweenBubbles ??
|
||||||
|
defaultSettings.typingEmulation.delayBetweenBubbles) > 0 &&
|
||||||
|
displayedMessageIndex() < props.messages.length - 1
|
||||||
|
) {
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
setTimeout(
|
||||||
|
resolve,
|
||||||
|
(props.settings.typingEmulation?.delayBetweenBubbles ??
|
||||||
|
defaultSettings.typingEmulation.delayBetweenBubbles) * 1000
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
const lastBubbleBlockId = props.messages[displayedMessageIndex()].id
|
const lastBubbleBlockId = props.messages[displayedMessageIndex()].id
|
||||||
await props.onNewBubbleDisplayed(lastBubbleBlockId)
|
await props.onNewBubbleDisplayed(lastBubbleBlockId)
|
||||||
setDisplayedMessageIndex(
|
setDisplayedMessageIndex(
|
||||||
@ -86,10 +100,17 @@ export const ChatChunk = (props: Props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<For each={props.messages.slice(0, displayedMessageIndex() + 1)}>
|
<For each={props.messages.slice(0, displayedMessageIndex() + 1)}>
|
||||||
{(message) => (
|
{(message, idx) => (
|
||||||
<HostBubble
|
<HostBubble
|
||||||
message={message}
|
message={message}
|
||||||
typingEmulation={props.settings.typingEmulation}
|
typingEmulation={props.settings.typingEmulation}
|
||||||
|
isTypingSkipped={
|
||||||
|
(props.settings.typingEmulation?.isDisabledOnFirstMessage ??
|
||||||
|
defaultSettings.typingEmulation
|
||||||
|
.isDisabledOnFirstMessage) &&
|
||||||
|
props.inputIndex === 0 &&
|
||||||
|
idx() === 0
|
||||||
|
}
|
||||||
onTransitionEnd={displayNextMessage}
|
onTransitionEnd={displayNextMessage}
|
||||||
onCompleted={props.onSubmit}
|
onCompleted={props.onSubmit}
|
||||||
/>
|
/>
|
||||||
|
@ -20,6 +20,7 @@ import { Match, Switch } from 'solid-js'
|
|||||||
type Props = {
|
type Props = {
|
||||||
message: ChatMessage
|
message: ChatMessage
|
||||||
typingEmulation: Settings['typingEmulation']
|
typingEmulation: Settings['typingEmulation']
|
||||||
|
isTypingSkipped: boolean
|
||||||
onTransitionEnd: (offsetTop?: number) => void
|
onTransitionEnd: (offsetTop?: number) => void
|
||||||
onCompleted: (reply?: string) => void
|
onCompleted: (reply?: string) => void
|
||||||
}
|
}
|
||||||
@ -38,6 +39,7 @@ export const HostBubble = (props: Props) => {
|
|||||||
<Match when={props.message.type === BubbleBlockType.TEXT}>
|
<Match when={props.message.type === BubbleBlockType.TEXT}>
|
||||||
<TextBubble
|
<TextBubble
|
||||||
content={props.message.content as TextBubbleBlock['content']}
|
content={props.message.content as TextBubbleBlock['content']}
|
||||||
|
isTypingSkipped={props.isTypingSkipped}
|
||||||
typingEmulation={props.typingEmulation}
|
typingEmulation={props.typingEmulation}
|
||||||
onTransitionEnd={onTransitionEnd}
|
onTransitionEnd={onTransitionEnd}
|
||||||
/>
|
/>
|
||||||
|
@ -10,6 +10,7 @@ import { computeTypingDuration } from '@typebot.io/bot-engine/computeTypingDurat
|
|||||||
type Props = {
|
type Props = {
|
||||||
content: TextBubbleBlock['content']
|
content: TextBubbleBlock['content']
|
||||||
typingEmulation: Settings['typingEmulation']
|
typingEmulation: Settings['typingEmulation']
|
||||||
|
isTypingSkipped: boolean
|
||||||
onTransitionEnd: (offsetTop?: number) => void
|
onTransitionEnd: (offsetTop?: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ export const TextBubble = (props: Props) => {
|
|||||||
? computePlainText(props.content.richText)
|
? computePlainText(props.content.richText)
|
||||||
: ''
|
: ''
|
||||||
const typingDuration =
|
const typingDuration =
|
||||||
props.typingEmulation?.enabled === false
|
props.typingEmulation?.enabled === false || props.isTypingSkipped
|
||||||
? 0
|
? 0
|
||||||
: computeTypingDuration({
|
: computeTypingDuration({
|
||||||
bubbleContent: plainText,
|
bubbleContent: plainText,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/nextjs",
|
"name": "@typebot.io/nextjs",
|
||||||
"version": "0.2.35",
|
"version": "0.2.36",
|
||||||
"description": "Convenient library to display typebots on your Next.js website",
|
"description": "Convenient library to display typebots on your Next.js website",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/react",
|
"name": "@typebot.io/react",
|
||||||
"version": "0.2.35",
|
"version": "0.2.36",
|
||||||
"description": "Convenient library to display typebots on your React app",
|
"description": "Convenient library to display typebots on your React app",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
@ -10,7 +10,13 @@ export const defaultSettings = {
|
|||||||
isBrandingEnabled: false,
|
isBrandingEnabled: false,
|
||||||
isTypingEmulationEnabled: true,
|
isTypingEmulationEnabled: true,
|
||||||
},
|
},
|
||||||
typingEmulation: { enabled: true, speed: 300, maxDelay: 1.5 },
|
typingEmulation: {
|
||||||
|
enabled: true,
|
||||||
|
speed: 400,
|
||||||
|
maxDelay: 3,
|
||||||
|
delayBetweenBubbles: 0,
|
||||||
|
isDisabledOnFirstMessage: true,
|
||||||
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
description:
|
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.',
|
'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.',
|
||||||
|
@ -20,6 +20,8 @@ const typingEmulation = z.object({
|
|||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
speed: z.number().optional(),
|
speed: z.number().optional(),
|
||||||
maxDelay: z.number().optional(),
|
maxDelay: z.number().optional(),
|
||||||
|
delayBetweenBubbles: z.number().min(0).max(5).optional(),
|
||||||
|
isDisabledOnFirstMessage: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const metadataSchema = z.object({
|
const metadataSchema = z.object({
|
||||||
|
Reference in New Issue
Block a user