2
0
Files
bot/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx
2022-03-01 11:40:22 +01:00

151 lines
4.5 KiB
TypeScript

import React, { useEffect, useState } from 'react'
import { TransitionGroup, CSSTransition } from 'react-transition-group'
import { ChatStep } from './ChatStep'
import { AvatarSideContainer } from './AvatarSideContainer'
import { HostAvatarsContext } from '../../contexts/HostAvatarsContext'
import { useTypebot } from '../../contexts/TypebotContext'
import {
isBubbleStep,
isChoiceInput,
isInputStep,
isIntegrationStep,
isLogicStep,
} from 'utils'
import { executeLogic } from 'services/logic'
import { executeIntegration } from 'services/integration'
import { parseRetryStep, stepCanBeRetried } from 'services/inputs'
import { parseVariables } from 'index'
import { useAnswers } from 'contexts/AnswersContext'
import { Step } from 'models'
type ChatBlockProps = {
steps: Step[]
startStepIndex: number
onScroll: () => void
onBlockEnd: (edgeId?: string) => void
}
export const ChatBlock = ({
steps,
startStepIndex,
onScroll,
onBlockEnd,
}: ChatBlockProps) => {
const {
typebot,
updateVariableValue,
createEdge,
apiHost,
isPreview,
onNewLog,
} = useTypebot()
const { resultValues } = useAnswers()
const [displayedSteps, setDisplayedSteps] = useState<Step[]>([])
useEffect(() => {
const nextStep = steps[startStepIndex]
if (nextStep) setDisplayedSteps([...displayedSteps, nextStep])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
onScroll()
onNewStepDisplayed()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [displayedSteps])
const onNewStepDisplayed = async () => {
const currentStep = [...displayedSteps].pop()
if (!currentStep) return
if (isLogicStep(currentStep)) {
const nextEdgeId = executeLogic(
currentStep,
typebot.variables,
updateVariableValue
)
nextEdgeId ? onBlockEnd(nextEdgeId) : displayNextStep()
}
if (isIntegrationStep(currentStep)) {
const nextEdgeId = await executeIntegration({
step: currentStep,
context: {
apiHost,
typebotId: typebot.typebotId,
blockId: currentStep.blockId,
stepId: currentStep.id,
variables: typebot.variables,
isPreview,
updateVariableValue,
resultValues,
blocks: typebot.blocks,
onNewLog,
},
})
nextEdgeId ? onBlockEnd(nextEdgeId) : displayNextStep()
}
}
const displayNextStep = (answerContent?: string, isRetry?: boolean) => {
onScroll()
const currentStep = [...displayedSteps].pop()
if (currentStep) {
if (isRetry && stepCanBeRetried(currentStep))
return setDisplayedSteps([
...displayedSteps,
parseRetryStep(currentStep, typebot.variables, createEdge),
])
if (
isInputStep(currentStep) &&
currentStep.options?.variableId &&
answerContent
) {
updateVariableValue(currentStep.options.variableId, answerContent)
}
const isSingleChoiceStep =
isChoiceInput(currentStep) && !currentStep.options.isMultipleChoice
if (isSingleChoiceStep) {
const nextEdgeId = currentStep.items.find(
(i) => i.content === answerContent
)?.outgoingEdgeId
if (nextEdgeId) return onBlockEnd(nextEdgeId)
}
if (currentStep?.outgoingEdgeId || displayedSteps.length === steps.length)
return onBlockEnd(currentStep.outgoingEdgeId)
}
const nextStep = steps[displayedSteps.length]
if (nextStep) setDisplayedSteps([...displayedSteps, nextStep])
}
const avatarSrc = typebot.theme.chat.hostAvatar?.url
return (
<div className="flex w-full">
<HostAvatarsContext>
{(typebot.theme.chat.hostAvatar?.isEnabled ?? true) && (
<AvatarSideContainer
hostAvatarSrc={
avatarSrc && parseVariables(typebot.variables)(avatarSrc)
}
/>
)}
<div className="flex flex-col w-full min-w-0">
<TransitionGroup>
{displayedSteps
.filter((step) => isInputStep(step) || isBubbleStep(step))
.map((step) => (
<CSSTransition
key={step.id}
classNames="bubble"
timeout={500}
unmountOnExit
>
<ChatStep step={step} onTransitionEnd={displayNextStep} />
</CSSTransition>
))}
</TransitionGroup>
</div>
</HostAvatarsContext>
</div>
)
}