♻️ (viewer) Remove barrel exports and flatten folder arch
This commit is contained in:
19
apps/viewer/src/features/chat/helpers/addEdgeToTypebot.ts
Normal file
19
apps/viewer/src/features/chat/helpers/addEdgeToTypebot.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { SessionState, Edge } from '@typebot.io/schemas'
|
||||
|
||||
export const addEdgeToTypebot = (
|
||||
state: SessionState,
|
||||
edge: Edge
|
||||
): SessionState => ({
|
||||
...state,
|
||||
typebot: {
|
||||
...state.typebot,
|
||||
edges: [...state.typebot.edges, edge],
|
||||
},
|
||||
})
|
||||
|
||||
export const createPortalEdge = ({ to }: Pick<Edge, 'to'>) => ({
|
||||
id: createId(),
|
||||
from: { blockId: '', groupId: '' },
|
||||
to,
|
||||
})
|
||||
264
apps/viewer/src/features/chat/helpers/continueBotFlow.ts
Normal file
264
apps/viewer/src/features/chat/helpers/continueBotFlow.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Prisma } from '@typebot.io/prisma'
|
||||
import got from 'got'
|
||||
import {
|
||||
Block,
|
||||
BlockType,
|
||||
BubbleBlockType,
|
||||
ChatReply,
|
||||
InputBlock,
|
||||
InputBlockType,
|
||||
ResultInSession,
|
||||
SessionState,
|
||||
} from '@typebot.io/schemas'
|
||||
import { isInputBlock, isNotDefined } from '@typebot.io/lib'
|
||||
import { executeGroup } from './executeGroup'
|
||||
import { getNextGroup } from './getNextGroup'
|
||||
import { validateEmail } from '@/features/blocks/inputs/email/validateEmail'
|
||||
import { formatPhoneNumber } from '@/features/blocks/inputs/phone/formatPhoneNumber'
|
||||
import { validatePhoneNumber } from '@/features/blocks/inputs/phone/validatePhoneNumber'
|
||||
import { validateUrl } from '@/features/blocks/inputs/url/validateUrl'
|
||||
import { updateVariables } from '@/features/variables/updateVariables'
|
||||
import { parseVariables } from '@/features/variables/parseVariables'
|
||||
|
||||
export const continueBotFlow =
|
||||
(state: SessionState) =>
|
||||
async (
|
||||
reply?: string
|
||||
): Promise<ChatReply & { newSessionState?: SessionState }> => {
|
||||
const group = state.typebot.groups.find(
|
||||
(group) => group.id === state.currentBlock?.groupId
|
||||
)
|
||||
const blockIndex =
|
||||
group?.blocks.findIndex(
|
||||
(block) => block.id === state.currentBlock?.blockId
|
||||
) ?? -1
|
||||
|
||||
const block = blockIndex >= 0 ? group?.blocks[blockIndex ?? 0] : null
|
||||
|
||||
if (!block || !group)
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: 'Current block not found',
|
||||
})
|
||||
|
||||
if (!isInputBlock(block))
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: 'Current block is not an input block',
|
||||
})
|
||||
|
||||
if (reply && !isReplyValid(reply, block)) return parseRetryMessage(block)
|
||||
|
||||
const formattedReply = formatReply(reply, block.type)
|
||||
|
||||
if (!formattedReply && !canSkip(block.type)) {
|
||||
return parseRetryMessage(block)
|
||||
}
|
||||
|
||||
const newSessionState = await processAndSaveAnswer(
|
||||
state,
|
||||
block
|
||||
)(formattedReply)
|
||||
|
||||
const groupHasMoreBlocks = blockIndex < group.blocks.length - 1
|
||||
|
||||
const nextEdgeId = getOutgoingEdgeId(newSessionState)(block, formattedReply)
|
||||
|
||||
if (groupHasMoreBlocks && !nextEdgeId) {
|
||||
return executeGroup(newSessionState)({
|
||||
...group,
|
||||
blocks: group.blocks.slice(blockIndex + 1),
|
||||
})
|
||||
}
|
||||
|
||||
if (!nextEdgeId && state.linkedTypebots.queue.length === 0)
|
||||
return { messages: [] }
|
||||
|
||||
const nextGroup = getNextGroup(newSessionState)(nextEdgeId)
|
||||
|
||||
if (!nextGroup) return { messages: [] }
|
||||
|
||||
return executeGroup(newSessionState)(nextGroup.group)
|
||||
}
|
||||
|
||||
const processAndSaveAnswer =
|
||||
(state: SessionState, block: InputBlock) =>
|
||||
async (reply: string | null): Promise<SessionState> => {
|
||||
if (!reply) return state
|
||||
let newState = await saveAnswer(state, block)(reply)
|
||||
newState = await saveVariableValueIfAny(newState, block)(reply)
|
||||
return newState
|
||||
}
|
||||
|
||||
const saveVariableValueIfAny =
|
||||
(state: SessionState, block: InputBlock) =>
|
||||
async (reply: string): Promise<SessionState> => {
|
||||
if (!block.options.variableId) return state
|
||||
const foundVariable = state.typebot.variables.find(
|
||||
(variable) => variable.id === block.options.variableId
|
||||
)
|
||||
if (!foundVariable) return state
|
||||
|
||||
const newSessionState = await updateVariables(state)([
|
||||
{
|
||||
...foundVariable,
|
||||
value: Array.isArray(foundVariable.value)
|
||||
? foundVariable.value.concat(reply)
|
||||
: reply,
|
||||
},
|
||||
])
|
||||
|
||||
return newSessionState
|
||||
}
|
||||
|
||||
export const setResultAsCompleted = async (resultId: string) => {
|
||||
await prisma.result.update({
|
||||
where: { id: resultId },
|
||||
data: { isCompleted: true },
|
||||
})
|
||||
}
|
||||
|
||||
const parseRetryMessage = (
|
||||
block: InputBlock
|
||||
): Pick<ChatReply, 'messages' | 'input'> => {
|
||||
const retryMessage =
|
||||
'retryMessageContent' in block.options && block.options.retryMessageContent
|
||||
? block.options.retryMessageContent
|
||||
: 'Invalid message. Please, try again.'
|
||||
return {
|
||||
messages: [
|
||||
{
|
||||
id: block.id,
|
||||
type: BubbleBlockType.TEXT,
|
||||
content: {
|
||||
plainText: retryMessage,
|
||||
html: `<div>${retryMessage}</div>`,
|
||||
},
|
||||
},
|
||||
],
|
||||
input: block,
|
||||
}
|
||||
}
|
||||
|
||||
const saveAnswer =
|
||||
(state: SessionState, block: InputBlock) =>
|
||||
async (reply: string): Promise<SessionState> => {
|
||||
const resultId = state.result?.id
|
||||
const answer = {
|
||||
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 (resultId) {
|
||||
if (reply.includes('http') && block.type === InputBlockType.FILE) {
|
||||
answer.storageUsed = await computeStorageUsed(reply)
|
||||
}
|
||||
await prisma.answer.upsert({
|
||||
where: {
|
||||
resultId_blockId_groupId: {
|
||||
resultId,
|
||||
groupId: block.groupId,
|
||||
blockId: block.id,
|
||||
},
|
||||
},
|
||||
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) => {
|
||||
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 =
|
||||
({ typebot: { variables } }: Pick<SessionState, 'typebot'>) =>
|
||||
(block: InputBlock, reply: string | null) => {
|
||||
if (
|
||||
block.type === InputBlockType.CHOICE &&
|
||||
!block.options.isMultipleChoice &&
|
||||
reply
|
||||
) {
|
||||
const matchedItem = block.items.find(
|
||||
(item) => parseVariables(variables)(item.content) === reply
|
||||
)
|
||||
if (matchedItem?.outgoingEdgeId) return matchedItem.outgoingEdgeId
|
||||
}
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
export const formatReply = (
|
||||
inputValue: string | undefined,
|
||||
blockType: BlockType
|
||||
): string | null => {
|
||||
if (!inputValue) return null
|
||||
switch (blockType) {
|
||||
case InputBlockType.PHONE:
|
||||
return formatPhoneNumber(inputValue)
|
||||
}
|
||||
return inputValue
|
||||
}
|
||||
|
||||
export const isReplyValid = (inputValue: string, block: Block): boolean => {
|
||||
switch (block.type) {
|
||||
case InputBlockType.EMAIL:
|
||||
return validateEmail(inputValue)
|
||||
case InputBlockType.PHONE:
|
||||
return validatePhoneNumber(inputValue)
|
||||
case InputBlockType.URL:
|
||||
return validateUrl(inputValue)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const canSkip = (inputType: InputBlockType) =>
|
||||
inputType === InputBlockType.FILE
|
||||
176
apps/viewer/src/features/chat/helpers/executeGroup.ts
Normal file
176
apps/viewer/src/features/chat/helpers/executeGroup.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import {
|
||||
BubbleBlock,
|
||||
BubbleBlockType,
|
||||
ChatReply,
|
||||
Group,
|
||||
InputBlock,
|
||||
InputBlockType,
|
||||
RuntimeOptions,
|
||||
SessionState,
|
||||
} from '@typebot.io/schemas'
|
||||
import {
|
||||
isBubbleBlock,
|
||||
isDefined,
|
||||
isInputBlock,
|
||||
isIntegrationBlock,
|
||||
isLogicBlock,
|
||||
} from '@typebot.io/lib'
|
||||
import { executeLogic } from './executeLogic'
|
||||
import { getNextGroup } from './getNextGroup'
|
||||
import { executeIntegration } from './executeIntegration'
|
||||
import { injectVariableValuesInButtonsInputBlock } from '@/features/blocks/inputs/buttons/injectVariableValuesInButtonsInputBlock'
|
||||
import { deepParseVariables } from '@/features/variables/deepParseVariable'
|
||||
import { computePaymentInputRuntimeOptions } from '@/features/blocks/inputs/payment/computePaymentInputRuntimeOptions'
|
||||
|
||||
export const executeGroup =
|
||||
(
|
||||
state: SessionState,
|
||||
currentReply?: ChatReply,
|
||||
currentLastBubbleId?: string
|
||||
) =>
|
||||
async (
|
||||
group: Group
|
||||
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
||||
const messages: ChatReply['messages'] = currentReply?.messages ?? []
|
||||
let clientSideActions: ChatReply['clientSideActions'] =
|
||||
currentReply?.clientSideActions
|
||||
let logs: ChatReply['logs'] = currentReply?.logs
|
||||
let nextEdgeId = null
|
||||
let lastBubbleBlockId: string | undefined = currentLastBubbleId
|
||||
|
||||
let newSessionState = state
|
||||
|
||||
for (const block of group.blocks) {
|
||||
nextEdgeId = block.outgoingEdgeId
|
||||
|
||||
if (isBubbleBlock(block)) {
|
||||
messages.push(
|
||||
parseBubbleBlock(newSessionState.typebot.variables)(block)
|
||||
)
|
||||
lastBubbleBlockId = block.id
|
||||
continue
|
||||
}
|
||||
|
||||
if (isInputBlock(block))
|
||||
return {
|
||||
messages,
|
||||
input: await injectVariablesValueInBlock(newSessionState)(block),
|
||||
newSessionState: {
|
||||
...newSessionState,
|
||||
currentBlock: {
|
||||
groupId: group.id,
|
||||
blockId: block.id,
|
||||
},
|
||||
},
|
||||
clientSideActions,
|
||||
logs,
|
||||
}
|
||||
const executionResponse = isLogicBlock(block)
|
||||
? await executeLogic(newSessionState, lastBubbleBlockId)(block)
|
||||
: isIntegrationBlock(block)
|
||||
? await executeIntegration(newSessionState, lastBubbleBlockId)(block)
|
||||
: null
|
||||
|
||||
if (!executionResponse) continue
|
||||
if (
|
||||
'clientSideActions' in executionResponse &&
|
||||
executionResponse.clientSideActions
|
||||
)
|
||||
clientSideActions = [
|
||||
...(clientSideActions ?? []),
|
||||
...executionResponse.clientSideActions,
|
||||
]
|
||||
if (executionResponse.logs)
|
||||
logs = [...(logs ?? []), ...executionResponse.logs]
|
||||
if (executionResponse.newSessionState)
|
||||
newSessionState = executionResponse.newSessionState
|
||||
if (executionResponse.outgoingEdgeId) {
|
||||
nextEdgeId = executionResponse.outgoingEdgeId
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextEdgeId)
|
||||
return { messages, newSessionState, clientSideActions, logs }
|
||||
|
||||
const nextGroup = getNextGroup(newSessionState)(nextEdgeId)
|
||||
|
||||
if (nextGroup?.updatedContext) newSessionState = nextGroup.updatedContext
|
||||
|
||||
if (!nextGroup) {
|
||||
return { messages, newSessionState, clientSideActions, logs }
|
||||
}
|
||||
|
||||
return executeGroup(
|
||||
newSessionState,
|
||||
{
|
||||
messages,
|
||||
clientSideActions,
|
||||
logs,
|
||||
},
|
||||
lastBubbleBlockId
|
||||
)(nextGroup.group)
|
||||
}
|
||||
|
||||
const computeRuntimeOptions =
|
||||
(state: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||
(block: InputBlock): Promise<RuntimeOptions> | undefined => {
|
||||
switch (block.type) {
|
||||
case InputBlockType.PAYMENT: {
|
||||
return computePaymentInputRuntimeOptions(state)(block.options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getPrefilledInputValue =
|
||||
(variables: SessionState['typebot']['variables']) => (block: InputBlock) => {
|
||||
const variableValue = variables.find(
|
||||
(variable) =>
|
||||
variable.id === block.options.variableId && isDefined(variable.value)
|
||||
)?.value
|
||||
if (!variableValue || Array.isArray(variableValue)) return
|
||||
return variableValue
|
||||
}
|
||||
|
||||
const parseBubbleBlock =
|
||||
(variables: SessionState['typebot']['variables']) =>
|
||||
(block: BubbleBlock): ChatReply['messages'][0] => {
|
||||
switch (block.type) {
|
||||
case BubbleBlockType.EMBED: {
|
||||
const message = deepParseVariables(variables)(block)
|
||||
return {
|
||||
...message,
|
||||
content: {
|
||||
...message.content,
|
||||
height:
|
||||
typeof message.content.height === 'string'
|
||||
? parseFloat(message.content.height)
|
||||
: message.content.height,
|
||||
},
|
||||
}
|
||||
}
|
||||
default:
|
||||
return deepParseVariables(variables)(block)
|
||||
}
|
||||
}
|
||||
|
||||
const injectVariablesValueInBlock =
|
||||
(state: Pick<SessionState, 'result' | 'typebot'>) =>
|
||||
async (block: InputBlock): Promise<ChatReply['input']> => {
|
||||
switch (block.type) {
|
||||
case InputBlockType.CHOICE: {
|
||||
return injectVariableValuesInButtonsInputBlock(state.typebot.variables)(
|
||||
block
|
||||
)
|
||||
}
|
||||
default: {
|
||||
return deepParseVariables(state.typebot.variables)({
|
||||
...block,
|
||||
runtimeOptions: await computeRuntimeOptions(state)(block),
|
||||
prefilledValue: getPrefilledInputValue(state.typebot.variables)(
|
||||
block
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
34
apps/viewer/src/features/chat/helpers/executeIntegration.ts
Normal file
34
apps/viewer/src/features/chat/helpers/executeIntegration.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { executeOpenAIBlock } from '@/features/blocks/integrations/openai/executeOpenAIBlock'
|
||||
import { executeSendEmailBlock } from '@/features/blocks/integrations/sendEmail/executeSendEmailBlock'
|
||||
import { executeWebhookBlock } from '@/features/blocks/integrations/webhook/executeWebhookBlock'
|
||||
import { executeChatwootBlock } from '@/features/blocks/integrations/chatwoot/executeChatwootBlock'
|
||||
import { executeGoogleAnalyticsBlock } from '@/features/blocks/integrations/googleAnalytics/executeGoogleAnalyticsBlock'
|
||||
import { executeGoogleSheetBlock } from '@/features/blocks/integrations/googleSheets/executeGoogleSheetBlock'
|
||||
import {
|
||||
IntegrationBlock,
|
||||
IntegrationBlockType,
|
||||
SessionState,
|
||||
} from '@typebot.io/schemas'
|
||||
import { ExecuteIntegrationResponse } from '../types'
|
||||
|
||||
export const executeIntegration =
|
||||
(state: SessionState, lastBubbleBlockId?: string) =>
|
||||
async (block: IntegrationBlock): Promise<ExecuteIntegrationResponse> => {
|
||||
switch (block.type) {
|
||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||
return executeGoogleSheetBlock(state, block)
|
||||
case IntegrationBlockType.CHATWOOT:
|
||||
return executeChatwootBlock(state, block, lastBubbleBlockId)
|
||||
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
||||
return executeGoogleAnalyticsBlock(state, block, lastBubbleBlockId)
|
||||
case IntegrationBlockType.EMAIL:
|
||||
return executeSendEmailBlock(state, block)
|
||||
case IntegrationBlockType.WEBHOOK:
|
||||
case IntegrationBlockType.ZAPIER:
|
||||
case IntegrationBlockType.MAKE_COM:
|
||||
case IntegrationBlockType.PABBLY_CONNECT:
|
||||
return executeWebhookBlock(state, block)
|
||||
case IntegrationBlockType.OPEN_AI:
|
||||
return executeOpenAIBlock(state, block)
|
||||
}
|
||||
}
|
||||
30
apps/viewer/src/features/chat/helpers/executeLogic.ts
Normal file
30
apps/viewer/src/features/chat/helpers/executeLogic.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { executeWait } from '@/features/blocks/logic/wait/executeWait'
|
||||
import { LogicBlock, LogicBlockType, SessionState } from '@typebot.io/schemas'
|
||||
import { ExecuteLogicResponse } from '../types'
|
||||
import { executeScript } from '@/features/blocks/logic/script/executeScript'
|
||||
import { executeJumpBlock } from '@/features/blocks/logic/jump/executeJumpBlock'
|
||||
import { executeRedirect } from '@/features/blocks/logic/redirect/executeRedirect'
|
||||
import { executeCondition } from '@/features/blocks/logic/condition/executeCondition'
|
||||
import { executeSetVariable } from '@/features/blocks/logic/setVariable/executeSetVariable'
|
||||
import { executeTypebotLink } from '@/features/blocks/logic/typebotLink/executeTypebotLink'
|
||||
|
||||
export const executeLogic =
|
||||
(state: SessionState, lastBubbleBlockId?: string) =>
|
||||
async (block: LogicBlock): Promise<ExecuteLogicResponse> => {
|
||||
switch (block.type) {
|
||||
case LogicBlockType.SET_VARIABLE:
|
||||
return executeSetVariable(state, block)
|
||||
case LogicBlockType.CONDITION:
|
||||
return executeCondition(state, block)
|
||||
case LogicBlockType.REDIRECT:
|
||||
return executeRedirect(state, block, lastBubbleBlockId)
|
||||
case LogicBlockType.SCRIPT:
|
||||
return executeScript(state, block, lastBubbleBlockId)
|
||||
case LogicBlockType.TYPEBOT_LINK:
|
||||
return executeTypebotLink(state, block)
|
||||
case LogicBlockType.WAIT:
|
||||
return executeWait(state, block, lastBubbleBlockId)
|
||||
case LogicBlockType.JUMP:
|
||||
return executeJumpBlock(state, block.options)
|
||||
}
|
||||
}
|
||||
38
apps/viewer/src/features/chat/helpers/getNextGroup.ts
Normal file
38
apps/viewer/src/features/chat/helpers/getNextGroup.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { byId } from '@typebot.io/lib'
|
||||
import { Group, SessionState } from '@typebot.io/schemas'
|
||||
|
||||
export type NextGroup = {
|
||||
group: Group
|
||||
updatedContext?: SessionState
|
||||
}
|
||||
|
||||
export const getNextGroup =
|
||||
(state: SessionState) =>
|
||||
(edgeId?: string): NextGroup | null => {
|
||||
const { typebot } = state
|
||||
const nextEdge = typebot.edges.find(byId(edgeId))
|
||||
if (!nextEdge) {
|
||||
if (state.linkedTypebots.queue.length > 0) {
|
||||
const nextEdgeId = state.linkedTypebots.queue[0].edgeId
|
||||
const updatedContext = {
|
||||
...state,
|
||||
linkedBotQueue: state.linkedTypebots.queue.slice(1),
|
||||
}
|
||||
const nextGroup = getNextGroup(updatedContext)(nextEdgeId)
|
||||
if (!nextGroup) return null
|
||||
return {
|
||||
...nextGroup,
|
||||
updatedContext,
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
const nextGroup = typebot.groups.find(byId(nextEdge.to.groupId))
|
||||
if (!nextGroup) return null
|
||||
const startBlockIndex = nextEdge.to.blockId
|
||||
? nextGroup.blocks.findIndex(byId(nextEdge.to.blockId))
|
||||
: 0
|
||||
return {
|
||||
group: { ...nextGroup, blocks: nextGroup.blocks.slice(startBlockIndex) },
|
||||
}
|
||||
}
|
||||
12
apps/viewer/src/features/chat/helpers/getSessionState.ts
Normal file
12
apps/viewer/src/features/chat/helpers/getSessionState.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { ChatSession } from '@typebot.io/schemas'
|
||||
|
||||
export const getSession = async (
|
||||
sessionId: string
|
||||
): Promise<Pick<ChatSession, 'state' | 'id'> | null> => {
|
||||
const session = (await prisma.chatSession.findUnique({
|
||||
where: { id: sessionId },
|
||||
select: { id: true, state: true },
|
||||
})) as Pick<ChatSession, 'state' | 'id'> | null
|
||||
return session
|
||||
}
|
||||
5
apps/viewer/src/features/chat/helpers/index.ts
Normal file
5
apps/viewer/src/features/chat/helpers/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './continueBotFlow'
|
||||
export * from './executeGroup'
|
||||
export * from './getNextGroup'
|
||||
export * from './getSessionState'
|
||||
export * from './startBotFlow'
|
||||
26
apps/viewer/src/features/chat/helpers/startBotFlow.ts
Normal file
26
apps/viewer/src/features/chat/helpers/startBotFlow.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { ChatReply, SessionState } from '@typebot.io/schemas'
|
||||
import { executeGroup } from './executeGroup'
|
||||
import { getNextGroup } from './getNextGroup'
|
||||
|
||||
export const startBotFlow = async (
|
||||
state: SessionState,
|
||||
startGroupId?: string
|
||||
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
||||
if (startGroupId) {
|
||||
const group = state.typebot.groups.find(
|
||||
(group) => group.id === startGroupId
|
||||
)
|
||||
if (!group)
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: "startGroupId doesn't exist",
|
||||
})
|
||||
return executeGroup(state)(group)
|
||||
}
|
||||
const firstEdgeId = state.typebot.groups[0].blocks[0].outgoingEdgeId
|
||||
if (!firstEdgeId) return { messages: [], newSessionState: state }
|
||||
const nextGroup = getNextGroup(state)(firstEdgeId)
|
||||
if (!nextGroup) return { messages: [], newSessionState: state }
|
||||
return executeGroup(state)(nextGroup.group)
|
||||
}
|
||||
Reference in New Issue
Block a user