🚸 (whatsapp) Avoid multiple replies to be sent concurently
Closes #972, closes #1453
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -15,7 +15,8 @@
|
|||||||
"DATABASE_URL": "postgresql://postgres:typebot@127.0.0.1:5432/typebot",
|
"DATABASE_URL": "postgresql://postgres:typebot@127.0.0.1:5432/typebot",
|
||||||
"NEXT_PUBLIC_VIEWER_URL": "http://localhost:3001",
|
"NEXT_PUBLIC_VIEWER_URL": "http://localhost:3001",
|
||||||
"NEXTAUTH_URL": "http://localhost:3000",
|
"NEXTAUTH_URL": "http://localhost:3000",
|
||||||
"ENCRYPTION_SECRET": "H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S"
|
"ENCRYPTION_SECRET": "H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S",
|
||||||
|
"S3_ENDPOINT": "http://localhost:9000"
|
||||||
},
|
},
|
||||||
"[prisma]": {
|
"[prisma]": {
|
||||||
"editor.defaultFormatter": "Prisma.prisma"
|
"editor.defaultFormatter": "Prisma.prisma"
|
||||||
|
@ -12473,10 +12473,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"edge",
|
||||||
|
"nodejs"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"messages"
|
"messages",
|
||||||
|
"runtime"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lastBubbleBlockId": {
|
"lastBubbleBlockId": {
|
||||||
@ -12753,6 +12761,13 @@
|
|||||||
true
|
true
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"runtime": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"edge",
|
||||||
|
"nodejs"
|
||||||
|
]
|
||||||
|
},
|
||||||
"lastBubbleBlockId": {
|
"lastBubbleBlockId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -12762,7 +12777,8 @@
|
|||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"type",
|
"type",
|
||||||
"stream"
|
"stream",
|
||||||
|
"runtime"
|
||||||
],
|
],
|
||||||
"title": "Exec stream"
|
"title": "Exec stream"
|
||||||
},
|
},
|
||||||
|
@ -5,15 +5,18 @@ import { SessionState } from '@typebot.io/schemas'
|
|||||||
type Props = {
|
type Props = {
|
||||||
id?: string
|
id?: string
|
||||||
state: SessionState
|
state: SessionState
|
||||||
|
isReplying?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createSession = ({
|
export const createSession = ({
|
||||||
id,
|
id,
|
||||||
state,
|
state,
|
||||||
|
isReplying,
|
||||||
}: Props): Prisma.PrismaPromise<any> =>
|
}: Props): Prisma.PrismaPromise<any> =>
|
||||||
prisma.chatSession.create({
|
prisma.chatSession.create({
|
||||||
data: {
|
data: {
|
||||||
id,
|
id,
|
||||||
state,
|
state,
|
||||||
|
isReplying,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,7 @@ import { sessionStateSchema } from '@typebot.io/schemas'
|
|||||||
export const getSession = async (sessionId: string) => {
|
export const getSession = async (sessionId: string) => {
|
||||||
const session = await prisma.chatSession.findUnique({
|
const session = await prisma.chatSession.findUnique({
|
||||||
where: { id: sessionId },
|
where: { id: sessionId },
|
||||||
select: { id: true, state: true, updatedAt: true },
|
select: { id: true, state: true, updatedAt: true, isReplying: true },
|
||||||
})
|
})
|
||||||
if (!session) return null
|
if (!session) return null
|
||||||
return { ...session, state: sessionStateSchema.parse(session.state) }
|
return { ...session, state: sessionStateSchema.parse(session.state) }
|
||||||
|
20
packages/bot-engine/queries/setChatSessionHasReplying.ts
Normal file
20
packages/bot-engine/queries/setChatSessionHasReplying.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
existingSessionId: string | undefined
|
||||||
|
newSessionId: string
|
||||||
|
}
|
||||||
|
export const setChatSessionHasReplying = async ({
|
||||||
|
existingSessionId,
|
||||||
|
newSessionId,
|
||||||
|
}: Props) => {
|
||||||
|
if (existingSessionId) {
|
||||||
|
return prisma.chatSession.updateMany({
|
||||||
|
where: { id: existingSessionId },
|
||||||
|
data: { isReplying: true },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return prisma.chatSession.createMany({
|
||||||
|
data: { id: newSessionId, isReplying: true, state: {} },
|
||||||
|
})
|
||||||
|
}
|
@ -5,15 +5,18 @@ import { SessionState } from '@typebot.io/schemas'
|
|||||||
type Props = {
|
type Props = {
|
||||||
id: string
|
id: string
|
||||||
state: SessionState
|
state: SessionState
|
||||||
|
isReplying: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateSession = ({
|
export const updateSession = ({
|
||||||
id,
|
id,
|
||||||
state,
|
state,
|
||||||
|
isReplying,
|
||||||
}: Props): Prisma.PrismaPromise<any> =>
|
}: Props): Prisma.PrismaPromise<any> =>
|
||||||
prisma.chatSession.updateMany({
|
prisma.chatSession.updateMany({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
state,
|
state,
|
||||||
|
isReplying,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -16,7 +16,6 @@ type Props = {
|
|||||||
logs: ContinueChatResponse['logs']
|
logs: ContinueChatResponse['logs']
|
||||||
clientSideActions: ContinueChatResponse['clientSideActions']
|
clientSideActions: ContinueChatResponse['clientSideActions']
|
||||||
visitedEdges: VisitedEdge[]
|
visitedEdges: VisitedEdge[]
|
||||||
forceCreateSession?: boolean
|
|
||||||
hasCustomEmbedBubble?: boolean
|
hasCustomEmbedBubble?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +24,6 @@ export const saveStateToDatabase = async ({
|
|||||||
input,
|
input,
|
||||||
logs,
|
logs,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
forceCreateSession,
|
|
||||||
visitedEdges,
|
visitedEdges,
|
||||||
hasCustomEmbedBubble,
|
hasCustomEmbedBubble,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
@ -43,13 +41,12 @@ export const saveStateToDatabase = async ({
|
|||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
if (isCompleted && resultId) queries.push(deleteSession(id))
|
if (isCompleted && resultId) queries.push(deleteSession(id))
|
||||||
else queries.push(updateSession({ id, state }))
|
else queries.push(updateSession({ id, state, isReplying: false }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const session =
|
const session = id
|
||||||
id && !forceCreateSession
|
? { state, id }
|
||||||
? { state, id }
|
: await createSession({ id, state, isReplying: false })
|
||||||
: await createSession({ id, state })
|
|
||||||
|
|
||||||
if (!resultId) {
|
if (!resultId) {
|
||||||
if (queries.length > 0) await prisma.$transaction(queries)
|
if (queries.length > 0) await prisma.$transaction(queries)
|
||||||
|
@ -13,6 +13,7 @@ import { saveStateToDatabase } from '../saveStateToDatabase'
|
|||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
import { isDefined } from '@typebot.io/lib/utils'
|
import { isDefined } from '@typebot.io/lib/utils'
|
||||||
import { Reply } from '../types'
|
import { Reply } from '../types'
|
||||||
|
import { setChatSessionHasReplying } from '../queries/setChatSessionHasReplying'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
receivedMessage: WhatsAppIncomingMessage
|
receivedMessage: WhatsAppIncomingMessage
|
||||||
@ -67,6 +68,18 @@ export const resumeWhatsAppFlow = async ({
|
|||||||
|
|
||||||
const session = await getSession(sessionId)
|
const session = await getSession(sessionId)
|
||||||
|
|
||||||
|
if (session?.isReplying) {
|
||||||
|
console.log('Is currently replying, skipping...')
|
||||||
|
return {
|
||||||
|
message: 'Message received',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await setChatSessionHasReplying({
|
||||||
|
existingSessionId: session?.id,
|
||||||
|
newSessionId: sessionId,
|
||||||
|
})
|
||||||
|
|
||||||
const isSessionExpired =
|
const isSessionExpired =
|
||||||
session &&
|
session &&
|
||||||
isDefined(session.state.expiryTimeout) &&
|
isDefined(session.state.expiryTimeout) &&
|
||||||
@ -116,7 +129,6 @@ export const resumeWhatsAppFlow = async ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
await saveStateToDatabase({
|
await saveStateToDatabase({
|
||||||
forceCreateSession: !session && isDefined(input),
|
|
||||||
clientSideActions: [],
|
clientSideActions: [],
|
||||||
input,
|
input,
|
||||||
logs,
|
logs,
|
||||||
|
@ -349,10 +349,11 @@ model ClaimableCustomPlan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model ChatSession {
|
model ChatSession {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
state Json
|
state Json
|
||||||
|
isReplying Boolean?
|
||||||
}
|
}
|
||||||
|
|
||||||
model ThemeTemplate {
|
model ThemeTemplate {
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ChatSession" ADD COLUMN "isReplying" BOOLEAN;
|
@ -328,10 +328,11 @@ model ClaimableCustomPlan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model ChatSession {
|
model ChatSession {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
state Json
|
state Json
|
||||||
|
isReplying Boolean?
|
||||||
}
|
}
|
||||||
|
|
||||||
model ThemeTemplate {
|
model ThemeTemplate {
|
||||||
|
@ -28,13 +28,20 @@ import { preprocessTypebot } from '../typebot/helpers/preprocessTypebot'
|
|||||||
import { typebotV5Schema, typebotV6Schema } from '../typebot/typebot'
|
import { typebotV5Schema, typebotV6Schema } from '../typebot/typebot'
|
||||||
import { BubbleBlockType } from '../blocks/bubbles/constants'
|
import { BubbleBlockType } from '../blocks/bubbles/constants'
|
||||||
import { clientSideActionSchema } from './clientSideAction'
|
import { clientSideActionSchema } from './clientSideAction'
|
||||||
|
import { ChatSession as ChatSessionFromPrisma } from '@typebot.io/prisma'
|
||||||
|
|
||||||
const chatSessionSchema = z.object({
|
const chatSessionSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
state: sessionStateSchema,
|
state: sessionStateSchema,
|
||||||
})
|
isReplying: z
|
||||||
|
.boolean()
|
||||||
|
.nullable()
|
||||||
|
.describe(
|
||||||
|
'Used in WhatsApp runtime to avoid concurrent replies from the bot'
|
||||||
|
),
|
||||||
|
}) satisfies z.ZodType<ChatSessionFromPrisma, z.ZodTypeDef, unknown>
|
||||||
export type ChatSession = z.infer<typeof chatSessionSchema>
|
export type ChatSession = z.infer<typeof chatSessionSchema>
|
||||||
|
|
||||||
const textMessageSchema = z
|
const textMessageSchema = z
|
||||||
|
Reference in New Issue
Block a user