2022-12-22 17:02:34 +01:00
|
|
|
import { LiteBadge } from './LiteBadge'
|
2023-01-16 12:13:21 +01:00
|
|
|
import { createEffect, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
2024-02-23 08:31:14 +01:00
|
|
|
import { isDefined, isNotDefined, isNotEmpty } from '@typebot.io/lib'
|
2023-11-13 15:27:36 +01:00
|
|
|
import { startChatQuery } from '@/queries/startChatQuery'
|
2022-12-22 17:02:34 +01:00
|
|
|
import { ConversationContainer } from './ConversationContainer'
|
|
|
|
|
import { setIsMobile } from '@/utils/isMobileSignal'
|
2023-02-21 08:28:36 +01:00
|
|
|
import { BotContext, InitialChatReply, OutgoingLog } from '@/types'
|
2023-01-16 12:13:21 +01:00
|
|
|
import { ErrorMessage } from './ErrorMessage'
|
|
|
|
|
import {
|
2023-05-16 14:58:56 +02:00
|
|
|
getExistingResultIdFromStorage,
|
2024-03-07 15:39:09 +01:00
|
|
|
getInitialChatReplyFromStorage,
|
|
|
|
|
setInitialChatReplyInStorage,
|
2023-05-16 14:58:56 +02:00
|
|
|
setResultInStorage,
|
2024-03-07 15:39:09 +01:00
|
|
|
wipeExistingChatStateInStorage,
|
2023-05-16 14:58:56 +02:00
|
|
|
} from '@/utils/storage'
|
2023-01-16 12:13:21 +01:00
|
|
|
import { setCssVariablesValue } from '@/utils/setCssVariablesValue'
|
2023-03-13 08:40:48 +01:00
|
|
|
import immutableCss from '../assets/immutable.css'
|
2024-03-18 16:09:19 +01:00
|
|
|
import { Font, InputBlock, StartFrom } from '@typebot.io/schemas'
|
2023-12-13 10:22:02 +01:00
|
|
|
import { clsx } from 'clsx'
|
2023-12-20 08:31:22 +01:00
|
|
|
import { HTTPError } from 'ky'
|
2024-02-20 10:36:57 +01:00
|
|
|
import { injectFont } from '@/utils/injectFont'
|
2024-02-23 08:31:14 +01:00
|
|
|
import { ProgressBar } from './ProgressBar'
|
2024-03-07 09:18:03 +01:00
|
|
|
import { Portal } from 'solid-js/web'
|
2024-03-07 15:39:09 +01:00
|
|
|
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
|
|
|
|
import { persist } from '@/utils/persist'
|
2024-03-15 11:38:40 +01:00
|
|
|
import { setBotContainerHeight } from '@/utils/botContainerHeightSignal'
|
2024-04-10 10:19:54 +02:00
|
|
|
import {
|
|
|
|
|
defaultFontFamily,
|
|
|
|
|
defaultFontType,
|
|
|
|
|
defaultProgressBarPosition,
|
|
|
|
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
2022-12-22 17:02:34 +01:00
|
|
|
|
2023-02-21 08:28:36 +01:00
|
|
|
export type BotProps = {
|
2023-02-23 07:48:11 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
2023-02-21 08:28:36 +01:00
|
|
|
typebot: string | any
|
|
|
|
|
isPreview?: boolean
|
|
|
|
|
resultId?: string
|
|
|
|
|
prefilledVariables?: Record<string, unknown>
|
2022-12-22 17:02:34 +01:00
|
|
|
apiHost?: string
|
2024-02-20 10:36:57 +01:00
|
|
|
font?: Font
|
2024-03-07 09:18:03 +01:00
|
|
|
progressBarRef?: HTMLDivElement
|
2023-11-08 15:34:16 +01:00
|
|
|
onNewInputBlock?: (inputBlock: InputBlock) => void
|
2023-01-16 12:13:21 +01:00
|
|
|
onAnswer?: (answer: { message: string; blockId: string }) => void
|
|
|
|
|
onInit?: () => void
|
|
|
|
|
onEnd?: () => void
|
2023-02-21 08:28:36 +01:00
|
|
|
onNewLogs?: (logs: OutgoingLog[]) => void
|
2024-03-07 15:39:09 +01:00
|
|
|
onChatStatePersisted?: (isEnabled: boolean) => void
|
2023-11-13 15:27:36 +01:00
|
|
|
startFrom?: StartFrom
|
|
|
|
|
}
|
2022-12-22 17:02:34 +01:00
|
|
|
|
2023-01-25 11:27:47 +01:00
|
|
|
export const Bot = (props: BotProps & { class?: string }) => {
|
2022-12-22 17:02:34 +01:00
|
|
|
const [initialChatReply, setInitialChatReply] = createSignal<
|
|
|
|
|
InitialChatReply | undefined
|
2023-01-16 12:13:21 +01:00
|
|
|
>()
|
2023-01-25 11:27:47 +01:00
|
|
|
const [customCss, setCustomCss] = createSignal('')
|
|
|
|
|
const [isInitialized, setIsInitialized] = createSignal(false)
|
|
|
|
|
const [error, setError] = createSignal<Error | undefined>()
|
2022-12-22 17:02:34 +01:00
|
|
|
|
2023-01-16 12:13:21 +01:00
|
|
|
const initializeBot = async () => {
|
2024-02-20 10:36:57 +01:00
|
|
|
if (props.font) injectFont(props.font)
|
2023-01-25 11:27:47 +01:00
|
|
|
setIsInitialized(true)
|
2023-01-16 12:13:21 +01:00
|
|
|
const urlParams = new URLSearchParams(location.search)
|
|
|
|
|
props.onInit?.()
|
|
|
|
|
const prefilledVariables: { [key: string]: string } = {}
|
|
|
|
|
urlParams.forEach((value, key) => {
|
|
|
|
|
prefilledVariables[key] = value
|
|
|
|
|
})
|
2023-03-02 10:55:03 +01:00
|
|
|
const typebotIdFromProps =
|
|
|
|
|
typeof props.typebot === 'string' ? props.typebot : undefined
|
2023-11-13 15:27:36 +01:00
|
|
|
const isPreview =
|
|
|
|
|
typeof props.typebot !== 'string' || (props.isPreview ?? false)
|
2024-03-07 15:39:09 +01:00
|
|
|
const resultIdInStorage = getExistingResultIdFromStorage(typebotIdFromProps)
|
2023-12-20 08:31:22 +01:00
|
|
|
const { data, error } = await startChatQuery({
|
2023-07-27 17:25:02 +02:00
|
|
|
stripeRedirectStatus: urlParams.get('redirect_status') ?? undefined,
|
2023-01-16 12:13:21 +01:00
|
|
|
typebot: props.typebot,
|
|
|
|
|
apiHost: props.apiHost,
|
2023-11-13 15:27:36 +01:00
|
|
|
isPreview,
|
2024-03-07 15:39:09 +01:00
|
|
|
resultId: isNotEmpty(props.resultId) ? props.resultId : resultIdInStorage,
|
2023-01-16 12:13:21 +01:00
|
|
|
prefilledVariables: {
|
|
|
|
|
...prefilledVariables,
|
|
|
|
|
...props.prefilledVariables,
|
|
|
|
|
},
|
2023-11-13 15:27:36 +01:00
|
|
|
startFrom: props.startFrom,
|
2023-01-16 12:13:21 +01:00
|
|
|
})
|
2023-12-20 08:31:22 +01:00
|
|
|
if (error instanceof HTTPError) {
|
2023-11-13 15:27:36 +01:00
|
|
|
if (isPreview) {
|
2023-04-05 21:10:57 +02:00
|
|
|
return setError(
|
2023-12-20 08:31:22 +01:00
|
|
|
new Error(`An error occurred while loading the bot.`, {
|
|
|
|
|
cause: {
|
|
|
|
|
status: error.response.status,
|
|
|
|
|
body: await error.response.json(),
|
|
|
|
|
},
|
2023-04-03 18:14:32 +02:00
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
}
|
2023-12-20 08:31:22 +01:00
|
|
|
if (error.response.status === 400 || error.response.status === 403)
|
2023-04-05 21:10:57 +02:00
|
|
|
return setError(new Error('This bot is now closed.'))
|
2023-12-20 08:31:22 +01:00
|
|
|
if (error.response.status === 404)
|
2023-04-05 21:10:57 +02:00
|
|
|
return setError(new Error("The bot you're looking for doesn't exist."))
|
2023-12-20 08:31:22 +01:00
|
|
|
return setError(
|
|
|
|
|
new Error(
|
|
|
|
|
`Error! Couldn't initiate the chat. (${error.response.statusText})`
|
|
|
|
|
)
|
|
|
|
|
)
|
2022-12-22 17:02:34 +01:00
|
|
|
}
|
2023-01-16 12:13:21 +01:00
|
|
|
|
2023-08-07 12:20:38 +02:00
|
|
|
if (!data) {
|
2023-12-20 08:31:22 +01:00
|
|
|
if (error) {
|
|
|
|
|
console.error(error)
|
|
|
|
|
if (isPreview) {
|
|
|
|
|
return setError(
|
|
|
|
|
new Error(`Error! Could not reach server. Check your connection.`, {
|
|
|
|
|
cause: error,
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return setError(
|
|
|
|
|
new Error('Error! Could not reach server. Check your connection.')
|
|
|
|
|
)
|
2023-08-07 12:20:38 +02:00
|
|
|
}
|
2023-01-16 12:13:21 +01:00
|
|
|
|
2024-03-07 15:39:09 +01:00
|
|
|
if (
|
|
|
|
|
data.resultId &&
|
|
|
|
|
typebotIdFromProps &&
|
|
|
|
|
(data.typebot.settings.general?.rememberUser?.isEnabled ??
|
|
|
|
|
defaultSettings.general.rememberUser.isEnabled)
|
|
|
|
|
) {
|
|
|
|
|
if (resultIdInStorage && resultIdInStorage !== data.resultId)
|
|
|
|
|
wipeExistingChatStateInStorage(data.typebot.id)
|
|
|
|
|
const storage =
|
|
|
|
|
data.typebot.settings.general?.rememberUser?.storage ??
|
|
|
|
|
defaultSettings.general.rememberUser.storage
|
|
|
|
|
setResultInStorage(storage)(typebotIdFromProps, data.resultId)
|
|
|
|
|
const initialChatInStorage = getInitialChatReplyFromStorage(
|
|
|
|
|
data.typebot.id
|
2023-05-16 14:58:56 +02:00
|
|
|
)
|
2024-03-07 15:39:09 +01:00
|
|
|
if (initialChatInStorage) {
|
|
|
|
|
setInitialChatReply(initialChatInStorage)
|
|
|
|
|
} else {
|
|
|
|
|
setInitialChatReply(data)
|
|
|
|
|
setInitialChatReplyInStorage(data, {
|
|
|
|
|
typebotId: data.typebot.id,
|
|
|
|
|
storage,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
props.onChatStatePersisted?.(true)
|
|
|
|
|
} else {
|
|
|
|
|
setInitialChatReply(data)
|
|
|
|
|
if (data.input?.id && props.onNewInputBlock)
|
|
|
|
|
props.onNewInputBlock(data.input)
|
|
|
|
|
if (data.logs) props.onNewLogs?.(data.logs)
|
|
|
|
|
props.onChatStatePersisted?.(false)
|
|
|
|
|
}
|
2023-01-16 12:13:21 +01:00
|
|
|
|
2024-03-07 15:39:09 +01:00
|
|
|
setCustomCss(data.typebot.theme.customCss ?? '')
|
2023-01-16 12:13:21 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-25 11:27:47 +01:00
|
|
|
createEffect(() => {
|
2023-07-07 14:35:17 +02:00
|
|
|
if (isNotDefined(props.typebot) || isInitialized()) return
|
2023-01-16 12:13:21 +01:00
|
|
|
initializeBot().then()
|
2022-12-22 17:02:34 +01:00
|
|
|
})
|
|
|
|
|
|
2023-01-25 11:27:47 +01:00
|
|
|
createEffect(() => {
|
2023-07-15 10:46:36 +02:00
|
|
|
if (isNotDefined(props.typebot) || typeof props.typebot === 'string') return
|
2023-01-25 11:27:47 +01:00
|
|
|
setCustomCss(props.typebot.theme.customCss ?? '')
|
2024-02-23 08:31:14 +01:00
|
|
|
if (
|
|
|
|
|
props.typebot.theme.general?.progressBar?.isEnabled &&
|
|
|
|
|
initialChatReply() &&
|
|
|
|
|
!initialChatReply()?.typebot.theme.general?.progressBar?.isEnabled
|
|
|
|
|
) {
|
|
|
|
|
setIsInitialized(false)
|
|
|
|
|
initializeBot().then()
|
|
|
|
|
}
|
2023-01-25 11:27:47 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onCleanup(() => {
|
|
|
|
|
setIsInitialized(false)
|
|
|
|
|
})
|
|
|
|
|
|
2022-12-22 17:02:34 +01:00
|
|
|
return (
|
2023-01-16 12:13:21 +01:00
|
|
|
<>
|
2023-01-25 11:27:47 +01:00
|
|
|
<style>{customCss()}</style>
|
2023-03-13 08:40:48 +01:00
|
|
|
<style>{immutableCss}</style>
|
2023-01-16 12:13:21 +01:00
|
|
|
<Show when={error()} keyed>
|
|
|
|
|
{(error) => <ErrorMessage error={error} />}
|
|
|
|
|
</Show>
|
2022-12-22 17:02:34 +01:00
|
|
|
<Show when={initialChatReply()} keyed>
|
|
|
|
|
{(initialChatReply) => (
|
|
|
|
|
<BotContent
|
2023-01-25 11:27:47 +01:00
|
|
|
class={props.class}
|
2023-01-16 12:13:21 +01:00
|
|
|
initialChatReply={{
|
|
|
|
|
...initialChatReply,
|
|
|
|
|
typebot: {
|
|
|
|
|
...initialChatReply.typebot,
|
|
|
|
|
settings:
|
|
|
|
|
typeof props.typebot === 'string'
|
|
|
|
|
? initialChatReply.typebot?.settings
|
|
|
|
|
: props.typebot?.settings,
|
|
|
|
|
theme:
|
|
|
|
|
typeof props.typebot === 'string'
|
|
|
|
|
? initialChatReply.typebot?.theme
|
|
|
|
|
: props.typebot?.theme,
|
|
|
|
|
},
|
|
|
|
|
}}
|
2022-12-22 17:02:34 +01:00
|
|
|
context={{
|
|
|
|
|
apiHost: props.apiHost,
|
2023-01-25 11:27:47 +01:00
|
|
|
isPreview:
|
|
|
|
|
typeof props.typebot !== 'string' || (props.isPreview ?? false),
|
2022-12-22 17:02:34 +01:00
|
|
|
resultId: initialChatReply.resultId,
|
2023-07-27 17:25:02 +02:00
|
|
|
sessionId: initialChatReply.sessionId,
|
|
|
|
|
typebot: initialChatReply.typebot,
|
2024-03-07 15:39:09 +01:00
|
|
|
storage:
|
|
|
|
|
initialChatReply.typebot.settings.general?.rememberUser
|
|
|
|
|
?.isEnabled &&
|
|
|
|
|
!(
|
|
|
|
|
typeof props.typebot !== 'string' ||
|
|
|
|
|
(props.isPreview ?? false)
|
|
|
|
|
)
|
|
|
|
|
? initialChatReply.typebot.settings.general?.rememberUser
|
|
|
|
|
?.storage ?? defaultSettings.general.rememberUser.storage
|
|
|
|
|
: undefined,
|
2022-12-22 17:02:34 +01:00
|
|
|
}}
|
2024-03-07 09:18:03 +01:00
|
|
|
progressBarRef={props.progressBarRef}
|
2023-01-16 12:13:21 +01:00
|
|
|
onNewInputBlock={props.onNewInputBlock}
|
2023-01-25 11:27:47 +01:00
|
|
|
onNewLogs={props.onNewLogs}
|
2023-01-16 12:13:21 +01:00
|
|
|
onAnswer={props.onAnswer}
|
|
|
|
|
onEnd={props.onEnd}
|
2022-12-22 17:02:34 +01:00
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</Show>
|
2023-01-16 12:13:21 +01:00
|
|
|
</>
|
2022-12-22 17:02:34 +01:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type BotContentProps = {
|
|
|
|
|
initialChatReply: InitialChatReply
|
|
|
|
|
context: BotContext
|
2023-01-25 11:27:47 +01:00
|
|
|
class?: string
|
2024-03-07 09:18:03 +01:00
|
|
|
progressBarRef?: HTMLDivElement
|
2023-11-08 15:34:16 +01:00
|
|
|
onNewInputBlock?: (inputBlock: InputBlock) => void
|
2023-01-16 12:13:21 +01:00
|
|
|
onAnswer?: (answer: { message: string; blockId: string }) => void
|
|
|
|
|
onEnd?: () => void
|
2023-02-21 08:28:36 +01:00
|
|
|
onNewLogs?: (logs: OutgoingLog[]) => void
|
2022-12-22 17:02:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const BotContent = (props: BotContentProps) => {
|
2024-03-07 15:39:09 +01:00
|
|
|
const [progressValue, setProgressValue] = persist(
|
|
|
|
|
createSignal<number | undefined>(props.initialChatReply.progress),
|
|
|
|
|
{
|
|
|
|
|
storage: props.context.storage,
|
|
|
|
|
key: `typebot-${props.context.typebot.id}-progressValue`,
|
|
|
|
|
}
|
2024-02-23 08:31:14 +01:00
|
|
|
)
|
2022-12-22 17:02:34 +01:00
|
|
|
let botContainer: HTMLDivElement | undefined
|
|
|
|
|
|
|
|
|
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
|
|
|
return setIsMobile(entries[0].target.clientWidth < 400)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onMount(() => {
|
2023-01-16 12:13:21 +01:00
|
|
|
if (!botContainer) return
|
|
|
|
|
resizeObserver.observe(botContainer)
|
2024-03-15 11:38:40 +01:00
|
|
|
setBotContainerHeight(`${botContainer.clientHeight}px`)
|
2023-01-16 12:13:21 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
createEffect(() => {
|
2024-02-20 10:36:57 +01:00
|
|
|
injectFont(
|
2024-04-10 10:19:54 +02:00
|
|
|
props.initialChatReply.typebot.theme.general?.font ?? {
|
|
|
|
|
type: defaultFontType,
|
|
|
|
|
family: defaultFontFamily,
|
|
|
|
|
}
|
2024-02-20 10:36:57 +01:00
|
|
|
)
|
2023-01-16 12:13:21 +01:00
|
|
|
if (!botContainer) return
|
2024-02-23 08:31:14 +01:00
|
|
|
setCssVariablesValue(
|
|
|
|
|
props.initialChatReply.typebot.theme,
|
|
|
|
|
botContainer,
|
|
|
|
|
props.context.isPreview
|
|
|
|
|
)
|
2022-12-22 17:02:34 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onCleanup(() => {
|
2023-01-16 12:13:21 +01:00
|
|
|
if (!botContainer) return
|
|
|
|
|
resizeObserver.unobserve(botContainer)
|
2022-12-22 17:02:34 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return (
|
2023-01-16 12:13:21 +01:00
|
|
|
<div
|
|
|
|
|
ref={botContainer}
|
2023-12-13 10:22:02 +01:00
|
|
|
class={clsx(
|
2024-04-10 10:19:54 +02:00
|
|
|
'relative flex w-full h-full text-base overflow-hidden flex-col justify-center items-center typebot-container',
|
2023-01-25 11:27:47 +01:00
|
|
|
props.class
|
2023-12-13 10:22:02 +01:00
|
|
|
)}
|
2023-01-16 12:13:21 +01:00
|
|
|
>
|
2024-02-23 08:31:14 +01:00
|
|
|
<Show
|
|
|
|
|
when={
|
|
|
|
|
isDefined(progressValue()) &&
|
|
|
|
|
props.initialChatReply.typebot.theme.general?.progressBar?.isEnabled
|
|
|
|
|
}
|
|
|
|
|
>
|
2024-03-07 09:18:03 +01:00
|
|
|
<Show
|
|
|
|
|
when={
|
|
|
|
|
props.progressBarRef &&
|
|
|
|
|
(props.initialChatReply.typebot.theme.general?.progressBar
|
2024-04-10 10:19:54 +02:00
|
|
|
?.position ?? defaultProgressBarPosition) === 'fixed'
|
2024-03-07 09:18:03 +01:00
|
|
|
}
|
|
|
|
|
fallback={<ProgressBar value={progressValue() as number} />}
|
|
|
|
|
>
|
|
|
|
|
<Portal mount={props.progressBarRef}>
|
|
|
|
|
<ProgressBar value={progressValue() as number} />
|
|
|
|
|
</Portal>
|
|
|
|
|
</Show>
|
2024-02-23 08:31:14 +01:00
|
|
|
</Show>
|
2024-04-10 10:19:54 +02:00
|
|
|
<div class="flex w-full h-full justify-center items-center">
|
2023-01-16 12:13:21 +01:00
|
|
|
<ConversationContainer
|
|
|
|
|
context={props.context}
|
|
|
|
|
initialChatReply={props.initialChatReply}
|
|
|
|
|
onNewInputBlock={props.onNewInputBlock}
|
|
|
|
|
onAnswer={props.onAnswer}
|
|
|
|
|
onEnd={props.onEnd}
|
2023-01-25 11:27:47 +01:00
|
|
|
onNewLogs={props.onNewLogs}
|
2024-02-23 08:31:14 +01:00
|
|
|
onProgressUpdate={setProgressValue}
|
2023-01-16 12:13:21 +01:00
|
|
|
/>
|
2022-12-22 17:02:34 +01:00
|
|
|
</div>
|
2023-01-16 12:13:21 +01:00
|
|
|
<Show
|
2023-11-08 15:34:16 +01:00
|
|
|
when={
|
|
|
|
|
props.initialChatReply.typebot.settings.general?.isBrandingEnabled
|
|
|
|
|
}
|
2023-01-16 12:13:21 +01:00
|
|
|
>
|
|
|
|
|
<LiteBadge botContainer={botContainer} />
|
|
|
|
|
</Show>
|
|
|
|
|
</div>
|
2022-12-22 17:02:34 +01:00
|
|
|
)
|
|
|
|
|
}
|