@ -20,7 +20,7 @@ export const addEdgeToTypebot = (
|
||||
})
|
||||
|
||||
export const createPortalEdge = ({ to }: Pick<Edge, 'to'>) => ({
|
||||
id: createId(),
|
||||
id: 'virtual-' + createId(),
|
||||
from: { blockId: '', groupId: '' },
|
||||
to,
|
||||
})
|
||||
|
@ -52,6 +52,7 @@ export const continueChat = async ({ origin, sessionId, message }: Props) => {
|
||||
logs,
|
||||
lastMessageNewFormat,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
} = await continueBotFlow(message, {
|
||||
version: 2,
|
||||
state: session.state,
|
||||
@ -68,6 +69,7 @@ export const continueChat = async ({ origin, sessionId, message }: Props) => {
|
||||
logs,
|
||||
clientSideActions,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
hasCustomEmbedBubble: messages.some(
|
||||
(message) => message.type === 'custom-embed'
|
||||
),
|
||||
|
@ -16,6 +16,7 @@ import { isForgedBlockType } from '@typebot.io/schemas/features/blocks/forged/he
|
||||
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
|
||||
import { updateSession } from '../queries/updateSession'
|
||||
import { deepParseVariables } from '@typebot.io/variables/deepParseVariables'
|
||||
import { saveSetVariableHistoryItems } from '../queries/saveSetVariableHistoryItems'
|
||||
|
||||
type Props = {
|
||||
sessionId: string
|
||||
@ -114,11 +115,17 @@ export const getMessageStream = async ({ sessionId, messages }: Props) => {
|
||||
(variable) => variable.id === id
|
||||
)
|
||||
if (!variable) return
|
||||
const { updatedState, newSetVariableHistory } =
|
||||
updateVariablesInSession({
|
||||
newVariables: [{ ...variable, value }],
|
||||
state: session.state,
|
||||
currentBlockId: session.state.currentBlockId,
|
||||
})
|
||||
if (newSetVariableHistory.length > 0)
|
||||
await saveSetVariableHistoryItems(newSetVariableHistory)
|
||||
await updateSession({
|
||||
id: session.id,
|
||||
state: updateVariablesInSession(session.state)([
|
||||
{ ...variable, value },
|
||||
]),
|
||||
state: updatedState,
|
||||
isReplying: undefined,
|
||||
})
|
||||
},
|
||||
|
@ -33,6 +33,7 @@ export const startChat = async ({
|
||||
clientSideActions,
|
||||
newSessionState,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
} = await startSession({
|
||||
version: 2,
|
||||
startParams: {
|
||||
@ -69,6 +70,7 @@ export const startChat = async ({
|
||||
logs,
|
||||
clientSideActions,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
hasCustomEmbedBubble: messages.some(
|
||||
(message) => message.type === 'custom-embed'
|
||||
),
|
||||
|
@ -13,6 +13,7 @@ type Props = {
|
||||
typebot?: StartTypebot
|
||||
userId?: string
|
||||
prefilledVariables?: Record<string, unknown>
|
||||
sessionId?: string
|
||||
}
|
||||
|
||||
export const startChatPreview = async ({
|
||||
@ -24,6 +25,7 @@ export const startChatPreview = async ({
|
||||
typebot: startTypebot,
|
||||
userId,
|
||||
prefilledVariables,
|
||||
sessionId,
|
||||
}: Props) => {
|
||||
const {
|
||||
typebot,
|
||||
@ -34,6 +36,7 @@ export const startChatPreview = async ({
|
||||
clientSideActions,
|
||||
newSessionState,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
} = await startSession({
|
||||
version: 2,
|
||||
startParams: {
|
||||
@ -45,6 +48,7 @@ export const startChatPreview = async ({
|
||||
typebot: startTypebot,
|
||||
userId,
|
||||
prefilledVariables,
|
||||
sessionId,
|
||||
},
|
||||
message,
|
||||
})
|
||||
@ -61,9 +65,11 @@ export const startChatPreview = async ({
|
||||
logs,
|
||||
clientSideActions,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
hasCustomEmbedBubble: messages.some(
|
||||
(message) => message.type === 'custom-embed'
|
||||
),
|
||||
initialSessionId: sessionId,
|
||||
})
|
||||
|
||||
const isEnded =
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { executeCondition } from '@typebot.io/logic/executeCondition'
|
||||
import { ChoiceInputBlock, Variable } from '@typebot.io/schemas'
|
||||
import { executeCondition } from '../../logic/condition/executeCondition'
|
||||
|
||||
export const filterChoiceItems =
|
||||
(variables: Variable[]) =>
|
||||
(block: ChoiceInputBlock): ChoiceInputBlock => {
|
||||
const filteredItems = block.items.filter((item) => {
|
||||
if (item.displayCondition?.isEnabled && item.displayCondition?.condition)
|
||||
return executeCondition(variables)(item.displayCondition.condition)
|
||||
return executeCondition({
|
||||
variables,
|
||||
condition: item.displayCondition.condition,
|
||||
})
|
||||
|
||||
return true
|
||||
})
|
||||
|
@ -41,7 +41,6 @@ const getVariableValue =
|
||||
const [transformedVariable] = transformVariablesToList(variables)([
|
||||
variable.id,
|
||||
])
|
||||
updateVariablesInSession(state)([transformedVariable])
|
||||
return transformedVariable.value as string[]
|
||||
}
|
||||
return variable.value
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { executeCondition } from '@typebot.io/logic/executeCondition'
|
||||
import { PictureChoiceBlock, Variable } from '@typebot.io/schemas'
|
||||
import { executeCondition } from '../../logic/condition/executeCondition'
|
||||
|
||||
export const filterPictureChoiceItems =
|
||||
(variables: Variable[]) =>
|
||||
(block: PictureChoiceBlock): PictureChoiceBlock => {
|
||||
const filteredItems = block.items.filter((item) => {
|
||||
if (item.displayCondition?.isEnabled && item.displayCondition?.condition)
|
||||
return executeCondition(variables)(item.displayCondition.condition)
|
||||
return executeCondition({
|
||||
variables,
|
||||
condition: item.displayCondition.condition,
|
||||
})
|
||||
|
||||
return true
|
||||
})
|
||||
|
@ -25,6 +25,7 @@ export const executeGoogleSheetBlock = async (
|
||||
})
|
||||
case GoogleSheetsAction.GET:
|
||||
return getRow(state, {
|
||||
blockId: block.id,
|
||||
options: block.options,
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
})
|
||||
|
@ -14,9 +14,14 @@ import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesI
|
||||
export const getRow = async (
|
||||
state: SessionState,
|
||||
{
|
||||
blockId,
|
||||
outgoingEdgeId,
|
||||
options,
|
||||
}: { outgoingEdgeId?: string; options: GoogleSheetsGetOptions }
|
||||
}: {
|
||||
blockId: string
|
||||
outgoingEdgeId?: string
|
||||
options: GoogleSheetsGetOptions
|
||||
}
|
||||
): Promise<ExecuteIntegrationResponse> => {
|
||||
const logs: ChatLog[] = []
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
@ -79,10 +84,15 @@ export const getRow = async (
|
||||
[]
|
||||
)
|
||||
if (!newVariables) return { outgoingEdgeId }
|
||||
const newSessionState = updateVariablesInSession(state)(newVariables)
|
||||
const { updatedState, newSetVariableHistory } = updateVariablesInSession({
|
||||
state,
|
||||
newVariables,
|
||||
currentBlockId: blockId,
|
||||
})
|
||||
return {
|
||||
outgoingEdgeId,
|
||||
newSessionState,
|
||||
newSessionState: updatedState,
|
||||
newSetVariableHistory,
|
||||
}
|
||||
} catch (err) {
|
||||
logs.push({
|
||||
|
@ -107,12 +107,16 @@ export const createSpeechOpenAI = async (
|
||||
mimeType: 'audio/mpeg',
|
||||
})
|
||||
|
||||
newSessionState = updateVariablesInSession(newSessionState)([
|
||||
{
|
||||
...saveUrlInVariable,
|
||||
value: url,
|
||||
},
|
||||
])
|
||||
newSessionState = updateVariablesInSession({
|
||||
newVariables: [
|
||||
{
|
||||
...saveUrlInVariable,
|
||||
value: url,
|
||||
},
|
||||
],
|
||||
state: newSessionState,
|
||||
currentBlockId: undefined,
|
||||
}).updatedState
|
||||
|
||||
return {
|
||||
startTimeShouldBeUpdated: true,
|
||||
|
@ -22,7 +22,6 @@ import {
|
||||
defaultOpenAIOptions,
|
||||
} from '@typebot.io/schemas/features/blocks/integrations/openai/constants'
|
||||
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
||||
import { isPlaneteScale } from '@typebot.io/lib/isPlanetScale'
|
||||
|
||||
export const createChatCompletionOpenAI = async (
|
||||
state: SessionState,
|
||||
@ -68,9 +67,11 @@ export const createChatCompletionOpenAI = async (
|
||||
typebot.variables
|
||||
)(options.messages)
|
||||
if (variablesTransformedToList.length > 0)
|
||||
newSessionState = updateVariablesInSession(state)(
|
||||
variablesTransformedToList
|
||||
)
|
||||
newSessionState = updateVariablesInSession({
|
||||
state,
|
||||
newVariables: variablesTransformedToList,
|
||||
currentBlockId: undefined,
|
||||
}).updatedState
|
||||
|
||||
const temperature = parseVariableNumber(typebot.variables)(
|
||||
options.advancedSettings?.temperature
|
||||
|
@ -42,7 +42,11 @@ export const resumeChatCompletion =
|
||||
return newVariables
|
||||
}, [])
|
||||
if (newVariables && newVariables.length > 0)
|
||||
newSessionState = updateVariablesInSession(newSessionState)(newVariables)
|
||||
newSessionState = updateVariablesInSession({
|
||||
newVariables,
|
||||
state: newSessionState,
|
||||
currentBlockId: undefined,
|
||||
}).updatedState
|
||||
return {
|
||||
outgoingEdgeId,
|
||||
newSessionState,
|
||||
|
@ -70,10 +70,15 @@ export const resumeWebhookExecution = ({
|
||||
}
|
||||
}, [])
|
||||
if (newVariables && newVariables.length > 0) {
|
||||
const newSessionState = updateVariablesInSession(state)(newVariables)
|
||||
const { updatedState, newSetVariableHistory } = updateVariablesInSession({
|
||||
newVariables,
|
||||
state,
|
||||
currentBlockId: block.id,
|
||||
})
|
||||
return {
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
newSessionState,
|
||||
newSessionState: updatedState,
|
||||
newSetVariableHistory,
|
||||
logs,
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ export const executeZemanticAiBlock = async (
|
||||
block: ZemanticAiBlock
|
||||
): Promise<ExecuteIntegrationResponse> => {
|
||||
let newSessionState = state
|
||||
let setVariableHistory = []
|
||||
|
||||
if (!block.options?.credentialsId)
|
||||
return {
|
||||
@ -82,24 +83,34 @@ export const executeZemanticAiBlock = async (
|
||||
|
||||
for (const r of block.options.responseMapping || []) {
|
||||
const variable = typebot.variables.find(byId(r.variableId))
|
||||
let newVariables = []
|
||||
switch (r.valueToExtract) {
|
||||
case 'Summary':
|
||||
if (isDefined(variable) && !isEmpty(res.summary)) {
|
||||
newSessionState = updateVariablesInSession(newSessionState)([
|
||||
{ ...variable, value: res.summary },
|
||||
])
|
||||
newVariables.push({ ...variable, value: res.summary })
|
||||
}
|
||||
break
|
||||
case 'Results':
|
||||
if (isDefined(variable) && res.results.length) {
|
||||
newSessionState = updateVariablesInSession(newSessionState)([
|
||||
{ ...variable, value: JSON.stringify(res.results) },
|
||||
])
|
||||
newVariables.push({
|
||||
...variable,
|
||||
value: JSON.stringify(res.results),
|
||||
})
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
if (newVariables.length > 0) {
|
||||
const { newSetVariableHistory, updatedState } =
|
||||
updateVariablesInSession({
|
||||
newVariables,
|
||||
state: newSessionState,
|
||||
currentBlockId: block.id,
|
||||
})
|
||||
newSessionState = updatedState
|
||||
setVariableHistory.push(...newSetVariableHistory)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@ -112,6 +123,7 @@ export const executeZemanticAiBlock = async (
|
||||
description: 'Could not execute Zemantic AI request',
|
||||
},
|
||||
],
|
||||
newSetVariableHistory: setVariableHistory,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,201 +0,0 @@
|
||||
import { isNotDefined, isDefined } from '@typebot.io/lib'
|
||||
import { Comparison, Condition, Variable } from '@typebot.io/schemas'
|
||||
import { findUniqueVariableValue } from '@typebot.io/variables/findUniqueVariableValue'
|
||||
import { parseVariables } from '@typebot.io/variables/parseVariables'
|
||||
import {
|
||||
LogicalOperator,
|
||||
ComparisonOperators,
|
||||
defaultConditionItemContent,
|
||||
} from '@typebot.io/schemas/features/blocks/logic/condition/constants'
|
||||
|
||||
export const executeCondition =
|
||||
(variables: Variable[]) =>
|
||||
(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[]) =>
|
||||
(comparison: Comparison): boolean => {
|
||||
if (!comparison?.variableId) return false
|
||||
const inputValue =
|
||||
variables.find((v) => v.id === comparison.variableId)?.value ?? null
|
||||
const value =
|
||||
comparison.value === 'undefined' || comparison.value === 'null'
|
||||
? null
|
||||
: findUniqueVariableValue(variables)(comparison.value) ??
|
||||
parseVariables(variables)(comparison.value)
|
||||
if (isNotDefined(comparison.comparisonOperator)) return false
|
||||
switch (comparison.comparisonOperator) {
|
||||
case ComparisonOperators.CONTAINS: {
|
||||
if (Array.isArray(inputValue)) {
|
||||
const equal = (a: string | null, b: string | null) => {
|
||||
if (typeof a === 'string' && typeof b === 'string')
|
||||
return a.normalize() === b.normalize()
|
||||
return a !== b
|
||||
}
|
||||
return compare(equal, inputValue, value, 'some')
|
||||
}
|
||||
const contains = (a: string | null, b: string | null) => {
|
||||
if (b === '' || !b || !a) return false
|
||||
return a
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.normalize()
|
||||
.includes(b.toLowerCase().trim().normalize())
|
||||
}
|
||||
return compare(contains, inputValue, value, 'some')
|
||||
}
|
||||
case ComparisonOperators.NOT_CONTAINS: {
|
||||
if (Array.isArray(inputValue)) {
|
||||
const notEqual = (a: string | null, b: string | null) => {
|
||||
if (typeof a === 'string' && typeof b === 'string')
|
||||
return a.normalize() !== b.normalize()
|
||||
return a !== b
|
||||
}
|
||||
return compare(notEqual, inputValue, value)
|
||||
}
|
||||
const notContains = (a: string | null, b: string | null) => {
|
||||
if (b === '' || !b || !a) return true
|
||||
return !a
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.normalize()
|
||||
.includes(b.toLowerCase().trim().normalize())
|
||||
}
|
||||
return compare(notContains, inputValue, value)
|
||||
}
|
||||
case ComparisonOperators.EQUAL: {
|
||||
return compare(
|
||||
(a, b) => {
|
||||
if (typeof a === 'string' && typeof b === 'string')
|
||||
return a.normalize() === b.normalize()
|
||||
return a === b
|
||||
},
|
||||
inputValue,
|
||||
value
|
||||
)
|
||||
}
|
||||
case ComparisonOperators.NOT_EQUAL: {
|
||||
return compare(
|
||||
(a, b) => {
|
||||
if (typeof a === 'string' && typeof b === 'string')
|
||||
return a.normalize() !== b.normalize()
|
||||
return a !== b
|
||||
},
|
||||
inputValue,
|
||||
value
|
||||
)
|
||||
}
|
||||
case ComparisonOperators.GREATER: {
|
||||
if (isNotDefined(inputValue) || isNotDefined(value)) return false
|
||||
if (typeof inputValue === 'string') {
|
||||
if (typeof value === 'string')
|
||||
return parseDateOrNumber(inputValue) > parseDateOrNumber(value)
|
||||
return Number(inputValue) > value.length
|
||||
}
|
||||
if (typeof value === 'string') return inputValue.length > Number(value)
|
||||
return inputValue.length > value.length
|
||||
}
|
||||
case ComparisonOperators.LESS: {
|
||||
if (isNotDefined(inputValue) || isNotDefined(value)) return false
|
||||
if (typeof inputValue === 'string') {
|
||||
if (typeof value === 'string')
|
||||
return parseDateOrNumber(inputValue) < parseDateOrNumber(value)
|
||||
return Number(inputValue) < value.length
|
||||
}
|
||||
if (typeof value === 'string') return inputValue.length < Number(value)
|
||||
return inputValue.length < value.length
|
||||
}
|
||||
case ComparisonOperators.IS_SET: {
|
||||
return isDefined(inputValue) && inputValue.length > 0
|
||||
}
|
||||
case ComparisonOperators.IS_EMPTY: {
|
||||
return isNotDefined(inputValue) || inputValue.length === 0
|
||||
}
|
||||
case ComparisonOperators.STARTS_WITH: {
|
||||
const startsWith = (a: string | null, b: string | null) => {
|
||||
if (b === '' || !b || !a) return false
|
||||
return a
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.normalize()
|
||||
.startsWith(b.toLowerCase().trim().normalize())
|
||||
}
|
||||
return compare(startsWith, inputValue, value)
|
||||
}
|
||||
case ComparisonOperators.ENDS_WITH: {
|
||||
const endsWith = (a: string | null, b: string | null) => {
|
||||
if (b === '' || !b || !a) return false
|
||||
return a
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.normalize()
|
||||
.endsWith(b.toLowerCase().trim().normalize())
|
||||
}
|
||||
return compare(endsWith, inputValue, value)
|
||||
}
|
||||
case ComparisonOperators.MATCHES_REGEX: {
|
||||
const matchesRegex = (a: string | null, b: string | null) => {
|
||||
if (b === '' || !b || !a) return false
|
||||
const regex = preprocessRegex(b)
|
||||
if (!regex) return false
|
||||
return new RegExp(regex.pattern, regex.flags).test(a)
|
||||
}
|
||||
return compare(matchesRegex, inputValue, value, 'some')
|
||||
}
|
||||
case ComparisonOperators.NOT_MATCH_REGEX: {
|
||||
const matchesRegex = (a: string | null, b: string | null) => {
|
||||
if (b === '' || !b || !a) return false
|
||||
const regex = preprocessRegex(b)
|
||||
if (!regex) return true
|
||||
return !new RegExp(regex.pattern, regex.flags).test(a)
|
||||
}
|
||||
return compare(matchesRegex, inputValue, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const compare = (
|
||||
compareStrings: (a: string | null, b: string | null) => boolean,
|
||||
a: Exclude<Variable['value'], undefined>,
|
||||
b: Exclude<Variable['value'], undefined>,
|
||||
type: 'every' | 'some' = 'every'
|
||||
): boolean => {
|
||||
if (!a || typeof a === 'string') {
|
||||
if (!b || typeof b === 'string') return compareStrings(a, b)
|
||||
return type === 'every'
|
||||
? b.every((b) => compareStrings(a, b))
|
||||
: b.some((b) => compareStrings(a, b))
|
||||
}
|
||||
if (!b || typeof b === 'string') {
|
||||
return type === 'every'
|
||||
? a.every((a) => compareStrings(a, b))
|
||||
: a.some((a) => compareStrings(a, b))
|
||||
}
|
||||
if (type === 'every')
|
||||
return a.every((a) => b.every((b) => compareStrings(a, b)))
|
||||
return a.some((a) => b.some((b) => compareStrings(a, b)))
|
||||
}
|
||||
|
||||
const parseDateOrNumber = (value: string): number => {
|
||||
const parsed = Number(value)
|
||||
if (isNaN(parsed)) {
|
||||
const time = Date.parse(value)
|
||||
return time
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
const preprocessRegex = (regex: string) => {
|
||||
const regexWithFlags = regex.match(/\/(.+)\/([gimuy]*)$/)
|
||||
|
||||
if (regexWithFlags)
|
||||
return { pattern: regexWithFlags[1], flags: regexWithFlags[2] }
|
||||
|
||||
return { pattern: regex }
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
import { ConditionBlock, SessionState } from '@typebot.io/schemas'
|
||||
import { ExecuteLogicResponse } from '../../../types'
|
||||
import { executeCondition } from './executeCondition'
|
||||
|
||||
import { executeCondition } from '@typebot.io/logic/executeCondition'
|
||||
export const executeConditionBlock = (
|
||||
state: SessionState,
|
||||
block: ConditionBlock
|
||||
): ExecuteLogicResponse => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
const passedCondition = block.items.find(
|
||||
(item) => item.content && executeCondition(variables)(item.content)
|
||||
(item) =>
|
||||
item.content && executeCondition({ variables, condition: item.content })
|
||||
)
|
||||
return {
|
||||
outgoingEdgeId: passedCondition
|
||||
|
@ -24,14 +24,25 @@ export const executeScript = async (
|
||||
body: block.options.content,
|
||||
})
|
||||
|
||||
const newSessionState = newVariables
|
||||
? updateVariablesInSession(state)(newVariables)
|
||||
: state
|
||||
const updateVarResults = newVariables
|
||||
? updateVariablesInSession({
|
||||
newVariables,
|
||||
state,
|
||||
currentBlockId: block.id,
|
||||
})
|
||||
: undefined
|
||||
|
||||
let newSessionState = state
|
||||
|
||||
if (updateVarResults) {
|
||||
newSessionState = updateVarResults.updatedState
|
||||
}
|
||||
|
||||
return {
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
logs: error ? [{ status: 'error', description: error }] : [],
|
||||
newSessionState,
|
||||
newSetVariableHistory: updateVarResults?.newSetVariableHistory,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,10 @@
|
||||
import { SessionState, SetVariableBlock, Variable } from '@typebot.io/schemas'
|
||||
import {
|
||||
Answer,
|
||||
SessionState,
|
||||
SetVariableBlock,
|
||||
SetVariableHistoryItem,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
import { byId, isEmpty } from '@typebot.io/lib'
|
||||
import { ExecuteLogicResponse } from '../../../types'
|
||||
import { parseScriptToExecuteClientSideAction } from '../script/executeScript'
|
||||
@ -7,18 +13,27 @@ import { parseVariables } from '@typebot.io/variables/parseVariables'
|
||||
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { utcToZonedTime, format as tzFormat } from 'date-fns-tz'
|
||||
import {
|
||||
computeResultTranscript,
|
||||
parseTranscriptMessageText,
|
||||
} from '@typebot.io/logic/computeResultTranscript'
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { sessionOnlySetVariableOptions } from '@typebot.io/schemas/features/blocks/logic/setVariable/constants'
|
||||
import vm from 'vm'
|
||||
|
||||
export const executeSetVariable = (
|
||||
export const executeSetVariable = async (
|
||||
state: SessionState,
|
||||
block: SetVariableBlock
|
||||
): ExecuteLogicResponse => {
|
||||
): Promise<ExecuteLogicResponse> => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (!block.options?.variableId)
|
||||
return {
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
}
|
||||
const expressionToEvaluate = getExpressionToEvaluate(state)(block.options)
|
||||
const expressionToEvaluate = await getExpressionToEvaluate(state)(
|
||||
block.options,
|
||||
block.id
|
||||
)
|
||||
const isCustomValue = !block.options.type || block.options.type === 'Custom'
|
||||
if (
|
||||
expressionToEvaluate &&
|
||||
@ -52,10 +67,25 @@ export const executeSetVariable = (
|
||||
...existingVariable,
|
||||
value: evaluatedExpression,
|
||||
}
|
||||
const newSessionState = updateVariablesInSession(state)([newVariable])
|
||||
const { newSetVariableHistory, updatedState } = updateVariablesInSession({
|
||||
state,
|
||||
newVariables: [
|
||||
{
|
||||
...newVariable,
|
||||
isSessionVariable: sessionOnlySetVariableOptions.includes(
|
||||
block.options.type as (typeof sessionOnlySetVariableOptions)[number]
|
||||
)
|
||||
? true
|
||||
: newVariable.isSessionVariable,
|
||||
},
|
||||
],
|
||||
currentBlockId: block.id,
|
||||
})
|
||||
|
||||
return {
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
newSessionState,
|
||||
newSessionState: updatedState,
|
||||
newSetVariableHistory,
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,7 +115,10 @@ const evaluateSetVariableExpression =
|
||||
|
||||
const getExpressionToEvaluate =
|
||||
(state: SessionState) =>
|
||||
(options: SetVariableBlock['options']): string | null => {
|
||||
async (
|
||||
options: SetVariableBlock['options'],
|
||||
blockId: string
|
||||
): Promise<string | null> => {
|
||||
switch (options?.type) {
|
||||
case 'Contact name':
|
||||
return state.whatsApp?.contact.name ?? null
|
||||
@ -149,6 +182,34 @@ const getExpressionToEvaluate =
|
||||
case 'Environment name': {
|
||||
return state.whatsApp ? 'whatsapp' : 'web'
|
||||
}
|
||||
case 'Transcript': {
|
||||
const props = await parseTranscriptProps(state)
|
||||
if (!props) return ''
|
||||
const typebotWithEmptyVariables = {
|
||||
...state.typebotsQueue[0].typebot,
|
||||
variables: state.typebotsQueue[0].typebot.variables.map((v) => ({
|
||||
...v,
|
||||
value: undefined,
|
||||
})),
|
||||
}
|
||||
const transcript = computeResultTranscript({
|
||||
typebot: typebotWithEmptyVariables,
|
||||
stopAtBlockId: blockId,
|
||||
...props,
|
||||
})
|
||||
return (
|
||||
'return `' +
|
||||
transcript
|
||||
.map(
|
||||
(message) =>
|
||||
`${
|
||||
message.role === 'bot' ? 'Assistant:' : 'User:'
|
||||
} "${parseTranscriptMessageText(message)}"`
|
||||
)
|
||||
.join('\n\n') +
|
||||
'`'
|
||||
)
|
||||
}
|
||||
case 'Custom':
|
||||
case undefined: {
|
||||
return options?.expressionToEvaluate ?? null
|
||||
@ -160,3 +221,79 @@ const toISOWithTz = (date: Date, timeZone: string) => {
|
||||
const zonedDate = utcToZonedTime(date, timeZone)
|
||||
return tzFormat(zonedDate, "yyyy-MM-dd'T'HH:mm:ssXXX", { timeZone })
|
||||
}
|
||||
|
||||
type ParsedTranscriptProps = {
|
||||
answers: Pick<Answer, 'blockId' | 'content'>[]
|
||||
setVariableHistory: Pick<
|
||||
SetVariableHistoryItem,
|
||||
'blockId' | 'variableId' | 'value'
|
||||
>[]
|
||||
visitedEdges: string[]
|
||||
}
|
||||
|
||||
const parseTranscriptProps = async (
|
||||
state: SessionState
|
||||
): Promise<ParsedTranscriptProps | undefined> => {
|
||||
if (!state.typebotsQueue[0].resultId)
|
||||
return parsePreviewTranscriptProps(state)
|
||||
return parseResultTranscriptProps(state)
|
||||
}
|
||||
|
||||
const parsePreviewTranscriptProps = async (
|
||||
state: SessionState
|
||||
): Promise<ParsedTranscriptProps | undefined> => {
|
||||
if (!state.previewMetadata) return
|
||||
return {
|
||||
answers: state.previewMetadata.answers ?? [],
|
||||
setVariableHistory: state.previewMetadata.setVariableHistory ?? [],
|
||||
visitedEdges: state.previewMetadata.visitedEdges ?? [],
|
||||
}
|
||||
}
|
||||
|
||||
const parseResultTranscriptProps = async (
|
||||
state: SessionState
|
||||
): Promise<ParsedTranscriptProps | undefined> => {
|
||||
const result = await prisma.result.findUnique({
|
||||
where: {
|
||||
id: state.typebotsQueue[0].resultId,
|
||||
},
|
||||
select: {
|
||||
edges: {
|
||||
select: {
|
||||
edgeId: true,
|
||||
index: true,
|
||||
},
|
||||
},
|
||||
answers: {
|
||||
select: {
|
||||
blockId: true,
|
||||
content: true,
|
||||
},
|
||||
},
|
||||
answersV2: {
|
||||
select: {
|
||||
blockId: true,
|
||||
content: true,
|
||||
},
|
||||
},
|
||||
setVariableHistory: {
|
||||
select: {
|
||||
blockId: true,
|
||||
variableId: true,
|
||||
index: true,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!result) return
|
||||
return {
|
||||
answers: result.answersV2.concat(result.answers),
|
||||
setVariableHistory: (
|
||||
result.setVariableHistory as SetVariableHistoryItem[]
|
||||
).sort((a, b) => a.index - b.index),
|
||||
visitedEdges: result.edges
|
||||
.sort((a, b) => a.index - b.index)
|
||||
.map((edge) => edge.edgeId),
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
Group,
|
||||
InputBlock,
|
||||
SessionState,
|
||||
SetVariableHistoryItem,
|
||||
} from '@typebot.io/schemas'
|
||||
import { byId } from '@typebot.io/lib'
|
||||
import { isInputBlock } from '@typebot.io/schemas/helpers'
|
||||
@ -13,7 +14,7 @@ import { getNextGroup } from './getNextGroup'
|
||||
import { validateEmail } from './blocks/inputs/email/validateEmail'
|
||||
import { formatPhoneNumber } from './blocks/inputs/phone/formatPhoneNumber'
|
||||
import { resumeWebhookExecution } from './blocks/integrations/webhook/resumeWebhookExecution'
|
||||
import { upsertAnswer } from './queries/upsertAnswer'
|
||||
import { saveAnswer } from './queries/saveAnswer'
|
||||
import { parseButtonsReply } from './blocks/inputs/buttons/parseButtonsReply'
|
||||
import { ParsedReply, Reply } from './types'
|
||||
import { validateNumber } from './blocks/inputs/number/validateNumber'
|
||||
@ -57,11 +58,13 @@ export const continueBotFlow = async (
|
||||
ContinueChatResponse & {
|
||||
newSessionState: SessionState
|
||||
visitedEdges: VisitedEdge[]
|
||||
setVariableHistory: SetVariableHistoryItem[]
|
||||
}
|
||||
> => {
|
||||
let firstBubbleWasStreamed = false
|
||||
let newSessionState = { ...state }
|
||||
const visitedEdges: VisitedEdge[] = []
|
||||
const setVariableHistory: SetVariableHistoryItem[] = []
|
||||
|
||||
if (!newSessionState.currentBlockId) return startBotFlow({ state, version })
|
||||
|
||||
@ -76,16 +79,17 @@ export const continueBotFlow = async (
|
||||
message: 'Group / block not found',
|
||||
})
|
||||
|
||||
let variableToUpdate
|
||||
|
||||
if (block.type === LogicBlockType.SET_VARIABLE) {
|
||||
const existingVariable = state.typebotsQueue[0].typebot.variables.find(
|
||||
byId(block.options?.variableId)
|
||||
)
|
||||
if (existingVariable && reply && typeof reply === 'string') {
|
||||
const newVariable = {
|
||||
variableToUpdate = {
|
||||
...existingVariable,
|
||||
value: safeJsonParse(reply),
|
||||
}
|
||||
newSessionState = updateVariablesInSession(state)([newVariable])
|
||||
}
|
||||
}
|
||||
// Legacy
|
||||
@ -121,42 +125,41 @@ export const continueBotFlow = async (
|
||||
if (action) {
|
||||
if (action.run?.stream?.getStreamVariableId) {
|
||||
firstBubbleWasStreamed = true
|
||||
const variableToUpdate =
|
||||
state.typebotsQueue[0].typebot.variables.find(
|
||||
(v) => v.id === action?.run?.stream?.getStreamVariableId(options)
|
||||
)
|
||||
if (variableToUpdate)
|
||||
newSessionState = updateVariablesInSession(state)([
|
||||
{
|
||||
...variableToUpdate,
|
||||
value: reply,
|
||||
},
|
||||
])
|
||||
variableToUpdate = state.typebotsQueue[0].typebot.variables.find(
|
||||
(v) => v.id === action?.run?.stream?.getStreamVariableId(options)
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
action.run?.web?.displayEmbedBubble?.waitForEvent?.getSaveVariableId
|
||||
) {
|
||||
const variableToUpdate =
|
||||
state.typebotsQueue[0].typebot.variables.find(
|
||||
(v) =>
|
||||
v.id ===
|
||||
action?.run?.web?.displayEmbedBubble?.waitForEvent?.getSaveVariableId?.(
|
||||
options
|
||||
)
|
||||
)
|
||||
if (variableToUpdate)
|
||||
newSessionState = updateVariablesInSession(state)([
|
||||
{
|
||||
...variableToUpdate,
|
||||
value: reply,
|
||||
},
|
||||
])
|
||||
variableToUpdate = state.typebotsQueue[0].typebot.variables.find(
|
||||
(v) =>
|
||||
v.id ===
|
||||
action?.run?.web?.displayEmbedBubble?.waitForEvent?.getSaveVariableId?.(
|
||||
options
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (variableToUpdate) {
|
||||
const { newSetVariableHistory, updatedState } = updateVariablesInSession({
|
||||
state: newSessionState,
|
||||
currentBlockId: block.id,
|
||||
newVariables: [
|
||||
{
|
||||
...variableToUpdate,
|
||||
value: reply,
|
||||
},
|
||||
],
|
||||
})
|
||||
newSessionState = updatedState
|
||||
setVariableHistory.push(...newSetVariableHistory)
|
||||
}
|
||||
|
||||
let formattedReply: string | undefined
|
||||
|
||||
if (isInputBlock(block)) {
|
||||
@ -167,6 +170,7 @@ export const continueBotFlow = async (
|
||||
...(await parseRetryMessage(newSessionState)(block)),
|
||||
newSessionState,
|
||||
visitedEdges: [],
|
||||
setVariableHistory: [],
|
||||
}
|
||||
|
||||
formattedReply =
|
||||
@ -176,7 +180,9 @@ export const continueBotFlow = async (
|
||||
|
||||
const groupHasMoreBlocks = blockIndex < group.blocks.length - 1
|
||||
|
||||
const nextEdgeId = getOutgoingEdgeId(newSessionState)(block, formattedReply)
|
||||
const { edgeId: nextEdgeId, isOffDefaultPath } = getOutgoingEdgeId(
|
||||
newSessionState
|
||||
)(block, formattedReply)
|
||||
|
||||
if (groupHasMoreBlocks && !nextEdgeId) {
|
||||
const chatReply = await executeGroup(
|
||||
@ -188,6 +194,7 @@ export const continueBotFlow = async (
|
||||
version,
|
||||
state: newSessionState,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
firstBubbleWasStreamed,
|
||||
startTime,
|
||||
}
|
||||
@ -206,9 +213,14 @@ export const continueBotFlow = async (
|
||||
lastMessageNewFormat:
|
||||
formattedReply !== reply ? formattedReply : undefined,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
}
|
||||
|
||||
const nextGroup = await getNextGroup(newSessionState)(nextEdgeId)
|
||||
const nextGroup = await getNextGroup({
|
||||
state: newSessionState,
|
||||
edgeId: nextEdgeId,
|
||||
isOffDefaultPath,
|
||||
})
|
||||
|
||||
if (nextGroup.visitedEdge) visitedEdges.push(nextGroup.visitedEdge)
|
||||
|
||||
@ -221,6 +233,7 @@ export const continueBotFlow = async (
|
||||
lastMessageNewFormat:
|
||||
formattedReply !== reply ? formattedReply : undefined,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
}
|
||||
|
||||
const chatReply = await executeGroup(nextGroup.group, {
|
||||
@ -228,6 +241,7 @@ export const continueBotFlow = async (
|
||||
state: newSessionState,
|
||||
firstBubbleWasStreamed,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
startTime,
|
||||
})
|
||||
|
||||
@ -241,8 +255,7 @@ const processAndSaveAnswer =
|
||||
(state: SessionState, block: InputBlock) =>
|
||||
async (reply: string | undefined): Promise<SessionState> => {
|
||||
if (!reply) return state
|
||||
let newState = await saveAnswer(state, block)(reply)
|
||||
newState = saveVariableValueIfAny(newState, block)(reply)
|
||||
let newState = await saveAnswerInDb(state, block)(reply)
|
||||
return newState
|
||||
}
|
||||
|
||||
@ -255,16 +268,20 @@ const saveVariableValueIfAny =
|
||||
)
|
||||
if (!foundVariable) return state
|
||||
|
||||
const newSessionState = updateVariablesInSession(state)([
|
||||
{
|
||||
...foundVariable,
|
||||
value: Array.isArray(foundVariable.value)
|
||||
? foundVariable.value.concat(reply)
|
||||
: reply,
|
||||
},
|
||||
])
|
||||
const { updatedState } = updateVariablesInSession({
|
||||
newVariables: [
|
||||
{
|
||||
...foundVariable,
|
||||
value: Array.isArray(foundVariable.value)
|
||||
? foundVariable.value.concat(reply)
|
||||
: reply,
|
||||
},
|
||||
],
|
||||
currentBlockId: undefined,
|
||||
state,
|
||||
})
|
||||
|
||||
return newSessionState
|
||||
return updatedState
|
||||
}
|
||||
|
||||
const parseRetryMessage =
|
||||
@ -305,31 +322,43 @@ const parseDefaultRetryMessage = (block: InputBlock): string => {
|
||||
}
|
||||
}
|
||||
|
||||
const saveAnswer =
|
||||
const saveAnswerInDb =
|
||||
(state: SessionState, block: InputBlock) =>
|
||||
async (reply: string): Promise<SessionState> => {
|
||||
let newSessionState = state
|
||||
const groupId = state.typebotsQueue[0].typebot.groups.find((group) =>
|
||||
group.blocks.some((blockInGroup) => blockInGroup.id === block.id)
|
||||
)?.id
|
||||
if (!groupId) throw new Error('saveAnswer: Group not found')
|
||||
await upsertAnswer({
|
||||
await saveAnswer({
|
||||
answer: {
|
||||
blockId: block.id,
|
||||
groupId,
|
||||
content: reply,
|
||||
variableId: block.options?.variableId,
|
||||
},
|
||||
reply,
|
||||
state,
|
||||
})
|
||||
|
||||
newSessionState = {
|
||||
...saveVariableValueIfAny(newSessionState, block)(reply),
|
||||
previewMetadata: state.typebotsQueue[0].resultId
|
||||
? newSessionState.previewMetadata
|
||||
: {
|
||||
...newSessionState.previewMetadata,
|
||||
answers: (newSessionState.previewMetadata?.answers ?? []).concat({
|
||||
blockId: block.id,
|
||||
content: reply,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
const key = block.options?.variableId
|
||||
? state.typebotsQueue[0].typebot.variables.find(
|
||||
? newSessionState.typebotsQueue[0].typebot.variables.find(
|
||||
(variable) => variable.id === block.options?.variableId
|
||||
)?.name
|
||||
: parseGroupKey(block.id, { state })
|
||||
: parseGroupKey(block.id, { state: newSessionState })
|
||||
|
||||
return setNewAnswerInState(state)({
|
||||
return setNewAnswerInState(newSessionState)({
|
||||
key: key ?? block.id,
|
||||
value: reply,
|
||||
})
|
||||
@ -375,7 +404,10 @@ const setNewAnswerInState =
|
||||
|
||||
const getOutgoingEdgeId =
|
||||
(state: Pick<SessionState, 'typebotsQueue'>) =>
|
||||
(block: Block, reply: string | undefined) => {
|
||||
(
|
||||
block: Block,
|
||||
reply: string | undefined
|
||||
): { edgeId: string | undefined; isOffDefaultPath: boolean } => {
|
||||
const variables = state.typebotsQueue[0].typebot.variables
|
||||
if (
|
||||
block.type === InputBlockType.CHOICE &&
|
||||
@ -390,7 +422,8 @@ const getOutgoingEdgeId =
|
||||
parseVariables(variables)(item.content).normalize() ===
|
||||
reply.normalize()
|
||||
)
|
||||
if (matchedItem?.outgoingEdgeId) return matchedItem.outgoingEdgeId
|
||||
if (matchedItem?.outgoingEdgeId)
|
||||
return { edgeId: matchedItem.outgoingEdgeId, isOffDefaultPath: true }
|
||||
}
|
||||
if (
|
||||
block.type === InputBlockType.PICTURE_CHOICE &&
|
||||
@ -405,9 +438,10 @@ const getOutgoingEdgeId =
|
||||
parseVariables(variables)(item.title).normalize() ===
|
||||
reply.normalize()
|
||||
)
|
||||
if (matchedItem?.outgoingEdgeId) return matchedItem.outgoingEdgeId
|
||||
if (matchedItem?.outgoingEdgeId)
|
||||
return { edgeId: matchedItem.outgoingEdgeId, isOffDefaultPath: true }
|
||||
}
|
||||
return block.outgoingEdgeId
|
||||
return { edgeId: block.outgoingEdgeId, isOffDefaultPath: false }
|
||||
}
|
||||
|
||||
const parseReply =
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
InputBlock,
|
||||
RuntimeOptions,
|
||||
SessionState,
|
||||
SetVariableHistoryItem,
|
||||
} from '@typebot.io/schemas'
|
||||
import { isNotEmpty } from '@typebot.io/lib'
|
||||
import {
|
||||
@ -21,16 +22,16 @@ import { injectVariableValuesInPictureChoiceBlock } from './blocks/inputs/pictur
|
||||
import { getPrefilledInputValue } from './getPrefilledValue'
|
||||
import { parseDateInput } from './blocks/inputs/date/parseDateInput'
|
||||
import { deepParseVariables } from '@typebot.io/variables/deepParseVariables'
|
||||
import {
|
||||
BubbleBlockWithDefinedContent,
|
||||
parseBubbleBlock,
|
||||
} from './parseBubbleBlock'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
import { VisitedEdge } from '@typebot.io/prisma'
|
||||
import { env } from '@typebot.io/env'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { ExecuteIntegrationResponse, ExecuteLogicResponse } from './types'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import {
|
||||
BubbleBlockWithDefinedContent,
|
||||
parseBubbleBlock,
|
||||
} from './parseBubbleBlock'
|
||||
|
||||
type ContextProps = {
|
||||
version: 1 | 2
|
||||
@ -39,6 +40,7 @@ type ContextProps = {
|
||||
currentLastBubbleId?: string
|
||||
firstBubbleWasStreamed?: boolean
|
||||
visitedEdges: VisitedEdge[]
|
||||
setVariableHistory: SetVariableHistoryItem[]
|
||||
startTime?: number
|
||||
}
|
||||
|
||||
@ -48,6 +50,7 @@ export const executeGroup = async (
|
||||
version,
|
||||
state,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
currentReply,
|
||||
currentLastBubbleId,
|
||||
firstBubbleWasStreamed,
|
||||
@ -56,6 +59,7 @@ export const executeGroup = async (
|
||||
): Promise<
|
||||
ContinueChatResponse & {
|
||||
newSessionState: SessionState
|
||||
setVariableHistory: SetVariableHistoryItem[]
|
||||
visitedEdges: VisitedEdge[]
|
||||
}
|
||||
> => {
|
||||
@ -70,6 +74,7 @@ export const executeGroup = async (
|
||||
|
||||
let newSessionState = state
|
||||
|
||||
let isNextEdgeOffDefaultPath = false
|
||||
let index = -1
|
||||
for (const block of group.blocks) {
|
||||
if (
|
||||
@ -110,6 +115,7 @@ export const executeGroup = async (
|
||||
clientSideActions,
|
||||
logs,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
}
|
||||
const executionResponse = (
|
||||
isLogicBlock(block)
|
||||
@ -120,6 +126,29 @@ export const executeGroup = async (
|
||||
) as ExecuteLogicResponse | ExecuteIntegrationResponse | null
|
||||
|
||||
if (!executionResponse) continue
|
||||
if (
|
||||
executionResponse.newSetVariableHistory &&
|
||||
executionResponse.newSetVariableHistory?.length > 0
|
||||
) {
|
||||
if (!newSessionState.typebotsQueue[0].resultId)
|
||||
newSessionState = {
|
||||
...newSessionState,
|
||||
previewMetadata: {
|
||||
...newSessionState.previewMetadata,
|
||||
setVariableHistory: (
|
||||
newSessionState.previewMetadata?.setVariableHistory ?? []
|
||||
).concat(
|
||||
executionResponse.newSetVariableHistory.map((item) => ({
|
||||
blockId: item.blockId,
|
||||
variableId: item.variableId,
|
||||
value: item.value,
|
||||
}))
|
||||
),
|
||||
},
|
||||
}
|
||||
else setVariableHistory.push(...executionResponse.newSetVariableHistory)
|
||||
}
|
||||
|
||||
if (
|
||||
'startTimeShouldBeUpdated' in executionResponse &&
|
||||
executionResponse.startTimeShouldBeUpdated
|
||||
@ -165,33 +194,55 @@ export const executeGroup = async (
|
||||
clientSideActions,
|
||||
logs,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (executionResponse.outgoingEdgeId) {
|
||||
isNextEdgeOffDefaultPath =
|
||||
block.outgoingEdgeId !== executionResponse.outgoingEdgeId
|
||||
nextEdgeId = executionResponse.outgoingEdgeId
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextEdgeId && newSessionState.typebotsQueue.length === 1)
|
||||
return { messages, newSessionState, clientSideActions, logs, visitedEdges }
|
||||
return {
|
||||
messages,
|
||||
newSessionState,
|
||||
clientSideActions,
|
||||
logs,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
}
|
||||
|
||||
const nextGroup = await getNextGroup(newSessionState)(nextEdgeId ?? undefined)
|
||||
const nextGroup = await getNextGroup({
|
||||
state: newSessionState,
|
||||
edgeId: nextEdgeId ?? undefined,
|
||||
isOffDefaultPath: isNextEdgeOffDefaultPath,
|
||||
})
|
||||
|
||||
newSessionState = nextGroup.newSessionState
|
||||
|
||||
if (nextGroup.visitedEdge) visitedEdges.push(nextGroup.visitedEdge)
|
||||
|
||||
if (!nextGroup.group) {
|
||||
return { messages, newSessionState, clientSideActions, logs, visitedEdges }
|
||||
return {
|
||||
messages,
|
||||
newSessionState,
|
||||
clientSideActions,
|
||||
logs,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
}
|
||||
}
|
||||
|
||||
return executeGroup(nextGroup.group, {
|
||||
version,
|
||||
state: newSessionState,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
currentReply: {
|
||||
messages,
|
||||
clientSideActions,
|
||||
|
@ -2,12 +2,12 @@ import { VariableStore, LogsStore } from '@typebot.io/forge'
|
||||
import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
|
||||
import { ForgedBlock } from '@typebot.io/forge-repository/types'
|
||||
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
|
||||
import { isPlaneteScale } from '@typebot.io/lib/isPlanetScale'
|
||||
import {
|
||||
SessionState,
|
||||
ContinueChatResponse,
|
||||
Block,
|
||||
TypebotInSession,
|
||||
SetVariableHistoryItem,
|
||||
} from '@typebot.io/schemas'
|
||||
import { deepParseVariables } from '@typebot.io/variables/deepParseVariables'
|
||||
import {
|
||||
@ -73,6 +73,7 @@ export const executeForgedBlock = async (
|
||||
}
|
||||
|
||||
let newSessionState = state
|
||||
let setVariableHistory: SetVariableHistoryItem[] = []
|
||||
|
||||
const variables: VariableStore = {
|
||||
get: (id: string) => {
|
||||
@ -86,9 +87,13 @@ export const executeForgedBlock = async (
|
||||
(variable) => variable.id === id
|
||||
)
|
||||
if (!variable) return
|
||||
newSessionState = updateVariablesInSession(newSessionState)([
|
||||
{ ...variable, value },
|
||||
])
|
||||
const { newSetVariableHistory, updatedState } = updateVariablesInSession({
|
||||
newVariables: [{ ...variable, value }],
|
||||
state: newSessionState,
|
||||
currentBlockId: block.id,
|
||||
})
|
||||
newSessionState = updatedState
|
||||
setVariableHistory.push(...newSetVariableHistory)
|
||||
},
|
||||
parse: (text: string, params?: ParseVariablesOptions) =>
|
||||
parseVariables(
|
||||
@ -159,6 +164,7 @@ export const executeForgedBlock = async (
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
newSetVariableHistory: setVariableHistory,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { SessionState } from '@typebot.io/schemas'
|
||||
import { TypebotInSession } from '@typebot.io/schemas'
|
||||
|
||||
export const getFirstEdgeId = ({
|
||||
state,
|
||||
typebot,
|
||||
startEventId,
|
||||
}: {
|
||||
state: SessionState
|
||||
typebot: Pick<TypebotInSession, 'events' | 'groups' | 'version'>
|
||||
startEventId: string | undefined
|
||||
}) => {
|
||||
const { typebot } = state.typebotsQueue[0]
|
||||
if (startEventId) {
|
||||
const event = typebot.events?.find((e) => e.id === startEventId)
|
||||
if (!event)
|
||||
@ -18,6 +17,6 @@ export const getFirstEdgeId = ({
|
||||
})
|
||||
return event.outgoingEdgeId
|
||||
}
|
||||
if (typebot.version === '6') return typebot.events[0].outgoingEdgeId
|
||||
if (typebot.version === '6') return typebot.events?.[0].outgoingEdgeId
|
||||
return typebot.groups.at(0)?.blocks.at(0)?.outgoingEdgeId
|
||||
}
|
||||
|
@ -9,116 +9,138 @@ export type NextGroup = {
|
||||
visitedEdge?: VisitedEdge
|
||||
}
|
||||
|
||||
export const getNextGroup =
|
||||
(state: SessionState) =>
|
||||
async (edgeId?: string): Promise<NextGroup> => {
|
||||
const nextEdge = state.typebotsQueue[0].typebot.edges.find(byId(edgeId))
|
||||
if (!nextEdge) {
|
||||
if (state.typebotsQueue.length > 1) {
|
||||
const nextEdgeId = state.typebotsQueue[0].edgeIdToTriggerWhenDone
|
||||
const isMergingWithParent = state.typebotsQueue[0].isMergingWithParent
|
||||
const currentResultId = state.typebotsQueue[0].resultId
|
||||
if (!isMergingWithParent && currentResultId)
|
||||
await upsertResult({
|
||||
resultId: currentResultId,
|
||||
typebot: state.typebotsQueue[0].typebot,
|
||||
isCompleted: true,
|
||||
hasStarted: state.typebotsQueue[0].answers.length > 0,
|
||||
})
|
||||
let newSessionState = {
|
||||
...state,
|
||||
typebotsQueue: [
|
||||
{
|
||||
...state.typebotsQueue[1],
|
||||
typebot: isMergingWithParent
|
||||
? {
|
||||
...state.typebotsQueue[1].typebot,
|
||||
variables: state.typebotsQueue[1].typebot.variables
|
||||
.map((variable) => ({
|
||||
...variable,
|
||||
value:
|
||||
state.typebotsQueue[0].typebot.variables.find(
|
||||
(v) => v.name === variable.name
|
||||
)?.value ?? variable.value,
|
||||
}))
|
||||
.concat(
|
||||
state.typebotsQueue[0].typebot.variables.filter(
|
||||
(variable) =>
|
||||
isDefined(variable.value) &&
|
||||
isNotDefined(
|
||||
state.typebotsQueue[1].typebot.variables.find(
|
||||
(v) => v.name === variable.name
|
||||
)
|
||||
export const getNextGroup = async ({
|
||||
state,
|
||||
edgeId,
|
||||
isOffDefaultPath,
|
||||
}: {
|
||||
state: SessionState
|
||||
edgeId?: string
|
||||
isOffDefaultPath: boolean
|
||||
}): Promise<NextGroup> => {
|
||||
const nextEdge = state.typebotsQueue[0].typebot.edges.find(byId(edgeId))
|
||||
if (!nextEdge) {
|
||||
if (state.typebotsQueue.length > 1) {
|
||||
const nextEdgeId = state.typebotsQueue[0].edgeIdToTriggerWhenDone
|
||||
const isMergingWithParent = state.typebotsQueue[0].isMergingWithParent
|
||||
const currentResultId = state.typebotsQueue[0].resultId
|
||||
if (!isMergingWithParent && currentResultId)
|
||||
await upsertResult({
|
||||
resultId: currentResultId,
|
||||
typebot: state.typebotsQueue[0].typebot,
|
||||
isCompleted: true,
|
||||
hasStarted: state.typebotsQueue[0].answers.length > 0,
|
||||
})
|
||||
let newSessionState = {
|
||||
...state,
|
||||
typebotsQueue: [
|
||||
{
|
||||
...state.typebotsQueue[1],
|
||||
typebot: isMergingWithParent
|
||||
? {
|
||||
...state.typebotsQueue[1].typebot,
|
||||
variables: state.typebotsQueue[1].typebot.variables
|
||||
.map((variable) => ({
|
||||
...variable,
|
||||
value:
|
||||
state.typebotsQueue[0].typebot.variables.find(
|
||||
(v) => v.name === variable.name
|
||||
)?.value ?? variable.value,
|
||||
}))
|
||||
.concat(
|
||||
state.typebotsQueue[0].typebot.variables.filter(
|
||||
(variable) =>
|
||||
isDefined(variable.value) &&
|
||||
isNotDefined(
|
||||
state.typebotsQueue[1].typebot.variables.find(
|
||||
(v) => v.name === variable.name
|
||||
)
|
||||
) as VariableWithValue[]
|
||||
),
|
||||
}
|
||||
: state.typebotsQueue[1].typebot,
|
||||
answers: isMergingWithParent
|
||||
? [
|
||||
...state.typebotsQueue[1].answers.filter(
|
||||
(incomingAnswer) =>
|
||||
!state.typebotsQueue[0].answers.find(
|
||||
(currentAnswer) =>
|
||||
currentAnswer.key === incomingAnswer.key
|
||||
)
|
||||
)
|
||||
) as VariableWithValue[]
|
||||
),
|
||||
...state.typebotsQueue[0].answers,
|
||||
]
|
||||
: state.typebotsQueue[1].answers,
|
||||
},
|
||||
...state.typebotsQueue.slice(2),
|
||||
],
|
||||
} satisfies SessionState
|
||||
if (state.progressMetadata)
|
||||
newSessionState.progressMetadata = {
|
||||
...state.progressMetadata,
|
||||
totalAnswers:
|
||||
state.progressMetadata.totalAnswers +
|
||||
state.typebotsQueue[0].answers.length,
|
||||
}
|
||||
const nextGroup = await getNextGroup(newSessionState)(nextEdgeId)
|
||||
newSessionState = nextGroup.newSessionState
|
||||
if (!nextGroup)
|
||||
return {
|
||||
newSessionState,
|
||||
}
|
||||
}
|
||||
: state.typebotsQueue[1].typebot,
|
||||
answers: isMergingWithParent
|
||||
? [
|
||||
...state.typebotsQueue[1].answers.filter(
|
||||
(incomingAnswer) =>
|
||||
!state.typebotsQueue[0].answers.find(
|
||||
(currentAnswer) =>
|
||||
currentAnswer.key === incomingAnswer.key
|
||||
)
|
||||
),
|
||||
...state.typebotsQueue[0].answers,
|
||||
]
|
||||
: state.typebotsQueue[1].answers,
|
||||
},
|
||||
...state.typebotsQueue.slice(2),
|
||||
],
|
||||
} satisfies SessionState
|
||||
if (state.progressMetadata)
|
||||
newSessionState.progressMetadata = {
|
||||
...state.progressMetadata,
|
||||
totalAnswers:
|
||||
state.progressMetadata.totalAnswers +
|
||||
state.typebotsQueue[0].answers.length,
|
||||
}
|
||||
const nextGroup = await getNextGroup({
|
||||
state: newSessionState,
|
||||
edgeId: nextEdgeId,
|
||||
isOffDefaultPath,
|
||||
})
|
||||
newSessionState = nextGroup.newSessionState
|
||||
if (!nextGroup)
|
||||
return {
|
||||
...nextGroup,
|
||||
newSessionState,
|
||||
}
|
||||
}
|
||||
return {
|
||||
newSessionState: state,
|
||||
...nextGroup,
|
||||
newSessionState,
|
||||
}
|
||||
}
|
||||
const nextGroup = state.typebotsQueue[0].typebot.groups.find(
|
||||
byId(nextEdge.to.groupId)
|
||||
)
|
||||
if (!nextGroup)
|
||||
return {
|
||||
newSessionState: state,
|
||||
}
|
||||
const startBlockIndex = nextEdge.to.blockId
|
||||
? nextGroup.blocks.findIndex(byId(nextEdge.to.blockId))
|
||||
: 0
|
||||
const currentVisitedEdgeIndex = (state.currentVisitedEdgeIndex ?? -1) + 1
|
||||
const resultId = state.typebotsQueue[0].resultId
|
||||
return {
|
||||
group: {
|
||||
...nextGroup,
|
||||
blocks: nextGroup.blocks.slice(startBlockIndex),
|
||||
} as Group,
|
||||
newSessionState: {
|
||||
...state,
|
||||
currentVisitedEdgeIndex,
|
||||
},
|
||||
visitedEdge: resultId
|
||||
newSessionState: state,
|
||||
}
|
||||
}
|
||||
const nextGroup = state.typebotsQueue[0].typebot.groups.find(
|
||||
byId(nextEdge.to.groupId)
|
||||
)
|
||||
if (!nextGroup)
|
||||
return {
|
||||
newSessionState: state,
|
||||
}
|
||||
const startBlockIndex = nextEdge.to.blockId
|
||||
? nextGroup.blocks.findIndex(byId(nextEdge.to.blockId))
|
||||
: 0
|
||||
const currentVisitedEdgeIndex = isOffDefaultPath
|
||||
? (state.currentVisitedEdgeIndex ?? -1) + 1
|
||||
: state.currentVisitedEdgeIndex
|
||||
const resultId = state.typebotsQueue[0].resultId
|
||||
return {
|
||||
group: {
|
||||
...nextGroup,
|
||||
blocks: nextGroup.blocks.slice(startBlockIndex),
|
||||
} as Group,
|
||||
newSessionState: {
|
||||
...state,
|
||||
currentVisitedEdgeIndex,
|
||||
previewMetadata:
|
||||
resultId || !isOffDefaultPath
|
||||
? state.previewMetadata
|
||||
: {
|
||||
...state.previewMetadata,
|
||||
visitedEdges: (state.previewMetadata?.visitedEdges ?? []).concat(
|
||||
nextEdge.id
|
||||
),
|
||||
},
|
||||
},
|
||||
visitedEdge:
|
||||
resultId && isOffDefaultPath && !nextEdge.id.startsWith('virtual-')
|
||||
? {
|
||||
index: currentVisitedEdgeIndex,
|
||||
index: currentVisitedEdgeIndex as number,
|
||||
edgeId: nextEdge.id,
|
||||
resultId,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@typebot.io/variables": "workspace:*",
|
||||
"@udecode/plate-common": "30.4.5",
|
||||
"@typebot.io/logic": "workspace:*",
|
||||
"ai": "3.0.31",
|
||||
"chrono-node": "2.7.5",
|
||||
"date-fns": "2.30.0",
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
Typebot,
|
||||
} from '@typebot.io/schemas'
|
||||
import { deepParseVariables } from '@typebot.io/variables/deepParseVariables'
|
||||
import { isEmpty, isNotEmpty } from '@typebot.io/lib/utils'
|
||||
import { isDefined, isEmpty, isNotEmpty } from '@typebot.io/lib/utils'
|
||||
import {
|
||||
getVariablesToParseInfoInText,
|
||||
parseVariables,
|
||||
@ -49,7 +49,7 @@ export const parseBubbleBlock = (
|
||||
richText: parseVariablesInRichText(block.content?.richText ?? [], {
|
||||
variables,
|
||||
takeLatestIfList: typebotVersion !== '6',
|
||||
}),
|
||||
}).parsedElements,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -93,14 +93,15 @@ export const parseBubbleBlock = (
|
||||
}
|
||||
}
|
||||
|
||||
const parseVariablesInRichText = (
|
||||
export const parseVariablesInRichText = (
|
||||
elements: TDescendant[],
|
||||
{
|
||||
variables,
|
||||
takeLatestIfList,
|
||||
}: { variables: Variable[]; takeLatestIfList?: boolean }
|
||||
): TDescendant[] => {
|
||||
): { parsedElements: TDescendant[]; parsedVariableIds: string[] } => {
|
||||
const parsedElements: TDescendant[] = []
|
||||
const parsedVariableIds: string[] = []
|
||||
for (const element of elements) {
|
||||
if ('text' in element) {
|
||||
const text = element.text as string
|
||||
@ -112,6 +113,9 @@ const parseVariablesInRichText = (
|
||||
variables,
|
||||
takeLatestIfList,
|
||||
})
|
||||
parsedVariableIds.push(
|
||||
...variablesInText.map((v) => v.variableId).filter(isDefined)
|
||||
)
|
||||
if (variablesInText.length === 0) {
|
||||
parsedElements.push(element)
|
||||
continue
|
||||
@ -185,19 +189,28 @@ const parseVariablesInRichText = (
|
||||
? 'variable'
|
||||
: element.type
|
||||
|
||||
const {
|
||||
parsedElements: parsedChildren,
|
||||
parsedVariableIds: parsedChildrenVariableIds,
|
||||
} = parseVariablesInRichText(element.children as TDescendant[], {
|
||||
variables,
|
||||
takeLatestIfList,
|
||||
})
|
||||
|
||||
parsedVariableIds.push(...parsedChildrenVariableIds)
|
||||
parsedElements.push({
|
||||
...element,
|
||||
url: element.url
|
||||
? parseVariables(variables)(element.url as string)
|
||||
: undefined,
|
||||
type,
|
||||
children: parseVariablesInRichText(element.children as TDescendant[], {
|
||||
variables,
|
||||
takeLatestIfList,
|
||||
}),
|
||||
children: parsedChildren,
|
||||
})
|
||||
}
|
||||
return parsedElements
|
||||
return {
|
||||
parsedElements,
|
||||
parsedVariableIds,
|
||||
}
|
||||
}
|
||||
|
||||
const applyElementStyleToDescendants = (
|
||||
|
@ -12,11 +12,26 @@ export const createSession = ({
|
||||
id,
|
||||
state,
|
||||
isReplying,
|
||||
}: Props): Prisma.PrismaPromise<any> =>
|
||||
prisma.chatSession.create({
|
||||
data: {
|
||||
}: Props): Prisma.PrismaPromise<any> => {
|
||||
if (!id) {
|
||||
return prisma.chatSession.create({
|
||||
data: {
|
||||
id,
|
||||
state,
|
||||
isReplying,
|
||||
},
|
||||
})
|
||||
}
|
||||
return prisma.chatSession.upsert({
|
||||
where: { id },
|
||||
update: {
|
||||
state,
|
||||
isReplying,
|
||||
},
|
||||
create: {
|
||||
id,
|
||||
state,
|
||||
isReplying,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -4,24 +4,33 @@ import { Answer, Result } from '@typebot.io/schemas'
|
||||
type Props = {
|
||||
id: string
|
||||
}
|
||||
export const findResult = ({ id }: Props) =>
|
||||
prisma.result.findFirst({
|
||||
where: { id, isArchived: { not: true } },
|
||||
select: {
|
||||
id: true,
|
||||
variables: true,
|
||||
hasStarted: true,
|
||||
answers: {
|
||||
select: {
|
||||
content: true,
|
||||
blockId: true,
|
||||
variableId: true,
|
||||
export const findResult = async ({ id }: Props) => {
|
||||
const { answers, answersV2, ...result } =
|
||||
(await prisma.result.findFirst({
|
||||
where: { id, isArchived: { not: true } },
|
||||
select: {
|
||||
id: true,
|
||||
variables: true,
|
||||
hasStarted: true,
|
||||
answers: {
|
||||
select: {
|
||||
content: true,
|
||||
blockId: true,
|
||||
},
|
||||
},
|
||||
answersV2: {
|
||||
select: {
|
||||
content: true,
|
||||
blockId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}) as Promise<
|
||||
| (Pick<Result, 'id' | 'variables' | 'hasStarted'> & {
|
||||
answers: Pick<Answer, 'content' | 'blockId' | 'variableId'>[]
|
||||
})
|
||||
| null
|
||||
>
|
||||
})) ?? {}
|
||||
if (!result) return null
|
||||
return {
|
||||
...result,
|
||||
answers: (answersV2 ?? []).concat(answers ?? []),
|
||||
} as Pick<Result, 'id' | 'variables' | 'hasStarted'> & {
|
||||
answers: Pick<Answer, 'content' | 'blockId'>[]
|
||||
}
|
||||
}
|
||||
|
16
packages/bot-engine/queries/saveAnswer.ts
Normal file
16
packages/bot-engine/queries/saveAnswer.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { Prisma } from '@typebot.io/prisma'
|
||||
import { SessionState } from '@typebot.io/schemas'
|
||||
|
||||
type Props = {
|
||||
answer: Omit<Prisma.AnswerV2CreateManyInput, 'resultId'>
|
||||
reply: string
|
||||
state: SessionState
|
||||
}
|
||||
export const saveAnswer = async ({ answer, state }: Props) => {
|
||||
const resultId = state.typebotsQueue[0].resultId
|
||||
if (!resultId) return
|
||||
return prisma.answerV2.createMany({
|
||||
data: [{ ...answer, resultId }],
|
||||
})
|
||||
}
|
16
packages/bot-engine/queries/saveSetVariableHistoryItems.ts
Normal file
16
packages/bot-engine/queries/saveSetVariableHistoryItems.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { Prisma } from '@typebot.io/prisma'
|
||||
import { SetVariableHistoryItem } from '@typebot.io/schemas'
|
||||
|
||||
export const saveSetVariableHistoryItems = (
|
||||
setVariableHistory: SetVariableHistoryItem[]
|
||||
) =>
|
||||
prisma.setVariableHistoryItem.createMany({
|
||||
data: {
|
||||
...setVariableHistory.map((item) => ({
|
||||
...item,
|
||||
value: item.value === null ? Prisma.JsonNull : item.value,
|
||||
})),
|
||||
},
|
||||
skipDuplicates: true,
|
||||
})
|
@ -1,34 +0,0 @@
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { Prisma } from '@typebot.io/prisma'
|
||||
import { InputBlock, SessionState } from '@typebot.io/schemas'
|
||||
|
||||
type Props = {
|
||||
answer: Omit<Prisma.AnswerUncheckedCreateInput, 'resultId'>
|
||||
reply: string
|
||||
state: SessionState
|
||||
}
|
||||
export const upsertAnswer = async ({ answer, state }: Props) => {
|
||||
const resultId = state.typebotsQueue[0].resultId
|
||||
if (!resultId) return
|
||||
const where = {
|
||||
resultId,
|
||||
blockId: answer.blockId,
|
||||
groupId: answer.groupId,
|
||||
}
|
||||
const existingAnswer = await prisma.answer.findUnique({
|
||||
where: {
|
||||
resultId_blockId_groupId: where,
|
||||
},
|
||||
select: { resultId: true },
|
||||
})
|
||||
if (existingAnswer)
|
||||
return prisma.answer.updateMany({
|
||||
where,
|
||||
data: {
|
||||
content: answer.content,
|
||||
},
|
||||
})
|
||||
return prisma.answer.createMany({
|
||||
data: [{ ...answer, resultId }],
|
||||
})
|
||||
}
|
@ -1,29 +1,79 @@
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { Prisma } from '@typebot.io/prisma'
|
||||
import { TypebotInSession } from '@typebot.io/schemas'
|
||||
import { Prisma, SetVariableHistoryItem, VisitedEdge } from '@typebot.io/prisma'
|
||||
import { ContinueChatResponse, TypebotInSession } from '@typebot.io/schemas'
|
||||
import { filterNonSessionVariablesWithValues } from '@typebot.io/variables/filterVariablesWithValues'
|
||||
import { formatLogDetails } from '../logs/helpers/formatLogDetails'
|
||||
|
||||
type Props = {
|
||||
resultId: string
|
||||
typebot: TypebotInSession
|
||||
hasStarted: boolean
|
||||
isCompleted: boolean
|
||||
lastChatSessionId?: string
|
||||
logs?: ContinueChatResponse['logs']
|
||||
visitedEdges?: VisitedEdge[]
|
||||
setVariableHistory?: SetVariableHistoryItem[]
|
||||
}
|
||||
export const upsertResult = ({
|
||||
resultId,
|
||||
typebot,
|
||||
hasStarted,
|
||||
isCompleted,
|
||||
lastChatSessionId,
|
||||
logs,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
}: Props): Prisma.PrismaPromise<any> => {
|
||||
const variablesWithValue = filterNonSessionVariablesWithValues(
|
||||
typebot.variables
|
||||
)
|
||||
const logsToCreate =
|
||||
logs && logs.length > 0
|
||||
? {
|
||||
createMany: {
|
||||
data: logs.map((log) => ({
|
||||
...log,
|
||||
details: formatLogDetails(log.details),
|
||||
})),
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
|
||||
const setVariableHistoryToCreate =
|
||||
setVariableHistory && setVariableHistory.length > 0
|
||||
? ({
|
||||
createMany: {
|
||||
data: setVariableHistory.map((item) => ({
|
||||
...item,
|
||||
value: item.value === null ? Prisma.JsonNull : item.value,
|
||||
resultId: undefined,
|
||||
})),
|
||||
},
|
||||
} as Prisma.SetVariableHistoryItemUpdateManyWithoutResultNestedInput)
|
||||
: undefined
|
||||
|
||||
const visitedEdgesToCreate =
|
||||
visitedEdges && visitedEdges.length > 0
|
||||
? {
|
||||
createMany: {
|
||||
data: visitedEdges.map((edge) => ({
|
||||
...edge,
|
||||
resultId: undefined,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
|
||||
return prisma.result.upsert({
|
||||
where: { id: resultId },
|
||||
update: {
|
||||
isCompleted: isCompleted ? true : undefined,
|
||||
hasStarted,
|
||||
variables: variablesWithValue,
|
||||
lastChatSessionId,
|
||||
logs: logsToCreate,
|
||||
setVariableHistory: setVariableHistoryToCreate,
|
||||
edges: visitedEdgesToCreate,
|
||||
},
|
||||
create: {
|
||||
id: resultId,
|
||||
@ -31,6 +81,10 @@ export const upsertResult = ({
|
||||
isCompleted: isCompleted ? true : false,
|
||||
hasStarted,
|
||||
variables: variablesWithValue,
|
||||
lastChatSessionId,
|
||||
logs: logsToCreate,
|
||||
setVariableHistory: setVariableHistoryToCreate,
|
||||
edges: visitedEdgesToCreate,
|
||||
},
|
||||
select: { id: true },
|
||||
})
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { ContinueChatResponse, ChatSession } from '@typebot.io/schemas'
|
||||
import {
|
||||
ContinueChatResponse,
|
||||
ChatSession,
|
||||
SetVariableHistoryItem,
|
||||
} from '@typebot.io/schemas'
|
||||
import { upsertResult } from './queries/upsertResult'
|
||||
import { saveLogs } from './queries/saveLogs'
|
||||
import { updateSession } from './queries/updateSession'
|
||||
import { formatLogDetails } from './logs/helpers/formatLogDetails'
|
||||
import { createSession } from './queries/createSession'
|
||||
import { deleteSession } from './queries/deleteSession'
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
import { saveVisitedEdges } from './queries/saveVisitedEdges'
|
||||
import { Prisma, VisitedEdge } from '@typebot.io/prisma'
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
|
||||
@ -16,7 +16,9 @@ type Props = {
|
||||
logs: ContinueChatResponse['logs']
|
||||
clientSideActions: ContinueChatResponse['clientSideActions']
|
||||
visitedEdges: VisitedEdge[]
|
||||
setVariableHistory: SetVariableHistoryItem[]
|
||||
hasCustomEmbedBubble?: boolean
|
||||
initialSessionId?: string
|
||||
}
|
||||
|
||||
export const saveStateToDatabase = async ({
|
||||
@ -25,7 +27,9 @@ export const saveStateToDatabase = async ({
|
||||
logs,
|
||||
clientSideActions,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
hasCustomEmbedBubble,
|
||||
initialSessionId,
|
||||
}: Props) => {
|
||||
const containsSetVariableClientSideAction = clientSideActions?.some(
|
||||
(action) => action.expectsDedicatedReply
|
||||
@ -46,7 +50,7 @@ export const saveStateToDatabase = async ({
|
||||
|
||||
const session = id
|
||||
? { state, id }
|
||||
: await createSession({ id, state, isReplying: false })
|
||||
: await createSession({ id: initialSessionId, state, isReplying: false })
|
||||
|
||||
if (!resultId) {
|
||||
if (queries.length > 0) await prisma.$transaction(queries)
|
||||
@ -63,25 +67,13 @@ export const saveStateToDatabase = async ({
|
||||
!input && !containsSetVariableClientSideAction && answers.length > 0
|
||||
),
|
||||
hasStarted: answers.length > 0,
|
||||
lastChatSessionId: session.id,
|
||||
logs,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
})
|
||||
)
|
||||
|
||||
if (logs && logs.length > 0)
|
||||
try {
|
||||
await saveLogs(
|
||||
logs.map((log) => ({
|
||||
...log,
|
||||
resultId,
|
||||
details: formatLogDetails(log.details),
|
||||
}))
|
||||
)
|
||||
} catch (e) {
|
||||
console.error('Failed to save logs', e)
|
||||
Sentry.captureException(e)
|
||||
}
|
||||
|
||||
if (visitedEdges.length > 0) queries.push(saveVisitedEdges(visitedEdges))
|
||||
|
||||
await prisma.$transaction(queries)
|
||||
|
||||
return session
|
||||
|
@ -2,6 +2,7 @@ import { TRPCError } from '@trpc/server'
|
||||
import {
|
||||
ContinueChatResponse,
|
||||
SessionState,
|
||||
SetVariableHistoryItem,
|
||||
StartFrom,
|
||||
} from '@typebot.io/schemas'
|
||||
import { executeGroup } from './executeGroup'
|
||||
@ -25,10 +26,12 @@ export const startBotFlow = async ({
|
||||
ContinueChatResponse & {
|
||||
newSessionState: SessionState
|
||||
visitedEdges: VisitedEdge[]
|
||||
setVariableHistory: SetVariableHistoryItem[]
|
||||
}
|
||||
> => {
|
||||
let newSessionState = state
|
||||
const visitedEdges: VisitedEdge[] = []
|
||||
const setVariableHistory: SetVariableHistoryItem[] = []
|
||||
if (startFrom?.type === 'group') {
|
||||
const group = state.typebotsQueue[0].typebot.groups.find(
|
||||
(group) => group.id === startFrom.groupId
|
||||
@ -42,22 +45,34 @@ export const startBotFlow = async ({
|
||||
version,
|
||||
state: newSessionState,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
startTime,
|
||||
})
|
||||
}
|
||||
const firstEdgeId = getFirstEdgeId({
|
||||
state: newSessionState,
|
||||
typebot: newSessionState.typebotsQueue[0].typebot,
|
||||
startEventId: startFrom?.type === 'event' ? startFrom.eventId : undefined,
|
||||
})
|
||||
if (!firstEdgeId) return { messages: [], newSessionState, visitedEdges: [] }
|
||||
const nextGroup = await getNextGroup(newSessionState)(firstEdgeId)
|
||||
if (!firstEdgeId)
|
||||
return {
|
||||
messages: [],
|
||||
newSessionState,
|
||||
setVariableHistory: [],
|
||||
visitedEdges: [],
|
||||
}
|
||||
const nextGroup = await getNextGroup({
|
||||
state: newSessionState,
|
||||
edgeId: firstEdgeId,
|
||||
isOffDefaultPath: false,
|
||||
})
|
||||
newSessionState = nextGroup.newSessionState
|
||||
if (nextGroup.visitedEdge) visitedEdges.push(nextGroup.visitedEdge)
|
||||
if (!nextGroup.group) return { messages: [], newSessionState, visitedEdges }
|
||||
if (!nextGroup.group)
|
||||
return { messages: [], newSessionState, visitedEdges, setVariableHistory }
|
||||
return executeGroup(nextGroup.group, {
|
||||
version,
|
||||
state: newSessionState,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
startTime,
|
||||
})
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
SessionState,
|
||||
TypebotInSession,
|
||||
Block,
|
||||
SetVariableHistoryItem,
|
||||
} from '@typebot.io/schemas'
|
||||
import {
|
||||
StartChatInput,
|
||||
@ -31,7 +32,10 @@ import { injectVariablesFromExistingResult } from '@typebot.io/variables/injectV
|
||||
import { getNextGroup } from './getNextGroup'
|
||||
import { upsertResult } from './queries/upsertResult'
|
||||
import { continueBotFlow } from './continueBotFlow'
|
||||
import { parseVariables } from '@typebot.io/variables/parseVariables'
|
||||
import {
|
||||
getVariablesToParseInfoInText,
|
||||
parseVariables,
|
||||
} from '@typebot.io/variables/parseVariables'
|
||||
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
||||
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||
import { VisitedEdge } from '@typebot.io/prisma'
|
||||
@ -42,6 +46,9 @@ import {
|
||||
defaultGuestAvatarIsEnabled,
|
||||
defaultHostAvatarIsEnabled,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
import { parseVariablesInRichText } from './parseBubbleBlock'
|
||||
|
||||
type StartParams =
|
||||
| ({
|
||||
@ -68,6 +75,7 @@ export const startSession = async ({
|
||||
Omit<StartChatResponse, 'resultId' | 'isStreamEnabled' | 'sessionId'> & {
|
||||
newSessionState: SessionState
|
||||
visitedEdges: VisitedEdge[]
|
||||
setVariableHistory: SetVariableHistoryItem[]
|
||||
resultId?: string
|
||||
}
|
||||
> => {
|
||||
@ -145,6 +153,8 @@ export const startSession = async ({
|
||||
: typebot.theme.general?.progressBar?.isEnabled
|
||||
? { totalAnswers: 0 }
|
||||
: undefined,
|
||||
setVariableIdsForHistory:
|
||||
extractVariableIdsUsedForTranscript(typebotInSession),
|
||||
...initialSessionState,
|
||||
}
|
||||
|
||||
@ -164,6 +174,7 @@ export const startSession = async ({
|
||||
dynamicTheme: parseDynamicTheme(initialState),
|
||||
messages: [],
|
||||
visitedEdges: [],
|
||||
setVariableHistory: [],
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,14 +189,18 @@ export const startSession = async ({
|
||||
// If params has message and first block is an input block, we can directly continue the bot flow
|
||||
if (message) {
|
||||
const firstEdgeId = getFirstEdgeId({
|
||||
state: chatReply.newSessionState,
|
||||
typebot: chatReply.newSessionState.typebotsQueue[0].typebot,
|
||||
startEventId:
|
||||
startParams.type === 'preview' &&
|
||||
startParams.startFrom?.type === 'event'
|
||||
? startParams.startFrom.eventId
|
||||
: undefined,
|
||||
})
|
||||
const nextGroup = await getNextGroup(chatReply.newSessionState)(firstEdgeId)
|
||||
const nextGroup = await getNextGroup({
|
||||
state: chatReply.newSessionState,
|
||||
edgeId: firstEdgeId,
|
||||
isOffDefaultPath: false,
|
||||
})
|
||||
const newSessionState = nextGroup.newSessionState
|
||||
const firstBlock = nextGroup.group?.blocks.at(0)
|
||||
if (firstBlock && isInputBlock(firstBlock)) {
|
||||
@ -214,6 +229,7 @@ export const startSession = async ({
|
||||
newSessionState,
|
||||
logs,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
} = chatReply
|
||||
|
||||
const clientSideActions = startFlowClientActions ?? []
|
||||
@ -268,6 +284,7 @@ export const startSession = async ({
|
||||
dynamicTheme: parseDynamicTheme(newSessionState),
|
||||
logs: startLogs.length > 0 ? startLogs : undefined,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
}
|
||||
|
||||
return {
|
||||
@ -290,6 +307,7 @@ export const startSession = async ({
|
||||
dynamicTheme: parseDynamicTheme(newSessionState),
|
||||
logs: startLogs.length > 0 ? startLogs : undefined,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
}
|
||||
}
|
||||
|
||||
@ -497,3 +515,59 @@ const convertStartTypebotToTypebotInSession = (
|
||||
variables: startVariables,
|
||||
events: typebot.events,
|
||||
}
|
||||
|
||||
const extractVariableIdsUsedForTranscript = (
|
||||
typebot: TypebotInSession
|
||||
): string[] => {
|
||||
const variableIds: Set<string> = new Set()
|
||||
const parseVarParams = {
|
||||
variables: typebot.variables,
|
||||
takeLatestIfList: typebot.version !== '6',
|
||||
}
|
||||
typebot.groups.forEach((group) => {
|
||||
group.blocks.forEach((block) => {
|
||||
if (block.type === BubbleBlockType.TEXT) {
|
||||
const { parsedVariableIds } = parseVariablesInRichText(
|
||||
block.content?.richText ?? [],
|
||||
parseVarParams
|
||||
)
|
||||
parsedVariableIds.forEach((variableId) => variableIds.add(variableId))
|
||||
}
|
||||
if (
|
||||
block.type === BubbleBlockType.IMAGE ||
|
||||
block.type === BubbleBlockType.VIDEO ||
|
||||
block.type === BubbleBlockType.AUDIO
|
||||
) {
|
||||
if (!block.content?.url) return
|
||||
const variablesInfo = getVariablesToParseInfoInText(
|
||||
block.content.url,
|
||||
parseVarParams
|
||||
)
|
||||
variablesInfo.forEach((variableInfo) =>
|
||||
variableInfo.variableId
|
||||
? variableIds.add(variableInfo.variableId ?? '')
|
||||
: undefined
|
||||
)
|
||||
}
|
||||
if (block.type === LogicBlockType.CONDITION) {
|
||||
block.items.forEach((item) =>
|
||||
item.content?.comparisons?.forEach((comparison) => {
|
||||
if (comparison.variableId) variableIds.add(comparison.variableId)
|
||||
if (comparison.value) {
|
||||
const variableIdsInValue = getVariablesToParseInfoInText(
|
||||
comparison.value,
|
||||
parseVarParams
|
||||
)
|
||||
variableIdsInValue.forEach((variableInfo) => {
|
||||
variableInfo.variableId
|
||||
? variableIds.add(variableInfo.variableId)
|
||||
: undefined
|
||||
})
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
return [...variableIds]
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import {
|
||||
ContinueChatResponse,
|
||||
CustomEmbedBubble,
|
||||
SessionState,
|
||||
SetVariableHistoryItem,
|
||||
} from '@typebot.io/schemas'
|
||||
|
||||
export type EdgeId = string
|
||||
@ -9,6 +10,7 @@ export type EdgeId = string
|
||||
export type ExecuteLogicResponse = {
|
||||
outgoingEdgeId: EdgeId | undefined
|
||||
newSessionState?: SessionState
|
||||
newSetVariableHistory?: SetVariableHistoryItem[]
|
||||
} & Pick<ContinueChatResponse, 'clientSideActions' | 'logs'>
|
||||
|
||||
export type ExecuteIntegrationResponse = {
|
||||
@ -16,6 +18,7 @@ export type ExecuteIntegrationResponse = {
|
||||
newSessionState?: SessionState
|
||||
startTimeShouldBeUpdated?: boolean
|
||||
customEmbedBubble?: CustomEmbedBubble
|
||||
newSetVariableHistory?: SetVariableHistoryItem[]
|
||||
} & Pick<ContinueChatResponse, 'clientSideActions' | 'logs'>
|
||||
|
||||
type WhatsAppMediaMessage = {
|
||||
|
@ -114,6 +114,7 @@ export const resumeWhatsAppFlow = async ({
|
||||
messages,
|
||||
clientSideActions,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
} = resumeResponse
|
||||
|
||||
const isFirstChatChunk = (!session || isSessionExpired) ?? false
|
||||
@ -140,6 +141,7 @@ export const resumeWhatsAppFlow = async ({
|
||||
},
|
||||
},
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
})
|
||||
|
||||
return {
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
ContinueChatResponse,
|
||||
PublicTypebot,
|
||||
SessionState,
|
||||
SetVariableHistoryItem,
|
||||
Settings,
|
||||
Typebot,
|
||||
} from '@typebot.io/schemas'
|
||||
@ -35,6 +36,7 @@ export const startWhatsAppSession = async ({
|
||||
| (ContinueChatResponse & {
|
||||
newSessionState: SessionState
|
||||
visitedEdges: VisitedEdge[]
|
||||
setVariableHistory: SetVariableHistoryItem[]
|
||||
})
|
||||
| { error: string }
|
||||
> => {
|
||||
|
Reference in New Issue
Block a user