@@ -13,8 +13,9 @@ import { filterChoiceItems } from './filterChoiceItems'
|
||||
export const injectVariableValuesInButtonsInputBlock =
|
||||
(state: SessionState) =>
|
||||
(block: ChoiceInputBlock): ChoiceInputBlock => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (block.options.dynamicVariableId) {
|
||||
const variable = state.typebot.variables.find(
|
||||
const variable = variables.find(
|
||||
(variable) =>
|
||||
variable.id === block.options.dynamicVariableId &&
|
||||
isDefined(variable.value)
|
||||
@@ -31,18 +32,17 @@ export const injectVariableValuesInButtonsInputBlock =
|
||||
})),
|
||||
}
|
||||
}
|
||||
return deepParseVariables(state.typebot.variables)(
|
||||
filterChoiceItems(state.typebot.variables)(block)
|
||||
)
|
||||
return deepParseVariables(variables)(filterChoiceItems(variables)(block))
|
||||
}
|
||||
|
||||
const getVariableValue =
|
||||
(state: SessionState) =>
|
||||
(variable: VariableWithValue): (string | null)[] => {
|
||||
if (!Array.isArray(variable.value)) {
|
||||
const [transformedVariable] = transformStringVariablesToList(
|
||||
state.typebot.variables
|
||||
)([variable.id])
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
const [transformedVariable] = transformStringVariablesToList(variables)([
|
||||
variable.id,
|
||||
])
|
||||
updateVariables(state)([transformedVariable])
|
||||
return transformedVariable.value as string[]
|
||||
}
|
||||
|
||||
@@ -11,18 +11,17 @@ import Stripe from 'stripe'
|
||||
import { decrypt } from '@typebot.io/lib/api/encryption'
|
||||
|
||||
export const computePaymentInputRuntimeOptions =
|
||||
(state: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||
(options: PaymentInputOptions) =>
|
||||
(state: SessionState) => (options: PaymentInputOptions) =>
|
||||
createStripePaymentIntent(state)(options)
|
||||
|
||||
const createStripePaymentIntent =
|
||||
(state: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||
(state: SessionState) =>
|
||||
async (options: PaymentInputOptions): Promise<PaymentInputRuntimeOptions> => {
|
||||
const {
|
||||
result,
|
||||
resultId,
|
||||
typebot: { variables },
|
||||
} = state
|
||||
const isPreview = !result.id
|
||||
} = state.typebotsQueue[0]
|
||||
const isPreview = !resultId
|
||||
if (!options.credentialsId)
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import {
|
||||
SessionState,
|
||||
VariableWithValue,
|
||||
ItemType,
|
||||
PictureChoiceBlock,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { deepParseVariables } from '@/features/variables/deepParseVariable'
|
||||
import { filterPictureChoiceItems } from './filterPictureChoiceItems'
|
||||
|
||||
export const injectVariableValuesInPictureChoiceBlock =
|
||||
(variables: SessionState['typebot']['variables']) =>
|
||||
(variables: Variable[]) =>
|
||||
(block: PictureChoiceBlock): PictureChoiceBlock => {
|
||||
if (
|
||||
block.options.dynamicItems?.isEnabled &&
|
||||
|
||||
@@ -70,17 +70,18 @@ if (window.$chatwoot) {
|
||||
`
|
||||
|
||||
export const executeChatwootBlock = (
|
||||
{ typebot, result }: SessionState,
|
||||
state: SessionState,
|
||||
block: ChatwootBlock
|
||||
): ExecuteIntegrationResponse => {
|
||||
const { typebot, resultId } = state.typebotsQueue[0]
|
||||
const chatwootCode =
|
||||
block.options.task === 'Close widget'
|
||||
? chatwootCloseCode
|
||||
: isDefined(result.id)
|
||||
: isDefined(resultId)
|
||||
? parseChatwootOpenCode({
|
||||
...block.options,
|
||||
typebotId: typebot.id,
|
||||
resultId: result.id,
|
||||
resultId,
|
||||
})
|
||||
: ''
|
||||
return {
|
||||
|
||||
@@ -3,11 +3,12 @@ import { deepParseVariables } from '@/features/variables/deepParseVariable'
|
||||
import { GoogleAnalyticsBlock, SessionState } from '@typebot.io/schemas'
|
||||
|
||||
export const executeGoogleAnalyticsBlock = (
|
||||
{ typebot: { variables }, result }: SessionState,
|
||||
state: SessionState,
|
||||
block: GoogleAnalyticsBlock
|
||||
): ExecuteIntegrationResponse => {
|
||||
if (!result) return { outgoingEdgeId: block.outgoingEdgeId }
|
||||
const googleAnalytics = deepParseVariables(variables, {
|
||||
const { typebot, resultId } = state.typebotsQueue[0]
|
||||
if (!resultId) return { outgoingEdgeId: block.outgoingEdgeId }
|
||||
const googleAnalytics = deepParseVariables(typebot.variables, {
|
||||
guessCorrectTypes: true,
|
||||
removeEmptyStrings: true,
|
||||
})(block.options)
|
||||
|
||||
@@ -19,13 +19,11 @@ export const getRow = async (
|
||||
}: { outgoingEdgeId?: string; options: GoogleSheetsGetOptions }
|
||||
): Promise<ExecuteIntegrationResponse> => {
|
||||
const logs: ReplyLog[] = []
|
||||
const { sheetId, cellsToExtract, referenceCell, filter } = deepParseVariables(
|
||||
state.typebot.variables
|
||||
)(options)
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
const { sheetId, cellsToExtract, referenceCell, filter } =
|
||||
deepParseVariables(variables)(options)
|
||||
if (!sheetId) return { outgoingEdgeId }
|
||||
|
||||
const variables = state.typebot.variables
|
||||
|
||||
const doc = await getAuthenticatedGoogleDoc({
|
||||
credentialsId: options.credentialsId,
|
||||
spreadsheetId: options.spreadsheetId,
|
||||
|
||||
@@ -8,12 +8,13 @@ import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
||||
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||
|
||||
export const insertRow = async (
|
||||
{ typebot: { variables } }: SessionState,
|
||||
state: SessionState,
|
||||
{
|
||||
outgoingEdgeId,
|
||||
options,
|
||||
}: { outgoingEdgeId?: string; options: GoogleSheetsInsertRowOptions }
|
||||
): Promise<ExecuteIntegrationResponse> => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (!options.cellsToInsert || !options.sheetId) return { outgoingEdgeId }
|
||||
|
||||
const logs: ReplyLog[] = []
|
||||
|
||||
@@ -10,12 +10,13 @@ import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||
import { matchFilter } from './helpers/matchFilter'
|
||||
|
||||
export const updateRow = async (
|
||||
{ typebot: { variables } }: SessionState,
|
||||
state: SessionState,
|
||||
{
|
||||
outgoingEdgeId,
|
||||
options,
|
||||
}: { outgoingEdgeId?: string; options: GoogleSheetsUpdateRowOptions }
|
||||
): Promise<ExecuteIntegrationResponse> => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
const { sheetId, referenceCell, filter } =
|
||||
deepParseVariables(variables)(options)
|
||||
if (!options.cellsToUpsert || !sheetId || (!referenceCell && !filter))
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { Block, BubbleBlockType, SessionState } from '@typebot.io/schemas'
|
||||
import {
|
||||
Block,
|
||||
BubbleBlockType,
|
||||
SessionState,
|
||||
TypebotInSession,
|
||||
} from '@typebot.io/schemas'
|
||||
import {
|
||||
ChatCompletionOpenAIOptions,
|
||||
OpenAICredentials,
|
||||
@@ -51,13 +56,16 @@ export const createChatCompletionOpenAI = async (
|
||||
credentials.data,
|
||||
credentials.iv
|
||||
)) as OpenAICredentials['data']
|
||||
|
||||
const { typebot } = newSessionState.typebotsQueue[0]
|
||||
|
||||
const { variablesTransformedToList, messages } = parseChatCompletionMessages(
|
||||
newSessionState.typebot.variables
|
||||
typebot.variables
|
||||
)(options.messages)
|
||||
if (variablesTransformedToList.length > 0)
|
||||
newSessionState = updateVariables(state)(variablesTransformedToList)
|
||||
|
||||
const temperature = parseVariableNumber(newSessionState.typebot.variables)(
|
||||
const temperature = parseVariableNumber(typebot.variables)(
|
||||
options.advancedSettings?.temperature
|
||||
)
|
||||
|
||||
@@ -66,7 +74,7 @@ export const createChatCompletionOpenAI = async (
|
||||
isCredentialsV2(credentials) &&
|
||||
newSessionState.isStreamEnabled
|
||||
) {
|
||||
const assistantMessageVariableName = state.typebot.variables.find(
|
||||
const assistantMessageVariableName = typebot.variables.find(
|
||||
(variable) =>
|
||||
options.responseMapping.find(
|
||||
(m) => m.valueToExtract === 'Message content'
|
||||
@@ -81,9 +89,10 @@ export const createChatCompletionOpenAI = async (
|
||||
content?: string
|
||||
role: (typeof chatCompletionMessageRoles)[number]
|
||||
}[],
|
||||
displayStream: isNextBubbleMessageWithAssistantMessage(
|
||||
state.typebot
|
||||
)(blockId, assistantMessageVariableName),
|
||||
displayStream: isNextBubbleMessageWithAssistantMessage(typebot)(
|
||||
blockId,
|
||||
assistantMessageVariableName
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -117,7 +126,7 @@ export const createChatCompletionOpenAI = async (
|
||||
}
|
||||
|
||||
const isNextBubbleMessageWithAssistantMessage =
|
||||
(typebot: SessionState['typebot']) =>
|
||||
(typebot: TypebotInSession) =>
|
||||
(blockId: string, assistantVariableName?: string): boolean => {
|
||||
if (!assistantVariableName) return false
|
||||
const nextBlock = getNextBlock(typebot)(blockId)
|
||||
@@ -131,7 +140,7 @@ const isNextBubbleMessageWithAssistantMessage =
|
||||
}
|
||||
|
||||
const getNextBlock =
|
||||
(typebot: SessionState['typebot']) =>
|
||||
(typebot: TypebotInSession) =>
|
||||
(blockId: string): Block | undefined => {
|
||||
const group = typebot.groups.find((group) =>
|
||||
group.blocks.find(byId(blockId))
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ChatCompletionOpenAIOptions,
|
||||
OpenAICredentials,
|
||||
} from '@typebot.io/schemas/features/blocks/integrations/openai'
|
||||
import { SessionState } from '@typebot.io/schemas/features/chat'
|
||||
import { SessionState } from '@typebot.io/schemas/features/chat/sessionState'
|
||||
import { OpenAIStream } from 'ai'
|
||||
import {
|
||||
ChatCompletionRequestMessage,
|
||||
@@ -35,7 +35,8 @@ export const getChatCompletionStream =
|
||||
credentials.iv
|
||||
)) as OpenAICredentials['data']
|
||||
|
||||
const temperature = parseVariableNumber(state.typebot.variables)(
|
||||
const { typebot } = state.typebotsQueue[0]
|
||||
const temperature = parseVariableNumber(typebot.variables)(
|
||||
options.advancedSettings?.temperature
|
||||
)
|
||||
|
||||
|
||||
@@ -22,9 +22,8 @@ export const resumeChatCompletion =
|
||||
const newVariables = options.responseMapping.reduce<
|
||||
VariableWithUnknowValue[]
|
||||
>((newVariables, mapping) => {
|
||||
const existingVariable = newSessionState.typebot.variables.find(
|
||||
byId(mapping.variableId)
|
||||
)
|
||||
const { typebot } = newSessionState.typebotsQueue[0]
|
||||
const existingVariable = typebot.variables.find(byId(mapping.variableId))
|
||||
if (!existingVariable) return newVariables
|
||||
if (mapping.valueToExtract === 'Message content') {
|
||||
newVariables.push({
|
||||
|
||||
@@ -3,12 +3,13 @@ import { deepParseVariables } from '@/features/variables/deepParseVariable'
|
||||
import { PixelBlock, SessionState } from '@typebot.io/schemas'
|
||||
|
||||
export const executePixelBlock = (
|
||||
{ typebot: { variables }, result }: SessionState,
|
||||
state: SessionState,
|
||||
block: PixelBlock
|
||||
): ExecuteIntegrationResponse => {
|
||||
if (!result || !block.options.pixelId || !block.options.eventType)
|
||||
const { typebot, resultId } = state.typebotsQueue[0]
|
||||
if (!resultId || !block.options.pixelId || !block.options.eventType)
|
||||
return { outgoingEdgeId: block.outgoingEdgeId }
|
||||
const pixel = deepParseVariables(variables, {
|
||||
const pixel = deepParseVariables(typebot.variables, {
|
||||
guessCorrectTypes: true,
|
||||
removeEmptyStrings: true,
|
||||
})(block.options)
|
||||
|
||||
@@ -3,32 +3,32 @@ import prisma from '@/lib/prisma'
|
||||
import { render } from '@faire/mjml-react/utils/render'
|
||||
import { DefaultBotNotificationEmail } from '@typebot.io/emails'
|
||||
import {
|
||||
PublicTypebot,
|
||||
AnswerInSessionState,
|
||||
ReplyLog,
|
||||
ResultInSession,
|
||||
SendEmailBlock,
|
||||
SendEmailOptions,
|
||||
SessionState,
|
||||
SmtpCredentials,
|
||||
TypebotInSession,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
import { createTransport } from 'nodemailer'
|
||||
import Mail from 'nodemailer/lib/mailer'
|
||||
import { byId, isDefined, isEmpty, isNotDefined, omit } from '@typebot.io/lib'
|
||||
import { parseAnswers } from '@typebot.io/lib/results'
|
||||
import { getDefinedVariables, parseAnswers } from '@typebot.io/lib/results'
|
||||
import { decrypt } from '@typebot.io/lib/api'
|
||||
import { defaultFrom, defaultTransportOptions } from './constants'
|
||||
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||
import { findUniqueVariableValue } from '../../../variables/findUniqueVariableValue'
|
||||
|
||||
export const executeSendEmailBlock = async (
|
||||
{ result, typebot }: SessionState,
|
||||
state: SessionState,
|
||||
block: SendEmailBlock
|
||||
): Promise<ExecuteIntegrationResponse> => {
|
||||
const logs: ReplyLog[] = []
|
||||
const { options } = block
|
||||
const { variables } = typebot
|
||||
const isPreview = !result.id
|
||||
const { typebot, resultId, answers } = state.typebotsQueue[0]
|
||||
const isPreview = !resultId
|
||||
if (isPreview)
|
||||
return {
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
@@ -41,23 +41,23 @@ export const executeSendEmailBlock = async (
|
||||
}
|
||||
|
||||
const body =
|
||||
findUniqueVariableValue(variables)(options.body)?.toString() ??
|
||||
parseVariables(variables, { escapeHtml: true })(options.body ?? '')
|
||||
findUniqueVariableValue(typebot.variables)(options.body)?.toString() ??
|
||||
parseVariables(typebot.variables, { escapeHtml: true })(options.body ?? '')
|
||||
|
||||
try {
|
||||
const sendEmailLogs = await sendEmail({
|
||||
typebotId: typebot.id,
|
||||
result,
|
||||
typebot,
|
||||
answers,
|
||||
credentialsId: options.credentialsId,
|
||||
recipients: options.recipients.map(parseVariables(variables)),
|
||||
subject: parseVariables(variables)(options.subject ?? ''),
|
||||
recipients: options.recipients.map(parseVariables(typebot.variables)),
|
||||
subject: parseVariables(typebot.variables)(options.subject ?? ''),
|
||||
body,
|
||||
cc: (options.cc ?? []).map(parseVariables(variables)),
|
||||
bcc: (options.bcc ?? []).map(parseVariables(variables)),
|
||||
cc: (options.cc ?? []).map(parseVariables(typebot.variables)),
|
||||
bcc: (options.bcc ?? []).map(parseVariables(typebot.variables)),
|
||||
replyTo: options.replyTo
|
||||
? parseVariables(variables)(options.replyTo)
|
||||
? parseVariables(typebot.variables)(options.replyTo)
|
||||
: undefined,
|
||||
fileUrls: getFileUrls(variables)(options.attachmentsVariableId),
|
||||
fileUrls: getFileUrls(typebot.variables)(options.attachmentsVariableId),
|
||||
isCustomBody: options.isCustomBody,
|
||||
isBodyCode: options.isBodyCode,
|
||||
})
|
||||
@@ -74,8 +74,8 @@ export const executeSendEmailBlock = async (
|
||||
}
|
||||
|
||||
const sendEmail = async ({
|
||||
typebotId,
|
||||
result,
|
||||
typebot,
|
||||
answers,
|
||||
credentialsId,
|
||||
recipients,
|
||||
body,
|
||||
@@ -87,8 +87,8 @@ const sendEmail = async ({
|
||||
isCustomBody,
|
||||
fileUrls,
|
||||
}: SendEmailOptions & {
|
||||
typebotId: string
|
||||
result: ResultInSession
|
||||
typebot: TypebotInSession
|
||||
answers: AnswerInSessionState[]
|
||||
fileUrls?: string | string[]
|
||||
}): Promise<ReplyLog[] | undefined> => {
|
||||
const logs: ReplyLog[] = []
|
||||
@@ -112,8 +112,8 @@ const sendEmail = async ({
|
||||
body,
|
||||
isCustomBody,
|
||||
isBodyCode,
|
||||
typebotId,
|
||||
result,
|
||||
typebot,
|
||||
answersInSession: answers,
|
||||
})
|
||||
|
||||
if (!emailBody) {
|
||||
@@ -206,11 +206,11 @@ const getEmailBody = async ({
|
||||
body,
|
||||
isCustomBody,
|
||||
isBodyCode,
|
||||
typebotId,
|
||||
result,
|
||||
typebot,
|
||||
answersInSession,
|
||||
}: {
|
||||
typebotId: string
|
||||
result: ResultInSession
|
||||
typebot: TypebotInSession
|
||||
answersInSession: AnswerInSessionState[]
|
||||
} & Pick<SendEmailOptions, 'isCustomBody' | 'isBodyCode' | 'body'>): Promise<
|
||||
{ html?: string; text?: string } | undefined
|
||||
> => {
|
||||
@@ -219,11 +219,10 @@ const getEmailBody = async ({
|
||||
html: isBodyCode ? body : undefined,
|
||||
text: !isBodyCode ? body : undefined,
|
||||
}
|
||||
const typebot = (await prisma.publicTypebot.findUnique({
|
||||
where: { typebotId },
|
||||
})) as unknown as PublicTypebot
|
||||
if (!typebot) return
|
||||
const answers = parseAnswers(typebot, [])(result)
|
||||
const answers = parseAnswers({
|
||||
variables: getDefinedVariables(typebot.variables),
|
||||
answers: answersInSession,
|
||||
})
|
||||
return {
|
||||
html: render(
|
||||
<DefaultBotNotificationEmail
|
||||
|
||||
@@ -6,22 +6,19 @@ import {
|
||||
PabblyConnectBlock,
|
||||
SessionState,
|
||||
Webhook,
|
||||
Typebot,
|
||||
Variable,
|
||||
WebhookResponse,
|
||||
WebhookOptions,
|
||||
defaultWebhookAttributes,
|
||||
PublicTypebot,
|
||||
KeyValue,
|
||||
ReplyLog,
|
||||
ResultInSession,
|
||||
ExecutableWebhook,
|
||||
AnswerInSessionState,
|
||||
} from '@typebot.io/schemas'
|
||||
import { stringify } from 'qs'
|
||||
import { omit } from '@typebot.io/lib'
|
||||
import { parseAnswers } from '@typebot.io/lib/results'
|
||||
import { getDefinedVariables, parseAnswers } from '@typebot.io/lib/results'
|
||||
import got, { Method, HTTPError, OptionsInit } from 'got'
|
||||
import { parseSampleResult } from './parseSampleResult'
|
||||
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||
import { parseVariables } from '@/features/variables/parseVariables'
|
||||
import { resumeWebhookExecution } from './resumeWebhookExecution'
|
||||
@@ -36,7 +33,6 @@ export const executeWebhookBlock = async (
|
||||
state: SessionState,
|
||||
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
||||
): Promise<ExecuteIntegrationResponse> => {
|
||||
const { typebot, result } = state
|
||||
const logs: ReplyLog[] = []
|
||||
const webhook =
|
||||
block.options.webhook ??
|
||||
@@ -52,9 +48,8 @@ export const executeWebhookBlock = async (
|
||||
}
|
||||
const preparedWebhook = prepareWebhookAttributes(webhook, block.options)
|
||||
const parsedWebhook = await parseWebhookAttributes(
|
||||
typebot,
|
||||
block.groupId,
|
||||
result
|
||||
state,
|
||||
state.typebotsQueue[0].answers
|
||||
)(preparedWebhook)
|
||||
if (!parsedWebhook) {
|
||||
logs.push({
|
||||
@@ -97,14 +92,10 @@ const prepareWebhookAttributes = (
|
||||
const checkIfBodyIsAVariable = (body: string) => /^{{.+}}$/.test(body)
|
||||
|
||||
const parseWebhookAttributes =
|
||||
(
|
||||
typebot: SessionState['typebot'],
|
||||
groupId: string,
|
||||
result: ResultInSession
|
||||
) =>
|
||||
(state: SessionState, answers: AnswerInSessionState[]) =>
|
||||
async (webhook: Webhook): Promise<ParsedWebhook | undefined> => {
|
||||
if (!webhook.url || !webhook.method) return
|
||||
const { variables } = typebot
|
||||
const { typebot } = state.typebotsQueue[0]
|
||||
const basicAuth: { username?: string; password?: string } = {}
|
||||
const basicAuthHeaderIdx = webhook.headers.findIndex(
|
||||
(h) =>
|
||||
@@ -121,32 +112,29 @@ const parseWebhookAttributes =
|
||||
basicAuth.password = password
|
||||
webhook.headers.splice(basicAuthHeaderIdx, 1)
|
||||
}
|
||||
const headers = convertKeyValueTableToObject(webhook.headers, variables) as
|
||||
| ExecutableWebhook['headers']
|
||||
| undefined
|
||||
const headers = convertKeyValueTableToObject(
|
||||
webhook.headers,
|
||||
typebot.variables
|
||||
) as ExecutableWebhook['headers'] | undefined
|
||||
const queryParams = stringify(
|
||||
convertKeyValueTableToObject(webhook.queryParams, variables)
|
||||
convertKeyValueTableToObject(webhook.queryParams, typebot.variables)
|
||||
)
|
||||
const bodyContent = await getBodyContent(
|
||||
typebot,
|
||||
[]
|
||||
)({
|
||||
const bodyContent = await getBodyContent({
|
||||
body: webhook.body,
|
||||
result,
|
||||
groupId,
|
||||
variables,
|
||||
answers,
|
||||
variables: typebot.variables,
|
||||
})
|
||||
const { data: body, isJson } =
|
||||
bodyContent && webhook.method !== HttpMethod.GET
|
||||
? safeJsonParse(
|
||||
parseVariables(variables, {
|
||||
parseVariables(typebot.variables, {
|
||||
escapeForJson: !checkIfBodyIsAVariable(bodyContent),
|
||||
})(bodyContent)
|
||||
)
|
||||
: { data: undefined, isJson: false }
|
||||
|
||||
return {
|
||||
url: parseVariables(variables)(
|
||||
url: parseVariables(typebot.variables)(
|
||||
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
|
||||
),
|
||||
basicAuth,
|
||||
@@ -229,34 +217,25 @@ export const executeWebhook = async (
|
||||
}
|
||||
}
|
||||
|
||||
const getBodyContent =
|
||||
(
|
||||
typebot: Pick<Typebot | PublicTypebot, 'groups' | 'variables' | 'edges'>,
|
||||
linkedTypebots: (Typebot | PublicTypebot)[]
|
||||
) =>
|
||||
async ({
|
||||
body,
|
||||
result,
|
||||
groupId,
|
||||
variables,
|
||||
}: {
|
||||
body?: string | null
|
||||
result?: ResultInSession
|
||||
groupId: string
|
||||
variables: Variable[]
|
||||
}): Promise<string | undefined> => {
|
||||
if (!body) return
|
||||
return body === '{{state}}'
|
||||
? JSON.stringify(
|
||||
result
|
||||
? parseAnswers(typebot, linkedTypebots)(result)
|
||||
: await parseSampleResult(typebot, linkedTypebots)(
|
||||
groupId,
|
||||
variables
|
||||
)
|
||||
)
|
||||
: body
|
||||
}
|
||||
const getBodyContent = async ({
|
||||
body,
|
||||
answers,
|
||||
variables,
|
||||
}: {
|
||||
body?: string | null
|
||||
answers: AnswerInSessionState[]
|
||||
variables: Variable[]
|
||||
}): Promise<string | undefined> => {
|
||||
if (!body) return
|
||||
return body === '{{state}}'
|
||||
? JSON.stringify(
|
||||
parseAnswers({
|
||||
answers,
|
||||
variables: getDefinedVariables(variables),
|
||||
})
|
||||
)
|
||||
: body
|
||||
}
|
||||
|
||||
const convertKeyValueTableToObject = (
|
||||
keyValues: KeyValue[] | undefined,
|
||||
|
||||
@@ -5,11 +5,12 @@ import { byId } from '@typebot.io/lib'
|
||||
import {
|
||||
MakeComBlock,
|
||||
PabblyConnectBlock,
|
||||
ReplyLog,
|
||||
VariableWithUnknowValue,
|
||||
WebhookBlock,
|
||||
ZapierBlock,
|
||||
} from '@typebot.io/schemas'
|
||||
import { ReplyLog, SessionState } from '@typebot.io/schemas/features/chat'
|
||||
import { SessionState } from '@typebot.io/schemas/features/chat/sessionState'
|
||||
|
||||
type Props = {
|
||||
state: SessionState
|
||||
@@ -27,7 +28,7 @@ export const resumeWebhookExecution = ({
|
||||
logs = [],
|
||||
response,
|
||||
}: Props): ExecuteIntegrationResponse => {
|
||||
const { typebot } = state
|
||||
const { typebot } = state.typebotsQueue[0]
|
||||
const status = response.statusCode.toString()
|
||||
const isError = status.startsWith('4') || status.startsWith('5')
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ import { ExecuteLogicResponse } from '@/features/chat/types'
|
||||
import { executeCondition } from './executeCondition'
|
||||
|
||||
export const executeConditionBlock = (
|
||||
{ typebot: { variables } }: SessionState,
|
||||
state: SessionState,
|
||||
block: ConditionBlock
|
||||
): ExecuteLogicResponse => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
const passedCondition = block.items.find((item) =>
|
||||
executeCondition(variables)(item.content)
|
||||
)
|
||||
|
||||
@@ -11,9 +11,8 @@ export const executeJumpBlock = (
|
||||
state: SessionState,
|
||||
{ groupId, blockId }: JumpBlock['options']
|
||||
): ExecuteLogicResponse => {
|
||||
const groupToJumpTo = state.typebot.groups.find(
|
||||
(group) => group.id === groupId
|
||||
)
|
||||
const { typebot } = state.typebotsQueue[0]
|
||||
const groupToJumpTo = typebot.groups.find((group) => group.id === groupId)
|
||||
const blockToJumpTo =
|
||||
groupToJumpTo?.blocks.find((block) => block.id === blockId) ??
|
||||
groupToJumpTo?.blocks[0]
|
||||
|
||||
@@ -4,9 +4,10 @@ import { sanitizeUrl } from '@typebot.io/lib'
|
||||
import { ExecuteLogicResponse } from '@/features/chat/types'
|
||||
|
||||
export const executeRedirect = (
|
||||
{ typebot: { variables } }: SessionState,
|
||||
state: SessionState,
|
||||
block: RedirectBlock
|
||||
): ExecuteLogicResponse => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (!block.options?.url) return { outgoingEdgeId: block.outgoingEdgeId }
|
||||
const formattedUrl = sanitizeUrl(parseVariables(variables)(block.options.url))
|
||||
return {
|
||||
|
||||
@@ -5,9 +5,10 @@ import { parseVariables } from '@/features/variables/parseVariables'
|
||||
import { ScriptBlock, SessionState, Variable } from '@typebot.io/schemas'
|
||||
|
||||
export const executeScript = (
|
||||
{ typebot: { variables } }: SessionState,
|
||||
state: SessionState,
|
||||
block: ScriptBlock
|
||||
): ExecuteLogicResponse => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (!block.options.content) return { outgoingEdgeId: block.outgoingEdgeId }
|
||||
|
||||
const scriptToExecute = parseScriptToExecuteClientSideAction(
|
||||
|
||||
@@ -10,14 +10,14 @@ export const executeSetVariable = (
|
||||
state: SessionState,
|
||||
block: SetVariableBlock
|
||||
): ExecuteLogicResponse => {
|
||||
const { variables } = state.typebot
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (!block.options?.variableId)
|
||||
return {
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
}
|
||||
const expressionToEvaluate = getExpressionToEvaluate(state.result.id)(
|
||||
block.options
|
||||
)
|
||||
const expressionToEvaluate = getExpressionToEvaluate(
|
||||
state.typebotsQueue[0].resultId
|
||||
)(block.options)
|
||||
const isCustomValue = !block.options.type || block.options.type === 'Custom'
|
||||
if (
|
||||
expressionToEvaluate &&
|
||||
@@ -25,7 +25,7 @@ export const executeSetVariable = (
|
||||
block.options.type === 'Moment of the day')
|
||||
) {
|
||||
const scriptToExecute = parseScriptToExecuteClientSideAction(
|
||||
state.typebot.variables,
|
||||
variables,
|
||||
expressionToEvaluate
|
||||
)
|
||||
return {
|
||||
|
||||
@@ -6,19 +6,24 @@ import prisma from '@/lib/prisma'
|
||||
import {
|
||||
TypebotLinkBlock,
|
||||
SessionState,
|
||||
TypebotInSession,
|
||||
Variable,
|
||||
ReplyLog,
|
||||
Typebot,
|
||||
VariableWithValue,
|
||||
Edge,
|
||||
} from '@typebot.io/schemas'
|
||||
import { byId } from '@typebot.io/lib'
|
||||
import { ExecuteLogicResponse } from '@/features/chat/types'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { isDefined, isNotDefined } from '@typebot.io/lib/utils'
|
||||
import { createResultIfNotExist } from '@/features/chat/queries/createResultIfNotExist'
|
||||
|
||||
export const executeTypebotLink = async (
|
||||
state: SessionState,
|
||||
block: TypebotLinkBlock
|
||||
): Promise<ExecuteLogicResponse> => {
|
||||
const logs: ReplyLog[] = []
|
||||
if (!block.options.typebotId) {
|
||||
const typebotId = block.options.typebotId
|
||||
if (!typebotId) {
|
||||
logs.push({
|
||||
status: 'error',
|
||||
description: `Failed to link typebot`,
|
||||
@@ -26,7 +31,7 @@ export const executeTypebotLink = async (
|
||||
})
|
||||
return { outgoingEdgeId: block.outgoingEdgeId, logs }
|
||||
}
|
||||
const linkedTypebot = await getLinkedTypebot(state, block.options.typebotId)
|
||||
const linkedTypebot = await fetchTypebot(state, typebotId)
|
||||
if (!linkedTypebot) {
|
||||
logs.push({
|
||||
status: 'error',
|
||||
@@ -35,12 +40,17 @@ export const executeTypebotLink = async (
|
||||
})
|
||||
return { outgoingEdgeId: block.outgoingEdgeId, logs }
|
||||
}
|
||||
let newSessionState = addLinkedTypebotToState(state, block, linkedTypebot)
|
||||
let newSessionState = await addLinkedTypebotToState(
|
||||
state,
|
||||
block,
|
||||
linkedTypebot
|
||||
)
|
||||
|
||||
const nextGroupId =
|
||||
block.options.groupId ??
|
||||
linkedTypebot.groups.find((b) => b.blocks.some((s) => s.type === 'start'))
|
||||
?.id
|
||||
linkedTypebot.groups.find((group) =>
|
||||
group.blocks.some((block) => block.type === 'start')
|
||||
)?.id
|
||||
if (!nextGroupId) {
|
||||
logs.push({
|
||||
status: 'error',
|
||||
@@ -60,76 +70,123 @@ export const executeTypebotLink = async (
|
||||
}
|
||||
}
|
||||
|
||||
const addLinkedTypebotToState = (
|
||||
const addLinkedTypebotToState = async (
|
||||
state: SessionState,
|
||||
block: TypebotLinkBlock,
|
||||
linkedTypebot: TypebotInSession
|
||||
): SessionState => {
|
||||
const incomingVariables = fillVariablesWithExistingValues(
|
||||
linkedTypebot.variables,
|
||||
state.typebot.variables
|
||||
)
|
||||
linkedTypebot: Pick<Typebot, 'id' | 'edges' | 'groups' | 'variables'>
|
||||
): Promise<SessionState> => {
|
||||
const currentTypebotInQueue = state.typebotsQueue[0]
|
||||
const isPreview = isNotDefined(currentTypebotInQueue.resultId)
|
||||
|
||||
const resumeEdge = createResumeEdgeIfNecessary(state, block)
|
||||
|
||||
const currentTypebotWithResumeEdge = resumeEdge
|
||||
? {
|
||||
...currentTypebotInQueue,
|
||||
typebot: {
|
||||
...currentTypebotInQueue.typebot,
|
||||
edges: [...currentTypebotInQueue.typebot.edges, resumeEdge],
|
||||
},
|
||||
}
|
||||
: currentTypebotInQueue
|
||||
|
||||
const shouldMergeResults =
|
||||
block.options.mergeResults !== false ||
|
||||
currentTypebotInQueue.typebot.id === linkedTypebot.id ||
|
||||
block.options.typebotId === 'current'
|
||||
|
||||
if (
|
||||
currentTypebotInQueue.resultId &&
|
||||
currentTypebotInQueue.answers.length === 0 &&
|
||||
shouldMergeResults
|
||||
) {
|
||||
await createResultIfNotExist({
|
||||
resultId: currentTypebotInQueue.resultId,
|
||||
typebot: currentTypebotInQueue.typebot,
|
||||
hasStarted: false,
|
||||
isCompleted: false,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
typebot: {
|
||||
...state.typebot,
|
||||
groups: [...state.typebot.groups, ...linkedTypebot.groups],
|
||||
variables: [...state.typebot.variables, ...incomingVariables],
|
||||
edges: [...state.typebot.edges, ...linkedTypebot.edges],
|
||||
typebotsQueue: [
|
||||
{
|
||||
typebot: {
|
||||
...linkedTypebot,
|
||||
variables: fillVariablesWithExistingValues(
|
||||
linkedTypebot.variables,
|
||||
currentTypebotInQueue.typebot.variables
|
||||
),
|
||||
},
|
||||
resultId: isPreview
|
||||
? undefined
|
||||
: shouldMergeResults
|
||||
? currentTypebotInQueue.resultId
|
||||
: createId(),
|
||||
edgeIdToTriggerWhenDone: block.outgoingEdgeId ?? resumeEdge?.id,
|
||||
answers: shouldMergeResults ? currentTypebotInQueue.answers : [],
|
||||
isMergingWithParent: shouldMergeResults,
|
||||
},
|
||||
currentTypebotWithResumeEdge,
|
||||
...state.typebotsQueue.slice(1),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const createResumeEdgeIfNecessary = (
|
||||
state: SessionState,
|
||||
block: TypebotLinkBlock
|
||||
): Edge | undefined => {
|
||||
const currentTypebotInQueue = state.typebotsQueue[0]
|
||||
const blockId = block.id
|
||||
if (block.outgoingEdgeId) return
|
||||
const currentGroup = currentTypebotInQueue.typebot.groups.find((group) =>
|
||||
group.blocks.some((block) => block.id === blockId)
|
||||
)
|
||||
if (!currentGroup) return
|
||||
const currentBlockIndex = currentGroup.blocks.findIndex(
|
||||
(block) => block.id === blockId
|
||||
)
|
||||
const nextBlockInGroup =
|
||||
currentBlockIndex === -1
|
||||
? undefined
|
||||
: currentGroup.blocks[currentBlockIndex + 1]
|
||||
if (!nextBlockInGroup) return
|
||||
return {
|
||||
id: createId(),
|
||||
from: {
|
||||
groupId: '',
|
||||
blockId: '',
|
||||
},
|
||||
linkedTypebots: {
|
||||
typebots: [
|
||||
...state.linkedTypebots.typebots.filter(
|
||||
(existingTypebots) => existingTypebots.id !== linkedTypebot.id
|
||||
),
|
||||
],
|
||||
queue: block.outgoingEdgeId
|
||||
? [
|
||||
...state.linkedTypebots.queue,
|
||||
{ edgeId: block.outgoingEdgeId, typebotId: state.currentTypebotId },
|
||||
]
|
||||
: state.linkedTypebots.queue,
|
||||
to: {
|
||||
groupId: nextBlockInGroup.groupId,
|
||||
blockId: nextBlockInGroup.id,
|
||||
},
|
||||
currentTypebotId: linkedTypebot.id,
|
||||
}
|
||||
}
|
||||
|
||||
const fillVariablesWithExistingValues = (
|
||||
variables: Variable[],
|
||||
variablesWithValues: Variable[]
|
||||
): Variable[] =>
|
||||
variables.map((variable) => {
|
||||
const matchedVariable = variablesWithValues.find(
|
||||
(variableWithValue) => variableWithValue.name === variable.name
|
||||
)
|
||||
): VariableWithValue[] =>
|
||||
variables
|
||||
.map((variable) => {
|
||||
const matchedVariable = variablesWithValues.find(
|
||||
(variableWithValue) => variableWithValue.name === variable.name
|
||||
)
|
||||
|
||||
return {
|
||||
...variable,
|
||||
value: matchedVariable?.value ?? variable.value,
|
||||
}
|
||||
})
|
||||
return {
|
||||
...variable,
|
||||
value: matchedVariable?.value,
|
||||
}
|
||||
})
|
||||
.filter((variable) => isDefined(variable.value)) as VariableWithValue[]
|
||||
|
||||
const getLinkedTypebot = async (
|
||||
state: SessionState,
|
||||
typebotId: string
|
||||
): Promise<TypebotInSession | null> => {
|
||||
const { typebot, result } = state
|
||||
const isPreview = !result.id
|
||||
if (typebotId === 'current') return typebot
|
||||
const availableTypebots =
|
||||
'linkedTypebots' in state
|
||||
? [typebot, ...state.linkedTypebots.typebots]
|
||||
: [typebot]
|
||||
const linkedTypebot =
|
||||
availableTypebots.find(byId(typebotId)) ??
|
||||
(await fetchTypebot(isPreview, typebotId))
|
||||
return linkedTypebot
|
||||
}
|
||||
|
||||
const fetchTypebot = async (
|
||||
isPreview: boolean,
|
||||
typebotId: string
|
||||
): Promise<TypebotInSession | null> => {
|
||||
const fetchTypebot = async (state: SessionState, typebotId: string) => {
|
||||
const { typebot: typebotInState, resultId } = state.typebotsQueue[0]
|
||||
const isPreview = !resultId
|
||||
if (typebotId === 'current') return typebotInState
|
||||
if (isPreview) {
|
||||
const typebot = await prisma.typebot.findUnique({
|
||||
where: { id: typebotId },
|
||||
@@ -140,7 +197,7 @@ const fetchTypebot = async (
|
||||
variables: true,
|
||||
},
|
||||
})
|
||||
return typebot as TypebotInSession
|
||||
return typebot as Pick<Typebot, 'id' | 'edges' | 'groups' | 'variables'>
|
||||
}
|
||||
const typebot = await prisma.publicTypebot.findUnique({
|
||||
where: { typebotId },
|
||||
@@ -155,5 +212,5 @@ const fetchTypebot = async (
|
||||
return {
|
||||
...typebot,
|
||||
id: typebotId,
|
||||
} as TypebotInSession
|
||||
} as Pick<Typebot, 'id' | 'edges' | 'groups' | 'variables'>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import test, { expect } from '@playwright/test'
|
||||
import { importTypebotInDatabase } from '@typebot.io/lib/playwright/databaseActions'
|
||||
|
||||
const typebotId = 'cl0ibhi7s0018n21aarlmg0cm'
|
||||
const typebotWithMergeDisabledId = 'cl0ibhi7s0018n21aarlag0cm'
|
||||
const linkedTypebotId = 'cl0ibhv8d0130n21aw8doxhj5'
|
||||
|
||||
test.beforeAll(async () => {
|
||||
@@ -11,6 +12,13 @@ test.beforeAll(async () => {
|
||||
getTestAsset('typebots/linkTypebots/1.json'),
|
||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
||||
)
|
||||
await importTypebotInDatabase(
|
||||
getTestAsset('typebots/linkTypebots/1-merge-disabled.json'),
|
||||
{
|
||||
id: typebotWithMergeDisabledId,
|
||||
publicId: `${typebotWithMergeDisabledId}-public`,
|
||||
}
|
||||
)
|
||||
await importTypebotInDatabase(
|
||||
getTestAsset('typebots/linkTypebots/2.json'),
|
||||
{ id: linkedTypebotId, publicId: `${linkedTypebotId}-public` }
|
||||
@@ -28,3 +36,21 @@ test('should work as expected', async ({ page }) => {
|
||||
await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
|
||||
await expect(page.locator('text=Hello there!')).toBeVisible()
|
||||
})
|
||||
|
||||
test.describe('Merge disabled', () => {
|
||||
test('should work as expected', async ({ page }) => {
|
||||
await page.goto(`/${typebotWithMergeDisabledId}-public`)
|
||||
await page.locator('input').fill('Hello there!')
|
||||
await page.locator('input').press('Enter')
|
||||
await expect(page.getByText('Cheers!')).toBeVisible()
|
||||
await page.goto(
|
||||
`${process.env.NEXTAUTH_URL}/typebots/${typebotWithMergeDisabledId}/results`
|
||||
)
|
||||
await expect(page.locator('text=Submitted at')).toBeVisible()
|
||||
await expect(page.locator('text=Hello there!')).toBeHidden()
|
||||
await page.goto(
|
||||
`${process.env.NEXTAUTH_URL}/typebots/${linkedTypebotId}/results`
|
||||
)
|
||||
await expect(page.locator('text=Hello there!')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,9 +3,10 @@ import { parseVariables } from '@/features/variables/parseVariables'
|
||||
import { SessionState, WaitBlock } from '@typebot.io/schemas'
|
||||
|
||||
export const executeWait = (
|
||||
{ typebot: { variables } }: SessionState,
|
||||
state: SessionState,
|
||||
block: WaitBlock
|
||||
): ExecuteLogicResponse => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (!block.options.secondsToWaitFor)
|
||||
return { outgoingEdgeId: block.outgoingEdgeId }
|
||||
const parsedSecondsToWaitFor = safeParseInt(
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
IntegrationBlockType,
|
||||
PixelBlock,
|
||||
ReplyLog,
|
||||
ResultInSession,
|
||||
sendMessageInputSchema,
|
||||
SessionState,
|
||||
StartParams,
|
||||
@@ -148,22 +147,19 @@ const startSession = async (
|
||||
: prefilledVariables
|
||||
|
||||
const initialState: SessionState = {
|
||||
typebot: {
|
||||
id: typebot.id,
|
||||
groups: typebot.groups,
|
||||
edges: typebot.edges,
|
||||
variables: startVariables,
|
||||
},
|
||||
linkedTypebots: {
|
||||
typebots: [],
|
||||
queue: [],
|
||||
},
|
||||
result: {
|
||||
id: result?.id,
|
||||
variables: result?.variables ?? [],
|
||||
answers: result?.answers ?? [],
|
||||
},
|
||||
currentTypebotId: typebot.id,
|
||||
version: '2',
|
||||
typebotsQueue: [
|
||||
{
|
||||
resultId: result?.id,
|
||||
typebot: {
|
||||
id: typebot.id,
|
||||
groups: typebot.groups,
|
||||
edges: typebot.edges,
|
||||
variables: startVariables,
|
||||
},
|
||||
answers: [],
|
||||
},
|
||||
],
|
||||
dynamicTheme: parseDynamicThemeInState(typebot.theme),
|
||||
isStreamEnabled: startParams.isStreamEnabled,
|
||||
}
|
||||
@@ -212,12 +208,12 @@ const startSession = async (
|
||||
startClientSideAction.length > 0 ? startClientSideAction : undefined,
|
||||
typebot: {
|
||||
id: typebot.id,
|
||||
settings: deepParseVariables(newSessionState.typebot.variables)(
|
||||
typebot.settings
|
||||
),
|
||||
theme: deepParseVariables(newSessionState.typebot.variables)(
|
||||
typebot.theme
|
||||
),
|
||||
settings: deepParseVariables(
|
||||
newSessionState.typebotsQueue[0].typebot.variables
|
||||
)(typebot.settings),
|
||||
theme: deepParseVariables(
|
||||
newSessionState.typebotsQueue[0].typebot.variables
|
||||
)(typebot.theme),
|
||||
},
|
||||
dynamicTheme: parseDynamicThemeReply(newSessionState),
|
||||
logs: startLogs.length > 0 ? startLogs : undefined,
|
||||
@@ -239,12 +235,12 @@ const startSession = async (
|
||||
sessionId: session.id,
|
||||
typebot: {
|
||||
id: typebot.id,
|
||||
settings: deepParseVariables(newSessionState.typebot.variables)(
|
||||
typebot.settings
|
||||
),
|
||||
theme: deepParseVariables(newSessionState.typebot.variables)(
|
||||
typebot.theme
|
||||
),
|
||||
settings: deepParseVariables(
|
||||
newSessionState.typebotsQueue[0].typebot.variables
|
||||
)(typebot.settings),
|
||||
theme: deepParseVariables(
|
||||
newSessionState.typebotsQueue[0].typebot.variables
|
||||
)(typebot.theme),
|
||||
},
|
||||
messages,
|
||||
input,
|
||||
@@ -319,7 +315,7 @@ const getResult = async ({
|
||||
if (isPreview) return
|
||||
const existingResult =
|
||||
resultId && isRememberUserEnabled
|
||||
? ((await findResult({ id: resultId })) as ResultInSession)
|
||||
? await findResult({ id: resultId })
|
||||
: undefined
|
||||
|
||||
const prefilledVariableWithValue = prefilledVariables.filter(
|
||||
@@ -341,7 +337,7 @@ const getResult = async ({
|
||||
return {
|
||||
id: existingResult?.id ?? createId(),
|
||||
variables: updatedResult.variables,
|
||||
answers: existingResult?.answers,
|
||||
answers: existingResult?.answers ?? [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,10 +365,10 @@ const parseDynamicThemeReply = (
|
||||
): ChatReply['dynamicTheme'] => {
|
||||
if (!state?.dynamicTheme) return
|
||||
return {
|
||||
hostAvatarUrl: parseVariables(state?.typebot.variables)(
|
||||
hostAvatarUrl: parseVariables(state.typebotsQueue[0].typebot.variables)(
|
||||
state.dynamicTheme.hostAvatarUrl
|
||||
),
|
||||
guestAvatarUrl: parseVariables(state?.typebot.variables)(
|
||||
guestAvatarUrl: parseVariables(state.typebotsQueue[0].typebot.variables)(
|
||||
state.dynamicTheme.guestAvatarUrl
|
||||
),
|
||||
}
|
||||
|
||||
@@ -3,7 +3,12 @@ import { TRPCError } from '@trpc/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '../queries/getSession'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { PublicTypebot, SessionState, Typebot } from '@typebot.io/schemas'
|
||||
import {
|
||||
PublicTypebot,
|
||||
SessionState,
|
||||
Typebot,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
|
||||
export const updateTypebotInSession = publicProcedure
|
||||
.meta({
|
||||
@@ -32,7 +37,7 @@ export const updateTypebotInSession = publicProcedure
|
||||
const publicTypebot = (await prisma.publicTypebot.findFirst({
|
||||
where: {
|
||||
typebot: {
|
||||
id: session.state.typebot.id,
|
||||
id: session.state.typebotsQueue[0].typebot.id,
|
||||
OR: [
|
||||
{
|
||||
workspace: {
|
||||
@@ -74,21 +79,28 @@ const updateSessionState = (
|
||||
newTypebot: Pick<PublicTypebot, 'edges' | 'variables' | 'groups'>
|
||||
): SessionState => ({
|
||||
...currentState,
|
||||
typebot: {
|
||||
...currentState.typebot,
|
||||
edges: newTypebot.edges,
|
||||
variables: updateVariablesInSession(
|
||||
currentState.typebot.variables,
|
||||
newTypebot.variables
|
||||
),
|
||||
groups: newTypebot.groups,
|
||||
},
|
||||
typebotsQueue: currentState.typebotsQueue.map((typebotInQueue, index) =>
|
||||
index === 0
|
||||
? {
|
||||
...typebotInQueue,
|
||||
typebot: {
|
||||
...typebotInQueue.typebot,
|
||||
edges: newTypebot.edges,
|
||||
groups: newTypebot.groups,
|
||||
variables: updateVariablesInSession(
|
||||
typebotInQueue.typebot.variables,
|
||||
newTypebot.variables
|
||||
),
|
||||
},
|
||||
}
|
||||
: typebotInQueue
|
||||
),
|
||||
})
|
||||
|
||||
const updateVariablesInSession = (
|
||||
currentVariables: SessionState['typebot']['variables'],
|
||||
currentVariables: Variable[],
|
||||
newVariables: Typebot['variables']
|
||||
): SessionState['typebot']['variables'] => [
|
||||
): Variable[] => [
|
||||
...currentVariables,
|
||||
...newVariables.filter(
|
||||
(newVariable) =>
|
||||
|
||||
@@ -6,10 +6,17 @@ export const addEdgeToTypebot = (
|
||||
edge: Edge
|
||||
): SessionState => ({
|
||||
...state,
|
||||
typebot: {
|
||||
...state.typebot,
|
||||
edges: [...state.typebot.edges, edge],
|
||||
},
|
||||
typebotsQueue: state.typebotsQueue.map((typebot, index) =>
|
||||
index === 0
|
||||
? {
|
||||
...typebot,
|
||||
typebot: {
|
||||
...typebot.typebot,
|
||||
edges: [...typebot.typebot.edges, edge],
|
||||
},
|
||||
}
|
||||
: typebot
|
||||
),
|
||||
})
|
||||
|
||||
export const createPortalEdge = ({ to }: Pick<Edge, 'to'>) => ({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import {
|
||||
AnswerInSessionState,
|
||||
Block,
|
||||
BlockType,
|
||||
BubbleBlockType,
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
InputBlockType,
|
||||
IntegrationBlockType,
|
||||
LogicBlockType,
|
||||
ResultInSession,
|
||||
SessionState,
|
||||
SetVariableBlock,
|
||||
WebhookBlock,
|
||||
@@ -35,7 +35,7 @@ export const continueBotFlow =
|
||||
reply?: string
|
||||
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
||||
let newSessionState = { ...state }
|
||||
const group = state.typebot.groups.find(
|
||||
const group = state.typebotsQueue[0].typebot.groups.find(
|
||||
(group) => group.id === state.currentBlock?.groupId
|
||||
)
|
||||
const blockIndex =
|
||||
@@ -52,7 +52,7 @@ export const continueBotFlow =
|
||||
})
|
||||
|
||||
if (block.type === LogicBlockType.SET_VARIABLE) {
|
||||
const existingVariable = state.typebot.variables.find(
|
||||
const existingVariable = state.typebotsQueue[0].typebot.variables.find(
|
||||
byId(block.options.variableId)
|
||||
)
|
||||
if (existingVariable && reply) {
|
||||
@@ -103,7 +103,8 @@ export const continueBotFlow =
|
||||
formattedReply
|
||||
)
|
||||
const itemId = nextEdgeId
|
||||
? state.typebot.edges.find(byId(nextEdgeId))?.from.itemId
|
||||
? newSessionState.typebotsQueue[0].typebot.edges.find(byId(nextEdgeId))
|
||||
?.from.itemId
|
||||
: undefined
|
||||
newSessionState = await processAndSaveAnswer(
|
||||
state,
|
||||
@@ -128,7 +129,7 @@ export const continueBotFlow =
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextEdgeId && state.linkedTypebots.queue.length === 0)
|
||||
if (!nextEdgeId && state.typebotsQueue.length === 1)
|
||||
return {
|
||||
messages: [],
|
||||
newSessionState,
|
||||
@@ -138,7 +139,9 @@ export const continueBotFlow =
|
||||
|
||||
const nextGroup = getNextGroup(newSessionState)(nextEdgeId)
|
||||
|
||||
if (!nextGroup)
|
||||
newSessionState = nextGroup.newSessionState
|
||||
|
||||
if (!nextGroup.group)
|
||||
return {
|
||||
messages: [],
|
||||
newSessionState,
|
||||
@@ -168,7 +171,7 @@ const saveVariableValueIfAny =
|
||||
(state: SessionState, block: InputBlock) =>
|
||||
(reply: string): SessionState => {
|
||||
if (!block.options.variableId) return state
|
||||
const foundVariable = state.typebot.variables.find(
|
||||
const foundVariable = state.typebotsQueue[0].typebot.variables.find(
|
||||
(variable) => variable.id === block.options.variableId
|
||||
)
|
||||
if (!foundVariable) return state
|
||||
@@ -235,34 +238,47 @@ const saveAnswer =
|
||||
itemId,
|
||||
})
|
||||
|
||||
const key = block.options.variableId
|
||||
? state.typebotsQueue[0].typebot.variables.find(
|
||||
(variable) => variable.id === block.options.variableId
|
||||
)?.name
|
||||
: state.typebotsQueue[0].typebot.groups.find((group) =>
|
||||
group.blocks.find((blockInGroup) => blockInGroup.id === block.id)
|
||||
)?.title
|
||||
|
||||
return setNewAnswerInState(state)({
|
||||
blockId: block.id,
|
||||
variableId: block.options.variableId ?? null,
|
||||
content: reply,
|
||||
key: key ?? block.id,
|
||||
value: reply,
|
||||
})
|
||||
}
|
||||
|
||||
const setNewAnswerInState =
|
||||
(state: SessionState) => (newAnswer: ResultInSession['answers'][number]) => {
|
||||
const newAnswers = state.result.answers
|
||||
.filter((answer) => answer.blockId !== newAnswer.blockId)
|
||||
(state: SessionState) => (newAnswer: AnswerInSessionState) => {
|
||||
const answers = state.typebotsQueue[0].answers
|
||||
const newAnswers = answers
|
||||
.filter((answer) => answer.key !== newAnswer.key)
|
||||
.concat(newAnswer)
|
||||
|
||||
return {
|
||||
...state,
|
||||
result: {
|
||||
...state.result,
|
||||
answers: newAnswers,
|
||||
},
|
||||
typebotsQueue: state.typebotsQueue.map((typebot, index) =>
|
||||
index === 0
|
||||
? {
|
||||
...typebot,
|
||||
answers: newAnswers,
|
||||
}
|
||||
: typebot
|
||||
),
|
||||
} satisfies SessionState
|
||||
}
|
||||
|
||||
const getOutgoingEdgeId =
|
||||
({ typebot: { variables } }: Pick<SessionState, 'typebot'>) =>
|
||||
(state: Pick<SessionState, 'typebotsQueue'>) =>
|
||||
(
|
||||
block: InputBlock | SetVariableBlock | OpenAIBlock | WebhookBlock,
|
||||
reply: string | undefined
|
||||
) => {
|
||||
const variables = state.typebotsQueue[0].typebot.variables
|
||||
if (
|
||||
block.type === InputBlockType.CHOICE &&
|
||||
!block.options.isMultipleChoice &&
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
InputBlockType,
|
||||
RuntimeOptions,
|
||||
SessionState,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
import {
|
||||
isBubbleBlock,
|
||||
@@ -47,7 +48,9 @@ export const executeGroup =
|
||||
|
||||
if (isBubbleBlock(block)) {
|
||||
messages.push(
|
||||
parseBubbleBlock(newSessionState.typebot.variables)(block)
|
||||
parseBubbleBlock(newSessionState.typebotsQueue[0].typebot.variables)(
|
||||
block
|
||||
)
|
||||
)
|
||||
lastBubbleBlockId = block.id
|
||||
continue
|
||||
@@ -118,14 +121,14 @@ export const executeGroup =
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextEdgeId)
|
||||
if (!nextEdgeId && state.typebotsQueue.length === 1)
|
||||
return { messages, newSessionState, clientSideActions, logs }
|
||||
|
||||
const nextGroup = getNextGroup(newSessionState)(nextEdgeId)
|
||||
const nextGroup = getNextGroup(newSessionState)(nextEdgeId ?? undefined)
|
||||
|
||||
if (nextGroup?.updatedContext) newSessionState = nextGroup.updatedContext
|
||||
newSessionState = nextGroup.newSessionState
|
||||
|
||||
if (!nextGroup) {
|
||||
if (!nextGroup.group) {
|
||||
return { messages, newSessionState, clientSideActions, logs }
|
||||
}
|
||||
|
||||
@@ -141,7 +144,7 @@ export const executeGroup =
|
||||
}
|
||||
|
||||
const computeRuntimeOptions =
|
||||
(state: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||
(state: SessionState) =>
|
||||
(block: InputBlock): Promise<RuntimeOptions> | undefined => {
|
||||
switch (block.type) {
|
||||
case InputBlockType.PAYMENT: {
|
||||
@@ -151,7 +154,7 @@ const computeRuntimeOptions =
|
||||
}
|
||||
|
||||
const getPrefilledInputValue =
|
||||
(variables: SessionState['typebot']['variables']) => (block: InputBlock) => {
|
||||
(variables: Variable[]) => (block: InputBlock) => {
|
||||
const variableValue = variables.find(
|
||||
(variable) =>
|
||||
variable.id === block.options.variableId && isDefined(variable.value)
|
||||
@@ -161,7 +164,7 @@ const getPrefilledInputValue =
|
||||
}
|
||||
|
||||
const parseBubbleBlock =
|
||||
(variables: SessionState['typebot']['variables']) =>
|
||||
(variables: Variable[]) =>
|
||||
(block: BubbleBlock): ChatReply['messages'][0] => {
|
||||
switch (block.type) {
|
||||
case BubbleBlockType.TEXT:
|
||||
@@ -197,15 +200,17 @@ const parseInput =
|
||||
}
|
||||
case InputBlockType.PICTURE_CHOICE: {
|
||||
return injectVariableValuesInPictureChoiceBlock(
|
||||
state.typebot.variables
|
||||
state.typebotsQueue[0].typebot.variables
|
||||
)(block)
|
||||
}
|
||||
case InputBlockType.NUMBER: {
|
||||
const parsedBlock = deepParseVariables(state.typebot.variables)({
|
||||
const parsedBlock = deepParseVariables(
|
||||
state.typebotsQueue[0].typebot.variables
|
||||
)({
|
||||
...block,
|
||||
prefilledValue: getPrefilledInputValue(state.typebot.variables)(
|
||||
block
|
||||
),
|
||||
prefilledValue: getPrefilledInputValue(
|
||||
state.typebotsQueue[0].typebot.variables
|
||||
)(block),
|
||||
})
|
||||
return {
|
||||
...parsedBlock,
|
||||
@@ -224,12 +229,12 @@ const parseInput =
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return deepParseVariables(state.typebot.variables)({
|
||||
return deepParseVariables(state.typebotsQueue[0].typebot.variables)({
|
||||
...block,
|
||||
runtimeOptions: await computeRuntimeOptions(state)(block),
|
||||
prefilledValue: getPrefilledInputValue(state.typebot.variables)(
|
||||
block
|
||||
),
|
||||
prefilledValue: getPrefilledInputValue(
|
||||
state.typebotsQueue[0].typebot.variables
|
||||
)(block),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,37 +2,81 @@ import { byId } from '@typebot.io/lib'
|
||||
import { Group, SessionState } from '@typebot.io/schemas'
|
||||
|
||||
export type NextGroup = {
|
||||
group: Group
|
||||
updatedContext?: SessionState
|
||||
group?: Group
|
||||
newSessionState: SessionState
|
||||
}
|
||||
|
||||
export const getNextGroup =
|
||||
(state: SessionState) =>
|
||||
(edgeId?: string): NextGroup | null => {
|
||||
const { typebot } = state
|
||||
const nextEdge = typebot.edges.find(byId(edgeId))
|
||||
(edgeId?: string): NextGroup => {
|
||||
const nextEdge = state.typebotsQueue[0].typebot.edges.find(byId(edgeId))
|
||||
if (!nextEdge) {
|
||||
if (state.linkedTypebots.queue.length > 0) {
|
||||
const nextEdgeId = state.linkedTypebots.queue[0].edgeId
|
||||
const updatedContext = {
|
||||
if (state.typebotsQueue.length > 1) {
|
||||
const nextEdgeId = state.typebotsQueue[0].edgeIdToTriggerWhenDone
|
||||
const isMergingWithParent = state.typebotsQueue[0].isMergingWithParent
|
||||
const newSessionState = {
|
||||
...state,
|
||||
linkedBotQueue: state.linkedTypebots.queue.slice(1),
|
||||
}
|
||||
const nextGroup = getNextGroup(updatedContext)(nextEdgeId)
|
||||
if (!nextGroup) return null
|
||||
typebotsQueue: [
|
||||
{
|
||||
...state.typebotsQueue[1],
|
||||
typebot: isMergingWithParent
|
||||
? {
|
||||
...state.typebotsQueue[1].typebot,
|
||||
variables: state.typebotsQueue[1].typebot.variables.map(
|
||||
(variable) => ({
|
||||
...variable,
|
||||
value: state.typebotsQueue[0].answers.find(
|
||||
(answer) => answer.key === variable.name
|
||||
)?.value,
|
||||
})
|
||||
),
|
||||
}
|
||||
: state.typebotsQueue[1].typebot,
|
||||
answers: isMergingWithParent
|
||||
? [
|
||||
...state.typebotsQueue[1].answers.filter(
|
||||
(incomingAnswer) =>
|
||||
!state.typebotsQueue[0].answers.find(
|
||||
(currentAnswer) =>
|
||||
currentAnswer.key === incomingAnswer.key
|
||||
)
|
||||
),
|
||||
...state.typebotsQueue[0].answers,
|
||||
]
|
||||
: state.typebotsQueue[1].answers,
|
||||
},
|
||||
...state.typebotsQueue.slice(2),
|
||||
],
|
||||
} satisfies SessionState
|
||||
const nextGroup = getNextGroup(newSessionState)(nextEdgeId)
|
||||
if (!nextGroup)
|
||||
return {
|
||||
newSessionState,
|
||||
}
|
||||
return {
|
||||
...nextGroup,
|
||||
updatedContext,
|
||||
newSessionState,
|
||||
}
|
||||
}
|
||||
return null
|
||||
return {
|
||||
newSessionState: state,
|
||||
}
|
||||
}
|
||||
const nextGroup = typebot.groups.find(byId(nextEdge.to.groupId))
|
||||
if (!nextGroup) return null
|
||||
const nextGroup = state.typebotsQueue[0].typebot.groups.find(
|
||||
byId(nextEdge.to.groupId)
|
||||
)
|
||||
if (!nextGroup)
|
||||
return {
|
||||
newSessionState: state,
|
||||
}
|
||||
const startBlockIndex = nextEdge.to.blockId
|
||||
? nextGroup.blocks.findIndex(byId(nextEdge.to.blockId))
|
||||
: 0
|
||||
return {
|
||||
group: { ...nextGroup, blocks: nextGroup.blocks.slice(startBlockIndex) },
|
||||
group: {
|
||||
...nextGroup,
|
||||
blocks: nextGroup.blocks.slice(startBlockIndex),
|
||||
},
|
||||
newSessionState: state,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ type Props = {
|
||||
logs: ChatReply['logs']
|
||||
clientSideActions: ChatReply['clientSideActions']
|
||||
}
|
||||
|
||||
export const saveStateToDatabase = async ({
|
||||
session: { state, id },
|
||||
input,
|
||||
@@ -21,25 +22,30 @@ export const saveStateToDatabase = async ({
|
||||
|
||||
const session = id ? { state, id } : await createSession({ state })
|
||||
|
||||
if (!state?.result?.id) return session
|
||||
const resultId = state.typebotsQueue[0].resultId
|
||||
|
||||
if (!resultId) return session
|
||||
|
||||
const containsSetVariableClientSideAction = clientSideActions?.some(
|
||||
(action) => 'setVariable' in action
|
||||
)
|
||||
|
||||
const answers = state.typebotsQueue[0].answers
|
||||
|
||||
await upsertResult({
|
||||
state,
|
||||
resultId,
|
||||
typebot: state.typebotsQueue[0].typebot,
|
||||
isCompleted: Boolean(
|
||||
!input &&
|
||||
!containsSetVariableClientSideAction &&
|
||||
state.result.answers.length > 0
|
||||
!input && !containsSetVariableClientSideAction && answers.length > 0
|
||||
),
|
||||
hasStarted: answers.length > 0,
|
||||
})
|
||||
|
||||
if (logs && logs.length > 0)
|
||||
await saveLogs(
|
||||
logs.map((log) => ({
|
||||
...log,
|
||||
resultId: state.result.id as string,
|
||||
resultId,
|
||||
details: formatLogDetails(log.details),
|
||||
}))
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ export const startBotFlow = async (
|
||||
startGroupId?: string
|
||||
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
||||
if (startGroupId) {
|
||||
const group = state.typebot.groups.find(
|
||||
const group = state.typebotsQueue[0].typebot.groups.find(
|
||||
(group) => group.id === startGroupId
|
||||
)
|
||||
if (!group)
|
||||
@@ -18,9 +18,10 @@ export const startBotFlow = async (
|
||||
})
|
||||
return executeGroup(state)(group)
|
||||
}
|
||||
const firstEdgeId = state.typebot.groups[0].blocks[0].outgoingEdgeId
|
||||
const firstEdgeId =
|
||||
state.typebotsQueue[0].typebot.groups[0].blocks[0].outgoingEdgeId
|
||||
if (!firstEdgeId) return { messages: [], newSessionState: state }
|
||||
const nextGroup = getNextGroup(state)(firstEdgeId)
|
||||
if (!nextGroup) return { messages: [], newSessionState: state }
|
||||
if (!nextGroup.group) return { messages: [], newSessionState: state }
|
||||
return executeGroup(state)(nextGroup.group)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { getDefinedVariables } from '@typebot.io/lib/results'
|
||||
import { TypebotInSession } from '@typebot.io/schemas'
|
||||
|
||||
type Props = {
|
||||
resultId: string
|
||||
typebot: TypebotInSession
|
||||
hasStarted: boolean
|
||||
isCompleted: boolean
|
||||
}
|
||||
export const createResultIfNotExist = async ({
|
||||
resultId,
|
||||
typebot,
|
||||
hasStarted,
|
||||
isCompleted,
|
||||
}: Props) => {
|
||||
const existingResult = await prisma.result.findUnique({
|
||||
where: { id: resultId },
|
||||
select: { id: true },
|
||||
})
|
||||
if (existingResult) return
|
||||
return prisma.result.createMany({
|
||||
data: [
|
||||
{
|
||||
id: resultId,
|
||||
typebotId: typebot.id,
|
||||
isCompleted: isCompleted ? true : false,
|
||||
hasStarted,
|
||||
variables: getDefinedVariables(typebot.variables),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { Answer, Result } from '@typebot.io/schemas'
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
@@ -9,6 +10,18 @@ export const findResult = ({ id }: Props) =>
|
||||
select: {
|
||||
id: true,
|
||||
variables: true,
|
||||
answers: { select: { blockId: true, variableId: true, content: true } },
|
||||
hasStarted: true,
|
||||
answers: {
|
||||
select: {
|
||||
content: true,
|
||||
blockId: true,
|
||||
variableId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}) as Promise<
|
||||
| (Pick<Result, 'id' | 'variables' | 'hasStarted'> & {
|
||||
answers: Pick<Answer, 'content' | 'blockId' | 'variableId'>[]
|
||||
})
|
||||
| null
|
||||
>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { ChatSession } from '@typebot.io/schemas'
|
||||
import { ChatSession, sessionStateSchema } from '@typebot.io/schemas'
|
||||
|
||||
export const getSession = async (
|
||||
sessionId: string
|
||||
): Promise<Pick<ChatSession, 'state' | 'id'> | null> => {
|
||||
const session = (await prisma.chatSession.findUnique({
|
||||
const session = await prisma.chatSession.findUnique({
|
||||
where: { id: sessionId },
|
||||
select: { id: true, state: true },
|
||||
})) as Pick<ChatSession, 'state' | 'id'> | null
|
||||
return session
|
||||
})
|
||||
if (!session) return null
|
||||
return { ...session, state: sessionStateSchema.parse(session.state) }
|
||||
}
|
||||
|
||||
@@ -12,12 +12,13 @@ type Props = {
|
||||
state: SessionState
|
||||
}
|
||||
export const upsertAnswer = async ({ answer, reply, block, state }: Props) => {
|
||||
if (!state.result?.id) return
|
||||
const resultId = state.typebotsQueue[0].resultId
|
||||
if (!resultId) return
|
||||
if (reply.includes('http') && block.type === InputBlockType.FILE) {
|
||||
answer.storageUsed = await computeStorageUsed(reply)
|
||||
}
|
||||
const where = {
|
||||
resultId: state.result.id,
|
||||
resultId,
|
||||
blockId: block.id,
|
||||
groupId: block.groupId,
|
||||
}
|
||||
@@ -37,7 +38,7 @@ export const upsertAnswer = async ({ answer, reply, block, state }: Props) => {
|
||||
},
|
||||
})
|
||||
return prisma.answer.createMany({
|
||||
data: [{ ...answer, resultId: state.result.id }],
|
||||
data: [{ ...answer, resultId }],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +1,43 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { SessionState } from '@typebot.io/schemas'
|
||||
import { getDefinedVariables } from '@typebot.io/lib/results'
|
||||
import { TypebotInSession } from '@typebot.io/schemas'
|
||||
|
||||
type Props = {
|
||||
state: SessionState
|
||||
resultId: string
|
||||
typebot: TypebotInSession
|
||||
hasStarted: boolean
|
||||
isCompleted: boolean
|
||||
}
|
||||
export const upsertResult = async ({ state, isCompleted }: Props) => {
|
||||
export const upsertResult = async ({
|
||||
resultId,
|
||||
typebot,
|
||||
hasStarted,
|
||||
isCompleted,
|
||||
}: Props) => {
|
||||
const existingResult = await prisma.result.findUnique({
|
||||
where: { id: state.result.id },
|
||||
where: { id: resultId },
|
||||
select: { id: true },
|
||||
})
|
||||
const variablesWithValue = getDefinedVariables(typebot.variables)
|
||||
|
||||
if (existingResult) {
|
||||
return prisma.result.updateMany({
|
||||
where: { id: state.result.id },
|
||||
where: { id: resultId },
|
||||
data: {
|
||||
isCompleted: isCompleted ? true : undefined,
|
||||
hasStarted: state.result.answers.length > 0 ? true : undefined,
|
||||
variables: state.result.variables,
|
||||
hasStarted,
|
||||
variables: variablesWithValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
return prisma.result.createMany({
|
||||
data: [
|
||||
{
|
||||
id: state.result.id,
|
||||
typebotId: state.typebot.id,
|
||||
id: resultId,
|
||||
typebotId: typebot.id,
|
||||
isCompleted: isCompleted ? true : false,
|
||||
hasStarted: state.result.answers.length > 0 ? true : undefined,
|
||||
variables: state.result.variables,
|
||||
hasStarted,
|
||||
variables: variablesWithValue,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import {
|
||||
SessionState,
|
||||
VariableWithUnknowValue,
|
||||
VariableWithValue,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
import { safeStringify } from '@typebot.io/lib/safeStringify'
|
||||
@@ -11,40 +9,23 @@ export const updateVariables =
|
||||
(state: SessionState) =>
|
||||
(newVariables: VariableWithUnknowValue[]): SessionState => ({
|
||||
...state,
|
||||
typebot: {
|
||||
...state.typebot,
|
||||
variables: updateTypebotVariables(state)(newVariables),
|
||||
},
|
||||
result: {
|
||||
...state.result,
|
||||
variables: updateResultVariables(state)(newVariables),
|
||||
},
|
||||
typebotsQueue: state.typebotsQueue.map((typebotInQueue, index) =>
|
||||
index === 0
|
||||
? {
|
||||
...typebotInQueue,
|
||||
typebot: {
|
||||
...typebotInQueue.typebot,
|
||||
variables: updateTypebotVariables(typebotInQueue.typebot)(
|
||||
newVariables
|
||||
),
|
||||
},
|
||||
}
|
||||
: typebotInQueue
|
||||
),
|
||||
})
|
||||
|
||||
const updateResultVariables =
|
||||
({ result }: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||
(newVariables: VariableWithUnknowValue[]): VariableWithValue[] => {
|
||||
const serializedNewVariables = newVariables.map((variable) => ({
|
||||
...variable,
|
||||
value: Array.isArray(variable.value)
|
||||
? variable.value.map(safeStringify)
|
||||
: safeStringify(variable.value),
|
||||
}))
|
||||
|
||||
const updatedVariables = [
|
||||
...result.variables.filter((existingVariable) =>
|
||||
serializedNewVariables.every(
|
||||
(newVariable) => existingVariable.id !== newVariable.id
|
||||
)
|
||||
),
|
||||
...serializedNewVariables,
|
||||
].filter((variable) => isDefined(variable.value)) as VariableWithValue[]
|
||||
|
||||
return updatedVariables
|
||||
}
|
||||
|
||||
const updateTypebotVariables =
|
||||
({ typebot }: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||
(typebot: { variables: Variable[] }) =>
|
||||
(newVariables: VariableWithUnknowValue[]): Variable[] => {
|
||||
const serializedNewVariables = newVariables.map((variable) => ({
|
||||
...variable,
|
||||
|
||||
Reference in New Issue
Block a user