2
0

♻️ Introduce typebot v6 with events (#1013)

Closes #885
This commit is contained in:
Baptiste Arnaud
2023-11-08 15:34:16 +01:00
committed by GitHub
parent 68e4fc71fb
commit 35300eaf34
634 changed files with 58971 additions and 31449 deletions

View File

@@ -1,16 +1,16 @@
import { ExecuteIntegrationResponse } from '../../../types'
import { env } from '@typebot.io/env'
import { isDefined } from '@typebot.io/lib'
import {
ChatwootBlock,
ChatwootOptions,
SessionState,
} from '@typebot.io/schemas'
import { ChatwootBlock, SessionState } from '@typebot.io/schemas'
import { extractVariablesFromText } from '../../../variables/extractVariablesFromText'
import { parseGuessedValueType } from '../../../variables/parseGuessedValueType'
import { parseVariables } from '../../../variables/parseVariables'
import { defaultChatwootOptions } from '@typebot.io/schemas/features/blocks/integrations/chatwoot/constants'
const parseSetUserCode = (user: ChatwootOptions['user'], resultId: string) =>
const parseSetUserCode = (
user: NonNullable<ChatwootBlock['options']>['user'],
resultId: string
) =>
user?.email || user?.id
? `
window.$chatwoot.setUser(${user?.id ?? `"${resultId}"`}, {
@@ -27,7 +27,7 @@ const parseChatwootOpenCode = ({
user,
resultId,
typebotId,
}: ChatwootOptions & { typebotId: string; resultId: string }) => {
}: ChatwootBlock['options'] & { typebotId: string; resultId: string }) => {
const openChatwoot = `${parseSetUserCode(user, resultId)}
if(window.Typebot?.unmount) window.Typebot.unmount();
window.$chatwoot.setCustomAttributes({
@@ -46,7 +46,7 @@ const parseChatwootOpenCode = ({
if (window.$chatwoot) {${openChatwoot}}
else {
(function (d, t) {
var BASE_URL = "${baseUrl}";
var BASE_URL = "${baseUrl ?? defaultChatwootOptions.baseUrl}";
var g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
g.src = BASE_URL + "/packs/js/sdk.js";
@@ -78,7 +78,7 @@ export const executeChatwootBlock = (
if (state.whatsApp) return { outgoingEdgeId: block.outgoingEdgeId }
const { typebot, resultId } = state.typebotsQueue[0]
const chatwootCode =
block.options.task === 'Close widget'
block.options?.task === 'Close widget'
? chatwootCloseCode
: isDefined(resultId)
? parseChatwootOpenCode({
@@ -87,6 +87,7 @@ export const executeChatwootBlock = (
resultId,
})
: ''
return {
outgoingEdgeId: block.outgoingEdgeId,
clientSideActions: [

View File

@@ -7,7 +7,7 @@ export const executeGoogleAnalyticsBlock = (
block: GoogleAnalyticsBlock
): ExecuteIntegrationResponse => {
const { typebot, resultId } = state.typebotsQueue[0]
if (!resultId || state.whatsApp)
if (!resultId || state.whatsApp || !block.options)
return { outgoingEdgeId: block.outgoingEdgeId }
const googleAnalytics = deepParseVariables(typebot.variables, {
guessCorrectTypes: true,

View File

@@ -1,17 +1,15 @@
import {
GoogleSheetsBlock,
GoogleSheetsAction,
SessionState,
} from '@typebot.io/schemas'
import { GoogleSheetsBlock, SessionState } from '@typebot.io/schemas'
import { insertRow } from './insertRow'
import { updateRow } from './updateRow'
import { getRow } from './getRow'
import { ExecuteIntegrationResponse } from '../../../types'
import { GoogleSheetsAction } from '@typebot.io/schemas/features/blocks/integrations/googleSheets/constants'
export const executeGoogleSheetBlock = async (
state: SessionState,
block: GoogleSheetsBlock
): Promise<ExecuteIntegrationResponse> => {
if (!block.options) return { outgoingEdgeId: block.outgoingEdgeId }
const action = block.options.action
if (!action) return { outgoingEdgeId: block.outgoingEdgeId }
switch (action) {

View File

@@ -4,7 +4,7 @@ import {
VariableWithValue,
ReplyLog,
} from '@typebot.io/schemas'
import { isNotEmpty, byId } from '@typebot.io/lib'
import { isNotEmpty, byId, isDefined } from '@typebot.io/lib'
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
import { ExecuteIntegrationResponse } from '../../../types'
import { matchFilter } from './helpers/matchFilter'
@@ -20,7 +20,7 @@ export const getRow = async (
): Promise<ExecuteIntegrationResponse> => {
const logs: ReplyLog[] = []
const { variables } = state.typebotsQueue[0].typebot
const { sheetId, cellsToExtract, referenceCell, filter } =
const { sheetId, cellsToExtract, filter, ...parsedOptions } =
deepParseVariables(variables)(options)
if (!sheetId) return { outgoingEdgeId }
@@ -36,8 +36,9 @@ export const getRow = async (
const filteredRows = getTotalRows(
options.totalRowsToExtract,
rows.filter((row) =>
referenceCell
? row.get(referenceCell.column as string) === referenceCell.value
'referenceCell' in parsedOptions && parsedOptions.referenceCell
? row.get(parsedOptions.referenceCell?.column as string) ===
parsedOptions.referenceCell?.value
: matchFilter(row, filter)
)
)
@@ -50,17 +51,19 @@ export const getRow = async (
return { outgoingEdgeId, logs }
}
const extractingColumns = cellsToExtract
.map((cell) => cell.column)
?.map((cell) => cell.column)
.filter(isNotEmpty)
const selectedRows = filteredRows.map((row) =>
extractingColumns.reduce<{ [key: string]: string }>(
(obj, column) => ({ ...obj, [column]: row.get(column) }),
{}
const selectedRows = filteredRows
.map((row) =>
extractingColumns?.reduce<{ [key: string]: string }>(
(obj, column) => ({ ...obj, [column]: row.get(column) }),
{}
)
)
)
.filter(isDefined)
if (!selectedRows) return { outgoingEdgeId }
const newVariables = options.cellsToExtract.reduce<VariableWithValue[]>(
const newVariables = options.cellsToExtract?.reduce<VariableWithValue[]>(
(newVariables, cell) => {
const existingVariable = variables.find(byId(cell.variableId))
const value = selectedRows.map((row) => row[cell.column ?? ''])
@@ -75,6 +78,7 @@ export const getRow = async (
},
[]
)
if (!newVariables) return { outgoingEdgeId }
const newSessionState = updateVariablesInSession(state)(newVariables)
return {
outgoingEdgeId,

View File

@@ -3,11 +3,11 @@ import { env } from '@typebot.io/env'
import { encrypt } from '@typebot.io/lib/api/encryption/encrypt'
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
import { isDefined } from '@typebot.io/lib/utils'
import { GoogleSheetsCredentials } from '@typebot.io/schemas/features/blocks/integrations/googleSheets/schemas'
import { Credentials as CredentialsFromDb } from '@typebot.io/prisma'
import { GoogleSpreadsheet } from 'google-spreadsheet'
import { OAuth2Client, Credentials } from 'google-auth-library'
import prisma from '@typebot.io/lib/prisma'
import { GoogleSheetsCredentials } from '@typebot.io/schemas'
export const getAuthenticatedGoogleDoc = async ({
credentialsId,

View File

@@ -1,9 +1,9 @@
import { isDefined } from '@typebot.io/lib'
import { GoogleSheetsGetOptions } from '@typebot.io/schemas'
import {
GoogleSheetsGetOptions,
LogicalOperator,
ComparisonOperators,
} from '@typebot.io/schemas'
} from '@typebot.io/schemas/features/blocks/logic/condition/constants'
import { GoogleSpreadsheetRow } from 'google-spreadsheet'
export const matchFilter = (
@@ -12,7 +12,7 @@ export const matchFilter = (
) => {
if (!filter) return true
return filter.logicalOperator === LogicalOperator.AND
? filter.comparisons.every(
? filter.comparisons?.every(
(comparison) =>
comparison.column &&
matchComparison(
@@ -21,7 +21,7 @@ export const matchFilter = (
comparison.value
)
)
: filter.comparisons.some(
: filter.comparisons?.some(
(comparison) =>
comparison.column &&
matchComparison(

View File

@@ -17,8 +17,14 @@ export const updateRow = async (
}: { outgoingEdgeId?: string; options: GoogleSheetsUpdateRowOptions }
): Promise<ExecuteIntegrationResponse> => {
const { variables } = state.typebotsQueue[0].typebot
const { sheetId, referenceCell, filter } =
const { sheetId, filter, ...parsedOptions } =
deepParseVariables(variables)(options)
const referenceCell =
'referenceCell' in parsedOptions && parsedOptions.referenceCell
? parsedOptions.referenceCell
: null
if (!options.cellsToUpsert || !sheetId || (!referenceCell && !filter))
return { outgoingEdgeId }

View File

@@ -1,6 +1,5 @@
import {
Block,
BubbleBlockType,
Credentials,
SessionState,
TypebotInSession,
@@ -8,7 +7,6 @@ import {
import {
ChatCompletionOpenAIOptions,
OpenAICredentials,
chatCompletionMessageRoles,
} from '@typebot.io/schemas/features/blocks/integrations/openai'
import { byId, isEmpty } from '@typebot.io/lib'
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
@@ -20,6 +18,11 @@ import prisma from '@typebot.io/lib/prisma'
import { ExecuteIntegrationResponse } from '../../../types'
import { parseVariableNumber } from '../../../variables/parseVariableNumber'
import { updateVariablesInSession } from '../../../variables/updateVariablesInSession'
import {
chatCompletionMessageRoles,
defaultOpenAIOptions,
} from '@typebot.io/schemas/features/blocks/integrations/openai/constants'
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
export const createChatCompletionOpenAI = async (
state: SessionState,
@@ -38,6 +41,7 @@ export const createChatCompletionOpenAI = async (
status: 'error',
description: 'Make sure to select an OpenAI account',
}
if (!options.credentialsId) {
return {
outgoingEdgeId,
@@ -74,7 +78,7 @@ export const createChatCompletionOpenAI = async (
const assistantMessageVariableName = typebot.variables.find(
(variable) =>
options.responseMapping.find(
options.responseMapping?.find(
(m) => m.valueToExtract === 'Message content'
)?.variableId === variable.id
)?.name
@@ -109,7 +113,7 @@ export const createChatCompletionOpenAI = async (
const { chatCompletion, logs } = await executeChatCompletionOpenAIRequest({
apiKey,
messages,
model: options.model,
model: options.model ?? defaultOpenAIOptions.model,
temperature,
baseUrl: options.baseUrl,
apiVersion: options.apiVersion,
@@ -140,8 +144,8 @@ const isNextBubbleMessageWithAssistantMessage =
if (!nextBlock) return false
return (
nextBlock.type === BubbleBlockType.TEXT &&
nextBlock.content.richText?.length > 0 &&
nextBlock.content.richText?.at(0)?.children.at(0).text ===
(nextBlock.content?.richText?.length ?? 0) > 0 &&
nextBlock.content?.richText?.at(0)?.children.at(0).text ===
`{{${assistantVariableName}}}`
)
}

View File

@@ -12,7 +12,7 @@ type Props = Pick<
temperature: number | undefined
currentLogs?: ChatReply['logs']
isRetrying?: boolean
} & Pick<OpenAIBlock['options'], 'apiVersion' | 'baseUrl'>
} & Pick<NonNullable<OpenAIBlock['options']>, 'apiVersion' | 'baseUrl'>
export const executeChatCompletionOpenAIRequest = async ({
apiKey,

View File

@@ -7,7 +7,7 @@ export const executeOpenAIBlock = async (
state: SessionState,
block: OpenAIBlock
): Promise<ExecuteIntegrationResponse> => {
switch (block.options.task) {
switch (block.options?.task) {
case 'Create chat completion':
return createChatCompletionOpenAI(state, {
options: block.options,

View File

@@ -9,6 +9,7 @@ import { SessionState } from '@typebot.io/schemas/features/chat/sessionState'
import { OpenAIStream } from 'ai'
import { parseVariableNumber } from '../../../variables/parseVariableNumber'
import { ClientOptions, OpenAI } from 'openai'
import { defaultOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai/constants'
export const getChatCompletionStream =
(conn: Connection) =>
@@ -53,7 +54,7 @@ export const getChatCompletionStream =
const openai = new OpenAI(config)
const response = await openai.chat.completions.create({
model: options.model,
model: options.model ?? defaultOpenAIOptions.model,
temperature,
stream: true,
messages,

View File

@@ -15,7 +15,7 @@ export const parseChatCompletionMessages =
} => {
const variablesTransformedToList: VariableWithValue[] = []
const parsedMessages = messages
.flatMap((message) => {
?.flatMap((message) => {
if (!message.role) return
if (message.role === 'Messages sequence ✨') {
if (
@@ -71,6 +71,29 @@ export const parseChatCompletionMessages =
return allMessages
}
if (message.role === 'Dialogue') {
if (!message.dialogueVariableId) return
const dialogue = (variables.find(
(variable) => variable.id === message.dialogueVariableId
)?.value ?? []) as string[]
return dialogue.map<OpenAI.Chat.ChatCompletionMessageParam>(
(dialogueItem, index) => {
if (index === 0 && message.startsBy === 'assistant')
return {
role: 'assistant',
content: dialogueItem,
}
return {
role:
index % (message.startsBy === 'assistant' ? 1 : 2) === 0
? 'user'
: 'assistant',
content: dialogueItem,
}
}
)
}
return {
role: message.role,
content: parseVariables(variables)(message.content),
@@ -83,6 +106,8 @@ export const parseChatCompletionMessages =
(message) => isNotEmpty(message?.role) && isNotEmpty(message?.content)
) as OpenAI.Chat.ChatCompletionMessageParam[]
console.log('parsedMessages', parsedMessages)
return {
variablesTransformedToList,
messages: parsedMessages,

View File

@@ -19,7 +19,7 @@ export const resumeChatCompletion =
) =>
async (message: string, totalTokens?: number) => {
let newSessionState = state
const newVariables = options.responseMapping.reduce<
const newVariables = options.responseMapping?.reduce<
VariableWithUnknowValue[]
>((newVariables, mapping) => {
const { typebot } = newSessionState.typebotsQueue[0]
@@ -41,7 +41,7 @@ export const resumeChatCompletion =
}
return newVariables
}, [])
if (newVariables.length > 0)
if (newVariables && newVariables.length > 0)
newSessionState = updateVariablesInSession(newSessionState)(newVariables)
return {
outgoingEdgeId,

View File

@@ -9,7 +9,7 @@ export const executePixelBlock = (
const { typebot, resultId } = state.typebotsQueue[0]
if (
!resultId ||
!block.options.pixelId ||
!block.options?.pixelId ||
!block.options.eventType ||
state.whatsApp
)

View File

@@ -3,7 +3,6 @@ import {
AnswerInSessionState,
ReplyLog,
SendEmailBlock,
SendEmailOptions,
SessionState,
SmtpCredentials,
TypebotInSession,
@@ -20,6 +19,7 @@ import { env } from '@typebot.io/env'
import { ExecuteIntegrationResponse } from '../../../types'
import prisma from '@typebot.io/lib/prisma'
import { parseVariables } from '../../../variables/parseVariables'
import { defaultSendEmailOptions } from '@typebot.io/schemas/features/blocks/integrations/sendEmail/constants'
export const executeSendEmailBlock = async (
state: SessionState,
@@ -41,24 +41,34 @@ export const executeSendEmailBlock = async (
}
const bodyUniqueVariable = findUniqueVariableValue(typebot.variables)(
options.body
options?.body
)
const body = bodyUniqueVariable
? stringifyUniqueVariableValueAsHtml(bodyUniqueVariable)
: parseVariables(typebot.variables, { isInsideHtml: true })(
options.body ?? ''
options?.body ?? ''
)
if (!options?.recipients)
return { outgoingEdgeId: block.outgoingEdgeId, logs }
try {
const sendEmailLogs = await sendEmail({
typebot,
answers,
credentialsId: options.credentialsId,
credentialsId:
options.credentialsId ?? defaultSendEmailOptions.credentialsId,
recipients: options.recipients.map(parseVariables(typebot.variables)),
subject: parseVariables(typebot.variables)(options.subject ?? ''),
subject: options.subject
? parseVariables(typebot.variables)(options?.subject)
: undefined,
body,
cc: (options.cc ?? []).map(parseVariables(typebot.variables)),
bcc: (options.bcc ?? []).map(parseVariables(typebot.variables)),
cc: options.cc
? options.cc.map(parseVariables(typebot.variables))
: undefined,
bcc: options.bcc
? options.bcc.map(parseVariables(typebot.variables))
: undefined,
replyTo: options.replyTo
? parseVariables(typebot.variables)(options.replyTo)
: undefined,
@@ -91,7 +101,16 @@ const sendEmail = async ({
isBodyCode,
isCustomBody,
fileUrls,
}: SendEmailOptions & {
}: {
credentialsId: string
recipients: string[]
body: string | undefined
subject: string | undefined
cc: string[] | undefined
bcc: string[] | undefined
replyTo: string | undefined
isBodyCode: boolean | undefined
isCustomBody: boolean | undefined
typebot: TypebotInSession
answers: AnswerInSessionState[]
fileUrls?: string | string[]
@@ -216,9 +235,10 @@ const getEmailBody = async ({
}: {
typebot: TypebotInSession
answersInSession: AnswerInSessionState[]
} & Pick<SendEmailOptions, 'isCustomBody' | 'isBodyCode' | 'body'>): Promise<
{ html?: string; text?: string } | undefined
> => {
} & Pick<
NonNullable<SendEmailBlock['options']>,
'isCustomBody' | 'isBodyCode' | 'body'
>): Promise<{ html?: string; text?: string } | undefined> => {
if (isCustomBody || (isNotDefined(isCustomBody) && !isEmpty(body)))
return {
html: isBodyCode ? body : undefined,

View File

@@ -7,22 +7,23 @@ import {
Webhook,
Variable,
WebhookResponse,
WebhookOptions,
defaultWebhookAttributes,
KeyValue,
ReplyLog,
ExecutableWebhook,
AnswerInSessionState,
} from '@typebot.io/schemas'
import { stringify } from 'qs'
import { omit } from '@typebot.io/lib'
import { isDefined, isEmpty, omit } from '@typebot.io/lib'
import { getDefinedVariables, parseAnswers } from '@typebot.io/lib/results'
import got, { Method, HTTPError, OptionsInit } from 'got'
import { resumeWebhookExecution } from './resumeWebhookExecution'
import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/enums'
import { ExecuteIntegrationResponse } from '../../../types'
import { parseVariables } from '../../../variables/parseVariables'
import prisma from '@typebot.io/lib/prisma'
import {
HttpMethod,
defaultWebhookAttributes,
} from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
type ParsedWebhook = ExecutableWebhook & {
basicAuth: { username?: string; password?: string }
@@ -35,22 +36,17 @@ export const executeWebhookBlock = async (
): Promise<ExecuteIntegrationResponse> => {
const logs: ReplyLog[] = []
const webhook =
block.options.webhook ??
((await prisma.webhook.findUnique({
where: { id: block.webhookId },
})) as Webhook | null)
if (!webhook) {
logs.push({
status: 'error',
description: `Couldn't find webhook with id ${block.webhookId}`,
})
return { outgoingEdgeId: block.outgoingEdgeId, logs }
}
const preparedWebhook = prepareWebhookAttributes(webhook, block.options)
block.options?.webhook ??
('webhookId' in block
? ((await prisma.webhook.findUnique({
where: { id: block.webhookId },
})) as Webhook | null)
: null)
if (!webhook) return { outgoingEdgeId: block.outgoingEdgeId }
const parsedWebhook = await parseWebhookAttributes(
state,
state.typebotsQueue[0].answers
)(preparedWebhook)
)({ webhook, isCustomBody: block.options?.isCustomBody })
if (!parsedWebhook) {
logs.push({
status: 'error',
@@ -58,7 +54,7 @@ export const executeWebhookBlock = async (
})
return { outgoingEdgeId: block.outgoingEdgeId, logs }
}
if (block.options.isExecutedOnClient && !state.whatsApp)
if (block.options?.isExecutedOnClient && !state.whatsApp)
return {
outgoingEdgeId: block.outgoingEdgeId,
clientSideActions: [
@@ -78,40 +74,36 @@ export const executeWebhookBlock = async (
})
}
const prepareWebhookAttributes = (
webhook: Webhook,
options: WebhookOptions
): Webhook => {
if (options.isAdvancedConfig === false) {
return { ...webhook, body: '{{state}}', ...defaultWebhookAttributes }
} else if (options.isCustomBody === false) {
return { ...webhook, body: '{{state}}' }
}
return webhook
}
const checkIfBodyIsAVariable = (body: string) => /^{{.+}}$/.test(body)
const parseWebhookAttributes =
(state: SessionState, answers: AnswerInSessionState[]) =>
async (webhook: Webhook): Promise<ParsedWebhook | undefined> => {
async ({
webhook,
isCustomBody,
}: {
webhook: Webhook
isCustomBody?: boolean
}): Promise<ParsedWebhook | undefined> => {
if (!webhook.url || !webhook.method) return
const { typebot } = state.typebotsQueue[0]
const basicAuth: { username?: string; password?: string } = {}
const basicAuthHeaderIdx = webhook.headers.findIndex(
const basicAuthHeaderIdx = webhook.headers?.findIndex(
(h) =>
h.key?.toLowerCase() === 'authorization' &&
h.value?.toLowerCase()?.includes('basic')
)
const isUsernamePasswordBasicAuth =
basicAuthHeaderIdx !== -1 &&
webhook.headers[basicAuthHeaderIdx].value?.includes(':')
isDefined(basicAuthHeaderIdx) &&
webhook.headers?.at(basicAuthHeaderIdx)?.value?.includes(':')
if (isUsernamePasswordBasicAuth) {
const [username, password] =
webhook.headers[basicAuthHeaderIdx].value?.slice(6).split(':') ?? []
webhook.headers?.at(basicAuthHeaderIdx)?.value?.slice(6).split(':') ??
[]
basicAuth.username = username
basicAuth.password = password
webhook.headers.splice(basicAuthHeaderIdx, 1)
webhook.headers?.splice(basicAuthHeaderIdx, 1)
}
const headers = convertKeyValueTableToObject(
webhook.headers,
@@ -124,9 +116,11 @@ const parseWebhookAttributes =
body: webhook.body,
answers,
variables: typebot.variables,
isCustomBody,
})
const method = webhook.method ?? defaultWebhookAttributes.method
const { data: body, isJson } =
bodyContent && webhook.method !== HttpMethod.GET
bodyContent && method !== HttpMethod.GET
? safeJsonParse(
parseVariables(typebot.variables, {
isInsideJson: !checkIfBodyIsAVariable(bodyContent),
@@ -139,7 +133,7 @@ const parseWebhookAttributes =
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
),
basicAuth,
method: webhook.method,
method,
headers,
body,
isJson,
@@ -156,7 +150,7 @@ export const executeWebhook = async (
const request = {
url,
method: method as Method,
headers,
headers: headers ?? {},
...(basicAuth ?? {}),
json:
!contentType?.includes('x-www-form-urlencoded') && body && isJson
@@ -222,20 +216,21 @@ const getBodyContent = async ({
body,
answers,
variables,
isCustomBody,
}: {
body?: string | null
answers: AnswerInSessionState[]
variables: Variable[]
isCustomBody?: boolean
}): Promise<string | undefined> => {
if (!body) return
return body === '{{state}}'
return isEmpty(body) && isCustomBody !== true
? JSON.stringify(
parseAnswers({
answers,
variables: getDefinedVariables(variables),
})
)
: body
: body ?? undefined
}
const convertKeyValueTableToObject = (

View File

@@ -1,7 +1,5 @@
import {
InputBlock,
InputBlockType,
LogicBlockType,
PublicTypebot,
ResultHeaderCell,
Block,
@@ -11,6 +9,8 @@ import {
} from '@typebot.io/schemas'
import { isInputBlock, byId, isNotDefined } from '@typebot.io/lib'
import { parseResultHeader } from '@typebot.io/lib/results'
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
export const parseSampleResult =
(
@@ -58,11 +58,11 @@ const extractLinkedInputBlocks =
extractLinkedInputBlocks(
linkedTypebots.find((t) =>
'typebotId' in t
? t.typebotId === linkedBot.options.typebotId
: t.id === linkedBot.options.typebotId
? t.typebotId === linkedBot.options?.typebotId
: t.id === linkedBot.options?.typebotId
) as Typebot | PublicTypebot,
linkedTypebots
)(linkedBot.options.groupId, 'forward')
)(linkedBot.options?.groupId, 'forward')
)
)
: []
@@ -117,7 +117,7 @@ const parseResultSample = (
const getSampleValue = (block: InputBlock): string => {
switch (block.type) {
case InputBlockType.CHOICE:
return block.options.isMultipleChoice
return block.options?.isMultipleChoice
? block.items.map((item) => item.content).join(', ')
: block.items[0]?.content ?? 'Item'
case InputBlockType.DATE:
@@ -139,7 +139,7 @@ const getSampleValue = (block: InputBlock): string => {
case InputBlockType.PAYMENT:
return 'Success'
case InputBlockType.PICTURE_CHOICE:
return block.options.isMultipleChoice
return block.options?.isMultipleChoice
? block.items.map((item) => item.title ?? item.pictureSrc).join(', ')
: block.items[0]?.title ?? block.items[0]?.pictureSrc ?? 'Item'
}
@@ -178,16 +178,21 @@ const getGroupIds =
) =>
(groupId: string): string[] => {
const groups = typebot.edges.reduce<string[]>((groupIds, edge) => {
const fromGroupId = typebot.groups.find((g) =>
g.blocks.some(
(b) => 'blockId' in edge.from && b.id === edge.from.blockId
)
)?.id
if (!fromGroupId) return groupIds
if (direction === 'forward')
return (!existingGroupIds ||
!existingGroupIds?.includes(edge.to.groupId)) &&
edge.from.groupId === groupId
fromGroupId === groupId
? [...groupIds, edge.to.groupId]
: groupIds
return (!existingGroupIds ||
!existingGroupIds.includes(edge.from.groupId)) &&
return (!existingGroupIds || !existingGroupIds.includes(fromGroupId)) &&
edge.to.groupId === groupId
? [...groupIds, edge.from.groupId]
? [...groupIds, fromGroupId]
: groupIds
}, [])
const newGroups = [...(existingGroupIds ?? []), ...groups]

View File

@@ -49,7 +49,7 @@ export const resumeWebhookExecution = ({
}
)
const newVariables = block.options.responseVariableMapping.reduce<
const newVariables = block.options?.responseVariableMapping?.reduce<
VariableWithUnknowValue[]
>((newVariables, varMapping) => {
if (!varMapping?.bodyPath || !varMapping.variableId) return newVariables
@@ -66,7 +66,7 @@ export const resumeWebhookExecution = ({
return newVariables
}
}, [])
if (newVariables.length > 0) {
if (newVariables && newVariables.length > 0) {
const newSessionState = updateVariablesInSession(state)(newVariables)
return {
outgoingEdgeId: block.outgoingEdgeId,

View File

@@ -20,26 +20,26 @@ export const executeZemanticAiBlock = async (
): Promise<ExecuteIntegrationResponse> => {
let newSessionState = state
const noCredentialsError = {
status: 'error',
description: 'Make sure to select a Zemantic AI account',
}
const zemanticRequestError = {
status: 'error',
description: 'Could not execute Zemantic AI request',
}
if (!block.options?.credentialsId)
return {
outgoingEdgeId: block.outgoingEdgeId,
}
const credentials = await prisma.credentials.findUnique({
where: {
id: block.options.credentialsId,
id: block.options?.credentialsId,
},
})
if (!credentials) {
console.error('Could not find credentials in database')
return {
outgoingEdgeId: block.outgoingEdgeId,
logs: [noCredentialsError],
logs: [
{
status: 'error',
description: 'Make sure to select a Zemantic AI account',
},
],
}
}
const { apiKey } = (await decrypt(
@@ -109,7 +109,12 @@ export const executeZemanticAiBlock = async (
console.error(e)
return {
outgoingEdgeId: block.outgoingEdgeId,
logs: [zemanticRequestError],
logs: [
{
status: 'error',
description: 'Could not execute Zemantic AI request',
},
],
}
}