Skip to content

Commit

Permalink
⚡ Improve new bot engine client side actions
Browse files Browse the repository at this point in the history
We make sure to save client side actions in an array that will be executed sequentially
  • Loading branch information
baptisteArno committed Jan 26, 2023
1 parent 0fc82cf commit 9aab6dd
Show file tree
Hide file tree
Showing 15 changed files with 133 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,18 @@ const deleteOutgoingEdgeIdProps = (
const block = typebot.groups[fromGroupIndex].blocks[fromBlockIndex] as
| Block
| undefined
if (!block) return
const fromItemIndex =
edge.from.itemId && block && blockHasItems(block)
edge.from.itemId && blockHasItems(block)
? block.items.findIndex(byId(edge.from.itemId))
: -1
if (fromBlockIndex !== -1)
typebot.groups[fromGroupIndex].blocks[fromBlockIndex].outgoingEdgeId =
undefined
if (fromItemIndex !== -1)
(
if (fromItemIndex !== -1) {
;(
typebot.groups[fromGroupIndex].blocks[fromBlockIndex] as BlockWithItems
).items[fromItemIndex].outgoingEdgeId = undefined
} else if (fromBlockIndex !== -1)
typebot.groups[fromGroupIndex].blocks[fromBlockIndex].outgoingEdgeId =
undefined
}

export const cleanUpEdgeDraft = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,23 @@ export const executeChatwootBlock = (
const chatwootCode = parseChatwootOpenCode(block.options)
return {
outgoingEdgeId: block.outgoingEdgeId,
integrations: {
chatwoot: {
codeToExecute: {
content: parseVariables(variables, { fieldToParse: 'id' })(
chatwootCode
),
args: extractVariablesFromText(variables)(chatwootCode).map(
(variable) => ({
id: variable.id,
value: parseCorrectValueType(variable.value),
})
),
clientSideActions: [
{
chatwoot: {
codeToExecute: {
content: parseVariables(variables, { fieldToParse: 'id' })(
chatwootCode
),
args: extractVariablesFromText(variables)(chatwootCode).map(
(variable) => ({
id: variable.id,
value: parseCorrectValueType(variable.value),
})
),
},
},
},
},
],
logs: isPreview
? [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export const executeGoogleAnalyticsBlock = (
block: GoogleAnalyticsBlock
): ExecuteIntegrationResponse => ({
outgoingEdgeId: block.outgoingEdgeId,
integrations: {
googleAnalytics: deepParseVariable(variables)(block.options),
},
clientSideActions: [
{
googleAnalytics: deepParseVariable(variables)(block.options),
},
],
})
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export const insertRow = async (
options,
}: { outgoingEdgeId?: string; options: GoogleSheetsInsertRowOptions }
): Promise<ExecuteIntegrationResponse> => {
console.log('insertRow', options)
if (!options.cellsToInsert || !options.sheetId) return { outgoingEdgeId }

let log: ReplyLog | undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ export const executeCode = (

return {
outgoingEdgeId: block.outgoingEdgeId,
logic: {
codeToExecute: {
content,
args,
clientSideActions: [
{
codeToExecute: {
content,
args,
},
},
},
],
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ export const executeRedirect = (
if (!block.options?.url) return { outgoingEdgeId: block.outgoingEdgeId }
const formattedUrl = sanitizeUrl(parseVariables(variables)(block.options.url))
return {
logic: {
redirect: { url: formattedUrl, isNewTab: block.options.isNewTab },
},
clientSideActions: [
{
redirect: { url: formattedUrl, isNewTab: block.options.isNewTab },
},
],
outgoingEdgeId: block.outgoingEdgeId,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const sendMessageProcedure = publicProcedure
resultId,
dynamicTheme,
logs,
clientSideActions,
} = await startSession(startParams)
return {
sessionId,
Expand All @@ -63,9 +64,10 @@ export const sendMessageProcedure = publicProcedure
resultId,
dynamicTheme,
logs,
clientSideActions,
}
} else {
const { messages, input, logic, newSessionState, integrations, logs } =
const { messages, input, clientSideActions, newSessionState, logs } =
await continueBotFlow(session.state)(message)

await prisma.chatSession.updateMany({
Expand All @@ -78,8 +80,7 @@ export const sendMessageProcedure = publicProcedure
return {
messages,
input,
logic,
integrations,
clientSideActions,
dynamicTheme: parseDynamicThemeReply(newSessionState),
logs,
}
Expand Down Expand Up @@ -133,15 +134,15 @@ const startSession = async (startParams?: StartParams) => {
const {
messages,
input,
logic,
clientSideActions,
newSessionState: newInitialState,
logs,
} = await startBotFlow(initialState, startParams.startGroupId)

if (!input)
return {
messages,
logic,
clientSideActions,
typebot: {
id: typebot.id,
settings: deepParseVariable(newInitialState.typebot.variables)(
Expand Down Expand Up @@ -183,7 +184,7 @@ const startSession = async (startParams?: StartParams) => {
},
messages,
input,
logic,
clientSideActions,
dynamicTheme: parseDynamicThemeReply(newInitialState),
logs,
} satisfies ChatReply
Expand Down
26 changes: 14 additions & 12 deletions apps/viewer/src/features/chat/api/utils/executeGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export const executeGroup =
group: Group
): Promise<ChatReply & { newSessionState: SessionState }> => {
const messages: ChatReply['messages'] = currentReply?.messages ?? []
let logic: ChatReply['logic'] = currentReply?.logic
let integrations: ChatReply['integrations'] = currentReply?.integrations
let clientSideActions: ChatReply['clientSideActions'] =
currentReply?.clientSideActions
let logs: ChatReply['logs'] = currentReply?.logs
let nextEdgeId = null

Expand Down Expand Up @@ -59,8 +59,7 @@ export const executeGroup =
blockId: block.id,
},
},
logic,
integrations,
clientSideActions,
logs,
}
const executionResponse = isLogicBlock(block)
Expand All @@ -70,10 +69,14 @@ export const executeGroup =
: null

if (!executionResponse) continue
if ('logic' in executionResponse && executionResponse.logic)
logic = { ...logic, ...executionResponse.logic }
if ('integrations' in executionResponse && executionResponse.integrations)
integrations = { ...integrations, ...executionResponse.integrations }
if (
'clientSideActions' in executionResponse &&
executionResponse.clientSideActions
)
clientSideActions = [
...(clientSideActions ?? []),
...executionResponse.clientSideActions,
]
if (executionResponse.logs)
logs = [...(logs ?? []), ...executionResponse.logs]
if (executionResponse.newSessionState)
Expand All @@ -85,20 +88,19 @@ export const executeGroup =
}

if (!nextEdgeId)
return { messages, newSessionState, logic, integrations, logs }
return { messages, newSessionState, clientSideActions, logs }

const nextGroup = getNextGroup(newSessionState)(nextEdgeId)

if (nextGroup?.updatedContext) newSessionState = nextGroup.updatedContext

if (!nextGroup) {
return { messages, newSessionState, logic, integrations, logs }
return { messages, newSessionState, clientSideActions, logs }
}

return executeGroup(newSessionState, {
messages,
logic,
integrations,
clientSideActions,
logs,
})(nextGroup.group)
}
Expand Down
4 changes: 2 additions & 2 deletions apps/viewer/src/features/chat/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ export type EdgeId = string
export type ExecuteLogicResponse = {
outgoingEdgeId: EdgeId | undefined
newSessionState?: SessionState
} & Pick<ChatReply, 'logic' | 'logs'>
} & Pick<ChatReply, 'clientSideActions' | 'logs'>

export type ExecuteIntegrationResponse = {
outgoingEdgeId: EdgeId | undefined
newSessionState?: SessionState
} & Pick<ChatReply, 'integrations' | 'logs'>
} & Pick<ChatReply, 'clientSideActions' | 'logs'>
10 changes: 7 additions & 3 deletions packages/js/src/components/ConversationContainer/ChatChunk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ type Props = Pick<ChatReply, 'messages' | 'input'> & {
context: BotContext
onScrollToBottom: () => void
onSubmit: (input: string) => void
onEnd?: () => void
onSkip: () => void
onAllBubblesDisplayed: () => void
}

export const ChatChunk = (props: Props) => {
const [displayedMessageIndex, setDisplayedMessageIndex] = createSignal(0)

onMount(() => {
if (props.messages.length === 0) {
props.onAllBubblesDisplayed()
}
props.onScrollToBottom()
})

Expand All @@ -30,8 +33,9 @@ export const ChatChunk = (props: Props) => {
: displayedMessageIndex() + 1
)
props.onScrollToBottom()
if (!props.input && displayedMessageIndex() === props.messages.length)
return props.onEnd?.()
if (displayedMessageIndex() === props.messages.length) {
props.onAllBubblesDisplayed()
}
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { createEffect, createSignal, For } from 'solid-js'
import { sendMessageQuery } from '@/queries/sendMessageQuery'
import { ChatChunk } from './ChatChunk'
import { BotContext, InitialChatReply } from '@/types'
import { executeIntegrations } from '@/utils/executeIntegrations'
import { executeLogic } from '@/utils/executeLogic'
import { isNotDefined } from 'utils'
import { executeClientSideAction } from '@/utils/executeClientSideActions'

const parseDynamicTheme = (
initialTheme: Theme,
Expand Down Expand Up @@ -42,10 +42,13 @@ type Props = {
export const ConversationContainer = (props: Props) => {
let chatContainer: HTMLDivElement | undefined
let bottomSpacer: HTMLDivElement | undefined
const [chatChunks, setChatChunks] = createSignal<ChatReply[]>([
const [chatChunks, setChatChunks] = createSignal<
Pick<ChatReply, 'messages' | 'input' | 'clientSideActions'>[]
>([
{
input: props.initialChatReply.input,
messages: props.initialChatReply.messages,
clientSideActions: props.initialChatReply.clientSideActions,
},
])
const [dynamicTheme, setDynamicTheme] = createSignal<
Expand Down Expand Up @@ -77,17 +80,12 @@ export const ConversationContainer = (props: Props) => {
groupId: data.input.groupId,
})
}
if (data.integrations) {
executeIntegrations(data.integrations)
}
if (data.logic) {
await executeLogic(data.logic)
}
setChatChunks((displayedChunks) => [
...displayedChunks,
{
input: data.input,
messages: data.messages,
clientSideActions: data.clientSideActions,
},
])
}
Expand All @@ -99,6 +97,19 @@ export const ConversationContainer = (props: Props) => {
}, 50)
}

const handleAllBubblesDisplayed = async () => {
const lastChunk = chatChunks().at(-1)
if (!lastChunk) return
if (lastChunk.clientSideActions) {
for (const action of lastChunk.clientSideActions) {
await executeClientSideAction(action)
}
}
if (isNotDefined(lastChunk.input)) {
props.onEnd?.()
}
}

return (
<div
ref={chatContainer}
Expand All @@ -112,12 +123,12 @@ export const ConversationContainer = (props: Props) => {
input={chatChunk.input}
theme={theme()}
settings={props.initialChatReply.typebot.settings}
onAllBubblesDisplayed={handleAllBubblesDisplayed}
onSubmit={sendMessage}
onScrollToBottom={autoScrollToBottom}
onSkip={() => {
// TODO: implement skip
}}
onEnd={props.onEnd}
context={props.context}
/>
)}
Expand Down
22 changes: 22 additions & 0 deletions packages/js/src/utils/executeClientSideActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { executeChatwoot } from '@/features/blocks/integrations/chatwoot'
import { executeGoogleAnalyticsBlock } from '@/features/blocks/integrations/googleAnalytics/utils/executeGoogleAnalytics'
import { executeCode } from '@/features/blocks/logic/code'
import { executeRedirect } from '@/features/blocks/logic/redirect'
import type { ChatReply } from 'models'

export const executeClientSideAction = async (
clientSideAction: NonNullable<ChatReply['clientSideActions']>[0]
) => {
if ('chatwoot' in clientSideAction) {
executeChatwoot(clientSideAction.chatwoot)
}
if ('googleAnalytics' in clientSideAction) {
executeGoogleAnalyticsBlock(clientSideAction.googleAnalytics)
}
if ('codeToExecute' in clientSideAction) {
await executeCode(clientSideAction.codeToExecute)
}
if ('redirect' in clientSideAction) {
executeRedirect(clientSideAction.redirect)
}
}
14 changes: 0 additions & 14 deletions packages/js/src/utils/executeIntegrations.ts

This file was deleted.

Loading

0 comments on commit 9aab6dd

Please sign in to comment.