Skip to content

Commit

Permalink
feat(engine): ✨ Add retry bubbles
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Feb 10, 2022
1 parent 276f1c1 commit 8c8d77e
Show file tree
Hide file tree
Showing 15 changed files with 217 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ yarn-error.log
authenticatedState.json
playwright-report
dist
test-results
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FormLabel, Stack } from '@chakra-ui/react'
import { DebouncedInput } from 'components/shared/DebouncedInput'
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton'
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
import { EmailInputOptions, Variable } from 'models'
import React from 'react'
Expand All @@ -19,6 +20,8 @@ export const EmailInputSettingsBody = ({
onOptionsChange({ ...options, labels: { ...options.labels, button } })
const handleVariableChange = (variable?: Variable) =>
onOptionsChange({ ...options, variableId: variable?.id })
const handleRetryMessageChange = (retryMessageContent: string) =>
onOptionsChange({ ...options, retryMessageContent })

return (
<Stack spacing={4}>
Expand All @@ -42,6 +45,16 @@ export const EmailInputSettingsBody = ({
onChange={handleButtonLabelChange}
/>
</Stack>
<Stack>
<FormLabel mb="0" htmlFor="retry">
Retry message:
</FormLabel>
<InputWithVariableButton
id="retry"
initialValue={options.retryMessageContent}
onChange={handleRetryMessageChange}
/>
</Stack>
<Stack>
<FormLabel mb="0" htmlFor="variable">
Save answer in a variable:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FormLabel, Stack } from '@chakra-ui/react'
import { DebouncedInput } from 'components/shared/DebouncedInput'
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton'
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
import { EmailInputOptions, Variable } from 'models'
import React from 'react'
Expand All @@ -19,6 +20,8 @@ export const PhoneNumberSettingsBody = ({
onOptionsChange({ ...options, labels: { ...options.labels, button } })
const handleVariableChange = (variable?: Variable) =>
onOptionsChange({ ...options, variableId: variable?.id })
const handleRetryMessageChange = (retryMessageContent: string) =>
onOptionsChange({ ...options, retryMessageContent })

return (
<Stack spacing={4}>
Expand All @@ -42,6 +45,16 @@ export const PhoneNumberSettingsBody = ({
onChange={handleButtonLabelChange}
/>
</Stack>
<Stack>
<FormLabel mb="0" htmlFor="retry">
Retry message:
</FormLabel>
<InputWithVariableButton
id="retry"
initialValue={options.retryMessageContent}
onChange={handleRetryMessageChange}
/>
</Stack>
<Stack>
<FormLabel mb="0" htmlFor="variable">
Save answer in a variable:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FormLabel, Stack } from '@chakra-ui/react'
import { DebouncedInput } from 'components/shared/DebouncedInput'
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton'
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
import { UrlInputOptions, Variable } from 'models'
import React from 'react'
Expand All @@ -19,6 +20,8 @@ export const UrlInputSettingsBody = ({
onOptionsChange({ ...options, labels: { ...options.labels, button } })
const handleVariableChange = (variable?: Variable) =>
onOptionsChange({ ...options, variableId: variable?.id })
const handleRetryMessageChange = (retryMessageContent: string) =>
onOptionsChange({ ...options, retryMessageContent })

return (
<Stack spacing={4}>
Expand All @@ -42,6 +45,16 @@ export const UrlInputSettingsBody = ({
onChange={handleButtonLabelChange}
/>
</Stack>
<Stack>
<FormLabel mb="0" htmlFor="retry">
Retry message:
</FormLabel>
<InputWithVariableButton
id="retry"
initialValue={options.retryMessageContent}
onChange={handleRetryMessageChange}
/>
</Stack>
<Stack>
<FormLabel mb="0" htmlFor="variable">
Save answer in a variable:
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/playwright/services/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const createCredentials = () => {
'ya29.A0ARrdaM--PV_87ebjywDJpXKb77NBFJl16meVUapYdfNv6W6ZzqqC47fNaPaRjbDbOIIcp6f49cMaX5ndK9TAFnKwlVqz3nrK9nLKqgyDIhYsIq47smcAIZkK56SWPx3X3DwAFqRu2UPojpd2upWwo-3uJrod',
// This token is linked to a mock Google account (typebot.test.user@gmail.com)
refresh_token:
'1//0379tIHBxszeXCgYIARAAGAMSNwF-L9Ir0zhkzhblwXqn3_jYqRP3pajcUpqkjRU3fKZZ_eQakOa28amUHSQ-Q9fMzk89MpRTvkc',
'1//03NRE9V8T-aayCgYIARAAGAMSNwF-L9Ir6zVzF-wm30psz0lbDJj5Y9OgqTO0cvBISODMW4QTR0VK40BLnOQgcHCHkb9c769TAhQ',
})
return prisma.credentials.createMany({
data: [
Expand Down
17 changes: 16 additions & 1 deletion apps/builder/playwright/tests/inputs/email.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,25 @@ test.describe('Email input step', () => {
await page.fill('#placeholder', 'Your email...')
await expect(page.locator('text=Your email...')).toBeVisible()
await page.fill('#button', 'Go')
await page.fill(
`input[value="${defaultEmailInputOptions.retryMessageContent}"]`,
'Try again bro'
)

await page.click('text=Restart')
await typebotViewer(page)
.locator(`input[placeholder="Your email..."]`)
.fill('test@test')
await typebotViewer(page).locator('text=Go').click()
await expect(
typebotViewer(page).locator(`input[placeholder="Your email..."]`)
typebotViewer(page).locator('text=Try again bro')
).toBeVisible()
await typebotViewer(page)
.locator(`input[placeholder="Your email..."]`)
.fill('test@test.com')
await typebotViewer(page).locator('text=Go').click()
await expect(
typebotViewer(page).locator('text=test@test.com')
).toBeVisible()
})
})
17 changes: 14 additions & 3 deletions apps/builder/playwright/tests/inputs/phone.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,27 @@ test.describe('Phone input step', () => {
await page.click(`text=${defaultPhoneInputOptions.labels.placeholder}`)
await page.fill('#placeholder', '+33 XX XX XX XX')
await page.fill('#button', 'Go')
await page.fill(
`input[value="${defaultPhoneInputOptions.retryMessageContent}"]`,
'Try again bro'
)

await page.click('text=Restart')
await typebotViewer(page)
.locator(`input[placeholder="+33 XX XX XX XX"]`)
.fill('+33 6 73 18 45 36')
.fill('+33 6 73')
await expect(typebotViewer(page).locator(`img`)).toHaveAttribute(
'alt',
'France'
)
await typebotViewer(page).locator('text="Go"').click()
await expect(typebotViewer(page).locator('text=+33673184536')).toBeVisible()
await typebotViewer(page).locator('button >> text="Go"').click()
await expect(
typebotViewer(page).locator('text=Try again bro')
).toBeVisible()
await typebotViewer(page)
.locator(`input[placeholder="+33 XX XX XX XX"]`)
.fill('+33 6 73 54 45 67')
await typebotViewer(page).locator('button >> text="Go"').click()
await expect(typebotViewer(page).locator('text=+33673544567')).toBeVisible()
})
})
17 changes: 16 additions & 1 deletion apps/builder/playwright/tests/inputs/url.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,25 @@ test.describe('Url input step', () => {
await page.fill('#placeholder', 'Your URL...')
await expect(page.locator('text=Your URL...')).toBeVisible()
await page.fill('#button', 'Go')
await page.fill(
`input[value="${defaultUrlInputOptions.retryMessageContent}"]`,
'Try again bro'
)

await page.click('text=Restart')
await typebotViewer(page)
.locator(`input[placeholder="Your URL..."]`)
.fill('gg://test.com')
await typebotViewer(page).locator('button >> text="Go"').click()
await expect(
typebotViewer(page).locator(`input[placeholder="Your URL..."]`)
typebotViewer(page).locator('text=Try again bro')
).toBeVisible()
await typebotViewer(page)
.locator(`input[placeholder="Your URL..."]`)
.fill('https://website.com')
await typebotViewer(page).locator('button >> text="Go"').click()
await expect(
typebotViewer(page).locator('text=https://website.com')
).toBeVisible()
})
})
11 changes: 9 additions & 2 deletions packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from 'utils'
import { executeLogic } from 'services/logic'
import { executeIntegration } from 'services/integration'
import { parseRetryStep, stepCanBeRetried } from 'services/inputs'

type ChatBlockProps = {
steps: PublicStep[]
Expand All @@ -30,7 +31,7 @@ export const ChatBlock = ({
onScroll,
onBlockEnd,
}: ChatBlockProps) => {
const { typebot, updateVariableValue } = useTypebot()
const { typebot, updateVariableValue, createEdge } = useTypebot()
const [displayedSteps, setDisplayedSteps] = useState<PublicStep[]>([])

const currentStepIndex = displayedSteps.length - 1
Expand Down Expand Up @@ -70,9 +71,15 @@ export const ChatBlock = ({
}
}

const displayNextStep = (answerContent?: string) => {
const displayNextStep = (answerContent?: string, isRetry?: boolean) => {
const currentStep = [...displayedSteps].pop()
console.log(currentStep)
if (currentStep) {
if (isRetry && stepCanBeRetried(currentStep))
return setDisplayedSteps([
...displayedSteps,
parseRetryStep(currentStep, typebot.variables, createEdge),
])
if (
isInputStep(currentStep) &&
currentStep.options?.variableId &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import React, { useEffect, useState } from 'react'
import { useAnswers } from '../../../contexts/AnswersContext'
import { useHostAvatars } from '../../../contexts/HostAvatarsContext'
import { InputStep, InputStepType, PublicStep, Step } from 'models'
import { InputStep, InputStepType, PublicStep } from 'models'
import { GuestBubble } from './bubbles/GuestBubble'
import { TextForm } from './inputs/TextForm'
import { isBubbleStep, isInputStep } from 'utils'
import { DateForm } from './inputs/DateForm'
import { ChoiceForm } from './inputs/ChoiceForm'
import { HostBubble } from './bubbles/HostBubble'
import { isInputValid } from 'services/inputs'

export const ChatStep = ({
step,
onTransitionEnd,
}: {
step: PublicStep
onTransitionEnd: (answerContent?: string) => void
onTransitionEnd: (answerContent?: string, isRetry?: boolean) => void
}) => {
const { addAnswer } = useAnswers()

const handleInputSubmit = (content: string) => {
addAnswer({ stepId: step.id, blockId: step.blockId, content })
onTransitionEnd(content)
const handleInputSubmit = (content: string, isRetry: boolean) => {
if (!isRetry) addAnswer({ stepId: step.id, blockId: step.blockId, content })
onTransitionEnd(content, isRetry)
}

if (isBubbleStep(step))
Expand All @@ -35,7 +36,7 @@ const InputChatStep = ({
onSubmit,
}: {
step: InputStep
onSubmit: (value: string) => void
onSubmit: (value: string, isRetry: boolean) => void
}) => {
const { addNewAvatarOffset } = useHostAvatars()
const [answer, setAnswer] = useState<string>()
Expand All @@ -47,7 +48,7 @@ const InputChatStep = ({

const handleSubmit = (value: string) => {
setAnswer(value)
onSubmit(value)
onSubmit(value, !isInputValid(value, step.type))
}

if (answer) {
Expand Down
12 changes: 7 additions & 5 deletions packages/bot-engine/src/components/ConversationContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,24 @@ import { useFrame } from 'react-frame-component'
import { setCssVariablesValue } from '../services/theme'
import { useAnswers } from '../contexts/AnswersContext'
import { deepEqual } from 'fast-equals'
import { Answer, Edge, PublicBlock, PublicTypebot } from 'models'
import { Answer, Edge, PublicBlock, Theme } from 'models'
import { byId } from 'utils'
import { animateScroll as scroll } from 'react-scroll'
import { useTypebot } from 'contexts/TypebotContext'

type Props = {
typebot: PublicTypebot
theme: Theme
onNewBlockVisible: (edge: Edge) => void
onNewAnswer: (answer: Answer) => void
onCompleted: () => void
}
export const ConversationContainer = ({
typebot,
theme,
onNewBlockVisible,
onNewAnswer,
onCompleted,
}: Props) => {
const { typebot } = useTypebot()
const { document: frameDocument } = useFrame()
const [displayedBlocks, setDisplayedBlocks] = useState<
{ block: PublicBlock; startStepIndex: number }[]
Expand Down Expand Up @@ -51,8 +53,8 @@ export const ConversationContainer = ({
}, [])

useEffect(() => {
setCssVariablesValue(typebot.theme, frameDocument.body.style)
}, [typebot.theme, frameDocument])
setCssVariablesValue(theme, frameDocument.body.style)
}, [theme, frameDocument])

useEffect(() => {
const answer = [...answers].pop()
Expand Down
2 changes: 1 addition & 1 deletion packages/bot-engine/src/components/TypebotViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const TypebotViewer = ({
>
<div className="flex w-full h-full justify-center">
<ConversationContainer
typebot={typebot}
theme={typebot.theme}
onNewBlockVisible={handleNewBlockVisible}
onNewAnswer={handleNewAnswer}
onCompleted={handleCompleted}
Expand Down
12 changes: 11 additions & 1 deletion packages/bot-engine/src/contexts/TypebotContext.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { PublicTypebot } from 'models'
import { Edge, PublicTypebot } from 'models'
import React, { createContext, ReactNode, useContext, useState } from 'react'

const typebotContext = createContext<{
typebot: PublicTypebot
updateVariableValue: (variableId: string, value: string) => void
createEdge: (edge: Edge) => void
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
}>({})
Expand All @@ -25,11 +26,20 @@ export const TypebotContext = ({
),
}))
}

const createEdge = (edge: Edge) => {
setLocalTypebot((typebot) => ({
...typebot,
edges: [...typebot.edges, edge],
}))
}

return (
<typebotContext.Provider
value={{
typebot: localTypebot,
updateVariableValue,
createEdge,
}}
>
{children}
Expand Down
Loading

2 comments on commit 8c8d77e

@vercel
Copy link

@vercel vercel bot commented on 8c8d77e Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

builder-v2-typebot-io.vercel.app
builder-v2-git-main-typebot-io.vercel.app
next.typebot.io

@vercel
Copy link

@vercel vercel bot commented on 8c8d77e Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viewer-v2 – ./apps/viewer

typebot-viewer.vercel.app
viewer-v2-git-main-typebot-io.vercel.app
viewer-v2-typebot-io.vercel.app

Please sign in to comment.