⚡ Regroup database queries of /sendMessage in one place
Potentially reduces the total queries to database and it will help to migrate to Edge runtime
This commit is contained in:
@@ -27,6 +27,6 @@ test.describe.parallel('Templates page', () => {
|
|||||||
await page.click('text=Customer Support')
|
await page.click('text=Customer Support')
|
||||||
await expect(page.locator('text=How can I help you?')).toBeVisible()
|
await expect(page.locator('text=How can I help you?')).toBeVisible()
|
||||||
await page.click('text=Use this template')
|
await page.click('text=Use this template')
|
||||||
await expect(page).toHaveURL(new RegExp(`/edit`), { timeout: 10000 })
|
await expect(page).toHaveURL(new RegExp(`/edit`), { timeout: 20000 })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { filterChoiceItems } from './filterChoiceItems'
|
|||||||
|
|
||||||
export const injectVariableValuesInButtonsInputBlock =
|
export const injectVariableValuesInButtonsInputBlock =
|
||||||
(state: SessionState) =>
|
(state: SessionState) =>
|
||||||
async (block: ChoiceInputBlock): Promise<ChoiceInputBlock> => {
|
(block: ChoiceInputBlock): ChoiceInputBlock => {
|
||||||
if (block.options.dynamicVariableId) {
|
if (block.options.dynamicVariableId) {
|
||||||
const variable = state.typebot.variables.find(
|
const variable = state.typebot.variables.find(
|
||||||
(variable) =>
|
(variable) =>
|
||||||
@@ -20,7 +20,7 @@ export const injectVariableValuesInButtonsInputBlock =
|
|||||||
isDefined(variable.value)
|
isDefined(variable.value)
|
||||||
) as VariableWithValue | undefined
|
) as VariableWithValue | undefined
|
||||||
if (!variable) return block
|
if (!variable) return block
|
||||||
const value = await getVariableValue(state)(variable)
|
const value = getVariableValue(state)(variable)
|
||||||
return {
|
return {
|
||||||
...block,
|
...block,
|
||||||
items: value.filter(isDefined).map((item, idx) => ({
|
items: value.filter(isDefined).map((item, idx) => ({
|
||||||
@@ -38,12 +38,12 @@ export const injectVariableValuesInButtonsInputBlock =
|
|||||||
|
|
||||||
const getVariableValue =
|
const getVariableValue =
|
||||||
(state: SessionState) =>
|
(state: SessionState) =>
|
||||||
async (variable: VariableWithValue): Promise<(string | null)[]> => {
|
(variable: VariableWithValue): (string | null)[] => {
|
||||||
if (!Array.isArray(variable.value)) {
|
if (!Array.isArray(variable.value)) {
|
||||||
const [transformedVariable] = transformStringVariablesToList(
|
const [transformedVariable] = transformStringVariablesToList(
|
||||||
state.typebot.variables
|
state.typebot.variables
|
||||||
)([variable.id])
|
)([variable.id])
|
||||||
await updateVariables(state)([transformedVariable])
|
updateVariables(state)([transformedVariable])
|
||||||
return transformedVariable.value as string[]
|
return transformedVariable.value as string[]
|
||||||
}
|
}
|
||||||
return variable.value
|
return variable.value
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ import {
|
|||||||
import { isNotEmpty, byId } from '@typebot.io/lib'
|
import { isNotEmpty, byId } from '@typebot.io/lib'
|
||||||
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
||||||
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||||
import { saveErrorLog } from '@/features/logs/saveErrorLog'
|
|
||||||
import { updateVariables } from '@/features/variables/updateVariables'
|
import { updateVariables } from '@/features/variables/updateVariables'
|
||||||
import { deepParseVariables } from '@/features/variables/deepParseVariable'
|
import { deepParseVariables } from '@/features/variables/deepParseVariable'
|
||||||
import { matchFilter } from './helpers/matchFilter'
|
import { matchFilter } from './helpers/matchFilter'
|
||||||
import { saveInfoLog } from '@/features/logs/saveInfoLog'
|
|
||||||
|
|
||||||
export const getRow = async (
|
export const getRow = async (
|
||||||
state: SessionState,
|
state: SessionState,
|
||||||
@@ -20,15 +18,13 @@ export const getRow = async (
|
|||||||
options,
|
options,
|
||||||
}: { outgoingEdgeId?: string; options: GoogleSheetsGetOptions }
|
}: { outgoingEdgeId?: string; options: GoogleSheetsGetOptions }
|
||||||
): Promise<ExecuteIntegrationResponse> => {
|
): Promise<ExecuteIntegrationResponse> => {
|
||||||
|
const logs: ReplyLog[] = []
|
||||||
const { sheetId, cellsToExtract, referenceCell, filter } = deepParseVariables(
|
const { sheetId, cellsToExtract, referenceCell, filter } = deepParseVariables(
|
||||||
state.typebot.variables
|
state.typebot.variables
|
||||||
)(options)
|
)(options)
|
||||||
if (!sheetId) return { outgoingEdgeId }
|
if (!sheetId) return { outgoingEdgeId }
|
||||||
|
|
||||||
let log: ReplyLog | undefined
|
|
||||||
|
|
||||||
const variables = state.typebot.variables
|
const variables = state.typebot.variables
|
||||||
const resultId = state.result?.id
|
|
||||||
|
|
||||||
const doc = await getAuthenticatedGoogleDoc({
|
const doc = await getAuthenticatedGoogleDoc({
|
||||||
credentialsId: options.credentialsId,
|
credentialsId: options.credentialsId,
|
||||||
@@ -48,16 +44,12 @@ export const getRow = async (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (filteredRows.length === 0) {
|
if (filteredRows.length === 0) {
|
||||||
log = {
|
logs.push({
|
||||||
status: 'info',
|
status: 'info',
|
||||||
description: `Couldn't find any rows matching the filter`,
|
description: `Couldn't find any rows matching the filter`,
|
||||||
details: JSON.stringify(filter, null, 2),
|
details: JSON.stringify(filter, null, 2),
|
||||||
}
|
|
||||||
await saveInfoLog({
|
|
||||||
resultId,
|
|
||||||
message: log.description,
|
|
||||||
})
|
})
|
||||||
return { outgoingEdgeId, logs: log ? [log] : undefined }
|
return { outgoingEdgeId, logs }
|
||||||
}
|
}
|
||||||
const extractingColumns = cellsToExtract
|
const extractingColumns = cellsToExtract
|
||||||
.map((cell) => cell.column)
|
.map((cell) => cell.column)
|
||||||
@@ -85,24 +77,19 @@ export const getRow = async (
|
|||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
const newSessionState = await updateVariables(state)(newVariables)
|
const newSessionState = updateVariables(state)(newVariables)
|
||||||
return {
|
return {
|
||||||
outgoingEdgeId,
|
outgoingEdgeId,
|
||||||
newSessionState,
|
newSessionState,
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log = {
|
logs.push({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
description: `An error occurred while fetching the spreadsheet data`,
|
description: `An error occurred while fetching the spreadsheet data`,
|
||||||
details: err,
|
details: err,
|
||||||
}
|
|
||||||
await saveErrorLog({
|
|
||||||
resultId,
|
|
||||||
message: log.description,
|
|
||||||
details: err,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return { outgoingEdgeId, logs: log ? [log] : undefined }
|
return { outgoingEdgeId, logs }
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTotalRows = <T>(
|
const getTotalRows = <T>(
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ import {
|
|||||||
import { parseCellValues } from './helpers/parseCellValues'
|
import { parseCellValues } from './helpers/parseCellValues'
|
||||||
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
||||||
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||||
import { saveErrorLog } from '@/features/logs/saveErrorLog'
|
|
||||||
import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
|
|
||||||
|
|
||||||
export const insertRow = async (
|
export const insertRow = async (
|
||||||
{ result, typebot: { variables } }: SessionState,
|
{ typebot: { variables } }: SessionState,
|
||||||
{
|
{
|
||||||
outgoingEdgeId,
|
outgoingEdgeId,
|
||||||
options,
|
options,
|
||||||
@@ -18,7 +16,7 @@ export const insertRow = async (
|
|||||||
): Promise<ExecuteIntegrationResponse> => {
|
): Promise<ExecuteIntegrationResponse> => {
|
||||||
if (!options.cellsToInsert || !options.sheetId) return { outgoingEdgeId }
|
if (!options.cellsToInsert || !options.sheetId) return { outgoingEdgeId }
|
||||||
|
|
||||||
let log: ReplyLog | undefined
|
const logs: ReplyLog[] = []
|
||||||
|
|
||||||
const doc = await getAuthenticatedGoogleDoc({
|
const doc = await getAuthenticatedGoogleDoc({
|
||||||
credentialsId: options.credentialsId,
|
credentialsId: options.credentialsId,
|
||||||
@@ -31,27 +29,17 @@ export const insertRow = async (
|
|||||||
await doc.loadInfo()
|
await doc.loadInfo()
|
||||||
const sheet = doc.sheetsById[Number(options.sheetId)]
|
const sheet = doc.sheetsById[Number(options.sheetId)]
|
||||||
await sheet.addRow(parsedValues)
|
await sheet.addRow(parsedValues)
|
||||||
log = {
|
logs.push({
|
||||||
status: 'success',
|
status: 'success',
|
||||||
description: `Succesfully inserted row in ${doc.title} > ${sheet.title}`,
|
description: `Succesfully inserted row in ${doc.title} > ${sheet.title}`,
|
||||||
}
|
})
|
||||||
result &&
|
|
||||||
(await saveSuccessLog({
|
|
||||||
resultId: result.id,
|
|
||||||
message: log?.description,
|
|
||||||
}))
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log = {
|
logs.push({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
description: `An error occured while inserting the row`,
|
description: `An error occured while inserting the row`,
|
||||||
details: err,
|
details: err,
|
||||||
}
|
})
|
||||||
result &&
|
|
||||||
(await saveErrorLog({
|
|
||||||
resultId: result.id,
|
|
||||||
message: log.description,
|
|
||||||
details: err,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
return { outgoingEdgeId, logs: log ? [log] : undefined }
|
|
||||||
|
return { outgoingEdgeId, logs }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,10 @@ import { parseCellValues } from './helpers/parseCellValues'
|
|||||||
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
||||||
import { deepParseVariables } from '@/features/variables/deepParseVariable'
|
import { deepParseVariables } from '@/features/variables/deepParseVariable'
|
||||||
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||||
import { saveErrorLog } from '@/features/logs/saveErrorLog'
|
|
||||||
import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
|
|
||||||
import { matchFilter } from './helpers/matchFilter'
|
import { matchFilter } from './helpers/matchFilter'
|
||||||
import { saveInfoLog } from '@/features/logs/saveInfoLog'
|
|
||||||
|
|
||||||
export const updateRow = async (
|
export const updateRow = async (
|
||||||
{ result, typebot: { variables } }: SessionState,
|
{ typebot: { variables } }: SessionState,
|
||||||
{
|
{
|
||||||
outgoingEdgeId,
|
outgoingEdgeId,
|
||||||
options,
|
options,
|
||||||
@@ -24,7 +21,7 @@ export const updateRow = async (
|
|||||||
if (!options.cellsToUpsert || !sheetId || (!referenceCell && !filter))
|
if (!options.cellsToUpsert || !sheetId || (!referenceCell && !filter))
|
||||||
return { outgoingEdgeId }
|
return { outgoingEdgeId }
|
||||||
|
|
||||||
let log: ReplyLog | undefined
|
const logs: ReplyLog[] = []
|
||||||
|
|
||||||
const doc = await getAuthenticatedGoogleDoc({
|
const doc = await getAuthenticatedGoogleDoc({
|
||||||
credentialsId: options.credentialsId,
|
credentialsId: options.credentialsId,
|
||||||
@@ -42,18 +39,12 @@ export const updateRow = async (
|
|||||||
: matchFilter(row, filter as NonNullable<typeof filter>)
|
: matchFilter(row, filter as NonNullable<typeof filter>)
|
||||||
)
|
)
|
||||||
if (filteredRows.length === 0) {
|
if (filteredRows.length === 0) {
|
||||||
log = {
|
logs.push({
|
||||||
status: 'info',
|
status: 'info',
|
||||||
description: `Could not find any row that matches the filter`,
|
description: `Could not find any row that matches the filter`,
|
||||||
details: JSON.stringify(filter, null, 2),
|
details: filter,
|
||||||
}
|
})
|
||||||
result &&
|
return { outgoingEdgeId, logs }
|
||||||
(await saveInfoLog({
|
|
||||||
resultId: result.id,
|
|
||||||
message: log.description,
|
|
||||||
details: log.details,
|
|
||||||
}))
|
|
||||||
return { outgoingEdgeId, logs: log ? [log] : undefined }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -65,28 +56,17 @@ export const updateRow = async (
|
|||||||
await rows[rowIndex].save()
|
await rows[rowIndex].save()
|
||||||
}
|
}
|
||||||
|
|
||||||
log = log = {
|
logs.push({
|
||||||
status: 'success',
|
status: 'success',
|
||||||
description: `Succesfully updated matching rows`,
|
description: `Succesfully updated matching rows`,
|
||||||
}
|
})
|
||||||
result &&
|
|
||||||
(await saveSuccessLog({
|
|
||||||
resultId: result.id,
|
|
||||||
message: log.description,
|
|
||||||
}))
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
log = {
|
logs.push({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
description: `An error occured while updating the row`,
|
description: `An error occured while updating the row`,
|
||||||
details: err,
|
details: err,
|
||||||
}
|
})
|
||||||
result &&
|
|
||||||
(await saveErrorLog({
|
|
||||||
resultId: result.id,
|
|
||||||
message: log.description,
|
|
||||||
details: err,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
return { outgoingEdgeId, logs: log ? [log] : undefined }
|
return { outgoingEdgeId, logs }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const createChatCompletionOpenAI = async (
|
|||||||
newSessionState.typebot.variables
|
newSessionState.typebot.variables
|
||||||
)(options.messages)
|
)(options.messages)
|
||||||
if (variablesTransformedToList.length > 0)
|
if (variablesTransformedToList.length > 0)
|
||||||
newSessionState = await updateVariables(state)(variablesTransformedToList)
|
newSessionState = updateVariables(state)(variablesTransformedToList)
|
||||||
|
|
||||||
const temperature = parseVariableNumber(newSessionState.typebot.variables)(
|
const temperature = parseVariableNumber(newSessionState.typebot.variables)(
|
||||||
options.advancedSettings?.temperature
|
options.advancedSettings?.temperature
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export const executeChatCompletionOpenAIRequest = async ({
|
|||||||
logs.push({
|
logs.push({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
description: `Internal error`,
|
description: `Internal error`,
|
||||||
|
details: error,
|
||||||
})
|
})
|
||||||
return { logs }
|
return { logs }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
|
|
||||||
import { updateVariables } from '@/features/variables/updateVariables'
|
import { updateVariables } from '@/features/variables/updateVariables'
|
||||||
import { byId, isDefined } from '@typebot.io/lib'
|
import { byId, isDefined } from '@typebot.io/lib'
|
||||||
import { ChatReply, SessionState } from '@typebot.io/schemas'
|
import { ChatReply, SessionState } from '@typebot.io/schemas'
|
||||||
@@ -11,7 +10,7 @@ export const resumeChatCompletion =
|
|||||||
{
|
{
|
||||||
outgoingEdgeId,
|
outgoingEdgeId,
|
||||||
options,
|
options,
|
||||||
logs,
|
logs = [],
|
||||||
}: {
|
}: {
|
||||||
outgoingEdgeId?: string
|
outgoingEdgeId?: string
|
||||||
options: ChatCompletionOpenAIOptions
|
options: ChatCompletionOpenAIOptions
|
||||||
@@ -44,12 +43,11 @@ export const resumeChatCompletion =
|
|||||||
return newVariables
|
return newVariables
|
||||||
}, [])
|
}, [])
|
||||||
if (newVariables.length > 0)
|
if (newVariables.length > 0)
|
||||||
newSessionState = await updateVariables(newSessionState)(newVariables)
|
newSessionState = updateVariables(newSessionState)(newVariables)
|
||||||
state.result &&
|
logs.push({
|
||||||
(await saveSuccessLog({
|
description: 'OpenAI block successfully executed',
|
||||||
resultId: state.result.id,
|
status: 'success',
|
||||||
message: 'OpenAI block successfully executed',
|
})
|
||||||
}))
|
|
||||||
return {
|
return {
|
||||||
outgoingEdgeId,
|
outgoingEdgeId,
|
||||||
newSessionState,
|
newSessionState,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { render } from '@faire/mjml-react/utils/render'
|
|||||||
import { DefaultBotNotificationEmail } from '@typebot.io/emails'
|
import { DefaultBotNotificationEmail } from '@typebot.io/emails'
|
||||||
import {
|
import {
|
||||||
PublicTypebot,
|
PublicTypebot,
|
||||||
|
ReplyLog,
|
||||||
ResultInSession,
|
ResultInSession,
|
||||||
SendEmailBlock,
|
SendEmailBlock,
|
||||||
SendEmailOptions,
|
SendEmailOptions,
|
||||||
@@ -18,14 +19,13 @@ import { parseAnswers } from '@typebot.io/lib/results'
|
|||||||
import { decrypt } from '@typebot.io/lib/api'
|
import { decrypt } from '@typebot.io/lib/api'
|
||||||
import { defaultFrom, defaultTransportOptions } from './constants'
|
import { defaultFrom, defaultTransportOptions } from './constants'
|
||||||
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||||
import { saveErrorLog } from '@/features/logs/saveErrorLog'
|
|
||||||
import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
|
|
||||||
import { findUniqueVariableValue } from '../../../variables/findUniqueVariableValue'
|
import { findUniqueVariableValue } from '../../../variables/findUniqueVariableValue'
|
||||||
|
|
||||||
export const executeSendEmailBlock = async (
|
export const executeSendEmailBlock = async (
|
||||||
{ result, typebot }: SessionState,
|
{ result, typebot }: SessionState,
|
||||||
block: SendEmailBlock
|
block: SendEmailBlock
|
||||||
): Promise<ExecuteIntegrationResponse> => {
|
): Promise<ExecuteIntegrationResponse> => {
|
||||||
|
const logs: ReplyLog[] = []
|
||||||
const { options } = block
|
const { options } = block
|
||||||
const { variables } = typebot
|
const { variables } = typebot
|
||||||
const isPreview = !result.id
|
const isPreview = !result.id
|
||||||
@@ -45,7 +45,7 @@ export const executeSendEmailBlock = async (
|
|||||||
parseVariables(variables, { escapeHtml: true })(options.body ?? '')
|
parseVariables(variables, { escapeHtml: true })(options.body ?? '')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sendEmail({
|
const sendEmailLogs = await sendEmail({
|
||||||
typebotId: typebot.id,
|
typebotId: typebot.id,
|
||||||
result,
|
result,
|
||||||
credentialsId: options.credentialsId,
|
credentialsId: options.credentialsId,
|
||||||
@@ -61,17 +61,16 @@ export const executeSendEmailBlock = async (
|
|||||||
isCustomBody: options.isCustomBody,
|
isCustomBody: options.isCustomBody,
|
||||||
isBodyCode: options.isBodyCode,
|
isBodyCode: options.isBodyCode,
|
||||||
})
|
})
|
||||||
|
if (sendEmailLogs) logs.push(...sendEmailLogs)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await saveErrorLog({
|
logs.push({
|
||||||
resultId: result.id,
|
status: 'error',
|
||||||
message: 'Email not sent',
|
details: err,
|
||||||
details: {
|
description: `Email not sent`,
|
||||||
error: err,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return { outgoingEdgeId: block.outgoingEdgeId }
|
return { outgoingEdgeId: block.outgoingEdgeId, logs }
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendEmail = async ({
|
const sendEmail = async ({
|
||||||
@@ -91,7 +90,8 @@ const sendEmail = async ({
|
|||||||
typebotId: string
|
typebotId: string
|
||||||
result: ResultInSession
|
result: ResultInSession
|
||||||
fileUrls?: string | string[]
|
fileUrls?: string | string[]
|
||||||
}) => {
|
}): Promise<ReplyLog[] | undefined> => {
|
||||||
|
const logs: ReplyLog[] = []
|
||||||
const { name: replyToName } = parseEmailRecipient(replyTo)
|
const { name: replyToName } = parseEmailRecipient(replyTo)
|
||||||
|
|
||||||
const { host, port, isTlsEnabled, username, password, from } =
|
const { host, port, isTlsEnabled, username, password, from } =
|
||||||
@@ -117,9 +117,9 @@ const sendEmail = async ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!emailBody) {
|
if (!emailBody) {
|
||||||
await saveErrorLog({
|
logs.push({
|
||||||
resultId: result.id,
|
status: 'error',
|
||||||
message: 'Email not sent',
|
description: 'Email not sent',
|
||||||
details: {
|
details: {
|
||||||
error: 'No email body found',
|
error: 'No email body found',
|
||||||
transportConfig,
|
transportConfig,
|
||||||
@@ -131,6 +131,7 @@ const sendEmail = async ({
|
|||||||
emailBody,
|
emailBody,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
return logs
|
||||||
}
|
}
|
||||||
const transporter = createTransport(transportConfig)
|
const transporter = createTransport(transportConfig)
|
||||||
const fromName = isEmpty(replyToName) ? from.name : replyToName
|
const fromName = isEmpty(replyToName) ? from.name : replyToName
|
||||||
@@ -150,9 +151,9 @@ const sendEmail = async ({
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await transporter.sendMail(email)
|
await transporter.sendMail(email)
|
||||||
await saveSuccessLog({
|
logs.push({
|
||||||
resultId: result.id,
|
status: 'success',
|
||||||
message: 'Email successfully sent',
|
description: 'Email successfully sent',
|
||||||
details: {
|
details: {
|
||||||
transportConfig: {
|
transportConfig: {
|
||||||
...transportConfig,
|
...transportConfig,
|
||||||
@@ -162,11 +163,11 @@ const sendEmail = async ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await saveErrorLog({
|
logs.push({
|
||||||
resultId: result.id,
|
status: 'error',
|
||||||
message: 'Email not sent',
|
description: 'Email not sent',
|
||||||
details: {
|
details: {
|
||||||
error: err,
|
error: err instanceof Error ? err.toString() : err,
|
||||||
transportConfig: {
|
transportConfig: {
|
||||||
...transportConfig,
|
...transportConfig,
|
||||||
auth: { user: transportConfig.auth.user, pass: '******' },
|
auth: { user: transportConfig.auth.user, pass: '******' },
|
||||||
@@ -175,6 +176,8 @@ const sendEmail = async ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return logs
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEmailInfo = async (
|
const getEmailInfo = async (
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ import { parseAnswers } from '@typebot.io/lib/results'
|
|||||||
import got, { Method, HTTPError, OptionsInit } from 'got'
|
import got, { Method, HTTPError, OptionsInit } from 'got'
|
||||||
import { parseSampleResult } from './parseSampleResult'
|
import { parseSampleResult } from './parseSampleResult'
|
||||||
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||||
import { saveErrorLog } from '@/features/logs/saveErrorLog'
|
|
||||||
import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
|
|
||||||
import { parseVariables } from '@/features/variables/parseVariables'
|
import { parseVariables } from '@/features/variables/parseVariables'
|
||||||
import { resumeWebhookExecution } from './resumeWebhookExecution'
|
import { resumeWebhookExecution } from './resumeWebhookExecution'
|
||||||
|
|
||||||
@@ -39,21 +37,16 @@ export const executeWebhookBlock = async (
|
|||||||
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
||||||
): Promise<ExecuteIntegrationResponse> => {
|
): Promise<ExecuteIntegrationResponse> => {
|
||||||
const { typebot, result } = state
|
const { typebot, result } = state
|
||||||
let log: ReplyLog | undefined
|
const logs: ReplyLog[] = []
|
||||||
const webhook = (await prisma.webhook.findUnique({
|
const webhook = (await prisma.webhook.findUnique({
|
||||||
where: { id: block.webhookId },
|
where: { id: block.webhookId },
|
||||||
})) as Webhook | null
|
})) as Webhook | null
|
||||||
if (!webhook) {
|
if (!webhook) {
|
||||||
log = {
|
logs.push({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
description: `Couldn't find webhook with id ${block.webhookId}`,
|
description: `Couldn't find webhook with id ${block.webhookId}`,
|
||||||
}
|
})
|
||||||
result &&
|
return { outgoingEdgeId: block.outgoingEdgeId, logs }
|
||||||
(await saveErrorLog({
|
|
||||||
resultId: result.id,
|
|
||||||
message: log.description,
|
|
||||||
}))
|
|
||||||
return { outgoingEdgeId: block.outgoingEdgeId, logs: [log] }
|
|
||||||
}
|
}
|
||||||
const preparedWebhook = prepareWebhookAttributes(webhook, block.options)
|
const preparedWebhook = prepareWebhookAttributes(webhook, block.options)
|
||||||
const parsedWebhook = await parseWebhookAttributes(
|
const parsedWebhook = await parseWebhookAttributes(
|
||||||
@@ -62,16 +55,11 @@ export const executeWebhookBlock = async (
|
|||||||
result
|
result
|
||||||
)(preparedWebhook)
|
)(preparedWebhook)
|
||||||
if (!parsedWebhook) {
|
if (!parsedWebhook) {
|
||||||
log = {
|
logs.push({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
description: `Couldn't parse webhook attributes`,
|
description: `Couldn't parse webhook attributes`,
|
||||||
}
|
})
|
||||||
result &&
|
return { outgoingEdgeId: block.outgoingEdgeId, logs }
|
||||||
(await saveErrorLog({
|
|
||||||
resultId: result.id,
|
|
||||||
message: log.description,
|
|
||||||
}))
|
|
||||||
return { outgoingEdgeId: block.outgoingEdgeId, logs: [log] }
|
|
||||||
}
|
}
|
||||||
if (block.options.isExecutedOnClient)
|
if (block.options.isExecutedOnClient)
|
||||||
return {
|
return {
|
||||||
@@ -82,8 +70,14 @@ export const executeWebhookBlock = async (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
const webhookResponse = await executeWebhook(parsedWebhook, result)
|
const { response: webhookResponse, logs: executeWebhookLogs } =
|
||||||
return resumeWebhookExecution(state, block)(webhookResponse)
|
await executeWebhook(parsedWebhook)
|
||||||
|
return resumeWebhookExecution({
|
||||||
|
state,
|
||||||
|
block,
|
||||||
|
logs: executeWebhookLogs,
|
||||||
|
response: webhookResponse,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const prepareWebhookAttributes = (
|
const prepareWebhookAttributes = (
|
||||||
@@ -162,9 +156,9 @@ const parseWebhookAttributes =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const executeWebhook = async (
|
export const executeWebhook = async (
|
||||||
webhook: ParsedWebhook,
|
webhook: ParsedWebhook
|
||||||
result: ResultInSession
|
): Promise<{ response: WebhookResponse; logs?: ReplyLog[] }> => {
|
||||||
): Promise<WebhookResponse> => {
|
const logs: ReplyLog[] = []
|
||||||
const { headers, url, method, basicAuth, body, isJson } = webhook
|
const { headers, url, method, basicAuth, body, isJson } = webhook
|
||||||
const contentType = headers ? headers['Content-Type'] : undefined
|
const contentType = headers ? headers['Content-Type'] : undefined
|
||||||
|
|
||||||
@@ -183,9 +177,9 @@ export const executeWebhook = async (
|
|||||||
} satisfies OptionsInit
|
} satisfies OptionsInit
|
||||||
try {
|
try {
|
||||||
const response = await got(request.url, omit(request, 'url'))
|
const response = await got(request.url, omit(request, 'url'))
|
||||||
await saveSuccessLog({
|
logs.push({
|
||||||
resultId: result.id,
|
status: 'success',
|
||||||
message: 'Webhook successfuly executed.',
|
description: `Webhook successfuly executed.`,
|
||||||
details: {
|
details: {
|
||||||
statusCode: response.statusCode,
|
statusCode: response.statusCode,
|
||||||
request,
|
request,
|
||||||
@@ -193,8 +187,11 @@ export const executeWebhook = async (
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
statusCode: response.statusCode,
|
response: {
|
||||||
data: safeJsonParse(response.body).data,
|
statusCode: response.statusCode,
|
||||||
|
data: safeJsonParse(response.body).data,
|
||||||
|
},
|
||||||
|
logs,
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HTTPError) {
|
if (error instanceof HTTPError) {
|
||||||
@@ -202,30 +199,31 @@ export const executeWebhook = async (
|
|||||||
statusCode: error.response.statusCode,
|
statusCode: error.response.statusCode,
|
||||||
data: safeJsonParse(error.response.body as string).data,
|
data: safeJsonParse(error.response.body as string).data,
|
||||||
}
|
}
|
||||||
await saveErrorLog({
|
logs.push({
|
||||||
resultId: result.id,
|
status: 'error',
|
||||||
message: 'Webhook returned an error',
|
description: `Webhook returned an error.`,
|
||||||
details: {
|
details: {
|
||||||
|
statusCode: error.response.statusCode,
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return response
|
return { response, logs }
|
||||||
}
|
}
|
||||||
const response = {
|
const response = {
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
data: { message: `Error from Typebot server: ${error}` },
|
data: { message: `Error from Typebot server: ${error}` },
|
||||||
}
|
}
|
||||||
console.error(error)
|
console.error(error)
|
||||||
await saveErrorLog({
|
logs.push({
|
||||||
resultId: result.id,
|
status: 'error',
|
||||||
message: 'Webhook failed to execute',
|
description: `Webhook failed to execute.`,
|
||||||
details: {
|
details: {
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return response
|
return { response, logs }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
||||||
import { saveErrorLog } from '@/features/logs/saveErrorLog'
|
|
||||||
import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
|
|
||||||
import { parseVariables } from '@/features/variables/parseVariables'
|
import { parseVariables } from '@/features/variables/parseVariables'
|
||||||
import { updateVariables } from '@/features/variables/updateVariables'
|
import { updateVariables } from '@/features/variables/updateVariables'
|
||||||
import { byId } from '@typebot.io/lib'
|
import { byId } from '@typebot.io/lib'
|
||||||
@@ -13,75 +11,71 @@ import {
|
|||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { ReplyLog, SessionState } from '@typebot.io/schemas/features/chat'
|
import { ReplyLog, SessionState } from '@typebot.io/schemas/features/chat'
|
||||||
|
|
||||||
export const resumeWebhookExecution =
|
type Props = {
|
||||||
(
|
state: SessionState
|
||||||
state: SessionState,
|
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
||||||
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
logs?: ReplyLog[]
|
||||||
) =>
|
response: {
|
||||||
async (response: {
|
|
||||||
statusCode: number
|
statusCode: number
|
||||||
data?: unknown
|
data?: unknown
|
||||||
}): Promise<ExecuteIntegrationResponse> => {
|
}
|
||||||
const { typebot, result } = state
|
}
|
||||||
let log: ReplyLog | undefined
|
|
||||||
const status = response.statusCode.toString()
|
|
||||||
const isError = status.startsWith('4') || status.startsWith('5')
|
|
||||||
|
|
||||||
if (isError) {
|
export const resumeWebhookExecution = ({
|
||||||
log = {
|
state,
|
||||||
status: 'error',
|
block,
|
||||||
description: `Webhook returned error: ${response.data}`,
|
logs = [],
|
||||||
details: JSON.stringify(response.data, null, 2).substring(0, 1000),
|
response,
|
||||||
}
|
}: Props): ExecuteIntegrationResponse => {
|
||||||
result &&
|
const { typebot } = state
|
||||||
(await saveErrorLog({
|
const status = response.statusCode.toString()
|
||||||
resultId: result.id,
|
const isError = status.startsWith('4') || status.startsWith('5')
|
||||||
message: log.description,
|
|
||||||
details: log.details,
|
const responseFromClient = logs.length === 0
|
||||||
}))
|
|
||||||
} else {
|
if (responseFromClient)
|
||||||
log = {
|
logs.push(
|
||||||
status: 'success',
|
isError
|
||||||
description: `Webhook executed successfully!`,
|
? {
|
||||||
details: JSON.stringify(response.data, null, 2).substring(0, 1000),
|
status: 'error',
|
||||||
}
|
description: `Webhook returned error`,
|
||||||
result &&
|
details: response.data,
|
||||||
(await saveSuccessLog({
|
}
|
||||||
resultId: result.id,
|
: {
|
||||||
message: log.description,
|
status: 'success',
|
||||||
details: JSON.stringify(response.data, null, 2).substring(0, 1000),
|
description: `Webhook executed successfully!`,
|
||||||
}))
|
details: response.data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const newVariables = block.options.responseVariableMapping.reduce<
|
||||||
|
VariableWithUnknowValue[]
|
||||||
|
>((newVariables, varMapping) => {
|
||||||
|
if (!varMapping?.bodyPath || !varMapping.variableId) return newVariables
|
||||||
|
const existingVariable = typebot.variables.find(byId(varMapping.variableId))
|
||||||
|
if (!existingVariable) return newVariables
|
||||||
|
const func = Function(
|
||||||
|
'data',
|
||||||
|
`return data.${parseVariables(typebot.variables)(varMapping?.bodyPath)}`
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
const value: unknown = func(response)
|
||||||
|
return [...newVariables, { ...existingVariable, value }]
|
||||||
|
} catch (err) {
|
||||||
|
return newVariables
|
||||||
}
|
}
|
||||||
|
}, [])
|
||||||
const newVariables = block.options.responseVariableMapping.reduce<
|
if (newVariables.length > 0) {
|
||||||
VariableWithUnknowValue[]
|
const newSessionState = updateVariables(state)(newVariables)
|
||||||
>((newVariables, varMapping) => {
|
|
||||||
if (!varMapping?.bodyPath || !varMapping.variableId) return newVariables
|
|
||||||
const existingVariable = typebot.variables.find(
|
|
||||||
byId(varMapping.variableId)
|
|
||||||
)
|
|
||||||
if (!existingVariable) return newVariables
|
|
||||||
const func = Function(
|
|
||||||
'data',
|
|
||||||
`return data.${parseVariables(typebot.variables)(varMapping?.bodyPath)}`
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
const value: unknown = func(response)
|
|
||||||
return [...newVariables, { ...existingVariable, value }]
|
|
||||||
} catch (err) {
|
|
||||||
return newVariables
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
if (newVariables.length > 0) {
|
|
||||||
const newSessionState = await updateVariables(state)(newVariables)
|
|
||||||
return {
|
|
||||||
outgoingEdgeId: block.outgoingEdgeId,
|
|
||||||
newSessionState,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
outgoingEdgeId: block.outgoingEdgeId,
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
logs: log ? [log] : undefined,
|
newSessionState,
|
||||||
|
logs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
|
logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,5 +63,5 @@ test('should execute webhooks properly', async ({ page }) => {
|
|||||||
await expect(
|
await expect(
|
||||||
page.locator('text="Webhook successfuly executed." >> nth=1')
|
page.locator('text="Webhook successfuly executed." >> nth=1')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await expect(page.locator('text="Webhook returned an error"')).toBeVisible()
|
await expect(page.locator('text="Webhook returned an error."')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import { parseVariables } from '@/features/variables/parseVariables'
|
|||||||
import { parseGuessedValueType } from '@/features/variables/parseGuessedValueType'
|
import { parseGuessedValueType } from '@/features/variables/parseGuessedValueType'
|
||||||
import { parseScriptToExecuteClientSideAction } from '../script/executeScript'
|
import { parseScriptToExecuteClientSideAction } from '../script/executeScript'
|
||||||
|
|
||||||
export const executeSetVariable = async (
|
export const executeSetVariable = (
|
||||||
state: SessionState,
|
state: SessionState,
|
||||||
block: SetVariableBlock
|
block: SetVariableBlock
|
||||||
): Promise<ExecuteLogicResponse> => {
|
): ExecuteLogicResponse => {
|
||||||
const { variables } = state.typebot
|
const { variables } = state.typebot
|
||||||
if (!block.options?.variableId)
|
if (!block.options?.variableId)
|
||||||
return {
|
return {
|
||||||
@@ -48,7 +48,7 @@ export const executeSetVariable = async (
|
|||||||
...existingVariable,
|
...existingVariable,
|
||||||
value: evaluatedExpression,
|
value: evaluatedExpression,
|
||||||
}
|
}
|
||||||
const newSessionState = await updateVariables(state)([newVariable])
|
const newSessionState = updateVariables(state)([newVariable])
|
||||||
return {
|
return {
|
||||||
outgoingEdgeId: block.outgoingEdgeId,
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
newSessionState,
|
newSessionState,
|
||||||
|
|||||||
@@ -8,33 +8,32 @@ import {
|
|||||||
SessionState,
|
SessionState,
|
||||||
TypebotInSession,
|
TypebotInSession,
|
||||||
Variable,
|
Variable,
|
||||||
|
ReplyLog,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { byId } from '@typebot.io/lib'
|
import { byId } from '@typebot.io/lib'
|
||||||
import { ExecuteLogicResponse } from '@/features/chat/types'
|
import { ExecuteLogicResponse } from '@/features/chat/types'
|
||||||
import { saveErrorLog } from '@/features/logs/saveErrorLog'
|
|
||||||
|
|
||||||
export const executeTypebotLink = async (
|
export const executeTypebotLink = async (
|
||||||
state: SessionState,
|
state: SessionState,
|
||||||
block: TypebotLinkBlock
|
block: TypebotLinkBlock
|
||||||
): Promise<ExecuteLogicResponse> => {
|
): Promise<ExecuteLogicResponse> => {
|
||||||
|
const logs: ReplyLog[] = []
|
||||||
if (!block.options.typebotId) {
|
if (!block.options.typebotId) {
|
||||||
state.result &&
|
logs.push({
|
||||||
saveErrorLog({
|
status: 'error',
|
||||||
resultId: state.result.id,
|
description: `Failed to link typebot`,
|
||||||
message: 'Failed to link typebot',
|
details: `Typebot ID is not specified`,
|
||||||
details: 'Typebot ID is not specified',
|
})
|
||||||
})
|
return { outgoingEdgeId: block.outgoingEdgeId, logs }
|
||||||
return { outgoingEdgeId: block.outgoingEdgeId }
|
|
||||||
}
|
}
|
||||||
const linkedTypebot = await getLinkedTypebot(state, block.options.typebotId)
|
const linkedTypebot = await getLinkedTypebot(state, block.options.typebotId)
|
||||||
if (!linkedTypebot) {
|
if (!linkedTypebot) {
|
||||||
state.result &&
|
logs.push({
|
||||||
saveErrorLog({
|
status: 'error',
|
||||||
resultId: state.result.id,
|
description: `Failed to link typebot`,
|
||||||
message: '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 }
|
||||||
return { outgoingEdgeId: block.outgoingEdgeId }
|
|
||||||
}
|
}
|
||||||
let newSessionState = addLinkedTypebotToState(state, block, linkedTypebot)
|
let newSessionState = addLinkedTypebotToState(state, block, linkedTypebot)
|
||||||
|
|
||||||
@@ -43,13 +42,12 @@ export const executeTypebotLink = async (
|
|||||||
linkedTypebot.groups.find((b) => b.blocks.some((s) => s.type === 'start'))
|
linkedTypebot.groups.find((b) => b.blocks.some((s) => s.type === 'start'))
|
||||||
?.id
|
?.id
|
||||||
if (!nextGroupId) {
|
if (!nextGroupId) {
|
||||||
state.result &&
|
logs.push({
|
||||||
saveErrorLog({
|
status: 'error',
|
||||||
resultId: state.result.id,
|
description: `Failed to link typebot`,
|
||||||
message: '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 }
|
||||||
return { outgoingEdgeId: block.outgoingEdgeId }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const portalEdge = createPortalEdge({ to: { groupId: nextGroupId } })
|
const portalEdge = createPortalEdge({ to: { groupId: nextGroupId } })
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { ExecuteLogicResponse } from '@/features/chat/types'
|
|||||||
import { parseVariables } from '@/features/variables/parseVariables'
|
import { parseVariables } from '@/features/variables/parseVariables'
|
||||||
import { SessionState, WaitBlock } from '@typebot.io/schemas'
|
import { SessionState, WaitBlock } from '@typebot.io/schemas'
|
||||||
|
|
||||||
export const executeWait = async (
|
export const executeWait = (
|
||||||
{ typebot: { variables } }: SessionState,
|
{ typebot: { variables } }: SessionState,
|
||||||
block: WaitBlock
|
block: WaitBlock
|
||||||
): Promise<ExecuteLogicResponse> => {
|
): ExecuteLogicResponse => {
|
||||||
if (!block.options.secondsToWaitFor)
|
if (!block.options.secondsToWaitFor)
|
||||||
return { outgoingEdgeId: block.outgoingEdgeId }
|
return { outgoingEdgeId: block.outgoingEdgeId }
|
||||||
const parsedSecondsToWaitFor = safeParseInt(
|
const parsedSecondsToWaitFor = safeParseInt(
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import prisma from '@/lib/prisma'
|
|
||||||
import { publicProcedure } from '@/helpers/server/trpc'
|
import { publicProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { Prisma } from '@typebot.io/prisma'
|
|
||||||
import {
|
import {
|
||||||
ChatReply,
|
ChatReply,
|
||||||
chatReplySchema,
|
chatReplySchema,
|
||||||
ChatSession,
|
|
||||||
GoogleAnalyticsBlock,
|
GoogleAnalyticsBlock,
|
||||||
IntegrationBlockType,
|
IntegrationBlockType,
|
||||||
PixelBlock,
|
PixelBlock,
|
||||||
|
ReplyLog,
|
||||||
ResultInSession,
|
ResultInSession,
|
||||||
sendMessageInputSchema,
|
sendMessageInputSchema,
|
||||||
SessionState,
|
SessionState,
|
||||||
@@ -19,19 +17,20 @@ import {
|
|||||||
Variable,
|
Variable,
|
||||||
VariableWithValue,
|
VariableWithValue,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import {
|
|
||||||
continueBotFlow,
|
|
||||||
getSession,
|
|
||||||
setResultAsCompleted,
|
|
||||||
startBotFlow,
|
|
||||||
} from '../helpers'
|
|
||||||
import { env, isDefined, isNotEmpty, omit } from '@typebot.io/lib'
|
import { env, isDefined, isNotEmpty, omit } from '@typebot.io/lib'
|
||||||
import { prefillVariables } from '@/features/variables/prefillVariables'
|
import { prefillVariables } from '@/features/variables/prefillVariables'
|
||||||
import { injectVariablesFromExistingResult } from '@/features/variables/injectVariablesFromExistingResult'
|
import { injectVariablesFromExistingResult } from '@/features/variables/injectVariablesFromExistingResult'
|
||||||
import { deepParseVariables } from '@/features/variables/deepParseVariable'
|
import { deepParseVariables } from '@/features/variables/deepParseVariable'
|
||||||
import { parseVariables } from '@/features/variables/parseVariables'
|
import { parseVariables } from '@/features/variables/parseVariables'
|
||||||
import { saveLog } from '@/features/logs/saveLog'
|
|
||||||
import { NodeType, parse } from 'node-html-parser'
|
import { NodeType, parse } from 'node-html-parser'
|
||||||
|
import { saveStateToDatabase } from '../helpers/saveStateToDatabase'
|
||||||
|
import { getSession } from '../queries/getSession'
|
||||||
|
import { continueBotFlow } from '../helpers/continueBotFlow'
|
||||||
|
import { startBotFlow } from '../helpers/startBotFlow'
|
||||||
|
import { findTypebot } from '../queries/findTypebot'
|
||||||
|
import { findPublicTypebot } from '../queries/findPublicTypebot'
|
||||||
|
import { findResult } from '../queries/findResult'
|
||||||
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
|
|
||||||
export const sendMessage = publicProcedure
|
export const sendMessage = publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@@ -53,17 +52,6 @@ export const sendMessage = publicProcedure
|
|||||||
}) => {
|
}) => {
|
||||||
const session = sessionId ? await getSession(sessionId) : null
|
const session = sessionId ? await getSession(sessionId) : null
|
||||||
|
|
||||||
if (clientLogs) {
|
|
||||||
for (const log of clientLogs) {
|
|
||||||
await saveLog({
|
|
||||||
message: log.description,
|
|
||||||
status: log.status as 'error' | 'success' | 'info',
|
|
||||||
resultId: session?.state.result.id,
|
|
||||||
details: log.details,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
const {
|
const {
|
||||||
sessionId,
|
sessionId,
|
||||||
@@ -74,7 +62,7 @@ export const sendMessage = publicProcedure
|
|||||||
dynamicTheme,
|
dynamicTheme,
|
||||||
logs,
|
logs,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
} = await startSession(startParams, user?.id)
|
} = await startSession(startParams, user?.id, clientLogs)
|
||||||
return {
|
return {
|
||||||
sessionId,
|
sessionId,
|
||||||
typebot: typebot
|
typebot: typebot
|
||||||
@@ -95,24 +83,18 @@ export const sendMessage = publicProcedure
|
|||||||
const { messages, input, clientSideActions, newSessionState, logs } =
|
const { messages, input, clientSideActions, newSessionState, logs } =
|
||||||
await continueBotFlow(session.state)(message)
|
await continueBotFlow(session.state)(message)
|
||||||
|
|
||||||
const containsSetVariableClientSideAction = clientSideActions?.some(
|
const allLogs = clientLogs ? [...(logs ?? []), ...clientLogs] : logs
|
||||||
(action) => 'setVariable' in action
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
if (newSessionState)
|
||||||
!input &&
|
await saveStateToDatabase({
|
||||||
!containsSetVariableClientSideAction &&
|
session: {
|
||||||
session.state.result.answers.length > 0 &&
|
id: session.id,
|
||||||
session.state.result.id
|
state: newSessionState,
|
||||||
)
|
},
|
||||||
await setResultAsCompleted(session.state.result.id)
|
input,
|
||||||
|
logs: allLogs,
|
||||||
await prisma.chatSession.updateMany({
|
clientSideActions,
|
||||||
where: { id: session.id },
|
})
|
||||||
data: {
|
|
||||||
state: newSessionState,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages,
|
messages,
|
||||||
@@ -125,7 +107,11 @@ export const sendMessage = publicProcedure
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const startSession = async (startParams?: StartParams, userId?: string) => {
|
const startSession = async (
|
||||||
|
startParams?: StartParams,
|
||||||
|
userId?: string,
|
||||||
|
clientLogs?: ReplyLog[]
|
||||||
|
) => {
|
||||||
if (!startParams)
|
if (!startParams)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
@@ -231,11 +217,16 @@ const startSession = async (startParams?: StartParams, userId?: string) => {
|
|||||||
logs: startLogs.length > 0 ? startLogs : undefined,
|
logs: startLogs.length > 0 ? startLogs : undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = (await prisma.chatSession.create({
|
const allLogs = clientLogs ? [...(logs ?? []), ...clientLogs] : logs
|
||||||
data: {
|
|
||||||
|
const session = await saveStateToDatabase({
|
||||||
|
session: {
|
||||||
state: newSessionState,
|
state: newSessionState,
|
||||||
},
|
},
|
||||||
})) as ChatSession
|
input,
|
||||||
|
logs: allLogs,
|
||||||
|
clientSideActions,
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
resultId: result?.id,
|
resultId: result?.id,
|
||||||
@@ -270,45 +261,8 @@ const getTypebot = async (
|
|||||||
'You need to authenticate the request to start a bot in preview mode.',
|
'You need to authenticate the request to start a bot in preview mode.',
|
||||||
})
|
})
|
||||||
const typebotQuery = isPreview
|
const typebotQuery = isPreview
|
||||||
? await prisma.typebot.findFirst({
|
? await findTypebot({ id: typebot, userId })
|
||||||
where: { id: typebot, workspace: { members: { some: { userId } } } },
|
: await findPublicTypebot({ publicId: typebot })
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
groups: true,
|
|
||||||
edges: true,
|
|
||||||
settings: true,
|
|
||||||
theme: true,
|
|
||||||
variables: true,
|
|
||||||
isArchived: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
: await prisma.publicTypebot.findFirst({
|
|
||||||
where: { typebot: { publicId: typebot } },
|
|
||||||
select: {
|
|
||||||
groups: true,
|
|
||||||
edges: true,
|
|
||||||
settings: true,
|
|
||||||
theme: true,
|
|
||||||
variables: true,
|
|
||||||
typebotId: true,
|
|
||||||
typebot: {
|
|
||||||
select: {
|
|
||||||
isArchived: true,
|
|
||||||
isClosed: true,
|
|
||||||
workspace: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
plan: true,
|
|
||||||
additionalChatsIndex: true,
|
|
||||||
customChatsLimit: true,
|
|
||||||
isQuarantined: true,
|
|
||||||
isSuspended: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const parsedTypebot =
|
const parsedTypebot =
|
||||||
typebotQuery && 'typebot' in typebotQuery
|
typebotQuery && 'typebot' in typebotQuery
|
||||||
@@ -347,7 +301,6 @@ const getTypebot = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getResult = async ({
|
const getResult = async ({
|
||||||
typebotId,
|
|
||||||
isPreview,
|
isPreview,
|
||||||
resultId,
|
resultId,
|
||||||
prefilledVariables,
|
prefilledVariables,
|
||||||
@@ -358,56 +311,31 @@ const getResult = async ({
|
|||||||
isRememberUserEnabled: boolean
|
isRememberUserEnabled: boolean
|
||||||
}) => {
|
}) => {
|
||||||
if (isPreview) return
|
if (isPreview) return
|
||||||
const select = {
|
|
||||||
id: true,
|
|
||||||
variables: true,
|
|
||||||
answers: { select: { blockId: true, variableId: true, content: true } },
|
|
||||||
} satisfies Prisma.ResultSelect
|
|
||||||
|
|
||||||
const existingResult =
|
const existingResult =
|
||||||
resultId && isRememberUserEnabled
|
resultId && isRememberUserEnabled
|
||||||
? ((await prisma.result.findFirst({
|
? ((await findResult({ id: resultId })) as ResultInSession)
|
||||||
where: { id: resultId },
|
|
||||||
select,
|
|
||||||
})) as ResultInSession)
|
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
if (existingResult) {
|
const prefilledVariableWithValue = prefilledVariables.filter(
|
||||||
const prefilledVariableWithValue = prefilledVariables.filter(
|
(prefilledVariable) => isDefined(prefilledVariable.value)
|
||||||
(prefilledVariable) => isDefined(prefilledVariable.value)
|
)
|
||||||
)
|
|
||||||
const updatedResult = {
|
const updatedResult = {
|
||||||
variables: prefilledVariableWithValue.concat(
|
variables: prefilledVariableWithValue.concat(
|
||||||
existingResult.variables.filter(
|
existingResult?.variables.filter(
|
||||||
(resultVariable) =>
|
(resultVariable) =>
|
||||||
isDefined(resultVariable.value) &&
|
isDefined(resultVariable.value) &&
|
||||||
!prefilledVariableWithValue.some(
|
!prefilledVariableWithValue.some(
|
||||||
(prefilledVariable) =>
|
(prefilledVariable) =>
|
||||||
prefilledVariable.name === resultVariable.name
|
prefilledVariable.name === resultVariable.name
|
||||||
)
|
)
|
||||||
)
|
) ?? []
|
||||||
) as VariableWithValue[],
|
) as VariableWithValue[],
|
||||||
}
|
}
|
||||||
await prisma.result.updateMany({
|
return {
|
||||||
where: { id: existingResult.id },
|
id: existingResult?.id ?? createId(),
|
||||||
data: updatedResult,
|
variables: updatedResult.variables,
|
||||||
})
|
answers: existingResult?.answers,
|
||||||
return {
|
|
||||||
id: existingResult.id,
|
|
||||||
variables: updatedResult.variables,
|
|
||||||
answers: existingResult.answers,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return (await prisma.result.create({
|
|
||||||
data: {
|
|
||||||
isCompleted: false,
|
|
||||||
typebotId,
|
|
||||||
variables: prefilledVariables.filter((variable) =>
|
|
||||||
isDefined(variable.value)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
select,
|
|
||||||
})) as ResultInSession
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import prisma from '@/lib/prisma'
|
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { Prisma } from '@typebot.io/prisma'
|
|
||||||
import got from 'got'
|
|
||||||
import {
|
import {
|
||||||
Block,
|
Block,
|
||||||
BlockType,
|
BlockType,
|
||||||
@@ -16,7 +13,7 @@ import {
|
|||||||
SetVariableBlock,
|
SetVariableBlock,
|
||||||
WebhookBlock,
|
WebhookBlock,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { isInputBlock, isNotDefined, byId } from '@typebot.io/lib'
|
import { isInputBlock, byId } from '@typebot.io/lib'
|
||||||
import { executeGroup } from './executeGroup'
|
import { executeGroup } from './executeGroup'
|
||||||
import { getNextGroup } from './getNextGroup'
|
import { getNextGroup } from './getNextGroup'
|
||||||
import { validateEmail } from '@/features/blocks/inputs/email/validateEmail'
|
import { validateEmail } from '@/features/blocks/inputs/email/validateEmail'
|
||||||
@@ -28,6 +25,7 @@ import { parseVariables } from '@/features/variables/parseVariables'
|
|||||||
import { OpenAIBlock } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
import { OpenAIBlock } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
||||||
import { resumeChatCompletion } from '@/features/blocks/integrations/openai/resumeChatCompletion'
|
import { resumeChatCompletion } from '@/features/blocks/integrations/openai/resumeChatCompletion'
|
||||||
import { resumeWebhookExecution } from '@/features/blocks/integrations/webhook/resumeWebhookExecution'
|
import { resumeWebhookExecution } from '@/features/blocks/integrations/webhook/resumeWebhookExecution'
|
||||||
|
import { upsertAnswer } from '../queries/upsertAnswer'
|
||||||
|
|
||||||
export const continueBotFlow =
|
export const continueBotFlow =
|
||||||
(state: SessionState) =>
|
(state: SessionState) =>
|
||||||
@@ -60,13 +58,14 @@ export const continueBotFlow =
|
|||||||
...existingVariable,
|
...existingVariable,
|
||||||
value: reply,
|
value: reply,
|
||||||
}
|
}
|
||||||
newSessionState = await updateVariables(state)([newVariable])
|
newSessionState = updateVariables(state)([newVariable])
|
||||||
}
|
}
|
||||||
} else if (reply && block.type === IntegrationBlockType.WEBHOOK) {
|
} else if (reply && block.type === IntegrationBlockType.WEBHOOK) {
|
||||||
const result = await resumeWebhookExecution(
|
const result = resumeWebhookExecution({
|
||||||
state,
|
state,
|
||||||
block
|
block,
|
||||||
)(JSON.parse(reply))
|
response: JSON.parse(reply),
|
||||||
|
})
|
||||||
if (result.newSessionState) newSessionState = result.newSessionState
|
if (result.newSessionState) newSessionState = result.newSessionState
|
||||||
} else if (
|
} else if (
|
||||||
block.type === IntegrationBlockType.OPEN_AI &&
|
block.type === IntegrationBlockType.OPEN_AI &&
|
||||||
@@ -136,20 +135,20 @@ const processAndSaveAnswer =
|
|||||||
async (reply: string | null): Promise<SessionState> => {
|
async (reply: string | null): Promise<SessionState> => {
|
||||||
if (!reply) return state
|
if (!reply) return state
|
||||||
let newState = await saveAnswer(state, block, itemId)(reply)
|
let newState = await saveAnswer(state, block, itemId)(reply)
|
||||||
newState = await saveVariableValueIfAny(newState, block)(reply)
|
newState = saveVariableValueIfAny(newState, block)(reply)
|
||||||
return newState
|
return newState
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveVariableValueIfAny =
|
const saveVariableValueIfAny =
|
||||||
(state: SessionState, block: InputBlock) =>
|
(state: SessionState, block: InputBlock) =>
|
||||||
async (reply: string): Promise<SessionState> => {
|
(reply: string): SessionState => {
|
||||||
if (!block.options.variableId) return state
|
if (!block.options.variableId) return state
|
||||||
const foundVariable = state.typebot.variables.find(
|
const foundVariable = state.typebot.variables.find(
|
||||||
(variable) => variable.id === block.options.variableId
|
(variable) => variable.id === block.options.variableId
|
||||||
)
|
)
|
||||||
if (!foundVariable) return state
|
if (!foundVariable) return state
|
||||||
|
|
||||||
const newSessionState = await updateVariables(state)([
|
const newSessionState = updateVariables(state)([
|
||||||
{
|
{
|
||||||
...foundVariable,
|
...foundVariable,
|
||||||
value: Array.isArray(foundVariable.value)
|
value: Array.isArray(foundVariable.value)
|
||||||
@@ -161,13 +160,6 @@ const saveVariableValueIfAny =
|
|||||||
return newSessionState
|
return newSessionState
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setResultAsCompleted = async (resultId: string) => {
|
|
||||||
await prisma.result.updateMany({
|
|
||||||
where: { id: resultId },
|
|
||||||
data: { isCompleted: true },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseRetryMessage = (
|
const parseRetryMessage = (
|
||||||
block: InputBlock
|
block: InputBlock
|
||||||
): Pick<ChatReply, 'messages' | 'input'> => {
|
): Pick<ChatReply, 'messages' | 'input'> => {
|
||||||
@@ -192,55 +184,28 @@ const parseRetryMessage = (
|
|||||||
const saveAnswer =
|
const saveAnswer =
|
||||||
(state: SessionState, block: InputBlock, itemId?: string) =>
|
(state: SessionState, block: InputBlock, itemId?: string) =>
|
||||||
async (reply: string): Promise<SessionState> => {
|
async (reply: string): Promise<SessionState> => {
|
||||||
const resultId = state.result?.id
|
await upsertAnswer({
|
||||||
const answer: Omit<Prisma.AnswerUncheckedCreateInput, 'resultId'> = {
|
block,
|
||||||
blockId: block.id,
|
answer: {
|
||||||
|
blockId: block.id,
|
||||||
|
itemId,
|
||||||
|
groupId: block.groupId,
|
||||||
|
content: reply,
|
||||||
|
variableId: block.options.variableId,
|
||||||
|
storageUsed: 0,
|
||||||
|
},
|
||||||
|
reply,
|
||||||
|
state,
|
||||||
itemId,
|
itemId,
|
||||||
groupId: block.groupId,
|
})
|
||||||
content: reply,
|
|
||||||
variableId: block.options.variableId,
|
|
||||||
storageUsed: 0,
|
|
||||||
}
|
|
||||||
if (state.result.answers.length === 0 && resultId)
|
|
||||||
await setResultAsStarted(resultId)
|
|
||||||
|
|
||||||
const newSessionState = setNewAnswerInState(state)({
|
return setNewAnswerInState(state)({
|
||||||
blockId: block.id,
|
blockId: block.id,
|
||||||
variableId: block.options.variableId ?? null,
|
variableId: block.options.variableId ?? null,
|
||||||
content: reply,
|
content: reply,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (resultId) {
|
|
||||||
if (reply.includes('http') && block.type === InputBlockType.FILE) {
|
|
||||||
answer.storageUsed = await computeStorageUsed(reply)
|
|
||||||
}
|
|
||||||
await prisma.answer.upsert({
|
|
||||||
where: {
|
|
||||||
resultId_blockId_groupId: {
|
|
||||||
resultId,
|
|
||||||
blockId: block.id,
|
|
||||||
groupId: block.groupId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
create: { ...answer, resultId },
|
|
||||||
update: {
|
|
||||||
content: answer.content,
|
|
||||||
storageUsed: answer.storageUsed,
|
|
||||||
itemId: answer.itemId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return newSessionState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setResultAsStarted = async (resultId: string) => {
|
|
||||||
await prisma.result.updateMany({
|
|
||||||
where: { id: resultId },
|
|
||||||
data: { hasStarted: true },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const setNewAnswerInState =
|
const setNewAnswerInState =
|
||||||
(state: SessionState) => (newAnswer: ResultInSession['answers'][number]) => {
|
(state: SessionState) => (newAnswer: ResultInSession['answers'][number]) => {
|
||||||
const newAnswers = state.result.answers
|
const newAnswers = state.result.answers
|
||||||
@@ -256,21 +221,6 @@ const setNewAnswerInState =
|
|||||||
} satisfies SessionState
|
} satisfies SessionState
|
||||||
}
|
}
|
||||||
|
|
||||||
const computeStorageUsed = async (reply: string) => {
|
|
||||||
let storageUsed = 0
|
|
||||||
const fileUrls = reply.split(', ')
|
|
||||||
const hasReachedStorageLimit = fileUrls[0] === null
|
|
||||||
if (!hasReachedStorageLimit) {
|
|
||||||
for (const url of fileUrls) {
|
|
||||||
const { headers } = await got(url)
|
|
||||||
const size = headers['content-length']
|
|
||||||
if (isNotDefined(size)) continue
|
|
||||||
storageUsed += parseInt(size, 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return storageUsed
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOutgoingEdgeId =
|
const getOutgoingEdgeId =
|
||||||
({ typebot: { variables } }: Pick<SessionState, 'typebot'>) =>
|
({ typebot: { variables } }: Pick<SessionState, 'typebot'>) =>
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
export * from './continueBotFlow'
|
|
||||||
export * from './executeGroup'
|
|
||||||
export * from './getNextGroup'
|
|
||||||
export * from './getSessionState'
|
|
||||||
export * from './startBotFlow'
|
|
||||||
48
apps/viewer/src/features/chat/helpers/saveStateToDatabase.ts
Normal file
48
apps/viewer/src/features/chat/helpers/saveStateToDatabase.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { ChatReply, ChatSession } from '@typebot.io/schemas'
|
||||||
|
import { upsertResult } from '../queries/upsertResult'
|
||||||
|
import { saveLogs } from '../queries/saveLogs'
|
||||||
|
import { updateSession } from '../queries/updateSession'
|
||||||
|
import { formatLogDetails } from '@/features/logs/helpers/formatLogDetails'
|
||||||
|
import { createSession } from '../queries/createSession'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
session: Pick<ChatSession, 'state'> & { id?: string }
|
||||||
|
input: ChatReply['input']
|
||||||
|
logs: ChatReply['logs']
|
||||||
|
clientSideActions: ChatReply['clientSideActions']
|
||||||
|
}
|
||||||
|
export const saveStateToDatabase = async ({
|
||||||
|
session: { state, id },
|
||||||
|
input,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
}: Props) => {
|
||||||
|
if (id) await updateSession({ id, state })
|
||||||
|
|
||||||
|
const session = id ? { state, id } : await createSession({ state })
|
||||||
|
|
||||||
|
if (!state?.result?.id) return session
|
||||||
|
|
||||||
|
const containsSetVariableClientSideAction = clientSideActions?.some(
|
||||||
|
(action) => 'setVariable' in action
|
||||||
|
)
|
||||||
|
await upsertResult({
|
||||||
|
state,
|
||||||
|
isCompleted: Boolean(
|
||||||
|
!input &&
|
||||||
|
!containsSetVariableClientSideAction &&
|
||||||
|
state.result.answers.length > 0
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (logs && logs.length > 0)
|
||||||
|
await saveLogs(
|
||||||
|
logs.map((log) => ({
|
||||||
|
...log,
|
||||||
|
resultId: state.result.id as string,
|
||||||
|
details: formatLogDetails(log.details),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return session
|
||||||
|
}
|
||||||
13
apps/viewer/src/features/chat/queries/createSession.ts
Normal file
13
apps/viewer/src/features/chat/queries/createSession.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
import { SessionState } from '@typebot.io/schemas'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
state: SessionState
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createSession = async ({ state }: Props) =>
|
||||||
|
prisma.chatSession.create({
|
||||||
|
data: {
|
||||||
|
state,
|
||||||
|
},
|
||||||
|
})
|
||||||
34
apps/viewer/src/features/chat/queries/findPublicTypebot.ts
Normal file
34
apps/viewer/src/features/chat/queries/findPublicTypebot.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
publicId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const findPublicTypebot = ({ publicId }: Props) =>
|
||||||
|
prisma.publicTypebot.findFirst({
|
||||||
|
where: { typebot: { publicId } },
|
||||||
|
select: {
|
||||||
|
groups: true,
|
||||||
|
edges: true,
|
||||||
|
settings: true,
|
||||||
|
theme: true,
|
||||||
|
variables: true,
|
||||||
|
typebotId: true,
|
||||||
|
typebot: {
|
||||||
|
select: {
|
||||||
|
isArchived: true,
|
||||||
|
isClosed: true,
|
||||||
|
workspace: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
plan: true,
|
||||||
|
additionalChatsIndex: true,
|
||||||
|
customChatsLimit: true,
|
||||||
|
isQuarantined: true,
|
||||||
|
isSuspended: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
14
apps/viewer/src/features/chat/queries/findResult.ts
Normal file
14
apps/viewer/src/features/chat/queries/findResult.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
export const findResult = ({ id }: Props) =>
|
||||||
|
prisma.result.findFirst({
|
||||||
|
where: { id },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
variables: true,
|
||||||
|
answers: { select: { blockId: true, variableId: true, content: true } },
|
||||||
|
},
|
||||||
|
})
|
||||||
20
apps/viewer/src/features/chat/queries/findTypebot.ts
Normal file
20
apps/viewer/src/features/chat/queries/findTypebot.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id: string
|
||||||
|
userId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const findTypebot = ({ id, userId }: Props) =>
|
||||||
|
prisma.typebot.findFirst({
|
||||||
|
where: { id, workspace: { members: { some: { userId } } } },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
groups: true,
|
||||||
|
edges: true,
|
||||||
|
settings: true,
|
||||||
|
theme: true,
|
||||||
|
variables: true,
|
||||||
|
isArchived: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
5
apps/viewer/src/features/chat/queries/saveLogs.ts
Normal file
5
apps/viewer/src/features/chat/queries/saveLogs.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
import { Log } from '@typebot.io/schemas'
|
||||||
|
|
||||||
|
export const saveLogs = (logs: Omit<Log, 'id' | 'createdAt'>[]) =>
|
||||||
|
prisma.log.createMany({ data: logs })
|
||||||
15
apps/viewer/src/features/chat/queries/updateSession.ts
Normal file
15
apps/viewer/src/features/chat/queries/updateSession.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
import { SessionState } from '@typebot.io/schemas'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id: string
|
||||||
|
state: SessionState
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateSession = async ({ id, state }: Props) =>
|
||||||
|
prisma.chatSession.updateMany({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
state,
|
||||||
|
},
|
||||||
|
})
|
||||||
57
apps/viewer/src/features/chat/queries/upsertAnswer.ts
Normal file
57
apps/viewer/src/features/chat/queries/upsertAnswer.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
import { isNotDefined } from '@typebot.io/lib'
|
||||||
|
import { Prisma } from '@typebot.io/prisma'
|
||||||
|
import { InputBlock, InputBlockType, SessionState } from '@typebot.io/schemas'
|
||||||
|
import got from 'got'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
answer: Omit<Prisma.AnswerUncheckedCreateInput, 'resultId'>
|
||||||
|
block: InputBlock
|
||||||
|
reply: string
|
||||||
|
itemId?: string
|
||||||
|
state: SessionState
|
||||||
|
}
|
||||||
|
export const upsertAnswer = async ({ answer, reply, block, state }: Props) => {
|
||||||
|
if (!state.result?.id) return
|
||||||
|
if (reply.includes('http') && block.type === InputBlockType.FILE) {
|
||||||
|
answer.storageUsed = await computeStorageUsed(reply)
|
||||||
|
}
|
||||||
|
const where = {
|
||||||
|
resultId: state.result.id,
|
||||||
|
blockId: block.id,
|
||||||
|
groupId: block.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,
|
||||||
|
storageUsed: answer.storageUsed,
|
||||||
|
itemId: answer.itemId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return prisma.answer.createMany({
|
||||||
|
data: [{ ...answer, resultId: state.result.id }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const computeStorageUsed = async (reply: string) => {
|
||||||
|
let storageUsed = 0
|
||||||
|
const fileUrls = reply.split(', ')
|
||||||
|
const hasReachedStorageLimit = fileUrls[0] === null
|
||||||
|
if (!hasReachedStorageLimit) {
|
||||||
|
for (const url of fileUrls) {
|
||||||
|
const { headers } = await got(url)
|
||||||
|
const size = headers['content-length']
|
||||||
|
if (isNotDefined(size)) continue
|
||||||
|
storageUsed += parseInt(size, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return storageUsed
|
||||||
|
}
|
||||||
34
apps/viewer/src/features/chat/queries/upsertResult.ts
Normal file
34
apps/viewer/src/features/chat/queries/upsertResult.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
import { SessionState } from '@typebot.io/schemas'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
state: SessionState
|
||||||
|
isCompleted: boolean
|
||||||
|
}
|
||||||
|
export const upsertResult = async ({ state, isCompleted }: Props) => {
|
||||||
|
const existingResult = await prisma.result.findUnique({
|
||||||
|
where: { id: state.result.id },
|
||||||
|
select: { id: true },
|
||||||
|
})
|
||||||
|
if (existingResult) {
|
||||||
|
return prisma.result.updateMany({
|
||||||
|
where: { id: state.result.id },
|
||||||
|
data: {
|
||||||
|
isCompleted: isCompleted ? true : undefined,
|
||||||
|
hasStarted: state.result.answers.length > 0 ? true : undefined,
|
||||||
|
variables: state.result.variables,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return prisma.result.createMany({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: state.result.id,
|
||||||
|
typebotId: state.typebot.id,
|
||||||
|
isCompleted: isCompleted ? true : false,
|
||||||
|
hasStarted: state.result.answers.length > 0 ? true : undefined,
|
||||||
|
variables: state.result.variables,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
11
apps/viewer/src/features/logs/helpers/formatLogDetails.ts
Normal file
11
apps/viewer/src/features/logs/helpers/formatLogDetails.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { isNotDefined } from '@typebot.io/lib/utils'
|
||||||
|
|
||||||
|
export const formatLogDetails = (details: unknown): string | null => {
|
||||||
|
if (isNotDefined(details)) return null
|
||||||
|
if (details instanceof Error) return details.toString()
|
||||||
|
try {
|
||||||
|
return JSON.stringify(details, null, 2).substring(0, 1000)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { saveLog } from './saveLog'
|
|
||||||
|
|
||||||
export const saveInfoLog = ({
|
|
||||||
resultId,
|
|
||||||
message,
|
|
||||||
details,
|
|
||||||
}: {
|
|
||||||
resultId: string | undefined
|
|
||||||
message: string
|
|
||||||
details?: unknown
|
|
||||||
}) => saveLog({ status: 'info', resultId, message, details })
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { isNotDefined } from '@typebot.io/lib'
|
import { formatLogDetails } from './helpers/formatLogDetails'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
status: 'error' | 'success' | 'info'
|
status: 'error' | 'success' | 'info'
|
||||||
@@ -15,16 +15,7 @@ export const saveLog = ({ status, resultId, message, details }: Props) => {
|
|||||||
resultId,
|
resultId,
|
||||||
status,
|
status,
|
||||||
description: message,
|
description: message,
|
||||||
details: formatDetails(details) as string | null,
|
details: formatLogDetails(details) as string | null,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDetails = (details: unknown) => {
|
|
||||||
if (isNotDefined(details)) return null
|
|
||||||
try {
|
|
||||||
return JSON.stringify(details, null, 2).substring(0, 1000)
|
|
||||||
} catch {
|
|
||||||
return details
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import prisma from '@/lib/prisma'
|
|
||||||
import { isDefined } from '@typebot.io/lib'
|
import { isDefined } from '@typebot.io/lib'
|
||||||
import {
|
import {
|
||||||
SessionState,
|
SessionState,
|
||||||
@@ -10,7 +9,7 @@ import { safeStringify } from './safeStringify'
|
|||||||
|
|
||||||
export const updateVariables =
|
export const updateVariables =
|
||||||
(state: SessionState) =>
|
(state: SessionState) =>
|
||||||
async (newVariables: VariableWithUnknowValue[]): Promise<SessionState> => ({
|
(newVariables: VariableWithUnknowValue[]): SessionState => ({
|
||||||
...state,
|
...state,
|
||||||
typebot: {
|
typebot: {
|
||||||
...state.typebot,
|
...state.typebot,
|
||||||
@@ -18,15 +17,13 @@ export const updateVariables =
|
|||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
...state.result,
|
...state.result,
|
||||||
variables: await updateResultVariables(state)(newVariables),
|
variables: updateResultVariables(state)(newVariables),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateResultVariables =
|
const updateResultVariables =
|
||||||
({ result }: Pick<SessionState, 'result' | 'typebot'>) =>
|
({ result }: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||||
async (
|
(newVariables: VariableWithUnknowValue[]): VariableWithValue[] => {
|
||||||
newVariables: VariableWithUnknowValue[]
|
|
||||||
): Promise<VariableWithValue[]> => {
|
|
||||||
const serializedNewVariables = newVariables.map((variable) => ({
|
const serializedNewVariables = newVariables.map((variable) => ({
|
||||||
...variable,
|
...variable,
|
||||||
value: Array.isArray(variable.value)
|
value: Array.isArray(variable.value)
|
||||||
@@ -43,16 +40,6 @@ const updateResultVariables =
|
|||||||
...serializedNewVariables,
|
...serializedNewVariables,
|
||||||
].filter((variable) => isDefined(variable.value)) as VariableWithValue[]
|
].filter((variable) => isDefined(variable.value)) as VariableWithValue[]
|
||||||
|
|
||||||
if (result.id)
|
|
||||||
await prisma.result.updateMany({
|
|
||||||
where: {
|
|
||||||
id: result.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
variables: updatedVariables,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return updatedVariables
|
return updatedVariables
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user