Skip to content

Commit

Permalink
🧑‍💻 (chat) Introduce startChat and continueChat endpoints
Browse files Browse the repository at this point in the history
Closes #1030
  • Loading branch information
baptisteArno committed Nov 13, 2023
1 parent 63233eb commit 084588a
Show file tree
Hide file tree
Showing 74 changed files with 28,647 additions and 866 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import * as Sentry from '@sentry/nextjs'
import { User } from '@typebot.io/prisma'
import { NextApiRequest, NextApiResponse } from 'next'
import { getServerSession } from 'next-auth'
import { mockedUser } from '../mockedUser'
import { env } from '@typebot.io/env'
import { mockedUser } from '@typebot.io/lib/mockedUser'

export const getAuthenticatedUser = async (
req: NextApiRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export const AudioBubbleNode = ({ url }: Props) => {
return isDefined(url) ? (
<audio src={url} controls />
) : (
<Text color={'gray.500'}>
{t('editor.blocks.bubbles.audio.node.clickToEdit.text')}
</Text>
<Text color={'gray.500'}>{t('clickToEdit')}</Text>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ type Props = {
export const EmbedBubbleContent = ({ block }: Props) => {
const { t } = useTranslate()
if (!block.content?.url)
return (
<Text color="gray.500">
{t('editor.blocks.bubbles.embed.node.clickToEdit.text')}
</Text>
)
return <Text color="gray.500">{t('clickToEdit')}</Text>
return <Text>{t('editor.blocks.bubbles.embed.node.show.text')}</Text>
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ export const ImageBubbleContent = ({ block }: Props) => {
const containsVariables =
block.content?.url?.includes('{{') && block.content.url.includes('}}')
return !block.content?.url ? (
<Text color={'gray.500'}>
{t('editor.blocks.bubbles.image.node.clickToEdit.text')}
</Text>
<Text color={'gray.500'}>{t('clickToEdit')}</Text>
) : (
<Box w="full">
<Image
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ type Props = {
export const VideoBubbleContent = ({ block }: Props) => {
const { t } = useTranslate()
if (!block.content?.url || !block.content.type)
return (
<Text color="gray.500">
{t('editor.blocks.bubbles.video.node.clickToEdit.text')}
</Text>
)
return <Text color="gray.500">{t('clickToEdit')}</Text>
const containsVariables =
block.content?.url?.includes('{{') && block.content.url.includes('}}')
switch (block.content.type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test('should be configurable', async ({ page }) => {
await expect(page.getByTestId('selected-item-label').first()).toHaveText(
'My link typebot 2'
)
await page.click('input[placeholder="Select a block"]')
await page.click('input[placeholder="Select a group"]')
await page.click('text=Group #2')

await page.click('text=Preview')
Expand Down
13 changes: 9 additions & 4 deletions apps/builder/src/features/preview/components/WebPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { useGraph } from '@/features/graph/providers/GraphProvider'
import { useToast } from '@/hooks/useToast'
import { Standard } from '@typebot.io/nextjs'
import { ChatReply } from '@typebot.io/schemas'
import { ContinueChatResponse } from '@typebot.io/schemas'

export const WebPreview = () => {
const { typebot } = useTypebot()
Expand All @@ -13,7 +13,7 @@ export const WebPreview = () => {

const { showToast } = useToast()

const handleNewLogs = (logs: ChatReply['logs']) => {
const handleNewLogs = (logs: ContinueChatResponse['logs']) => {
logs?.forEach((log) => {
showToast({
icon: <WebhookIcon />,
Expand All @@ -40,8 +40,13 @@ export const WebPreview = () => {
<Standard
key={`web-preview${startPreviewAtGroup ?? ''}`}
typebot={typebot}
startGroupId={startPreviewAtGroup}
startEventId={startPreviewAtEvent}
startFrom={
startPreviewAtGroup
? { type: 'group', groupId: startPreviewAtGroup }
: startPreviewAtEvent
? { type: 'event', eventId: startPreviewAtEvent }
: undefined
}
onNewInputBlock={(block) =>
setPreviewingBlock({
id: block.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { BuoyIcon, ExternalLinkIcon } from '@/components/icons'

export const WhatsAppPreviewInstructions = (props: StackProps) => {
const { typebot, save } = useTypebot()
const { startPreviewAtGroup } = useEditor()
const { startPreviewAtGroup, startPreviewAtEvent } = useEditor()
const [phoneNumber, setPhoneNumber] = useState(
getPhoneNumberFromLocalStorage() ?? ''
)
Expand Down Expand Up @@ -56,7 +56,11 @@ export const WhatsAppPreviewInstructions = (props: StackProps) => {
mutate({
to: phoneNumber,
typebotId: typebot.id,
startGroupId: startPreviewAtGroup,
startFrom: startPreviewAtGroup
? { type: 'group', groupId: startPreviewAtGroup }
: startPreviewAtEvent
? { type: 'event', eventId: startPreviewAtEvent }
: undefined,
})
}

Expand Down
222 changes: 110 additions & 112 deletions apps/builder/src/features/whatsapp/startWhatsAppPreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { restartSession } from '@typebot.io/bot-engine/queries/restartSession'
import { sendChatReplyToWhatsApp } from '@typebot.io/bot-engine/whatsapp/sendChatReplyToWhatsApp'
import { sendWhatsAppMessage } from '@typebot.io/bot-engine/whatsapp/sendWhatsAppMessage'
import { isReadTypebotForbidden } from '../typebot/helpers/isReadTypebotForbidden'
import { SessionState } from '@typebot.io/schemas'
import { SessionState, startFromSchema } from '@typebot.io/schemas'

export const startWhatsAppPreview = authenticatedProcedure
.meta({
Expand All @@ -31,143 +31,141 @@ export const startWhatsAppPreview = authenticatedProcedure
value.replace(/\s/g, '').replace(/\+/g, '').replace(/-/g, '')
),
typebotId: z.string(),
startGroupId: z.string().optional(),
startFrom: startFromSchema.optional(),
})
)
.output(
z.object({
message: z.string(),
})
)
.mutation(
async ({ input: { to, typebotId, startGroupId }, ctx: { user } }) => {
if (
!env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID ||
!env.META_SYSTEM_USER_TOKEN ||
!env.WHATSAPP_PREVIEW_TEMPLATE_NAME
)
throw new TRPCError({
code: 'BAD_REQUEST',
message:
'Missing WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID or META_SYSTEM_USER_TOKEN or WHATSAPP_PREVIEW_TEMPLATE_NAME env variables',
})
.mutation(async ({ input: { to, typebotId, startFrom }, ctx: { user } }) => {
if (
!env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID ||
!env.META_SYSTEM_USER_TOKEN ||
!env.WHATSAPP_PREVIEW_TEMPLATE_NAME
)
throw new TRPCError({
code: 'BAD_REQUEST',
message:
'Missing WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID or META_SYSTEM_USER_TOKEN or WHATSAPP_PREVIEW_TEMPLATE_NAME env variables',
})

const existingTypebot = await prisma.typebot.findFirst({
where: {
id: typebotId,
},
select: {
id: true,
workspaceId: true,
collaborators: {
select: {
userId: true,
},
const existingTypebot = await prisma.typebot.findFirst({
where: {
id: typebotId,
},
select: {
id: true,
workspaceId: true,
collaborators: {
select: {
userId: true,
},
},
})
if (
!existingTypebot?.id ||
(await isReadTypebotForbidden(existingTypebot, user))
)
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
},
})
if (
!existingTypebot?.id ||
(await isReadTypebotForbidden(existingTypebot, user))
)
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })

const sessionId = `wa-preview-${to}`
const sessionId = `wa-preview-${to}`

const existingSession = await prisma.chatSession.findFirst({
where: {
id: sessionId,
},
select: {
updatedAt: true,
state: true,
},
})
const existingSession = await prisma.chatSession.findFirst({
where: {
id: sessionId,
},
select: {
updatedAt: true,
state: true,
},
})

// For users that did not interact with the bot in the last 24 hours, we need to send a template message.
const canSendDirectMessagesToUser =
(existingSession?.updatedAt.getTime() ?? 0) >
Date.now() - 24 * 60 * 60 * 1000
// For users that did not interact with the bot in the last 24 hours, we need to send a template message.
const canSendDirectMessagesToUser =
(existingSession?.updatedAt.getTime() ?? 0) >
Date.now() - 24 * 60 * 60 * 1000

const {
newSessionState,
const {
newSessionState,
messages,
input,
clientSideActions,
logs,
visitedEdges,
} = await startSession({
version: 2,
message: undefined,
startParams: {
isOnlyRegistering: !canSendDirectMessagesToUser,
type: 'preview',
typebotId,
startFrom,
userId: user.id,
},
initialSessionState: {
whatsApp: (existingSession?.state as SessionState | undefined)
?.whatsApp,
},
})

if (canSendDirectMessagesToUser) {
await sendChatReplyToWhatsApp({
to,
typingEmulation: newSessionState.typingEmulation,
messages,
input,
clientSideActions,
logs,
visitedEdges,
} = await startSession({
version: 2,
message: undefined,
startParams: {
isOnlyRegistering: !canSendDirectMessagesToUser,
typebot: typebotId,
isPreview: true,
startGroupId,
credentials: {
phoneNumberId: env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID,
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
},
userId: user.id,
initialSessionState: {
whatsApp: (existingSession?.state as SessionState | undefined)
?.whatsApp,
state: newSessionState,
})
await saveStateToDatabase({
clientSideActions: [],
input,
logs,
session: {
id: sessionId,
state: newSessionState,
},
visitedEdges,
})

if (canSendDirectMessagesToUser) {
await sendChatReplyToWhatsApp({
} else {
await restartSession({
state: newSessionState,
id: sessionId,
})
try {
await sendWhatsAppMessage({
to,
typingEmulation: newSessionState.typingEmulation,
messages,
input,
clientSideActions,
message: {
type: 'template',
template: {
language: {
code: env.WHATSAPP_PREVIEW_TEMPLATE_LANG,
},
name: env.WHATSAPP_PREVIEW_TEMPLATE_NAME,
},
},
credentials: {
phoneNumberId: env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID,
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
},
state: newSessionState,
})
await saveStateToDatabase({
clientSideActions: [],
input,
logs,
session: {
id: sessionId,
state: newSessionState,
},
visitedEdges,
})
} else {
await restartSession({
state: newSessionState,
id: sessionId,
} catch (err) {
if (err instanceof HTTPError) console.log(err.response.body)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Request to Meta to send preview message failed',
cause: err,
})
try {
await sendWhatsAppMessage({
to,
message: {
type: 'template',
template: {
language: {
code: env.WHATSAPP_PREVIEW_TEMPLATE_LANG,
},
name: env.WHATSAPP_PREVIEW_TEMPLATE_NAME,
},
},
credentials: {
phoneNumberId: env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID,
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
},
})
} catch (err) {
if (err instanceof HTTPError) console.log(err.response.body)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Request to Meta to send preview message failed',
cause: err,
})
}
}
return {
message: 'success',
}
}
)
return {
message: 'success',
}
})
2 changes: 1 addition & 1 deletion apps/builder/src/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { NextApiRequest, NextApiResponse } from 'next'
import { customAdapter } from '../../../features/auth/api/customAdapter'
import { User } from '@typebot.io/prisma'
import { getAtPath, isDefined } from '@typebot.io/lib'
import { mockedUser } from '@/features/auth/mockedUser'
import { mockedUser } from '@typebot.io/lib/mockedUser'
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
import { sendVerificationRequest } from '@/features/auth/helpers/sendVerificationRequest'
import { Ratelimit } from '@upstash/ratelimit'
Expand Down
Loading

4 comments on commit 084588a

@vercel
Copy link

@vercel vercel bot commented on 084588a Nov 13, 2023

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-git-main-typebot-io.vercel.app
app.typebot.io
builder-v2-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 084588a Nov 13, 2023

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

vhpage.cr8.ai
vitamyway.com
webwhats.chat
whatchat.site
www.wiccom.it
acessovip.shop
adsgrow.com.br
am.nigerias.io
an.nigerias.io
app.yvon.earth
ar.nigerias.io
bot.enreso.org
bot.mail2wa.me
bot.rslabs.pro
bot.share5.net
bots.bng.tools
bots.bridge.ai
chad.gocto.com
chat.ftplay.me
chat.hayuri.id
chat.uprize.hu
chatgpt.lam.ee
chicken.cr8.ai
debitozero.com
drayumi.social
gollum.riku.ai
gsbulletin.com
journey.cr8.ai
kopibayane.com
panther.cr7.ai
panther.cr8.ai
pay.sifuim.com
penguin.cr8.ai
petaikorea.com
privetvplus.me
segredomeu.com
semaknilai.com
talk.gocare.io
ticketfute.com
unicorn.cr8.ai
whats-app.chat
whatsnaweb.com
apo.nigerias.io
app.blogely.com
apr.nigerias.io
aso.nigerias.io
baohanh.qmap.vn
blackcan.cr8.ai
blackvip.online
bot.4display.nl
bot.digitalbled.com
bot.dsignagency.com
bot.enthrallart.com
viewer-v2-typebot-io.vercel.app
prenotazione.ristorantekintsugi.it
download.thailandmicespecialist.com
mdb.assessoria.aloisio.progenbr.com
mdb.assessoria.girotto.progenbr.com
mdb.assessoria.marinho.progenbr.com
mdb.assessoria.rodrigo.progenbr.com
register.thailandmicespecialist.com
mdb.assessoria.desideri.progenbr.com
mdb.assessoria.fernanda.progenbr.com
mdb.assessoria.jbatista.progenbr.com
mdb.assessoria.mauricio.progenbr.com
mdb.evento.autocadastro.progenbr.com
form.shopmercedesbenzsouthorlando.com
mdb.evento.equipeinterna.progenbr.com
bot.studiotecnicoimmobiliaremerelli.it
mdb.assessoria.boaventura.progenbr.com
mdb.assessoria.jtrebesqui.progenbr.com
pesquisa.escolamodacomproposito.com.br
anamnese.clinicaramosodontologia.com.br
gabinete.baleia.formulario.progenbr.com
mdb.assessoria.carreirinha.progenbr.com
chrome-os-inquiry-system.itschromeos.com
mdb.assessoria.paulomarques.progenbr.com
viewer-v2-git-main-typebot-io.vercel.app
main-menu-for-itschromeos.itschromeos.com
mdb.assessoria.qrcode.ademir.progenbr.com
mdb.assessoria.qrcode.arthur.progenbr.com
mdb.assessoria.qrcode.danilo.progenbr.com
mdb.assessoria.qrcode.marcao.progenbr.com
mdb.assessoria.qrcode.marcio.progenbr.com
mdb.assessoria.qrcode.aloisio.progenbr.com
mdb.assessoria.qrcode.girotto.progenbr.com
mdb.assessoria.qrcode.marinho.progenbr.com
mdb.assessoria.qrcode.rodrigo.progenbr.com
mdb.assessoria.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.desideri.progenbr.com
mdb.assessoria.qrcode.fernanda.progenbr.com
mdb.assessoria.qrcode.jbatista.progenbr.com
mdb.assessoria.qrcode.mauricio.progenbr.com
mdb.assessoria.fernanda.regional.progenbr.com
mdb.assessoria.qrcode.boaventura.progenbr.com
mdb.assessoria.qrcode.jtrebesqui.progenbr.com
mdb.assessoria.qrcode.carreirinha.progenbr.com
mdb.assessoria.qrcode.paulomarques.progenbr.com
mdb.assessoria.qrcode.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.fernanda.regional.progenbr.com

@vercel
Copy link

@vercel vercel bot commented on 084588a Nov 13, 2023

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:

docs – ./apps/docs

docs-git-main-typebot-io.vercel.app
docs-typebot-io.vercel.app
docs.typebot.io

@vercel
Copy link

@vercel vercel bot commented on 084588a Nov 13, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.