@@ -2,7 +2,6 @@ import {
|
||||
SessionState,
|
||||
VariableWithValue,
|
||||
ChoiceInputBlock,
|
||||
ItemType,
|
||||
} from '@typebot.io/schemas'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { filterChoiceItems } from './filterChoiceItems'
|
||||
@@ -14,10 +13,10 @@ export const injectVariableValuesInButtonsInputBlock =
|
||||
(state: SessionState) =>
|
||||
(block: ChoiceInputBlock): ChoiceInputBlock => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (block.options.dynamicVariableId) {
|
||||
if (block.options?.dynamicVariableId) {
|
||||
const variable = variables.find(
|
||||
(variable) =>
|
||||
variable.id === block.options.dynamicVariableId &&
|
||||
variable.id === block.options?.dynamicVariableId &&
|
||||
isDefined(variable.value)
|
||||
) as VariableWithValue | undefined
|
||||
if (!variable) return block
|
||||
@@ -26,7 +25,6 @@ export const injectVariableValuesInButtonsInputBlock =
|
||||
...block,
|
||||
items: value.filter(isDefined).map((item, idx) => ({
|
||||
id: idx.toString(),
|
||||
type: ItemType.BUTTON,
|
||||
blockId: block.id,
|
||||
content: item,
|
||||
})),
|
||||
|
||||
@@ -7,7 +7,7 @@ export const parseButtonsReply =
|
||||
(inputValue: string, block: ChoiceInputBlock): ParsedReply => {
|
||||
const displayedItems =
|
||||
injectVariableValuesInButtonsInputBlock(state)(block).items
|
||||
if (block.options.isMultipleChoice) {
|
||||
if (block.options?.isMultipleChoice) {
|
||||
const longestItemsFirst = [...displayedItems].sort(
|
||||
(a, b) => (b.content?.length ?? 0) - (a.content?.length ?? 0)
|
||||
)
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { getPrefilledInputValue } from '../../../getPrefilledValue'
|
||||
import {
|
||||
DateInputBlock,
|
||||
DateInputOptions,
|
||||
SessionState,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
import { DateInputBlock, SessionState, Variable } from '@typebot.io/schemas'
|
||||
import { deepParseVariables } from '../../../variables/deepParseVariables'
|
||||
import { parseVariables } from '../../../variables/parseVariables'
|
||||
|
||||
export const parseDateInput =
|
||||
(state: SessionState) => (block: DateInputBlock) => {
|
||||
if (!block.options) return block
|
||||
return {
|
||||
...block,
|
||||
options: {
|
||||
@@ -34,8 +30,10 @@ export const parseDateInput =
|
||||
}
|
||||
|
||||
const parseDateLimit = (
|
||||
limit: DateInputOptions['min'] | DateInputOptions['max'],
|
||||
hasTime: DateInputOptions['hasTime'],
|
||||
limit:
|
||||
| NonNullable<DateInputBlock['options']>['min']
|
||||
| NonNullable<DateInputBlock['options']>['max'],
|
||||
hasTime: NonNullable<DateInputBlock['options']>['hasTime'],
|
||||
variables: Variable[]
|
||||
) => {
|
||||
if (!limit) return
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { ParsedReply } from '../../../types'
|
||||
import { DateInputBlock } from '@typebot.io/schemas'
|
||||
import { parse as chronoParse } from 'chrono-node'
|
||||
import { format } from 'date-fns'
|
||||
import { defaultDateInputOptions } from '@typebot.io/schemas/features/blocks/inputs/date/constants'
|
||||
|
||||
export const parseDateReply = (
|
||||
reply: string,
|
||||
@@ -10,8 +12,10 @@ export const parseDateReply = (
|
||||
const parsedDate = chronoParse(reply)
|
||||
if (parsedDate.length === 0) return { status: 'fail' }
|
||||
const formatString =
|
||||
block.options.format ??
|
||||
(block.options.hasTime ? 'dd/MM/yyyy HH:mm' : 'dd/MM/yyyy')
|
||||
block.options?.format ??
|
||||
(block.options?.hasTime
|
||||
? defaultDateInputOptions.formatWithTime
|
||||
: defaultDateInputOptions.format)
|
||||
|
||||
const detectedStartDate = parseDateWithNeutralTimezone(
|
||||
parsedDate[0].start.date()
|
||||
@@ -25,25 +29,27 @@ export const parseDateReply = (
|
||||
? format(detectedEndDate, formatString)
|
||||
: undefined
|
||||
|
||||
if (block.options.isRange && !endDate) return { status: 'fail' }
|
||||
if (block.options?.isRange && !endDate) return { status: 'fail' }
|
||||
|
||||
const max = block.options?.max
|
||||
if (
|
||||
block.options.max &&
|
||||
(detectedStartDate > new Date(block.options.max) ||
|
||||
(detectedEndDate && detectedEndDate > new Date(block.options.max)))
|
||||
isDefined(max) &&
|
||||
(detectedStartDate > new Date(max) ||
|
||||
(detectedEndDate && detectedEndDate > new Date(max)))
|
||||
)
|
||||
return { status: 'fail' }
|
||||
|
||||
const min = block.options?.min
|
||||
if (
|
||||
block.options.min &&
|
||||
(detectedStartDate < new Date(block.options.min) ||
|
||||
(detectedEndDate && detectedEndDate < new Date(block.options.min)))
|
||||
isDefined(min) &&
|
||||
(detectedStartDate < new Date(min) ||
|
||||
(detectedEndDate && detectedEndDate < new Date(min)))
|
||||
)
|
||||
return { status: 'fail' }
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
reply: block.options.isRange ? `${startDate} to ${endDate}` : startDate,
|
||||
reply: block.options?.isRange ? `${startDate} to ${endDate}` : startDate,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import {
|
||||
PaymentInputOptions,
|
||||
PaymentInputBlock,
|
||||
PaymentInputRuntimeOptions,
|
||||
SessionState,
|
||||
StripeCredentials,
|
||||
@@ -9,20 +9,23 @@ import Stripe from 'stripe'
|
||||
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
|
||||
import { parseVariables } from '../../../variables/parseVariables'
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { defaultPaymentInputOptions } from '@typebot.io/schemas/features/blocks/inputs/payment/constants'
|
||||
|
||||
export const computePaymentInputRuntimeOptions =
|
||||
(state: SessionState) => (options: PaymentInputOptions) =>
|
||||
(state: SessionState) => (options: PaymentInputBlock['options']) =>
|
||||
createStripePaymentIntent(state)(options)
|
||||
|
||||
const createStripePaymentIntent =
|
||||
(state: SessionState) =>
|
||||
async (options: PaymentInputOptions): Promise<PaymentInputRuntimeOptions> => {
|
||||
async (
|
||||
options: PaymentInputBlock['options']
|
||||
): Promise<PaymentInputRuntimeOptions> => {
|
||||
const {
|
||||
resultId,
|
||||
typebot: { variables },
|
||||
} = state.typebotsQueue[0]
|
||||
const isPreview = !resultId
|
||||
if (!options.credentialsId)
|
||||
if (!options?.credentialsId)
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Missing credentialsId',
|
||||
@@ -39,9 +42,10 @@ const createStripePaymentIntent =
|
||||
: stripeKeys.live.secretKey,
|
||||
{ apiVersion: '2022-11-15' }
|
||||
)
|
||||
const currency = options?.currency ?? defaultPaymentInputOptions.currency
|
||||
const amount = Math.round(
|
||||
Number(parseVariables(variables)(options.amount)) *
|
||||
(isZeroDecimalCurrency(options.currency) ? 1 : 100)
|
||||
(isZeroDecimalCurrency(currency) ? 1 : 100)
|
||||
)
|
||||
if (isNaN(amount))
|
||||
throw new TRPCError({
|
||||
@@ -55,7 +59,7 @@ const createStripePaymentIntent =
|
||||
)
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount,
|
||||
currency: options.currency,
|
||||
currency,
|
||||
receipt_email: receiptEmail === '' ? undefined : receiptEmail,
|
||||
description: options.additionalInformation?.description,
|
||||
automatic_payment_methods: {
|
||||
@@ -84,7 +88,7 @@ const createStripePaymentIntent =
|
||||
? stripeKeys.test.publicKey
|
||||
: stripeKeys.live.publicKey,
|
||||
amountLabel: priceFormatter.format(
|
||||
amount / (isZeroDecimalCurrency(options.currency) ? 1 : 100)
|
||||
amount / (isZeroDecimalCurrency(currency) ? 1 : 100)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
VariableWithValue,
|
||||
ItemType,
|
||||
PictureChoiceBlock,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
@@ -12,19 +11,19 @@ export const injectVariableValuesInPictureChoiceBlock =
|
||||
(variables: Variable[]) =>
|
||||
(block: PictureChoiceBlock): PictureChoiceBlock => {
|
||||
if (
|
||||
block.options.dynamicItems?.isEnabled &&
|
||||
block.options?.dynamicItems?.isEnabled &&
|
||||
block.options.dynamicItems.pictureSrcsVariableId
|
||||
) {
|
||||
const pictureSrcsVariable = variables.find(
|
||||
(variable) =>
|
||||
variable.id === block.options.dynamicItems?.pictureSrcsVariableId &&
|
||||
variable.id === block.options?.dynamicItems?.pictureSrcsVariableId &&
|
||||
isDefined(variable.value)
|
||||
) as VariableWithValue | undefined
|
||||
if (!pictureSrcsVariable) return block
|
||||
const titlesVariable = block.options.dynamicItems.titlesVariableId
|
||||
? (variables.find(
|
||||
(variable) =>
|
||||
variable.id === block.options.dynamicItems?.titlesVariableId &&
|
||||
variable.id === block.options?.dynamicItems?.titlesVariableId &&
|
||||
isDefined(variable.value)
|
||||
) as VariableWithValue | undefined)
|
||||
: undefined
|
||||
@@ -37,7 +36,7 @@ export const injectVariableValuesInPictureChoiceBlock =
|
||||
? (variables.find(
|
||||
(variable) =>
|
||||
variable.id ===
|
||||
block.options.dynamicItems?.descriptionsVariableId &&
|
||||
block.options?.dynamicItems?.descriptionsVariableId &&
|
||||
isDefined(variable.value)
|
||||
) as VariableWithValue | undefined)
|
||||
: undefined
|
||||
@@ -55,7 +54,6 @@ export const injectVariableValuesInPictureChoiceBlock =
|
||||
...block,
|
||||
items: variableValues.filter(isDefined).map((pictureSrc, idx) => ({
|
||||
id: idx.toString(),
|
||||
type: ItemType.PICTURE_CHOICE,
|
||||
blockId: block.id,
|
||||
pictureSrc,
|
||||
title: titlesVariableValues?.[idx] ?? '',
|
||||
|
||||
@@ -8,7 +8,7 @@ export const parsePictureChoicesReply =
|
||||
const displayedItems = injectVariableValuesInPictureChoiceBlock(
|
||||
state.typebotsQueue[0].typebot.variables
|
||||
)(block).items
|
||||
if (block.options.isMultipleChoice) {
|
||||
if (block.options?.isMultipleChoice) {
|
||||
const longestItemsFirst = [...displayedItems].sort(
|
||||
(a, b) => (b.title?.length ?? 0) - (a.title?.length ?? 0)
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { RatingInputBlock } from '@typebot.io/schemas'
|
||||
import { defaultRatingInputOptions } from '@typebot.io/schemas/features/blocks/inputs/rating/constants'
|
||||
|
||||
export const validateRatingReply = (reply: string, block: RatingInputBlock) =>
|
||||
Number(reply) <= block.options.length
|
||||
Number(reply) <= (block.options?.length ?? defaultRatingInputOptions.length)
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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}}}`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AbTestBlock, SessionState } from '@typebot.io/schemas'
|
||||
import { ExecuteLogicResponse } from '../../../types'
|
||||
import { defaultAbTestOptions } from '@typebot.io/schemas/features/blocks/logic/abTest/constants'
|
||||
|
||||
export const executeAbTest = (
|
||||
_: SessionState,
|
||||
@@ -7,7 +8,10 @@ export const executeAbTest = (
|
||||
): ExecuteLogicResponse => {
|
||||
const aEdgeId = block.items[0].outgoingEdgeId
|
||||
const random = Math.random() * 100
|
||||
if (random < block.options.aPercent && aEdgeId) {
|
||||
if (
|
||||
random < (block.options?.aPercent ?? defaultAbTestOptions.aPercent) &&
|
||||
aEdgeId
|
||||
) {
|
||||
return { outgoingEdgeId: aEdgeId }
|
||||
}
|
||||
const bEdgeId = block.items[1].outgoingEdgeId
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import { isNotDefined, isDefined } from '@typebot.io/lib'
|
||||
import {
|
||||
Comparison,
|
||||
ComparisonOperators,
|
||||
Condition,
|
||||
LogicalOperator,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
import { Comparison, Condition, Variable } from '@typebot.io/schemas'
|
||||
import { findUniqueVariableValue } from '../../../variables/findUniqueVariableValue'
|
||||
import { parseVariables } from '../../../variables/parseVariables'
|
||||
import {
|
||||
LogicalOperator,
|
||||
ComparisonOperators,
|
||||
defaultConditionItemContent,
|
||||
} from '@typebot.io/schemas/features/blocks/logic/condition/constants'
|
||||
|
||||
export const executeCondition =
|
||||
(variables: Variable[]) =>
|
||||
(condition: Condition): boolean =>
|
||||
condition.logicalOperator === LogicalOperator.AND
|
||||
(condition: Condition): boolean => {
|
||||
if (!condition.comparisons) return false
|
||||
return (condition.logicalOperator ??
|
||||
defaultConditionItemContent.logicalOperator) === LogicalOperator.AND
|
||||
? condition.comparisons.every(executeComparison(variables))
|
||||
: condition.comparisons.some(executeComparison(variables))
|
||||
}
|
||||
|
||||
const executeComparison =
|
||||
(variables: Variable[]) =>
|
||||
|
||||
@@ -7,8 +7,8 @@ export const executeConditionBlock = (
|
||||
block: ConditionBlock
|
||||
): ExecuteLogicResponse => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
const passedCondition = block.items.find((item) =>
|
||||
executeCondition(variables)(item.content)
|
||||
const passedCondition = block.items.find(
|
||||
(item) => item.content && executeCondition(variables)(item.content)
|
||||
)
|
||||
return {
|
||||
outgoingEdgeId: passedCondition
|
||||
|
||||
@@ -6,22 +6,23 @@ import { JumpBlock } from '@typebot.io/schemas/features/blocks/logic/jump'
|
||||
|
||||
export const executeJumpBlock = (
|
||||
state: SessionState,
|
||||
{ groupId, blockId }: JumpBlock['options']
|
||||
{ groupId, blockId }: JumpBlock['options'] = {}
|
||||
): ExecuteLogicResponse => {
|
||||
if (!groupId) return { outgoingEdgeId: undefined }
|
||||
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]
|
||||
|
||||
if (!blockToJumpTo?.groupId)
|
||||
if (!blockToJumpTo)
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: 'Block to jump to is not found',
|
||||
})
|
||||
|
||||
const portalEdge = createPortalEdge({
|
||||
to: { groupId: blockToJumpTo?.groupId, blockId: blockToJumpTo?.id },
|
||||
to: { groupId, blockId: blockToJumpTo?.id },
|
||||
})
|
||||
const newSessionState = addEdgeToTypebot(state, portalEdge)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export const executeScript = (
|
||||
block: ScriptBlock
|
||||
): ExecuteLogicResponse => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (!block.options.content || state.whatsApp)
|
||||
if (!block.options?.content || state.whatsApp)
|
||||
return { outgoingEdgeId: block.outgoingEdgeId }
|
||||
|
||||
const scriptToExecute = parseScriptToExecuteClientSideAction(
|
||||
|
||||
@@ -76,7 +76,7 @@ const evaluateSetVariableExpression =
|
||||
const getExpressionToEvaluate =
|
||||
(state: SessionState) =>
|
||||
(options: SetVariableBlock['options']): string | null => {
|
||||
switch (options.type) {
|
||||
switch (options?.type) {
|
||||
case 'Contact name':
|
||||
return state.whatsApp?.contact.name ?? null
|
||||
case 'Phone number': {
|
||||
@@ -102,6 +102,12 @@ const getExpressionToEvaluate =
|
||||
return `const itemIndex = ${options.mapListItemParams?.baseListVariableId}.indexOf(${options.mapListItemParams?.baseItemVariableId})
|
||||
return ${options.mapListItemParams?.targetListVariableId}.at(itemIndex)`
|
||||
}
|
||||
case 'Append value(s)': {
|
||||
return `if(!${options.item}) return ${options.variableId};
|
||||
if(!${options.variableId}) return [${options.item}];
|
||||
if(!Array.isArray(${options.variableId})) return [${options.variableId}, ${options.item}];
|
||||
return (${options.variableId}).concat(${options.item});`
|
||||
}
|
||||
case 'Empty': {
|
||||
return null
|
||||
}
|
||||
@@ -117,7 +123,7 @@ const getExpressionToEvaluate =
|
||||
}
|
||||
case 'Custom':
|
||||
case undefined: {
|
||||
return options.expressionToEvaluate ?? null
|
||||
return options?.expressionToEvaluate ?? null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,19 +14,21 @@ import { isNotDefined } from '@typebot.io/lib/utils'
|
||||
import { createResultIfNotExist } from '../../../queries/createResultIfNotExist'
|
||||
import { executeJumpBlock } from '../jump/executeJumpBlock'
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { defaultTypebotLinkOptions } from '@typebot.io/schemas/features/blocks/logic/typebotLink/constants'
|
||||
import { saveVisitedEdges } from '../../../queries/saveVisitedEdges'
|
||||
|
||||
export const executeTypebotLink = async (
|
||||
state: SessionState,
|
||||
block: TypebotLinkBlock
|
||||
): Promise<ExecuteLogicResponse> => {
|
||||
const logs: ReplyLog[] = []
|
||||
const typebotId = block.options.typebotId
|
||||
const typebotId = block.options?.typebotId
|
||||
if (
|
||||
typebotId === 'current' ||
|
||||
typebotId === state.typebotsQueue[0].typebot.id
|
||||
) {
|
||||
return executeJumpBlock(state, {
|
||||
groupId: block.options.groupId,
|
||||
groupId: block.options?.groupId,
|
||||
})
|
||||
}
|
||||
if (!typebotId) {
|
||||
@@ -42,7 +44,7 @@ export const executeTypebotLink = async (
|
||||
logs.push({
|
||||
status: 'error',
|
||||
description: `Failed to link typebot`,
|
||||
details: `Typebot with ID ${block.options.typebotId} not found`,
|
||||
details: `Typebot with ID ${block.options?.typebotId} not found`,
|
||||
})
|
||||
return { outgoingEdgeId: block.outgoingEdgeId, logs }
|
||||
}
|
||||
@@ -53,7 +55,7 @@ export const executeTypebotLink = async (
|
||||
)
|
||||
|
||||
const nextGroupId =
|
||||
block.options.groupId ??
|
||||
block.options?.groupId ??
|
||||
linkedTypebot.groups.find((group) =>
|
||||
group.blocks.some((block) => block.type === 'start')
|
||||
)?.id
|
||||
@@ -61,7 +63,7 @@ export const executeTypebotLink = async (
|
||||
logs.push({
|
||||
status: 'error',
|
||||
description: `Failed to link typebot`,
|
||||
details: `Group with ID "${block.options.groupId}" not found`,
|
||||
details: `Group with ID "${block.options?.groupId}" not found`,
|
||||
})
|
||||
return { outgoingEdgeId: block.outgoingEdgeId, logs }
|
||||
}
|
||||
@@ -96,12 +98,14 @@ const addLinkedTypebotToState = async (
|
||||
}
|
||||
: currentTypebotInQueue
|
||||
|
||||
const shouldMergeResults = block.options.mergeResults !== false
|
||||
const shouldMergeResults =
|
||||
currentTypebotInQueue.typebot.version === '6'
|
||||
? block.options?.mergeResults ?? defaultTypebotLinkOptions.mergeResults
|
||||
: block.options?.mergeResults !== false
|
||||
|
||||
if (
|
||||
currentTypebotInQueue.resultId &&
|
||||
currentTypebotInQueue.answers.length === 0 &&
|
||||
shouldMergeResults
|
||||
currentTypebotInQueue.answers.length === 0
|
||||
) {
|
||||
await createResultIfNotExist({
|
||||
resultId: currentTypebotInQueue.resultId,
|
||||
@@ -159,11 +163,10 @@ const createResumeEdgeIfNecessary = (
|
||||
return {
|
||||
id: createId(),
|
||||
from: {
|
||||
groupId: '',
|
||||
blockId: '',
|
||||
},
|
||||
to: {
|
||||
groupId: nextBlockInGroup.groupId,
|
||||
groupId: currentGroup.id,
|
||||
blockId: nextBlockInGroup.id,
|
||||
},
|
||||
}
|
||||
@@ -196,6 +199,7 @@ const fetchTypebot = async (state: SessionState, typebotId: string) => {
|
||||
edges: true,
|
||||
groups: true,
|
||||
variables: true,
|
||||
events: true,
|
||||
},
|
||||
})
|
||||
return typebotInSessionStateSchema.parse(typebot)
|
||||
@@ -208,6 +212,7 @@ const fetchTypebot = async (state: SessionState, typebotId: string) => {
|
||||
edges: true,
|
||||
groups: true,
|
||||
variables: true,
|
||||
events: true,
|
||||
},
|
||||
})
|
||||
if (!typebot) return null
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { User } from '@typebot.io/prisma'
|
||||
import {
|
||||
LogicBlockType,
|
||||
Block,
|
||||
PublicTypebot,
|
||||
Typebot,
|
||||
TypebotLinkBlock,
|
||||
} from '@typebot.io/schemas'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { fetchLinkedTypebots } from './fetchLinkedTypebots'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
|
||||
type Props = {
|
||||
typebots: Pick<PublicTypebot, 'groups'>[]
|
||||
@@ -23,18 +24,18 @@ export const getPreviouslyLinkedTypebots =
|
||||
.flatMap((typebot) =>
|
||||
(
|
||||
typebot.groups
|
||||
.flatMap((group) => group.blocks)
|
||||
.flatMap<Block>((group) => group.blocks)
|
||||
.filter(
|
||||
(block) =>
|
||||
block.type === LogicBlockType.TYPEBOT_LINK &&
|
||||
isDefined(block.options.typebotId) &&
|
||||
isDefined(block.options?.typebotId) &&
|
||||
!capturedLinkedBots.some(
|
||||
(bot) =>
|
||||
('typebotId' in bot ? bot.typebotId : bot.id) ===
|
||||
block.options.typebotId
|
||||
block.options?.typebotId
|
||||
)
|
||||
) as TypebotLinkBlock[]
|
||||
).map((s) => s.options.typebotId)
|
||||
).map((b) => b.options?.typebotId)
|
||||
)
|
||||
.filter(isDefined)
|
||||
if (linkedTypebotIds.length === 0) return capturedLinkedBots
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
import { ExecuteLogicResponse } from '../../../types'
|
||||
import { SessionState, WaitBlock } from '@typebot.io/schemas'
|
||||
import { parseVariables } from '../../../variables/parseVariables'
|
||||
import { isNotDefined } from '@typebot.io/lib'
|
||||
|
||||
export const executeWait = (
|
||||
state: SessionState,
|
||||
block: WaitBlock
|
||||
): ExecuteLogicResponse => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (!block.options?.secondsToWaitFor)
|
||||
return { outgoingEdgeId: block.outgoingEdgeId }
|
||||
|
||||
const parsedSecondsToWaitFor = safeParseInt(
|
||||
parseVariables(variables)(block.options.secondsToWaitFor)
|
||||
)
|
||||
|
||||
if (isNotDefined(parsedSecondsToWaitFor))
|
||||
return { outgoingEdgeId: block.outgoingEdgeId }
|
||||
|
||||
return {
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
clientSideActions:
|
||||
parsedSecondsToWaitFor || block.options.shouldPause
|
||||
parsedSecondsToWaitFor || block.options?.shouldPause
|
||||
? [
|
||||
{
|
||||
wait: { secondsToWaitFor: parsedSecondsToWaitFor ?? 0 },
|
||||
|
||||
Reference in New Issue
Block a user