🐛(setVariable) Improve how set variable is evaluated
Avoid evaluating in sandbox when not necessary
This commit is contained in:
@@ -5,8 +5,9 @@ import {
|
|||||||
SetVariableHistoryItem,
|
SetVariableHistoryItem,
|
||||||
Variable,
|
Variable,
|
||||||
VariableWithUnknowValue,
|
VariableWithUnknowValue,
|
||||||
|
VariableWithValue,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { byId, isEmpty } from '@typebot.io/lib'
|
import { byId, isEmpty, isNotDefined } from '@typebot.io/lib'
|
||||||
import { ExecuteLogicResponse } from '../../../types'
|
import { ExecuteLogicResponse } from '../../../types'
|
||||||
import { parseScriptToExecuteClientSideAction } from '../script/executeScript'
|
import { parseScriptToExecuteClientSideAction } from '../script/executeScript'
|
||||||
import { parseVariables } from '@typebot.io/variables/parseVariables'
|
import { parseVariables } from '@typebot.io/variables/parseVariables'
|
||||||
@@ -41,19 +42,19 @@ export const executeSetVariable = async (
|
|||||||
block.id,
|
block.id,
|
||||||
setVariableHistory
|
setVariableHistory
|
||||||
)
|
)
|
||||||
const isCustomValue = !block.options.type || block.options.type === 'Custom'
|
|
||||||
const isCode =
|
const isCode =
|
||||||
(!block.options.type || block.options.type === 'Custom') &&
|
(!block.options.type || block.options.type === 'Custom') &&
|
||||||
(block.options.isCode ?? defaultSetVariableOptions.isCode)
|
(block.options.isCode ?? defaultSetVariableOptions.isCode)
|
||||||
if (
|
if (
|
||||||
expressionToEvaluate &&
|
expressionToEvaluate &&
|
||||||
|
expressionToEvaluate.type === 'code' &&
|
||||||
!state.whatsApp &&
|
!state.whatsApp &&
|
||||||
((isCustomValue && block.options.isExecutedOnClient) ||
|
(block.options.isExecutedOnClient ||
|
||||||
block.options.type === 'Moment of the day')
|
block.options.type === 'Moment of the day')
|
||||||
) {
|
) {
|
||||||
const scriptToExecute = parseScriptToExecuteClientSideAction(
|
const scriptToExecute = parseScriptToExecuteClientSideAction(
|
||||||
variables,
|
variables,
|
||||||
expressionToEvaluate
|
expressionToEvaluate.code
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
outgoingEdgeId: block.outgoingEdgeId,
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
@@ -116,15 +117,31 @@ export const executeSetVariable = async (
|
|||||||
|
|
||||||
const evaluateSetVariableExpression =
|
const evaluateSetVariableExpression =
|
||||||
(variables: Variable[]) =>
|
(variables: Variable[]) =>
|
||||||
(str: string): { value: unknown; error?: string } => {
|
(
|
||||||
|
expression:
|
||||||
|
| {
|
||||||
|
type: 'code'
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
| { type: 'value'; value: VariableWithValue['value'] }
|
||||||
|
): { value: unknown; error?: string } => {
|
||||||
|
if (expression.type === 'value') return { value: expression.value }
|
||||||
const isSingleVariable =
|
const isSingleVariable =
|
||||||
str.startsWith('{{') && str.endsWith('}}') && str.split('{{').length === 2
|
expression.code.startsWith('{{') &&
|
||||||
if (isSingleVariable) return { value: parseVariables(variables)(str) }
|
expression.code.endsWith('}}') &&
|
||||||
|
expression.code.split('{{').length === 2
|
||||||
|
if (isSingleVariable)
|
||||||
|
return { value: parseVariables(variables)(expression.code) }
|
||||||
// To avoid octal number evaluation
|
// To avoid octal number evaluation
|
||||||
if (!isNaN(str as unknown as number) && /0[^.].+/.test(str))
|
if (
|
||||||
return { value: str }
|
!isNaN(expression.code as unknown as number) &&
|
||||||
|
/0[^.].+/.test(expression.code)
|
||||||
|
)
|
||||||
|
return { value: expression.code }
|
||||||
try {
|
try {
|
||||||
const body = parseVariables(variables, { fieldToParse: 'id' })(str)
|
const body = parseVariables(variables, { fieldToParse: 'id' })(
|
||||||
|
expression.code
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
value: createCodeRunner({ variables })(
|
value: createCodeRunner({ variables })(
|
||||||
body.includes('return ') ? body : `return ${body}`
|
body.includes('return ') ? body : `return ${body}`
|
||||||
@@ -132,7 +149,7 @@ const evaluateSetVariableExpression =
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return {
|
||||||
value: parseVariables(variables)(str),
|
value: parseVariables(variables)(expression.code),
|
||||||
error: stringifyError(err),
|
error: stringifyError(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,82 +161,176 @@ const getExpressionToEvaluate =
|
|||||||
options: SetVariableBlock['options'],
|
options: SetVariableBlock['options'],
|
||||||
blockId: string,
|
blockId: string,
|
||||||
setVariableHistory: SetVariableHistoryItem[]
|
setVariableHistory: SetVariableHistoryItem[]
|
||||||
): Promise<string | null> => {
|
): Promise<
|
||||||
|
| { type: 'code'; code: string }
|
||||||
|
| { type: 'value'; value: VariableWithValue['value'] }
|
||||||
|
| null
|
||||||
|
> => {
|
||||||
switch (options?.type) {
|
switch (options?.type) {
|
||||||
case 'Contact name':
|
case 'Contact name':
|
||||||
return state.whatsApp?.contact.name ?? null
|
return state.whatsApp?.contact.name
|
||||||
|
? { type: 'value', value: state.whatsApp.contact.name }
|
||||||
|
: null
|
||||||
case 'Phone number': {
|
case 'Phone number': {
|
||||||
const phoneNumber = state.whatsApp?.contact.phoneNumber
|
return state.whatsApp?.contact.phoneNumber
|
||||||
return phoneNumber ? `"${state.whatsApp?.contact.phoneNumber}"` : null
|
? { type: 'value', value: state.whatsApp.contact.phoneNumber }
|
||||||
|
: null
|
||||||
}
|
}
|
||||||
case 'Now': {
|
case 'Now': {
|
||||||
const timeZone = parseVariables(
|
const timeZone = parseVariables(
|
||||||
state.typebotsQueue[0].typebot.variables
|
state.typebotsQueue[0].typebot.variables
|
||||||
)(options.timeZone)
|
)(options.timeZone)
|
||||||
if (isEmpty(timeZone)) return 'new Date().toISOString()'
|
if (isEmpty(timeZone))
|
||||||
return toISOWithTz(new Date(), timeZone)
|
return { type: 'value', value: new Date().toISOString() }
|
||||||
|
return { type: 'value', value: toISOWithTz(new Date(), timeZone) }
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'Today':
|
case 'Today':
|
||||||
return 'new Date().toISOString()'
|
return { type: 'value', value: new Date().toISOString() }
|
||||||
case 'Tomorrow': {
|
case 'Tomorrow': {
|
||||||
const timeZone = parseVariables(
|
const timeZone = parseVariables(
|
||||||
state.typebotsQueue[0].typebot.variables
|
state.typebotsQueue[0].typebot.variables
|
||||||
)(options.timeZone)
|
)(options.timeZone)
|
||||||
if (isEmpty(timeZone))
|
if (isEmpty(timeZone))
|
||||||
return 'new Date(Date.now() + 86400000).toISOString()'
|
return {
|
||||||
return toISOWithTz(new Date(Date.now() + 86400000), timeZone)
|
type: 'value',
|
||||||
|
value: new Date(Date.now() + 86400000).toISOString(),
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'value',
|
||||||
|
value: toISOWithTz(new Date(Date.now() + 86400000), timeZone),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case 'Yesterday': {
|
case 'Yesterday': {
|
||||||
const timeZone = parseVariables(
|
const timeZone = parseVariables(
|
||||||
state.typebotsQueue[0].typebot.variables
|
state.typebotsQueue[0].typebot.variables
|
||||||
)(options.timeZone)
|
)(options.timeZone)
|
||||||
if (isEmpty(timeZone))
|
if (isEmpty(timeZone))
|
||||||
return 'new Date(Date.now() - 86400000).toISOString()'
|
return {
|
||||||
return toISOWithTz(new Date(Date.now() - 86400000), timeZone)
|
type: 'value',
|
||||||
|
value: new Date(Date.now() - 86400000).toISOString(),
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'value',
|
||||||
|
value: toISOWithTz(new Date(Date.now() - 86400000), timeZone),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case 'Random ID': {
|
case 'Random ID': {
|
||||||
return `"${createId()}"`
|
return { type: 'value', value: createId() }
|
||||||
}
|
}
|
||||||
case 'Result ID':
|
case 'Result ID':
|
||||||
case 'User ID': {
|
case 'User ID': {
|
||||||
return state.typebotsQueue[0].resultId ?? `"${createId()}"`
|
return {
|
||||||
|
type: 'value',
|
||||||
|
value: state.typebotsQueue[0].resultId ?? createId(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case 'Map item with same index': {
|
case 'Map item with same index': {
|
||||||
return `const itemIndex = ${options.mapListItemParams?.baseListVariableId}.indexOf(${options.mapListItemParams?.baseItemVariableId})
|
const baseListVariableValue =
|
||||||
return ${options.mapListItemParams?.targetListVariableId}.at(itemIndex)`
|
state.typebotsQueue[0].typebot.variables.find(
|
||||||
|
byId(options.mapListItemParams?.baseListVariableId)
|
||||||
|
)?.value
|
||||||
|
const baseItemVariableValue =
|
||||||
|
state.typebotsQueue[0].typebot.variables.find(
|
||||||
|
byId(options.mapListItemParams?.baseItemVariableId)
|
||||||
|
)?.value
|
||||||
|
const targetListVariableValue =
|
||||||
|
state.typebotsQueue[0].typebot.variables.find(
|
||||||
|
byId(options.mapListItemParams?.targetListVariableId)
|
||||||
|
)?.value
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Array.isArray(baseListVariableValue) ||
|
||||||
|
!baseItemVariableValue ||
|
||||||
|
typeof baseItemVariableValue !== 'string'
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
const itemIndex = baseListVariableValue.indexOf(baseItemVariableValue)
|
||||||
|
if (itemIndex === -1 || !Array.isArray(targetListVariableValue))
|
||||||
|
return null
|
||||||
|
const value = targetListVariableValue.at(itemIndex)
|
||||||
|
if (isEmpty(value)) return null
|
||||||
|
return {
|
||||||
|
type: 'value',
|
||||||
|
value,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case 'Pop': {
|
case 'Pop': {
|
||||||
return `${options.variableId} && Array.isArray(${options.variableId}) ? ${options.variableId}.slice(0, -1) : []`
|
const variableValue = state.typebotsQueue[0].typebot.variables.find(
|
||||||
|
byId(options.variableId)
|
||||||
|
)?.value
|
||||||
|
if (isNotDefined(variableValue)) return null
|
||||||
|
if (!Array.isArray(variableValue))
|
||||||
|
return {
|
||||||
|
type: 'value',
|
||||||
|
value: variableValue,
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'value',
|
||||||
|
value: variableValue.slice(0, -1),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case 'Shift': {
|
case 'Shift': {
|
||||||
return `${options.variableId} && Array.isArray(${options.variableId}) ? ${options.variableId}.slice(1) : []`
|
const variableValue = state.typebotsQueue[0].typebot.variables.find(
|
||||||
|
byId(options.variableId)
|
||||||
|
)?.value
|
||||||
|
if (isNotDefined(variableValue)) return null
|
||||||
|
if (!Array.isArray(variableValue))
|
||||||
|
return {
|
||||||
|
type: 'value',
|
||||||
|
value: variableValue,
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'value',
|
||||||
|
value: variableValue.slice(1),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case 'Append value(s)': {
|
case 'Append value(s)': {
|
||||||
|
if (!options.variableId) return null
|
||||||
const item = parseVariables(state.typebotsQueue[0].typebot.variables)(
|
const item = parseVariables(state.typebotsQueue[0].typebot.variables)(
|
||||||
options.item
|
options.item
|
||||||
).replaceAll('`', '\\`')
|
)
|
||||||
if (isEmpty(item)) return `return ${options.variableId}`
|
const variableValue = state.typebotsQueue[0].typebot.variables.find(
|
||||||
return `if(!${options.variableId}) return [\`${item}\`];
|
byId(options.variableId)
|
||||||
if(!Array.isArray(${options.variableId})) return [${options.variableId}, \`${item}\`];
|
)?.value
|
||||||
return (${options.variableId}).concat(\`${item}\`);`
|
if (isNotDefined(variableValue))
|
||||||
|
return {
|
||||||
|
type: 'value',
|
||||||
|
value: [item],
|
||||||
|
}
|
||||||
|
if (isEmpty(item))
|
||||||
|
return {
|
||||||
|
type: 'value',
|
||||||
|
value: Array.isArray(variableValue)
|
||||||
|
? variableValue
|
||||||
|
: [variableValue],
|
||||||
|
}
|
||||||
|
if (!Array.isArray(variableValue))
|
||||||
|
return { type: 'value', value: [variableValue, item] }
|
||||||
|
return { type: 'value', value: variableValue.concat(item) }
|
||||||
}
|
}
|
||||||
case 'Empty': {
|
case 'Empty': {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
case 'Moment of the day': {
|
case 'Moment of the day': {
|
||||||
return `const now = new Date()
|
return {
|
||||||
|
type: 'code',
|
||||||
|
code: `const now = new Date()
|
||||||
if(now.getHours() < 12) return 'morning'
|
if(now.getHours() < 12) return 'morning'
|
||||||
if(now.getHours() >= 12 && now.getHours() < 18) return 'afternoon'
|
if(now.getHours() >= 12 && now.getHours() < 18) return 'afternoon'
|
||||||
if(now.getHours() >= 18) return 'evening'
|
if(now.getHours() >= 18) return 'evening'
|
||||||
if(now.getHours() >= 22 || now.getHours() < 6) return 'night'`
|
if(now.getHours() >= 22 || now.getHours() < 6) return 'night'`,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case 'Environment name': {
|
case 'Environment name': {
|
||||||
return state.whatsApp ? 'whatsapp' : 'web'
|
return {
|
||||||
|
type: 'value',
|
||||||
|
value: state.whatsApp ? 'whatsapp' : 'web',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case 'Transcript': {
|
case 'Transcript': {
|
||||||
const props = await parseTranscriptProps(state)
|
const props = await parseTranscriptProps(state)
|
||||||
if (!props) return ''
|
if (!props) return null
|
||||||
const typebotWithEmptyVariables = {
|
const typebotWithEmptyVariables = {
|
||||||
...state.typebotsQueue[0].typebot,
|
...state.typebotsQueue[0].typebot,
|
||||||
variables: state.typebotsQueue[0].typebot.variables.map((v) => ({
|
variables: state.typebotsQueue[0].typebot.variables.map((v) => ({
|
||||||
@@ -234,22 +345,23 @@ const getExpressionToEvaluate =
|
|||||||
setVariableHistory:
|
setVariableHistory:
|
||||||
props.setVariableHistory.concat(setVariableHistory),
|
props.setVariableHistory.concat(setVariableHistory),
|
||||||
})
|
})
|
||||||
return (
|
return {
|
||||||
'return `' +
|
type: 'value',
|
||||||
transcript
|
value: transcript
|
||||||
.map(
|
.map(
|
||||||
(message) =>
|
(message) =>
|
||||||
`${
|
`${
|
||||||
message.role === 'bot' ? 'Assistant:' : 'User:'
|
message.role === 'bot' ? 'Assistant:' : 'User:'
|
||||||
} "${parseTranscriptMessageText(message)}"`
|
} "${parseTranscriptMessageText(message)}"`
|
||||||
)
|
)
|
||||||
.join('\n\n') +
|
.join('\n\n'),
|
||||||
'`'
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case 'Custom':
|
case 'Custom':
|
||||||
case undefined: {
|
case undefined: {
|
||||||
return options?.expressionToEvaluate ?? null
|
return options?.expressionToEvaluate
|
||||||
|
? { type: 'code', code: options.expressionToEvaluate }
|
||||||
|
: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user