🔥 Remove disable response saving option
Doesn't work properly when it comes to keep tracking storage usage
This commit is contained in:
@ -54,7 +54,7 @@ export const Graph = ({
|
||||
} = useGraph()
|
||||
const { updateGroupCoordinates } = useGroupsCoordinates()
|
||||
const [graphPosition, setGraphPosition] = useState(
|
||||
graphPositionDefaultValue(typebot.groups[0].graphCoordinates)
|
||||
graphPositionDefaultValue(typebot.groups[0]?.graphCoordinates)
|
||||
)
|
||||
const [debouncedGraphPosition] = useDebounce(graphPosition, 200)
|
||||
const transform = useMemo(
|
||||
|
@ -40,7 +40,7 @@ export const ItemNodesList = ({
|
||||
|
||||
const isLastBlock =
|
||||
isDefined(typebot) &&
|
||||
typebot.groups[groupIndex].blocks[blockIndex + 1] === undefined
|
||||
typebot.groups[groupIndex]?.blocks?.[blockIndex + 1] === undefined
|
||||
|
||||
const [position, setPosition] = useState({
|
||||
x: 0,
|
||||
|
@ -46,12 +46,6 @@ export const GeneralSettingsForm = ({
|
||||
isHideQueryParamsEnabled,
|
||||
})
|
||||
|
||||
const handleDisableResultsSavingChange = (isResultSavingEnabled: boolean) =>
|
||||
onGeneralSettingsChange({
|
||||
...generalSettings,
|
||||
isResultSavingEnabled: !isResultSavingEnabled,
|
||||
})
|
||||
|
||||
return (
|
||||
<Stack spacing={6}>
|
||||
<ChangePlanModal
|
||||
@ -96,16 +90,6 @@ export const GeneralSettingsForm = ({
|
||||
onCheckChange={handleHideQueryParamsChange}
|
||||
moreInfoContent="If your URL contains query params, they will be automatically hidden when the bot starts."
|
||||
/>
|
||||
<SwitchWithLabel
|
||||
label="Disable responses saving"
|
||||
initialValue={
|
||||
isDefined(generalSettings.isResultSavingEnabled)
|
||||
? !generalSettings.isResultSavingEnabled
|
||||
: false
|
||||
}
|
||||
onCheckChange={handleDisableResultsSavingChange}
|
||||
moreInfoContent="Prevent responses from being saved on Typebot. Chats limit usage will still be tracked."
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
@ -24,11 +24,6 @@ test.describe.parallel('Settings page', () => {
|
||||
page.locator('input[type="checkbox"] >> nth=-3')
|
||||
).toHaveAttribute('checked', '')
|
||||
|
||||
await page.click('text="Disable responses saving"')
|
||||
await expect(
|
||||
page.locator('input[type="checkbox"] >> nth=-1')
|
||||
).toHaveAttribute('checked', '')
|
||||
|
||||
await expect(page.getByPlaceholder('Type your answer...')).toHaveValue(
|
||||
'Baptiste'
|
||||
)
|
||||
|
@ -1839,6 +1839,12 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"company": {
|
||||
"type": "string"
|
||||
},
|
||||
"workspaceId": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -1867,9 +1873,27 @@
|
||||
},
|
||||
"additionalStorage": {
|
||||
"type": "number"
|
||||
},
|
||||
"vat": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"company",
|
||||
"workspaceId",
|
||||
"currency",
|
||||
"plan",
|
||||
|
@ -3009,9 +3009,6 @@
|
||||
},
|
||||
"isNewResultOnRefreshEnabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isResultSavingEnabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@ -4993,9 +4990,6 @@
|
||||
},
|
||||
"isNewResultOnRefreshEnabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isResultSavingEnabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -107,8 +107,6 @@ export const TypebotPageV2 = ({
|
||||
|
||||
const sendNewVariables =
|
||||
(resultId: string) => async (variables: VariableWithValue[]) => {
|
||||
if (publishedTypebot.settings.general.isResultSavingEnabled === false)
|
||||
return
|
||||
const { error } = await updateResultQuery(resultId, { variables })
|
||||
if (error) setError(error)
|
||||
}
|
||||
@ -117,10 +115,8 @@ export const TypebotPageV2 = ({
|
||||
answer: AnswerInput & { uploadedFiles: boolean }
|
||||
) => {
|
||||
if (!resultId) return setError(new Error('Error: result was not created'))
|
||||
if (publishedTypebot.settings.general.isResultSavingEnabled !== false) {
|
||||
const { error } = await upsertAnswerQuery({ ...answer, resultId })
|
||||
if (error) setError(error)
|
||||
}
|
||||
if (chatStarted) return
|
||||
updateResultQuery(resultId, {
|
||||
hasStarted: true,
|
||||
@ -128,8 +124,6 @@ export const TypebotPageV2 = ({
|
||||
}
|
||||
|
||||
const handleCompleted = async () => {
|
||||
if (publishedTypebot.settings.general.isResultSavingEnabled === false)
|
||||
return
|
||||
if (!resultId) return setError(new Error('Error: result was not created'))
|
||||
const { error } = await updateResultQuery(resultId, { isCompleted: true })
|
||||
if (error) setError(error)
|
||||
|
@ -11,17 +11,18 @@ import Stripe from 'stripe'
|
||||
import { decrypt } from 'utils/api/encryption'
|
||||
|
||||
export const computePaymentInputRuntimeOptions =
|
||||
(state: Pick<SessionState, 'isPreview' | 'typebot'>) =>
|
||||
(state: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||
(options: PaymentInputOptions) =>
|
||||
createStripePaymentIntent(state)(options)
|
||||
|
||||
const createStripePaymentIntent =
|
||||
(state: Pick<SessionState, 'isPreview' | 'typebot'>) =>
|
||||
(state: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||
async (options: PaymentInputOptions): Promise<PaymentInputRuntimeOptions> => {
|
||||
const {
|
||||
isPreview,
|
||||
result,
|
||||
typebot: { variables },
|
||||
} = state
|
||||
const isPreview = !result.id
|
||||
if (!options.credentialsId)
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
|
@ -50,10 +50,11 @@ if (window.$chatwoot) {
|
||||
}`
|
||||
|
||||
export const executeChatwootBlock = (
|
||||
{ typebot: { variables }, isPreview }: SessionState,
|
||||
{ typebot: { variables }, result }: SessionState,
|
||||
block: ChatwootBlock,
|
||||
lastBubbleBlockId?: string
|
||||
): ExecuteIntegrationResponse => {
|
||||
const isPreview = !result.id
|
||||
const chatwootCode = parseChatwootOpenCode(block.options)
|
||||
return {
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
|
@ -6,7 +6,7 @@ import { render } from '@faire/mjml-react/utils/render'
|
||||
import { DefaultBotNotificationEmail } from 'emails'
|
||||
import {
|
||||
PublicTypebot,
|
||||
ResultValues,
|
||||
ResultInSession,
|
||||
SendEmailBlock,
|
||||
SendEmailOptions,
|
||||
SessionState,
|
||||
@ -20,11 +20,12 @@ import { decrypt } from 'utils/api'
|
||||
import { defaultFrom, defaultTransportOptions } from '../constants'
|
||||
|
||||
export const executeSendEmailBlock = async (
|
||||
{ result, typebot, isPreview }: SessionState,
|
||||
{ result, typebot }: SessionState,
|
||||
block: SendEmailBlock
|
||||
): Promise<ExecuteIntegrationResponse> => {
|
||||
const { options } = block
|
||||
const { variables } = typebot
|
||||
const isPreview = !result.id
|
||||
if (isPreview)
|
||||
return {
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
@ -37,7 +38,7 @@ export const executeSendEmailBlock = async (
|
||||
}
|
||||
await sendEmail({
|
||||
typebotId: typebot.id,
|
||||
resultId: result?.id,
|
||||
result,
|
||||
credentialsId: options.credentialsId,
|
||||
recipients: options.recipients.map(parseVariables(variables)),
|
||||
subject: parseVariables(variables)(options.subject ?? ''),
|
||||
@ -57,7 +58,7 @@ export const executeSendEmailBlock = async (
|
||||
|
||||
const sendEmail = async ({
|
||||
typebotId,
|
||||
resultId,
|
||||
result,
|
||||
credentialsId,
|
||||
recipients,
|
||||
body,
|
||||
@ -70,7 +71,7 @@ const sendEmail = async ({
|
||||
fileUrls,
|
||||
}: SendEmailOptions & {
|
||||
typebotId: string
|
||||
resultId?: string
|
||||
result: ResultInSession
|
||||
fileUrls?: string | string[]
|
||||
}) => {
|
||||
const { name: replyToName } = parseEmailRecipient(replyTo)
|
||||
@ -94,12 +95,12 @@ const sendEmail = async ({
|
||||
isCustomBody,
|
||||
isBodyCode,
|
||||
typebotId,
|
||||
resultId,
|
||||
result,
|
||||
})
|
||||
|
||||
if (!emailBody) {
|
||||
await saveErrorLog({
|
||||
resultId,
|
||||
resultId: result.id,
|
||||
message: 'Email not sent',
|
||||
details: {
|
||||
error: 'No email body found',
|
||||
@ -132,7 +133,7 @@ const sendEmail = async ({
|
||||
try {
|
||||
await transporter.sendMail(email)
|
||||
await saveSuccessLog({
|
||||
resultId,
|
||||
resultId: result.id,
|
||||
message: 'Email successfully sent',
|
||||
details: {
|
||||
transportConfig: {
|
||||
@ -144,7 +145,7 @@ const sendEmail = async ({
|
||||
})
|
||||
} catch (err) {
|
||||
await saveErrorLog({
|
||||
resultId,
|
||||
resultId: result.id,
|
||||
message: 'Email not sent',
|
||||
details: {
|
||||
error: err,
|
||||
@ -182,10 +183,10 @@ const getEmailBody = async ({
|
||||
isCustomBody,
|
||||
isBodyCode,
|
||||
typebotId,
|
||||
resultId,
|
||||
result,
|
||||
}: {
|
||||
typebotId: string
|
||||
resultId?: string
|
||||
result: ResultInSession
|
||||
} & Pick<SendEmailOptions, 'isCustomBody' | 'isBodyCode' | 'body'>): Promise<
|
||||
{ html?: string; text?: string } | undefined
|
||||
> => {
|
||||
@ -198,12 +199,7 @@ const getEmailBody = async ({
|
||||
where: { typebotId },
|
||||
})) as unknown as PublicTypebot
|
||||
if (!typebot) return
|
||||
const resultValues = (await prisma.result.findUnique({
|
||||
where: { id: resultId },
|
||||
include: { answers: true },
|
||||
})) as ResultValues | null
|
||||
if (!resultValues) return
|
||||
const answers = parseAnswers(typebot, [])(resultValues)
|
||||
const answers = parseAnswers(typebot, [])(result)
|
||||
return {
|
||||
html: render(
|
||||
<DefaultBotNotificationEmail
|
||||
|
@ -16,16 +16,15 @@ import {
|
||||
WebhookOptions,
|
||||
defaultWebhookAttributes,
|
||||
HttpMethod,
|
||||
ResultValues,
|
||||
PublicTypebot,
|
||||
KeyValue,
|
||||
ReplyLog,
|
||||
ResultInSession,
|
||||
} from 'models'
|
||||
import { stringify } from 'qs'
|
||||
import { byId, omit } from 'utils'
|
||||
import { parseAnswers } from 'utils/results'
|
||||
import got, { Method, Headers, HTTPError } from 'got'
|
||||
import { getResultValues } from '@/features/results/api'
|
||||
import { parseSampleResult } from './parseSampleResult'
|
||||
|
||||
export const executeWebhookBlock = async (
|
||||
@ -50,14 +49,11 @@ export const executeWebhookBlock = async (
|
||||
return { outgoingEdgeId: block.outgoingEdgeId, logs: [log] }
|
||||
}
|
||||
const preparedWebhook = prepareWebhookAttributes(webhook, block.options)
|
||||
const resultValues =
|
||||
(result && (await getResultValues(result.id))) ?? undefined
|
||||
const webhookResponse = await executeWebhook({ typebot })(
|
||||
preparedWebhook,
|
||||
typebot.variables,
|
||||
block.groupId,
|
||||
resultValues,
|
||||
result?.id
|
||||
result
|
||||
)
|
||||
const status = webhookResponse.statusCode.toString()
|
||||
const isError = status.startsWith('4') || status.startsWith('5')
|
||||
@ -139,8 +135,7 @@ export const executeWebhook =
|
||||
webhook: Webhook,
|
||||
variables: Variable[],
|
||||
groupId: string,
|
||||
resultValues?: ResultValues,
|
||||
resultId?: string
|
||||
result: ResultInSession
|
||||
): Promise<WebhookResponse> => {
|
||||
if (!webhook.url || !webhook.method)
|
||||
return {
|
||||
@ -176,7 +171,7 @@ export const executeWebhook =
|
||||
[]
|
||||
)({
|
||||
body: webhook.body,
|
||||
resultValues,
|
||||
result,
|
||||
groupId,
|
||||
variables,
|
||||
})
|
||||
@ -206,7 +201,7 @@ export const executeWebhook =
|
||||
try {
|
||||
const response = await got(request.url, omit(request, 'url'))
|
||||
await saveSuccessLog({
|
||||
resultId,
|
||||
resultId: result.id,
|
||||
message: 'Webhook successfuly executed.',
|
||||
details: {
|
||||
statusCode: response.statusCode,
|
||||
@ -225,7 +220,7 @@ export const executeWebhook =
|
||||
data: safeJsonParse(error.response.body as string).data,
|
||||
}
|
||||
await saveErrorLog({
|
||||
resultId,
|
||||
resultId: result.id,
|
||||
message: 'Webhook returned an error',
|
||||
details: {
|
||||
request,
|
||||
@ -240,7 +235,7 @@ export const executeWebhook =
|
||||
}
|
||||
console.error(error)
|
||||
await saveErrorLog({
|
||||
resultId,
|
||||
resultId: result.id,
|
||||
message: 'Webhook failed to execute',
|
||||
details: {
|
||||
request,
|
||||
@ -258,20 +253,20 @@ const getBodyContent =
|
||||
) =>
|
||||
async ({
|
||||
body,
|
||||
resultValues,
|
||||
result,
|
||||
groupId,
|
||||
variables,
|
||||
}: {
|
||||
body?: string | null
|
||||
resultValues?: ResultValues
|
||||
result?: ResultInSession
|
||||
groupId: string
|
||||
variables: Variable[]
|
||||
}): Promise<string | undefined> => {
|
||||
if (!body) return
|
||||
return body === '{{state}}'
|
||||
? JSON.stringify(
|
||||
resultValues
|
||||
? parseAnswers(typebot, linkedTypebots)(resultValues)
|
||||
result
|
||||
? parseAnswers(typebot, linkedTypebots)(result)
|
||||
: await parseSampleResult(typebot, linkedTypebots)(
|
||||
groupId,
|
||||
variables
|
||||
|
@ -29,7 +29,7 @@ export const parseSampleResult =
|
||||
|
||||
return {
|
||||
message: 'This is a sample result, it has been generated ⬇️',
|
||||
'Submitted at': new Date().toISOString(),
|
||||
submittedAt: new Date().toISOString(),
|
||||
...parseResultSample(linkedInputBlocks, header, variables),
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +115,8 @@ const getLinkedTypebot = async (
|
||||
state: SessionState,
|
||||
typebotId: string
|
||||
): Promise<TypebotInSession | null> => {
|
||||
const { typebot, isPreview } = state
|
||||
const { typebot, result } = state
|
||||
const isPreview = !result.id
|
||||
if (typebotId === 'current') return typebot
|
||||
const availableTypebots =
|
||||
'linkedTypebots' in state
|
||||
@ -123,12 +124,12 @@ const getLinkedTypebot = async (
|
||||
: [typebot]
|
||||
const linkedTypebot =
|
||||
availableTypebots.find(byId(typebotId)) ??
|
||||
(await fetchTypebot({ isPreview }, typebotId))
|
||||
(await fetchTypebot(isPreview, typebotId))
|
||||
return linkedTypebot
|
||||
}
|
||||
|
||||
const fetchTypebot = async (
|
||||
{ isPreview }: Pick<SessionState, 'isPreview'>,
|
||||
isPreview: boolean,
|
||||
typebotId: string
|
||||
): Promise<TypebotInSession | null> => {
|
||||
if (isPreview) {
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
ChatReply,
|
||||
chatReplySchema,
|
||||
ChatSession,
|
||||
Result,
|
||||
ResultInSession,
|
||||
sendMessageInputSchema,
|
||||
SessionState,
|
||||
StartParams,
|
||||
@ -85,7 +85,11 @@ export const sendMessageProcedure = publicProcedure
|
||||
},
|
||||
})
|
||||
|
||||
if (!input && session.state.result?.hasStarted)
|
||||
if (
|
||||
!input &&
|
||||
session.state.result.answers.length > 0 &&
|
||||
session.state.result.id
|
||||
)
|
||||
await setResultAsCompleted(session.state.result.id)
|
||||
|
||||
return {
|
||||
@ -106,9 +110,6 @@ const startSession = async (startParams?: StartParams, userId?: string) => {
|
||||
message: 'No typebot provided in startParams',
|
||||
})
|
||||
|
||||
const isPreview =
|
||||
startParams?.isPreview || typeof startParams?.typebot !== 'string'
|
||||
|
||||
const typebot = await getTypebot(startParams, userId)
|
||||
|
||||
const prefilledVariables = startParams.prefilledVariables
|
||||
@ -117,8 +118,8 @@ const startSession = async (startParams?: StartParams, userId?: string) => {
|
||||
|
||||
const result = await getResult({
|
||||
...startParams,
|
||||
isPreview,
|
||||
typebot: typebot.id,
|
||||
isPreview: startParams.isPreview || typeof startParams.typebot !== 'string',
|
||||
typebotId: typebot.id,
|
||||
prefilledVariables,
|
||||
isNewResultOnRefreshEnabled:
|
||||
typebot.settings.general.isNewResultOnRefreshEnabled ?? false,
|
||||
@ -140,10 +141,11 @@ const startSession = async (startParams?: StartParams, userId?: string) => {
|
||||
typebots: [],
|
||||
queue: [],
|
||||
},
|
||||
result: result
|
||||
? { id: result.id, variables: result.variables, hasStarted: false }
|
||||
: undefined,
|
||||
isPreview,
|
||||
result: {
|
||||
id: result?.id,
|
||||
variables: result?.variables ?? [],
|
||||
answers: result?.answers ?? [],
|
||||
},
|
||||
currentTypebotId: typebot.id,
|
||||
dynamicTheme: parseDynamicThemeInState(typebot.theme),
|
||||
}
|
||||
@ -297,20 +299,21 @@ const getTypebot = async (
|
||||
}
|
||||
|
||||
const getResult = async ({
|
||||
typebot,
|
||||
typebotId,
|
||||
isPreview,
|
||||
resultId,
|
||||
prefilledVariables,
|
||||
isNewResultOnRefreshEnabled,
|
||||
}: Pick<StartParams, 'isPreview' | 'resultId' | 'typebot'> & {
|
||||
}: Pick<StartParams, 'isPreview' | 'resultId'> & {
|
||||
typebotId: string
|
||||
prefilledVariables: Variable[]
|
||||
isNewResultOnRefreshEnabled: boolean
|
||||
}) => {
|
||||
if (isPreview || typeof typebot !== 'string') return
|
||||
if (isPreview) return
|
||||
const select = {
|
||||
id: true,
|
||||
variables: true,
|
||||
hasStarted: true,
|
||||
answers: { select: { blockId: true, variableId: true, content: true } },
|
||||
} satisfies Prisma.ResultSelect
|
||||
|
||||
const existingResult =
|
||||
@ -318,7 +321,7 @@ const getResult = async ({
|
||||
? ((await prisma.result.findFirst({
|
||||
where: { id: resultId },
|
||||
select,
|
||||
})) as Pick<Result, 'id' | 'variables' | 'hasStarted'>)
|
||||
})) as ResultInSession)
|
||||
: undefined
|
||||
|
||||
if (existingResult) {
|
||||
@ -344,19 +347,19 @@ const getResult = async ({
|
||||
return {
|
||||
id: existingResult.id,
|
||||
variables: updatedResult.variables,
|
||||
hasStarted: existingResult.hasStarted,
|
||||
answers: existingResult.answers,
|
||||
}
|
||||
} else {
|
||||
return (await prisma.result.create({
|
||||
data: {
|
||||
isCompleted: false,
|
||||
typebotId: typebot,
|
||||
typebotId,
|
||||
variables: prefilledVariables.filter((variable) =>
|
||||
isDefined(variable.value)
|
||||
),
|
||||
},
|
||||
select,
|
||||
})) as Pick<Result, 'id' | 'variables' | 'hasStarted'>
|
||||
})) as ResultInSession
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { validateUrl } from '@/features/blocks/inputs/url/api'
|
||||
import { parseVariables, updateVariables } from '@/features/variables'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Prisma } from 'db'
|
||||
import got from 'got'
|
||||
import {
|
||||
Block,
|
||||
@ -15,6 +16,7 @@ import {
|
||||
ChatReply,
|
||||
InputBlock,
|
||||
InputBlockType,
|
||||
ResultInSession,
|
||||
SessionState,
|
||||
} from 'models'
|
||||
import { isInputBlock, isNotDefined } from 'utils'
|
||||
@ -86,17 +88,9 @@ const processAndSaveAnswer =
|
||||
(state: SessionState, block: InputBlock) =>
|
||||
async (reply: string | null): Promise<SessionState> => {
|
||||
if (!reply) return state
|
||||
if (!state.isPreview && state.result) {
|
||||
await saveAnswer(state.result.id, block)(reply)
|
||||
if (!state.result.hasStarted) await setResultAsStarted(state.result.id)
|
||||
}
|
||||
const newState = await saveVariableValueIfAny(state, block)(reply)
|
||||
return {
|
||||
...newState,
|
||||
result: newState.result
|
||||
? { ...newState.result, hasStarted: true }
|
||||
: undefined,
|
||||
}
|
||||
let newState = await saveAnswer(state, block)(reply)
|
||||
newState = await saveVariableValueIfAny(newState, block)(reply)
|
||||
return newState
|
||||
}
|
||||
|
||||
const saveVariableValueIfAny =
|
||||
@ -115,13 +109,6 @@ const saveVariableValueIfAny =
|
||||
return newSessionState
|
||||
}
|
||||
|
||||
const setResultAsStarted = async (resultId: string) => {
|
||||
await prisma.result.update({
|
||||
where: { id: resultId },
|
||||
data: { hasStarted: true },
|
||||
})
|
||||
}
|
||||
|
||||
export const setResultAsCompleted = async (resultId: string) => {
|
||||
await prisma.result.update({
|
||||
where: { id: resultId },
|
||||
@ -152,20 +139,31 @@ const parseRetryMessage = (
|
||||
}
|
||||
|
||||
const saveAnswer =
|
||||
(resultId: string, block: InputBlock) => async (reply: string) => {
|
||||
(state: SessionState, block: InputBlock) =>
|
||||
async (reply: string): Promise<SessionState> => {
|
||||
const resultId = state.result?.id
|
||||
const answer = {
|
||||
resultId: resultId,
|
||||
resultId,
|
||||
blockId: block.id,
|
||||
groupId: block.groupId,
|
||||
content: reply,
|
||||
variableId: block.options.variableId,
|
||||
storageUsed: 0,
|
||||
}
|
||||
if (state.result.answers.length === 0 && state.result.id)
|
||||
await setResultAsStarted(state.result.id)
|
||||
|
||||
const newSessionState = setNewAnswerInState(state)({
|
||||
blockId: block.id,
|
||||
variableId: block.options.variableId ?? null,
|
||||
content: reply,
|
||||
})
|
||||
|
||||
if (reply.includes('http') && block.type === InputBlockType.FILE) {
|
||||
answer.storageUsed = await computeStorageUsed(reply)
|
||||
}
|
||||
|
||||
if (resultId)
|
||||
await prisma.answer.upsert({
|
||||
where: {
|
||||
resultId_blockId_groupId: {
|
||||
@ -174,9 +172,32 @@ const saveAnswer =
|
||||
blockId: block.id,
|
||||
},
|
||||
},
|
||||
create: answer,
|
||||
create: answer as Prisma.AnswerUncheckedCreateInput,
|
||||
update: answer,
|
||||
})
|
||||
return newSessionState
|
||||
}
|
||||
|
||||
const setResultAsStarted = async (resultId: string) => {
|
||||
await prisma.result.update({
|
||||
where: { id: resultId },
|
||||
data: { hasStarted: true },
|
||||
})
|
||||
}
|
||||
|
||||
const setNewAnswerInState =
|
||||
(state: SessionState) => (newAnswer: ResultInSession['answers'][number]) => {
|
||||
const newAnswers = state.result.answers
|
||||
.filter((answer) => answer.blockId !== newAnswer.blockId)
|
||||
.concat(newAnswer)
|
||||
|
||||
return {
|
||||
...state,
|
||||
result: {
|
||||
...state.result,
|
||||
answers: newAnswers,
|
||||
},
|
||||
} satisfies SessionState
|
||||
}
|
||||
|
||||
const computeStorageUsed = async (reply: string) => {
|
||||
|
@ -113,7 +113,7 @@ export const executeGroup =
|
||||
}
|
||||
|
||||
const computeRuntimeOptions =
|
||||
(state: Pick<SessionState, 'isPreview' | 'typebot'>) =>
|
||||
(state: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||
(block: InputBlock): Promise<RuntimeOptions> | undefined => {
|
||||
switch (block.type) {
|
||||
case InputBlockType.PAYMENT: {
|
||||
@ -158,7 +158,7 @@ const parseBubbleBlock =
|
||||
}
|
||||
|
||||
const injectVariablesValueInBlock =
|
||||
(state: Pick<SessionState, 'isPreview' | 'typebot'>) =>
|
||||
(state: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||
async (block: InputBlock): Promise<ChatReply['input']> => {
|
||||
switch (block.type) {
|
||||
case InputBlockType.CHOICE: {
|
||||
|
@ -1 +0,0 @@
|
||||
export * from './utils'
|
@ -1,8 +0,0 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { ResultValues } from 'models'
|
||||
|
||||
export const getResultValues = async (resultId: string) =>
|
||||
(await prisma.result.findUnique({
|
||||
where: { id: resultId },
|
||||
include: { answers: true },
|
||||
})) as ResultValues | null
|
@ -1 +0,0 @@
|
||||
export * from './getResultValues'
|
@ -157,12 +157,10 @@ export const updateVariables =
|
||||
...state.typebot,
|
||||
variables: updateTypebotVariables(state)(newVariables),
|
||||
},
|
||||
result: state.result
|
||||
? {
|
||||
result: {
|
||||
...state.result,
|
||||
variables: await updateResultVariables(state)(newVariables),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
})
|
||||
|
||||
const updateResultVariables =
|
||||
@ -170,7 +168,6 @@ const updateResultVariables =
|
||||
async (
|
||||
newVariables: VariableWithUnknowValue[]
|
||||
): Promise<VariableWithValue[]> => {
|
||||
if (!result) return []
|
||||
const serializedNewVariables = newVariables.map((variable) => ({
|
||||
...variable,
|
||||
value: Array.isArray(variable.value)
|
||||
@ -187,6 +184,7 @@ const updateResultVariables =
|
||||
...serializedNewVariables,
|
||||
].filter((variable) => isDefined(variable.value)) as VariableWithValue[]
|
||||
|
||||
if (result.id)
|
||||
await prisma.result.update({
|
||||
where: {
|
||||
id: result.id,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { safeStringify } from '@/features/variables'
|
||||
import {
|
||||
AnswerInput,
|
||||
ResultValues,
|
||||
ResultValuesInput,
|
||||
Variable,
|
||||
VariableWithUnknowValue,
|
||||
VariableWithValue,
|
||||
@ -11,7 +11,7 @@ import { isDefined } from 'utils'
|
||||
|
||||
const answersContext = createContext<{
|
||||
resultId?: string
|
||||
resultValues: ResultValues
|
||||
resultValues: ResultValuesInput
|
||||
addAnswer: (
|
||||
existingVariables: Variable[]
|
||||
) => (
|
||||
@ -35,7 +35,7 @@ export const AnswersProvider = ({
|
||||
onVariablesUpdated?: (variables: VariableWithValue[]) => void
|
||||
children: ReactNode
|
||||
}) => {
|
||||
const [resultValues, setResultValues] = useState<ResultValues>({
|
||||
const [resultValues, setResultValues] = useState<ResultValuesInput>({
|
||||
answers: [],
|
||||
variables: [],
|
||||
createdAt: new Date(),
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
Edge,
|
||||
Group,
|
||||
PublicTypebot,
|
||||
ResultValues,
|
||||
ResultValuesInput,
|
||||
Typebot,
|
||||
Variable,
|
||||
VariableWithUnknowValue,
|
||||
@ -45,7 +45,7 @@ export type IntegrationState = {
|
||||
blockId: string
|
||||
isPreview: boolean
|
||||
variables: Variable[]
|
||||
resultValues: ResultValues
|
||||
resultValues: ResultValuesInput
|
||||
groups: Group[]
|
||||
resultId?: string
|
||||
parentTypebotIds: string[]
|
||||
|
@ -6,8 +6,6 @@ import {
|
||||
redirectOptionsSchema,
|
||||
} from './blocks'
|
||||
import { publicTypebotSchema } from './publicTypebot'
|
||||
import { ChatSession as ChatSessionPrisma } from 'db'
|
||||
import { schemaForType } from './utils'
|
||||
import { logSchema, resultSchema } from './result'
|
||||
import { typebotSchema } from './typebot'
|
||||
import {
|
||||
@ -18,6 +16,7 @@ import {
|
||||
audioBubbleContentSchema,
|
||||
embedBubbleContentSchema,
|
||||
} from './blocks/bubbles'
|
||||
import { answerSchema } from './answer'
|
||||
|
||||
const typebotInSessionStateSchema = publicTypebotSchema.pick({
|
||||
id: true,
|
||||
@ -31,6 +30,23 @@ const dynamicThemeSchema = z.object({
|
||||
guestAvatarUrl: z.string().optional(),
|
||||
})
|
||||
|
||||
const answerInSessionStateSchema = answerSchema.pick({
|
||||
content: true,
|
||||
blockId: true,
|
||||
variableId: true,
|
||||
})
|
||||
|
||||
const resultInSessionStateSchema = resultSchema
|
||||
.pick({
|
||||
variables: true,
|
||||
})
|
||||
.and(
|
||||
z.object({
|
||||
answers: z.array(answerInSessionStateSchema),
|
||||
id: z.string().optional(),
|
||||
})
|
||||
)
|
||||
|
||||
export const sessionStateSchema = z.object({
|
||||
typebot: typebotInSessionStateSchema,
|
||||
dynamicTheme: dynamicThemeSchema.optional(),
|
||||
@ -39,10 +55,7 @@ export const sessionStateSchema = z.object({
|
||||
queue: z.array(z.object({ edgeId: z.string(), typebotId: z.string() })),
|
||||
}),
|
||||
currentTypebotId: z.string(),
|
||||
result: resultSchema
|
||||
.pick({ id: true, variables: true, hasStarted: true })
|
||||
.optional(),
|
||||
isPreview: z.boolean(),
|
||||
result: resultInSessionStateSchema,
|
||||
currentBlock: z
|
||||
.object({
|
||||
blockId: z.string(),
|
||||
@ -51,14 +64,12 @@ export const sessionStateSchema = z.object({
|
||||
.optional(),
|
||||
})
|
||||
|
||||
const chatSessionSchema = schemaForType<ChatSessionPrisma>()(
|
||||
z.object({
|
||||
const chatSessionSchema = z.object({
|
||||
id: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
state: sessionStateSchema,
|
||||
})
|
||||
)
|
||||
|
||||
const textMessageSchema = z.object({
|
||||
type: z.enum([BubbleBlockType.TEXT]),
|
||||
@ -234,6 +245,7 @@ export const chatReplySchema = z.object({
|
||||
export type ChatSession = z.infer<typeof chatSessionSchema>
|
||||
export type SessionState = z.infer<typeof sessionStateSchema>
|
||||
export type TypebotInSession = z.infer<typeof typebotInSessionStateSchema>
|
||||
export type ResultInSession = z.infer<typeof resultInSessionStateSchema>
|
||||
export type ChatReply = z.infer<typeof chatReplySchema>
|
||||
export type ChatMessage = z.infer<typeof chatMessageSchema>
|
||||
export type SendMessageInput = z.infer<typeof sendMessageInputSchema>
|
||||
|
@ -48,11 +48,16 @@ export type ResultWithAnswersInput = z.infer<
|
||||
>
|
||||
export type Log = z.infer<typeof logSchema>
|
||||
|
||||
export type ResultValues = Pick<
|
||||
export type ResultValuesInput = Pick<
|
||||
ResultWithAnswersInput,
|
||||
'answers' | 'createdAt' | 'variables'
|
||||
>
|
||||
|
||||
export type ResultValues = Pick<
|
||||
ResultWithAnswers,
|
||||
'answers' | 'createdAt' | 'variables'
|
||||
>
|
||||
|
||||
export type ResultHeaderCell = {
|
||||
id: string
|
||||
label: string
|
||||
|
@ -6,7 +6,6 @@ const generalSettings = z.object({
|
||||
isInputPrefillEnabled: z.boolean().optional(),
|
||||
isHideQueryParamsEnabled: z.boolean().optional(),
|
||||
isNewResultOnRefreshEnabled: z.boolean().optional(),
|
||||
isResultSavingEnabled: z.boolean().optional(),
|
||||
})
|
||||
|
||||
const typingEmulation = z.object({
|
||||
@ -36,7 +35,6 @@ export const defaultSettings: Settings = {
|
||||
isNewResultOnRefreshEnabled: true,
|
||||
isInputPrefillEnabled: true,
|
||||
isHideQueryParamsEnabled: true,
|
||||
isResultSavingEnabled: true,
|
||||
},
|
||||
typingEmulation: { enabled: true, speed: 300, maxDelay: 1.5 },
|
||||
metadata: {
|
||||
|
@ -6,9 +6,9 @@ import {
|
||||
Answer,
|
||||
VariableWithValue,
|
||||
Typebot,
|
||||
ResultWithAnswersInput,
|
||||
ResultWithAnswers,
|
||||
InputBlockType,
|
||||
ResultInSession,
|
||||
} from 'models'
|
||||
import { isInputBlock, isDefined, byId, isNotEmpty } from './utils'
|
||||
|
||||
@ -218,16 +218,16 @@ export const parseAnswers =
|
||||
createdAt,
|
||||
answers,
|
||||
variables: resultVariables,
|
||||
}: Pick<ResultWithAnswersInput, 'answers' | 'variables'> & {
|
||||
// TODO: remove once we are using 100% tRPC
|
||||
createdAt: Date | string
|
||||
}): {
|
||||
}: Omit<ResultInSession, 'hasStarted'> & { createdAt?: Date | string }): {
|
||||
[key: string]: string
|
||||
} => {
|
||||
const header = parseResultHeader(typebot, linkedTypebots)
|
||||
return {
|
||||
submittedAt:
|
||||
typeof createdAt === 'string' ? createdAt : createdAt.toISOString(),
|
||||
submittedAt: !createdAt
|
||||
? new Date().toISOString()
|
||||
: typeof createdAt === 'string'
|
||||
? createdAt
|
||||
: createdAt.toISOString(),
|
||||
...[...answers, ...resultVariables].reduce<{
|
||||
[key: string]: string
|
||||
}>((o, answerOrVariable) => {
|
||||
|
Reference in New Issue
Block a user