@ -2,7 +2,7 @@ import {
|
||||
SessionState,
|
||||
GoogleSheetsGetOptions,
|
||||
VariableWithValue,
|
||||
ReplyLog,
|
||||
ChatLog,
|
||||
} from '@typebot.io/schemas'
|
||||
import { isNotEmpty, byId, isDefined } from '@typebot.io/lib'
|
||||
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
||||
@ -18,7 +18,7 @@ export const getRow = async (
|
||||
options,
|
||||
}: { outgoingEdgeId?: string; options: GoogleSheetsGetOptions }
|
||||
): Promise<ExecuteIntegrationResponse> => {
|
||||
const logs: ReplyLog[] = []
|
||||
const logs: ChatLog[] = []
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
const { sheetId, cellsToExtract, filter, ...parsedOptions } =
|
||||
deepParseVariables(variables)(options)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
SessionState,
|
||||
GoogleSheetsInsertRowOptions,
|
||||
ReplyLog,
|
||||
ChatLog,
|
||||
} from '@typebot.io/schemas'
|
||||
import { parseCellValues } from './helpers/parseCellValues'
|
||||
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
||||
@ -17,7 +17,7 @@ export const insertRow = async (
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (!options.cellsToInsert || !options.sheetId) return { outgoingEdgeId }
|
||||
|
||||
const logs: ReplyLog[] = []
|
||||
const logs: ChatLog[] = []
|
||||
|
||||
const doc = await getAuthenticatedGoogleDoc({
|
||||
credentialsId: options.credentialsId,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
SessionState,
|
||||
GoogleSheetsUpdateRowOptions,
|
||||
ReplyLog,
|
||||
ChatLog,
|
||||
} from '@typebot.io/schemas'
|
||||
import { parseCellValues } from './helpers/parseCellValues'
|
||||
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
||||
@ -28,7 +28,7 @@ export const updateRow = async (
|
||||
if (!options.cellsToUpsert || !sheetId || (!referenceCell && !filter))
|
||||
return { outgoingEdgeId }
|
||||
|
||||
const logs: ReplyLog[] = []
|
||||
const logs: ChatLog[] = []
|
||||
|
||||
const doc = await getAuthenticatedGoogleDoc({
|
||||
credentialsId: options.credentialsId,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { isNotEmpty } from '@typebot.io/lib/utils'
|
||||
import { ChatReply } from '@typebot.io/schemas'
|
||||
import { ContinueChatResponse } from '@typebot.io/schemas'
|
||||
import { OpenAIBlock } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
||||
import { HTTPError } from 'got'
|
||||
import { ClientOptions, OpenAI } from 'openai'
|
||||
@ -10,7 +10,7 @@ type Props = Pick<
|
||||
> & {
|
||||
apiKey: string
|
||||
temperature: number | undefined
|
||||
currentLogs?: ChatReply['logs']
|
||||
currentLogs?: ContinueChatResponse['logs']
|
||||
isRetrying?: boolean
|
||||
} & Pick<NonNullable<OpenAIBlock['options']>, 'apiVersion' | 'baseUrl'>
|
||||
|
||||
@ -25,9 +25,9 @@ export const executeChatCompletionOpenAIRequest = async ({
|
||||
currentLogs = [],
|
||||
}: Props): Promise<{
|
||||
chatCompletion?: OpenAI.Chat.Completions.ChatCompletion
|
||||
logs?: ChatReply['logs']
|
||||
logs?: ContinueChatResponse['logs']
|
||||
}> => {
|
||||
const logs: ChatReply['logs'] = currentLogs
|
||||
const logs: ContinueChatResponse['logs'] = currentLogs
|
||||
if (messages.length === 0) return { logs }
|
||||
try {
|
||||
const config = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { byId, isDefined } from '@typebot.io/lib'
|
||||
import { ChatReply, SessionState } from '@typebot.io/schemas'
|
||||
import { ContinueChatResponse, SessionState } from '@typebot.io/schemas'
|
||||
import { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
||||
import { VariableWithUnknowValue } from '@typebot.io/schemas/features/typebot/variable'
|
||||
import { updateVariablesInSession } from '../../../variables/updateVariablesInSession'
|
||||
@ -14,7 +14,7 @@ export const resumeChatCompletion =
|
||||
}: {
|
||||
outgoingEdgeId?: string
|
||||
options: ChatCompletionOpenAIOptions
|
||||
logs?: ChatReply['logs']
|
||||
logs?: ContinueChatResponse['logs']
|
||||
}
|
||||
) =>
|
||||
async (message: string, totalTokens?: number) => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { DefaultBotNotificationEmail, render } from '@typebot.io/emails'
|
||||
import {
|
||||
AnswerInSessionState,
|
||||
ReplyLog,
|
||||
ChatLog,
|
||||
SendEmailBlock,
|
||||
SessionState,
|
||||
SmtpCredentials,
|
||||
@ -25,7 +25,7 @@ export const executeSendEmailBlock = async (
|
||||
state: SessionState,
|
||||
block: SendEmailBlock
|
||||
): Promise<ExecuteIntegrationResponse> => {
|
||||
const logs: ReplyLog[] = []
|
||||
const logs: ChatLog[] = []
|
||||
const { options } = block
|
||||
const { typebot, resultId, answers } = state.typebotsQueue[0]
|
||||
const isPreview = !resultId
|
||||
@ -114,8 +114,8 @@ const sendEmail = async ({
|
||||
typebot: TypebotInSession
|
||||
answers: AnswerInSessionState[]
|
||||
fileUrls?: string | string[]
|
||||
}): Promise<ReplyLog[] | undefined> => {
|
||||
const logs: ReplyLog[] = []
|
||||
}): Promise<ChatLog[] | undefined> => {
|
||||
const logs: ChatLog[] = []
|
||||
const { name: replyToName } = parseEmailRecipient(replyTo)
|
||||
|
||||
const { host, port, isTlsEnabled, username, password, from } =
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
Variable,
|
||||
WebhookResponse,
|
||||
KeyValue,
|
||||
ReplyLog,
|
||||
ChatLog,
|
||||
ExecutableWebhook,
|
||||
AnswerInSessionState,
|
||||
} from '@typebot.io/schemas'
|
||||
@ -34,7 +34,7 @@ export const executeWebhookBlock = async (
|
||||
state: SessionState,
|
||||
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
||||
): Promise<ExecuteIntegrationResponse> => {
|
||||
const logs: ReplyLog[] = []
|
||||
const logs: ChatLog[] = []
|
||||
const webhook =
|
||||
block.options?.webhook ??
|
||||
('webhookId' in block
|
||||
@ -142,8 +142,8 @@ const parseWebhookAttributes =
|
||||
|
||||
export const executeWebhook = async (
|
||||
webhook: ParsedWebhook
|
||||
): Promise<{ response: WebhookResponse; logs?: ReplyLog[] }> => {
|
||||
const logs: ReplyLog[] = []
|
||||
): Promise<{ response: WebhookResponse; logs?: ChatLog[] }> => {
|
||||
const logs: ChatLog[] = []
|
||||
const { headers, url, method, basicAuth, body, isJson } = webhook
|
||||
const contentType = headers ? headers['Content-Type'] : undefined
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { byId } from '@typebot.io/lib'
|
||||
import {
|
||||
MakeComBlock,
|
||||
PabblyConnectBlock,
|
||||
ReplyLog,
|
||||
ChatLog,
|
||||
VariableWithUnknowValue,
|
||||
WebhookBlock,
|
||||
ZapierBlock,
|
||||
@ -15,7 +15,7 @@ import { updateVariablesInSession } from '../../../variables/updateVariablesInSe
|
||||
type Props = {
|
||||
state: SessionState
|
||||
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
||||
logs?: ReplyLog[]
|
||||
logs?: ChatLog[]
|
||||
response: {
|
||||
statusCode: number
|
||||
data?: unknown
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
TypebotLinkBlock,
|
||||
SessionState,
|
||||
Variable,
|
||||
ReplyLog,
|
||||
ChatLog,
|
||||
Edge,
|
||||
typebotInSessionStateSchema,
|
||||
TypebotInSession,
|
||||
@ -19,7 +19,7 @@ export const executeTypebotLink = async (
|
||||
state: SessionState,
|
||||
block: TypebotLinkBlock
|
||||
): Promise<ExecuteLogicResponse> => {
|
||||
const logs: ReplyLog[] = []
|
||||
const logs: ChatLog[] = []
|
||||
const typebotId = block.options?.typebotId
|
||||
if (!typebotId) {
|
||||
logs.push({
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
AnswerInSessionState,
|
||||
Block,
|
||||
ChatReply,
|
||||
ContinueChatResponse,
|
||||
Group,
|
||||
InputBlock,
|
||||
SessionState,
|
||||
@ -46,7 +46,10 @@ export const continueBotFlow = async (
|
||||
reply: string | undefined,
|
||||
{ state, version }: Params
|
||||
): Promise<
|
||||
ChatReply & { newSessionState: SessionState; visitedEdges: VisitedEdge[] }
|
||||
ContinueChatResponse & {
|
||||
newSessionState: SessionState
|
||||
visitedEdges: VisitedEdge[]
|
||||
}
|
||||
> => {
|
||||
let firstBubbleWasStreamed = false
|
||||
let newSessionState = { ...state }
|
||||
@ -202,7 +205,9 @@ const saveVariableValueIfAny =
|
||||
|
||||
const parseRetryMessage =
|
||||
(state: SessionState) =>
|
||||
async (block: InputBlock): Promise<Pick<ChatReply, 'messages' | 'input'>> => {
|
||||
async (
|
||||
block: InputBlock
|
||||
): Promise<Pick<ContinueChatResponse, 'messages' | 'input'>> => {
|
||||
const retryMessage =
|
||||
block.options &&
|
||||
'retryMessageContent' in block.options &&
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {
|
||||
ChatReply,
|
||||
ContinueChatResponse,
|
||||
Group,
|
||||
InputBlock,
|
||||
RuntimeOptions,
|
||||
@ -31,7 +31,7 @@ import { VisitedEdge } from '@typebot.io/prisma'
|
||||
type ContextProps = {
|
||||
version: 1 | 2
|
||||
state: SessionState
|
||||
currentReply?: ChatReply
|
||||
currentReply?: ContinueChatResponse
|
||||
currentLastBubbleId?: string
|
||||
firstBubbleWasStreamed?: boolean
|
||||
visitedEdges: VisitedEdge[]
|
||||
@ -48,12 +48,16 @@ export const executeGroup = async (
|
||||
firstBubbleWasStreamed,
|
||||
}: ContextProps
|
||||
): Promise<
|
||||
ChatReply & { newSessionState: SessionState; visitedEdges: VisitedEdge[] }
|
||||
ContinueChatResponse & {
|
||||
newSessionState: SessionState
|
||||
visitedEdges: VisitedEdge[]
|
||||
}
|
||||
> => {
|
||||
const messages: ChatReply['messages'] = currentReply?.messages ?? []
|
||||
let clientSideActions: ChatReply['clientSideActions'] =
|
||||
const messages: ContinueChatResponse['messages'] =
|
||||
currentReply?.messages ?? []
|
||||
let clientSideActions: ContinueChatResponse['clientSideActions'] =
|
||||
currentReply?.clientSideActions
|
||||
let logs: ChatReply['logs'] = currentReply?.logs
|
||||
let logs: ContinueChatResponse['logs'] = currentReply?.logs
|
||||
let nextEdgeId = null
|
||||
let lastBubbleBlockId: string | undefined = currentLastBubbleId
|
||||
|
||||
@ -173,7 +177,7 @@ const computeRuntimeOptions =
|
||||
|
||||
export const parseInput =
|
||||
(state: SessionState) =>
|
||||
async (block: InputBlock): Promise<ChatReply['input']> => {
|
||||
async (block: InputBlock): Promise<ContinueChatResponse['input']> => {
|
||||
switch (block.type) {
|
||||
case InputBlockType.CHOICE: {
|
||||
return injectVariableValuesInButtonsInputBlock(state)(block)
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { parseVideoUrl } from '@typebot.io/lib/parseVideoUrl'
|
||||
import { BubbleBlock, Variable, ChatReply, Typebot } from '@typebot.io/schemas'
|
||||
import {
|
||||
BubbleBlock,
|
||||
Variable,
|
||||
ContinueChatResponse,
|
||||
Typebot,
|
||||
} from '@typebot.io/schemas'
|
||||
import { deepParseVariables } from './variables/deepParseVariables'
|
||||
import { isEmpty, isNotEmpty } from '@typebot.io/lib/utils'
|
||||
import {
|
||||
@ -27,7 +32,7 @@ export type BubbleBlockWithDefinedContent = BubbleBlock & {
|
||||
export const parseBubbleBlock = (
|
||||
block: BubbleBlockWithDefinedContent,
|
||||
{ version, variables, typebotVersion }: Params
|
||||
): ChatReply['messages'][0] => {
|
||||
): ContinueChatResponse['messages'][0] => {
|
||||
switch (block.type) {
|
||||
case BubbleBlockType.TEXT: {
|
||||
if (version === 1)
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { SessionState, ChatReply } from '@typebot.io/schemas'
|
||||
import { SessionState, ContinueChatResponse } from '@typebot.io/schemas'
|
||||
import { parseVariables } from './variables/parseVariables'
|
||||
|
||||
export const parseDynamicTheme = (
|
||||
state: SessionState | undefined
|
||||
): ChatReply['dynamicTheme'] => {
|
||||
): ContinueChatResponse['dynamicTheme'] => {
|
||||
if (!state?.dynamicTheme) return
|
||||
return {
|
||||
hostAvatarUrl: parseVariables(state?.typebotsQueue[0].typebot.variables)(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChatReply, ChatSession } from '@typebot.io/schemas'
|
||||
import { ContinueChatResponse, ChatSession } from '@typebot.io/schemas'
|
||||
import { upsertResult } from './queries/upsertResult'
|
||||
import { saveLogs } from './queries/saveLogs'
|
||||
import { updateSession } from './queries/updateSession'
|
||||
@ -11,9 +11,9 @@ import { VisitedEdge } from '@typebot.io/prisma'
|
||||
|
||||
type Props = {
|
||||
session: Pick<ChatSession, 'state'> & { id?: string }
|
||||
input: ChatReply['input']
|
||||
logs: ChatReply['logs']
|
||||
clientSideActions: ChatReply['clientSideActions']
|
||||
input: ContinueChatResponse['input']
|
||||
logs: ContinueChatResponse['logs']
|
||||
clientSideActions: ContinueChatResponse['clientSideActions']
|
||||
visitedEdges: VisitedEdge[]
|
||||
forceCreateSession?: boolean
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { ChatReply, SessionState, StartElementId } from '@typebot.io/schemas'
|
||||
import {
|
||||
ContinueChatResponse,
|
||||
SessionState,
|
||||
StartFrom,
|
||||
} from '@typebot.io/schemas'
|
||||
import { executeGroup } from './executeGroup'
|
||||
import { getNextGroup } from './getNextGroup'
|
||||
import { VisitedEdge } from '@typebot.io/prisma'
|
||||
@ -7,20 +11,24 @@ import { VisitedEdge } from '@typebot.io/prisma'
|
||||
type Props = {
|
||||
version: 1 | 2
|
||||
state: SessionState
|
||||
} & StartElementId
|
||||
startFrom?: StartFrom
|
||||
}
|
||||
|
||||
export const startBotFlow = async ({
|
||||
version,
|
||||
state,
|
||||
...props
|
||||
startFrom,
|
||||
}: Props): Promise<
|
||||
ChatReply & { newSessionState: SessionState; visitedEdges: VisitedEdge[] }
|
||||
ContinueChatResponse & {
|
||||
newSessionState: SessionState
|
||||
visitedEdges: VisitedEdge[]
|
||||
}
|
||||
> => {
|
||||
let newSessionState = state
|
||||
const visitedEdges: VisitedEdge[] = []
|
||||
if ('startGroupId' in props) {
|
||||
if (startFrom?.type === 'group') {
|
||||
const group = state.typebotsQueue[0].typebot.groups.find(
|
||||
(group) => group.id === props.startGroupId
|
||||
(group) => group.id === startFrom.groupId
|
||||
)
|
||||
if (!group)
|
||||
throw new TRPCError({
|
||||
@ -35,7 +43,7 @@ export const startBotFlow = async ({
|
||||
}
|
||||
const firstEdgeId = getFirstEdgeId({
|
||||
state: newSessionState,
|
||||
startEventId: 'startEventId' in props ? props.startEventId : undefined,
|
||||
startEventId: startFrom?.type === 'event' ? startFrom.eventId : undefined,
|
||||
})
|
||||
if (!firstEdgeId) return { messages: [], newSessionState, visitedEdges: [] }
|
||||
const nextGroup = await getNextGroup(newSessionState)(firstEdgeId)
|
||||
|
@ -12,13 +12,13 @@ import {
|
||||
Block,
|
||||
} from '@typebot.io/schemas'
|
||||
import {
|
||||
ChatReply,
|
||||
StartParams,
|
||||
StartChatInput,
|
||||
StartChatResponse,
|
||||
StartPreviewChatInput,
|
||||
StartTypebot,
|
||||
startTypebotSchema,
|
||||
} from '@typebot.io/schemas/features/chat/schema'
|
||||
import parse, { NodeType } from 'node-html-parser'
|
||||
import { env } from '@typebot.io/env'
|
||||
import { parseDynamicTheme } from './parseDynamicTheme'
|
||||
import { findTypebot } from './queries/findTypebot'
|
||||
import { findPublicTypebot } from './queries/findPublicTypebot'
|
||||
@ -36,11 +36,19 @@ import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integr
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { VisitedEdge } from '@typebot.io/prisma'
|
||||
|
||||
type StartParams =
|
||||
| ({
|
||||
type: 'preview'
|
||||
userId: string
|
||||
} & StartPreviewChatInput)
|
||||
| ({
|
||||
type: 'live'
|
||||
} & StartChatInput)
|
||||
|
||||
type Props = {
|
||||
version: 1 | 2
|
||||
message: string | undefined
|
||||
startParams: StartParams
|
||||
userId: string | undefined
|
||||
initialSessionState?: Pick<SessionState, 'whatsApp' | 'expiryTimeout'>
|
||||
}
|
||||
|
||||
@ -48,26 +56,24 @@ export const startSession = async ({
|
||||
version,
|
||||
message,
|
||||
startParams,
|
||||
userId,
|
||||
initialSessionState,
|
||||
}: Props): Promise<
|
||||
ChatReply & { newSessionState: SessionState; visitedEdges: VisitedEdge[] }
|
||||
Omit<StartChatResponse, 'resultId'> & {
|
||||
newSessionState: SessionState
|
||||
visitedEdges: VisitedEdge[]
|
||||
resultId?: string
|
||||
}
|
||||
> => {
|
||||
if (!startParams)
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'StartParams are missing',
|
||||
})
|
||||
const typebot = await getTypebot(startParams)
|
||||
|
||||
const typebot = await getTypebot(startParams, userId)
|
||||
|
||||
const prefilledVariables = startParams.prefilledVariables
|
||||
? prefillVariables(typebot.variables, startParams.prefilledVariables)
|
||||
: typebot.variables
|
||||
const prefilledVariables =
|
||||
startParams.type === 'live' && startParams.prefilledVariables
|
||||
? prefillVariables(typebot.variables, startParams.prefilledVariables)
|
||||
: typebot.variables
|
||||
|
||||
const result = await getResult({
|
||||
...startParams,
|
||||
isPreview: startParams.isPreview || typeof startParams.typebot !== 'string',
|
||||
resultId: startParams.type === 'live' ? startParams.resultId : undefined,
|
||||
isPreview: startParams.type === 'preview',
|
||||
typebotId: typebot.id,
|
||||
prefilledVariables,
|
||||
isRememberUserEnabled:
|
||||
@ -148,11 +154,8 @@ export const startSession = async ({
|
||||
let chatReply = await startBotFlow({
|
||||
version,
|
||||
state: initialState,
|
||||
...('startGroupId' in startParams
|
||||
? { startGroupId: startParams.startGroupId }
|
||||
: 'startEventId' in startParams
|
||||
? { startEventId: startParams.startEventId }
|
||||
: {}),
|
||||
startFrom:
|
||||
startParams.type === 'preview' ? startParams.startFrom : undefined,
|
||||
})
|
||||
|
||||
// If params has message and first block is an input block, we can directly continue the bot flow
|
||||
@ -266,20 +269,16 @@ export const startSession = async ({
|
||||
}
|
||||
}
|
||||
|
||||
const getTypebot = async (
|
||||
{ typebot, isPreview }: Pick<StartParams, 'typebot' | 'isPreview'>,
|
||||
userId?: string
|
||||
): Promise<StartTypebot> => {
|
||||
if (typeof typebot !== 'string') return typebot
|
||||
if (isPreview && !userId && !env.NEXT_PUBLIC_E2E_TEST)
|
||||
throw new TRPCError({
|
||||
code: 'UNAUTHORIZED',
|
||||
message:
|
||||
'You need to authenticate the request to start a bot in preview mode.',
|
||||
})
|
||||
const typebotQuery = isPreview
|
||||
? await findTypebot({ id: typebot, userId })
|
||||
: await findPublicTypebot({ publicId: typebot })
|
||||
const getTypebot = async (startParams: StartParams): Promise<StartTypebot> => {
|
||||
if (startParams.type === 'preview' && startParams.typebot)
|
||||
return startParams.typebot
|
||||
const typebotQuery =
|
||||
startParams.type === 'preview'
|
||||
? await findTypebot({
|
||||
id: startParams.typebotId,
|
||||
userId: startParams.userId,
|
||||
})
|
||||
: await findPublicTypebot({ publicId: startParams.publicId })
|
||||
|
||||
const parsedTypebot =
|
||||
typebotQuery && 'typebot' in typebotQuery
|
||||
@ -319,7 +318,9 @@ const getResult = async ({
|
||||
resultId,
|
||||
prefilledVariables,
|
||||
isRememberUserEnabled,
|
||||
}: Pick<StartParams, 'isPreview' | 'resultId'> & {
|
||||
}: {
|
||||
resultId: string | undefined
|
||||
isPreview: boolean
|
||||
typebotId: string
|
||||
prefilledVariables: Variable[]
|
||||
isRememberUserEnabled: boolean
|
||||
@ -375,7 +376,7 @@ const parseDynamicThemeInState = (theme: Theme) => {
|
||||
|
||||
const parseStartClientSideAction = (
|
||||
typebot: StartTypebot
|
||||
): NonNullable<ChatReply['clientSideActions']>[number] | undefined => {
|
||||
): NonNullable<StartChatResponse['clientSideActions']>[number] | undefined => {
|
||||
const blocks = typebot.groups.flatMap<Block>((group) => group.blocks)
|
||||
const pixelBlocks = (
|
||||
blocks.filter(
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { ChatReply, SessionState } from '@typebot.io/schemas'
|
||||
import { ContinueChatResponse, SessionState } from '@typebot.io/schemas'
|
||||
|
||||
export type EdgeId = string
|
||||
|
||||
export type ExecuteLogicResponse = {
|
||||
outgoingEdgeId: EdgeId | undefined
|
||||
newSessionState?: SessionState
|
||||
} & Pick<ChatReply, 'clientSideActions' | 'logs'>
|
||||
} & Pick<ContinueChatResponse, 'clientSideActions' | 'logs'>
|
||||
|
||||
export type ExecuteIntegrationResponse = {
|
||||
outgoingEdgeId: EdgeId | undefined
|
||||
newSessionState?: SessionState
|
||||
} & Pick<ChatReply, 'clientSideActions' | 'logs'>
|
||||
} & Pick<ContinueChatResponse, 'clientSideActions' | 'logs'>
|
||||
|
||||
export type ParsedReply =
|
||||
| { status: 'success'; reply: string }
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { safeStringify } from '@typebot.io/lib/safeStringify'
|
||||
import { StartParams, Variable } from '@typebot.io/schemas'
|
||||
import { StartChatInput, Variable } from '@typebot.io/schemas'
|
||||
|
||||
export const prefillVariables = (
|
||||
variables: Variable[],
|
||||
prefilledVariables: NonNullable<StartParams['prefilledVariables']>
|
||||
prefilledVariables: NonNullable<StartChatInput['prefilledVariables']>
|
||||
): Variable[] =>
|
||||
variables.map((variable) => {
|
||||
const prefilledVariable = prefilledVariables[variable.name]
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ButtonItem, ChatReply } from '@typebot.io/schemas'
|
||||
import { ButtonItem, ContinueChatResponse } from '@typebot.io/schemas'
|
||||
import { WhatsAppSendingMessage } from '@typebot.io/schemas/features/whatsapp'
|
||||
import { convertRichTextToWhatsAppText } from './convertRichTextToWhatsAppText'
|
||||
import { isDefined, isEmpty } from '@typebot.io/lib/utils'
|
||||
@ -8,8 +8,8 @@ import { defaultPictureChoiceOptions } from '@typebot.io/schemas/features/blocks
|
||||
import { defaultChoiceInputOptions } from '@typebot.io/schemas/features/blocks/inputs/choice/constants'
|
||||
|
||||
export const convertInputToWhatsAppMessages = (
|
||||
input: NonNullable<ChatReply['input']>,
|
||||
lastMessage: ChatReply['messages'][number] | undefined
|
||||
input: NonNullable<ContinueChatResponse['input']>,
|
||||
lastMessage: ContinueChatResponse['messages'][number] | undefined
|
||||
): WhatsAppSendingMessage[] => {
|
||||
const lastMessageText =
|
||||
lastMessage?.type === BubbleBlockType.TEXT
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChatReply } from '@typebot.io/schemas'
|
||||
import { ContinueChatResponse } from '@typebot.io/schemas'
|
||||
import { WhatsAppSendingMessage } from '@typebot.io/schemas/features/whatsapp'
|
||||
import { convertRichTextToWhatsAppText } from './convertRichTextToWhatsAppText'
|
||||
import { isSvgSrc } from '@typebot.io/lib/utils'
|
||||
@ -8,7 +8,7 @@ import { VideoBubbleContentType } from '@typebot.io/schemas/features/blocks/bubb
|
||||
const mp4HttpsUrlRegex = /^https:\/\/.*\.mp4$/
|
||||
|
||||
export const convertMessageToWhatsAppMessage = (
|
||||
message: ChatReply['messages'][number]
|
||||
message: ContinueChatResponse['messages'][number]
|
||||
): WhatsAppSendingMessage | undefined => {
|
||||
switch (message.type) {
|
||||
case BubbleBlockType.TEXT: {
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { ChatReply, SessionState, Settings } from '@typebot.io/schemas'
|
||||
import {
|
||||
ContinueChatResponse,
|
||||
SessionState,
|
||||
Settings,
|
||||
} from '@typebot.io/schemas'
|
||||
import {
|
||||
WhatsAppCredentials,
|
||||
WhatsAppSendingMessage,
|
||||
@ -21,7 +25,7 @@ type Props = {
|
||||
typingEmulation: SessionState['typingEmulation']
|
||||
credentials: WhatsAppCredentials['data']
|
||||
state: SessionState
|
||||
} & Pick<ChatReply, 'messages' | 'input' | 'clientSideActions'>
|
||||
} & Pick<ContinueChatResponse, 'messages' | 'input' | 'clientSideActions'>
|
||||
|
||||
export const sendChatReplyToWhatsApp = async ({
|
||||
to,
|
||||
@ -171,7 +175,9 @@ const getTypingDuration = ({
|
||||
}
|
||||
}
|
||||
|
||||
const isLastMessageIncludedInInput = (input: ChatReply['input']): boolean => {
|
||||
const isLastMessageIncludedInInput = (
|
||||
input: ContinueChatResponse['input']
|
||||
): boolean => {
|
||||
if (isNotDefined(input)) return false
|
||||
return input.type === InputBlockType.CHOICE
|
||||
}
|
||||
@ -179,7 +185,9 @@ const isLastMessageIncludedInInput = (input: ChatReply['input']): boolean => {
|
||||
const executeClientSideAction =
|
||||
(context: { to: string; credentials: WhatsAppCredentials['data'] }) =>
|
||||
async (
|
||||
clientSideAction: NonNullable<ChatReply['clientSideActions']>[number]
|
||||
clientSideAction: NonNullable<
|
||||
ContinueChatResponse['clientSideActions']
|
||||
>[number]
|
||||
): Promise<{ replyToSend: string | undefined } | void> => {
|
||||
if ('wait' in clientSideAction) {
|
||||
await new Promise((resolve) =>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import {
|
||||
ChatReply,
|
||||
ContinueChatResponse,
|
||||
PublicTypebot,
|
||||
SessionState,
|
||||
Settings,
|
||||
@ -20,7 +20,7 @@ import { VisitedEdge } from '@typebot.io/prisma'
|
||||
|
||||
type Props = {
|
||||
incomingMessage?: string
|
||||
workspaceId?: string
|
||||
workspaceId: string
|
||||
credentials: WhatsAppCredentials['data'] & Pick<WhatsAppCredentials, 'id'>
|
||||
contact: NonNullable<SessionState['whatsApp']>['contact']
|
||||
}
|
||||
@ -31,7 +31,7 @@ export const startWhatsAppSession = async ({
|
||||
credentials,
|
||||
contact,
|
||||
}: Props): Promise<
|
||||
| (ChatReply & {
|
||||
| (ContinueChatResponse & {
|
||||
newSessionState: SessionState
|
||||
visitedEdges: VisitedEdge[]
|
||||
})
|
||||
@ -89,9 +89,10 @@ export const startWhatsAppSession = async ({
|
||||
version: 2,
|
||||
message: incomingMessage,
|
||||
startParams: {
|
||||
typebot: publicTypebot.typebot.publicId as string,
|
||||
type: 'live',
|
||||
publicId: publicTypebot.typebot.publicId as string,
|
||||
isOnlyRegistering: false,
|
||||
},
|
||||
userId: undefined,
|
||||
initialSessionState: {
|
||||
whatsApp: {
|
||||
contact,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/js",
|
||||
"version": "0.2.15",
|
||||
"version": "0.2.16",
|
||||
"description": "Javascript library to display typebots on your website",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { LiteBadge } from './LiteBadge'
|
||||
import { createEffect, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { isNotDefined, isNotEmpty } from '@typebot.io/lib'
|
||||
import { getInitialChatReplyQuery } from '@/queries/getInitialChatReplyQuery'
|
||||
import { startChatQuery } from '@/queries/startChatQuery'
|
||||
import { ConversationContainer } from './ConversationContainer'
|
||||
import { setIsMobile } from '@/utils/isMobileSignal'
|
||||
import { BotContext, InitialChatReply, OutgoingLog } from '@/types'
|
||||
@ -12,7 +12,8 @@ import {
|
||||
} from '@/utils/storage'
|
||||
import { setCssVariablesValue } from '@/utils/setCssVariablesValue'
|
||||
import immutableCss from '../assets/immutable.css'
|
||||
import { InputBlock, StartElementId } from '@typebot.io/schemas'
|
||||
import { InputBlock } from '@typebot.io/schemas'
|
||||
import { StartFrom } from '@typebot.io/schemas'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
export type BotProps = {
|
||||
@ -27,7 +28,8 @@ export type BotProps = {
|
||||
onInit?: () => void
|
||||
onEnd?: () => void
|
||||
onNewLogs?: (logs: OutgoingLog[]) => void
|
||||
} & StartElementId
|
||||
startFrom?: StartFrom
|
||||
}
|
||||
|
||||
export const Bot = (props: BotProps & { class?: string }) => {
|
||||
const [initialChatReply, setInitialChatReply] = createSignal<
|
||||
@ -47,11 +49,13 @@ export const Bot = (props: BotProps & { class?: string }) => {
|
||||
})
|
||||
const typebotIdFromProps =
|
||||
typeof props.typebot === 'string' ? props.typebot : undefined
|
||||
const { data, error } = await getInitialChatReplyQuery({
|
||||
const isPreview =
|
||||
typeof props.typebot !== 'string' || (props.isPreview ?? false)
|
||||
const { data, error } = await startChatQuery({
|
||||
stripeRedirectStatus: urlParams.get('redirect_status') ?? undefined,
|
||||
typebot: props.typebot,
|
||||
apiHost: props.apiHost,
|
||||
isPreview: props.isPreview ?? false,
|
||||
isPreview,
|
||||
resultId: isNotEmpty(props.resultId)
|
||||
? props.resultId
|
||||
: getExistingResultIdFromStorage(typebotIdFromProps),
|
||||
@ -59,14 +63,10 @@ export const Bot = (props: BotProps & { class?: string }) => {
|
||||
...prefilledVariables,
|
||||
...props.prefilledVariables,
|
||||
},
|
||||
...('startGroupId' in props
|
||||
? { startGroupId: props.startGroupId }
|
||||
: 'startEventId' in props
|
||||
? { startEventId: props.startEventId }
|
||||
: {}),
|
||||
startFrom: props.startFrom,
|
||||
})
|
||||
if (error && 'code' in error && typeof error.code === 'string') {
|
||||
if (typeof props.typebot !== 'string' || (props.isPreview ?? false)) {
|
||||
if (isPreview) {
|
||||
return setError(
|
||||
new Error('An error occurred while loading the bot.', {
|
||||
cause: error.message,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BotContext, ChatChunk as ChatChunkType } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { ChatReply, Settings, Theme } from '@typebot.io/schemas'
|
||||
import { ContinueChatResponse, Settings, Theme } from '@typebot.io/schemas'
|
||||
import { createSignal, For, onMount, Show } from 'solid-js'
|
||||
import { HostBubble } from '../bubbles/HostBubble'
|
||||
import { InputChatBlock } from '../InputChatBlock'
|
||||
@ -9,7 +9,7 @@ import { StreamingBubble } from '../bubbles/StreamingBubble'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
||||
|
||||
type Props = Pick<ChatReply, 'messages' | 'input'> & {
|
||||
type Props = Pick<ContinueChatResponse, 'messages' | 'input'> & {
|
||||
theme: Theme
|
||||
settings: Settings
|
||||
inputIndex: number
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {
|
||||
ChatReply,
|
||||
ContinueChatResponse,
|
||||
InputBlock,
|
||||
SendMessageInput,
|
||||
Theme,
|
||||
ChatLog,
|
||||
} from '@typebot.io/schemas'
|
||||
import {
|
||||
createEffect,
|
||||
@ -12,7 +12,7 @@ import {
|
||||
onMount,
|
||||
Show,
|
||||
} from 'solid-js'
|
||||
import { sendMessageQuery } from '@/queries/sendMessageQuery'
|
||||
import { continueChatQuery } from '@/queries/continueChatQuery'
|
||||
import { ChatChunk } from './ChatChunk'
|
||||
import {
|
||||
BotContext,
|
||||
@ -30,10 +30,11 @@ import {
|
||||
setFormattedMessages,
|
||||
} from '@/utils/formattedMessagesSignal'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
import { saveClientLogsQuery } from '@/queries/saveClientLogsQuery'
|
||||
|
||||
const parseDynamicTheme = (
|
||||
initialTheme: Theme,
|
||||
dynamicTheme: ChatReply['dynamicTheme']
|
||||
dynamicTheme: ContinueChatResponse['dynamicTheme']
|
||||
): Theme => ({
|
||||
...initialTheme,
|
||||
chat: {
|
||||
@ -74,7 +75,7 @@ export const ConversationContainer = (props: Props) => {
|
||||
},
|
||||
])
|
||||
const [dynamicTheme, setDynamicTheme] = createSignal<
|
||||
ChatReply['dynamicTheme']
|
||||
ContinueChatResponse['dynamicTheme']
|
||||
>(props.initialChatReply.dynamicTheme)
|
||||
const [theme, setTheme] = createSignal(props.initialChatReply.typebot.theme)
|
||||
const [isSending, setIsSending] = createSignal(false)
|
||||
@ -136,9 +137,16 @@ export const ConversationContainer = (props: Props) => {
|
||||
|
||||
const sendMessage = async (
|
||||
message: string | undefined,
|
||||
clientLogs?: SendMessageInput['clientLogs']
|
||||
clientLogs?: ChatLog[]
|
||||
) => {
|
||||
if (clientLogs) props.onNewLogs?.(clientLogs)
|
||||
if (clientLogs) {
|
||||
props.onNewLogs?.(clientLogs)
|
||||
await saveClientLogsQuery({
|
||||
apiHost: props.context.apiHost,
|
||||
sessionId: props.initialChatReply.sessionId,
|
||||
clientLogs,
|
||||
})
|
||||
}
|
||||
setHasError(false)
|
||||
const currentInputBlock = [...chatChunks()].pop()?.input
|
||||
if (currentInputBlock?.id && props.onAnswer && message)
|
||||
@ -153,11 +161,10 @@ export const ConversationContainer = (props: Props) => {
|
||||
const longRequest = setTimeout(() => {
|
||||
setIsSending(true)
|
||||
}, 1000)
|
||||
const { data, error } = await sendMessageQuery({
|
||||
const { data, error } = await continueChatQuery({
|
||||
apiHost: props.context.apiHost,
|
||||
sessionId: props.initialChatReply.sessionId,
|
||||
message,
|
||||
clientLogs,
|
||||
})
|
||||
clearTimeout(longRequest)
|
||||
setIsSending(false)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type {
|
||||
ChatReply,
|
||||
ContinueChatResponse,
|
||||
ChoiceInputBlock,
|
||||
EmailInputBlock,
|
||||
FileInputBlock,
|
||||
@ -39,7 +39,7 @@ import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constan
|
||||
|
||||
type Props = {
|
||||
ref: HTMLDivElement | undefined
|
||||
block: NonNullable<ChatReply['input']>
|
||||
block: NonNullable<ContinueChatResponse['input']>
|
||||
hasHostAvatar: boolean
|
||||
guestAvatar?: NonNullable<Theme['chat']>['guestAvatar']
|
||||
inputIndex: number
|
||||
@ -113,7 +113,7 @@ export const InputChatBlock = (props: Props) => {
|
||||
|
||||
const Input = (props: {
|
||||
context: BotContext
|
||||
block: NonNullable<ChatReply['input']>
|
||||
block: NonNullable<ContinueChatResponse['input']>
|
||||
inputIndex: number
|
||||
isInputPrefillEnabled: boolean
|
||||
onSubmit: (answer: InputSubmitContent) => void
|
||||
@ -252,11 +252,11 @@ const Input = (props: {
|
||||
}
|
||||
|
||||
const isButtonsBlock = (
|
||||
block: ChatReply['input']
|
||||
block: ContinueChatResponse['input']
|
||||
): ChoiceInputBlock | undefined =>
|
||||
block?.type === InputBlockType.CHOICE ? block : undefined
|
||||
|
||||
const isPictureChoiceBlock = (
|
||||
block: ChatReply['input']
|
||||
block: ContinueChatResponse['input']
|
||||
): PictureChoiceBlock | undefined =>
|
||||
block?.type === InputBlockType.PICTURE_CHOICE ? block : undefined
|
||||
|
@ -10,7 +10,7 @@ export const defaultBotProps: BotProps = {
|
||||
onInit: undefined,
|
||||
onNewLogs: undefined,
|
||||
isPreview: undefined,
|
||||
startGroupId: undefined,
|
||||
startFrom: undefined,
|
||||
prefilledVariables: undefined,
|
||||
apiHost: undefined,
|
||||
resultId: undefined,
|
||||
|
22
packages/embeds/js/src/queries/continueChatQuery.ts
Normal file
22
packages/embeds/js/src/queries/continueChatQuery.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { guessApiHost } from '@/utils/guessApiHost'
|
||||
import { isNotEmpty, sendRequest } from '@typebot.io/lib'
|
||||
import { ContinueChatResponse } from '@typebot.io/schemas'
|
||||
|
||||
export const continueChatQuery = ({
|
||||
apiHost,
|
||||
message,
|
||||
sessionId,
|
||||
}: {
|
||||
apiHost?: string
|
||||
message: string | undefined
|
||||
sessionId: string
|
||||
}) =>
|
||||
sendRequest<ContinueChatResponse>({
|
||||
method: 'POST',
|
||||
url: `${
|
||||
isNotEmpty(apiHost) ? apiHost : guessApiHost()
|
||||
}/api/v1/sessions/${sessionId}/continueChat`,
|
||||
body: {
|
||||
message,
|
||||
},
|
||||
})
|
@ -1,74 +0,0 @@
|
||||
import { BotContext, InitialChatReply } from '@/types'
|
||||
import { guessApiHost } from '@/utils/guessApiHost'
|
||||
import type {
|
||||
SendMessageInput,
|
||||
StartElementId,
|
||||
StartParams,
|
||||
} from '@typebot.io/schemas'
|
||||
import { isNotDefined, isNotEmpty, sendRequest } from '@typebot.io/lib'
|
||||
import {
|
||||
getPaymentInProgressInStorage,
|
||||
removePaymentInProgressFromStorage,
|
||||
} from '@/features/blocks/inputs/payment/helpers/paymentInProgressStorage'
|
||||
|
||||
export async function getInitialChatReplyQuery({
|
||||
typebot,
|
||||
isPreview,
|
||||
apiHost,
|
||||
prefilledVariables,
|
||||
resultId,
|
||||
stripeRedirectStatus,
|
||||
...props
|
||||
}: StartParams & {
|
||||
stripeRedirectStatus?: string
|
||||
apiHost?: string
|
||||
} & StartElementId) {
|
||||
if (isNotDefined(typebot))
|
||||
throw new Error('Typebot ID is required to get initial messages')
|
||||
|
||||
const paymentInProgressStateStr = getPaymentInProgressInStorage() ?? undefined
|
||||
const paymentInProgressState = paymentInProgressStateStr
|
||||
? (JSON.parse(paymentInProgressStateStr) as {
|
||||
sessionId: string
|
||||
typebot: BotContext['typebot']
|
||||
})
|
||||
: undefined
|
||||
if (paymentInProgressState) removePaymentInProgressFromStorage()
|
||||
const { data, error } = await sendRequest<InitialChatReply>({
|
||||
method: 'POST',
|
||||
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v2/sendMessage`,
|
||||
body: {
|
||||
startParams: paymentInProgressState
|
||||
? undefined
|
||||
: {
|
||||
isPreview,
|
||||
typebot,
|
||||
prefilledVariables,
|
||||
resultId,
|
||||
isStreamEnabled: true,
|
||||
startGroupId:
|
||||
'startGroupId' in props ? props.startGroupId : undefined,
|
||||
startEventId:
|
||||
'startEventId' in props ? props.startEventId : undefined,
|
||||
},
|
||||
sessionId: paymentInProgressState?.sessionId,
|
||||
message: paymentInProgressState
|
||||
? stripeRedirectStatus === 'failed'
|
||||
? 'fail'
|
||||
: 'Success'
|
||||
: undefined,
|
||||
} satisfies SendMessageInput,
|
||||
})
|
||||
|
||||
return {
|
||||
data: data
|
||||
? {
|
||||
...data,
|
||||
...(paymentInProgressState
|
||||
? { typebot: paymentInProgressState.typebot }
|
||||
: {}),
|
||||
}
|
||||
: undefined,
|
||||
error,
|
||||
}
|
||||
}
|
22
packages/embeds/js/src/queries/saveClientLogsQuery.ts
Normal file
22
packages/embeds/js/src/queries/saveClientLogsQuery.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { guessApiHost } from '@/utils/guessApiHost'
|
||||
import type { ChatLog } from '@typebot.io/schemas'
|
||||
import { isNotEmpty, sendRequest } from '@typebot.io/lib'
|
||||
|
||||
export const saveClientLogsQuery = ({
|
||||
apiHost,
|
||||
sessionId,
|
||||
clientLogs,
|
||||
}: {
|
||||
apiHost?: string
|
||||
sessionId: string
|
||||
clientLogs: ChatLog[]
|
||||
}) =>
|
||||
sendRequest({
|
||||
method: 'POST',
|
||||
url: `${
|
||||
isNotEmpty(apiHost) ? apiHost : guessApiHost()
|
||||
}/api/v1/sessions/${sessionId}/clientLogs`,
|
||||
body: {
|
||||
clientLogs,
|
||||
},
|
||||
})
|
@ -1,13 +0,0 @@
|
||||
import { guessApiHost } from '@/utils/guessApiHost'
|
||||
import type { ChatReply, SendMessageInput } from '@typebot.io/schemas'
|
||||
import { isNotEmpty, sendRequest } from '@typebot.io/lib'
|
||||
|
||||
export const sendMessageQuery = ({
|
||||
apiHost,
|
||||
...body
|
||||
}: SendMessageInput & { apiHost?: string }) =>
|
||||
sendRequest<ChatReply>({
|
||||
method: 'POST',
|
||||
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v2/sendMessage`,
|
||||
body,
|
||||
})
|
104
packages/embeds/js/src/queries/startChatQuery.ts
Normal file
104
packages/embeds/js/src/queries/startChatQuery.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { BotContext, InitialChatReply } from '@/types'
|
||||
import { guessApiHost } from '@/utils/guessApiHost'
|
||||
import { isNotDefined, isNotEmpty, sendRequest } from '@typebot.io/lib'
|
||||
import {
|
||||
getPaymentInProgressInStorage,
|
||||
removePaymentInProgressFromStorage,
|
||||
} from '@/features/blocks/inputs/payment/helpers/paymentInProgressStorage'
|
||||
import {
|
||||
StartChatInput,
|
||||
StartFrom,
|
||||
StartPreviewChatInput,
|
||||
} from '@typebot.io/schemas'
|
||||
|
||||
export async function startChatQuery({
|
||||
typebot,
|
||||
isPreview,
|
||||
apiHost,
|
||||
prefilledVariables,
|
||||
resultId,
|
||||
stripeRedirectStatus,
|
||||
startFrom,
|
||||
}: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
typebot: string | any
|
||||
stripeRedirectStatus?: string
|
||||
apiHost?: string
|
||||
startFrom?: StartFrom
|
||||
isPreview: boolean
|
||||
prefilledVariables?: Record<string, unknown>
|
||||
resultId?: string
|
||||
}) {
|
||||
if (isNotDefined(typebot))
|
||||
throw new Error('Typebot ID is required to get initial messages')
|
||||
|
||||
const paymentInProgressStateStr = getPaymentInProgressInStorage() ?? undefined
|
||||
const paymentInProgressState = paymentInProgressStateStr
|
||||
? (JSON.parse(paymentInProgressStateStr) as {
|
||||
sessionId: string
|
||||
typebot: BotContext['typebot']
|
||||
})
|
||||
: undefined
|
||||
if (paymentInProgressState) {
|
||||
removePaymentInProgressFromStorage()
|
||||
const { data, error } = await sendRequest<InitialChatReply>({
|
||||
method: 'POST',
|
||||
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/sessions/${
|
||||
paymentInProgressState.sessionId
|
||||
}/continueChat`,
|
||||
body: {
|
||||
message: paymentInProgressState
|
||||
? stripeRedirectStatus === 'failed'
|
||||
? 'fail'
|
||||
: 'Success'
|
||||
: undefined,
|
||||
},
|
||||
})
|
||||
return {
|
||||
data: data
|
||||
? {
|
||||
...data,
|
||||
...(paymentInProgressState
|
||||
? { typebot: paymentInProgressState.typebot }
|
||||
: {}),
|
||||
}
|
||||
: undefined,
|
||||
error,
|
||||
}
|
||||
}
|
||||
const typebotId = typeof typebot === 'string' ? typebot : typebot.id
|
||||
if (isPreview) {
|
||||
const { data, error } = await sendRequest<InitialChatReply>({
|
||||
method: 'POST',
|
||||
url: `${
|
||||
isNotEmpty(apiHost) ? apiHost : guessApiHost()
|
||||
}/api/v1/typebots/${typebotId}/preview/startChat`,
|
||||
body: {
|
||||
isStreamEnabled: true,
|
||||
startFrom,
|
||||
typebot,
|
||||
} satisfies Omit<StartPreviewChatInput, 'typebotId'>,
|
||||
})
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
}
|
||||
}
|
||||
|
||||
const { data, error } = await sendRequest<InitialChatReply>({
|
||||
method: 'POST',
|
||||
url: `${
|
||||
isNotEmpty(apiHost) ? apiHost : guessApiHost()
|
||||
}/api/v1/typebots/${typebotId}/startChat`,
|
||||
body: {
|
||||
isStreamEnabled: true,
|
||||
prefilledVariables,
|
||||
resultId,
|
||||
} satisfies Omit<StartChatInput, 'publicId'>,
|
||||
})
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import type { ChatReply } from '@typebot.io/schemas'
|
||||
import { ContinueChatResponse, StartChatResponse } from '@typebot.io/schemas'
|
||||
|
||||
export type InputSubmitContent = {
|
||||
label?: string
|
||||
@ -13,9 +13,9 @@ export type BotContext = {
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export type InitialChatReply = ChatReply & {
|
||||
typebot: NonNullable<ChatReply['typebot']>
|
||||
sessionId: NonNullable<ChatReply['sessionId']>
|
||||
export type InitialChatReply = StartChatResponse & {
|
||||
typebot: NonNullable<StartChatResponse['typebot']>
|
||||
sessionId: NonNullable<StartChatResponse['sessionId']>
|
||||
}
|
||||
|
||||
export type OutgoingLog = {
|
||||
@ -30,7 +30,7 @@ export type ClientSideActionContext = {
|
||||
}
|
||||
|
||||
export type ChatChunk = Pick<
|
||||
ChatReply,
|
||||
ContinueChatResponse,
|
||||
'messages' | 'input' | 'clientSideActions'
|
||||
> & {
|
||||
streamingMessageId?: string
|
||||
|
@ -8,11 +8,11 @@ import { executeWait } from '@/features/blocks/logic/wait/utils/executeWait'
|
||||
import { executeWebhook } from '@/features/blocks/integrations/webhook/executeWebhook'
|
||||
import { executePixel } from '@/features/blocks/integrations/pixel/executePixel'
|
||||
import { ClientSideActionContext } from '@/types'
|
||||
import type { ChatReply, ReplyLog } from '@typebot.io/schemas'
|
||||
import type { ContinueChatResponse, ChatLog } from '@typebot.io/schemas'
|
||||
import { injectStartProps } from './injectStartProps'
|
||||
|
||||
type Props = {
|
||||
clientSideAction: NonNullable<ChatReply['clientSideActions']>[0]
|
||||
clientSideAction: NonNullable<ContinueChatResponse['clientSideActions']>[0]
|
||||
context: ClientSideActionContext
|
||||
onMessageStream?: (props: { id: string; message: string }) => void
|
||||
}
|
||||
@ -23,7 +23,7 @@ export const executeClientSideAction = async ({
|
||||
onMessageStream,
|
||||
}: Props): Promise<
|
||||
| { blockedPopupUrl: string }
|
||||
| { replyToSend: string | undefined; logs?: ReplyLog[] }
|
||||
| { replyToSend: string | undefined; logs?: ChatLog[] }
|
||||
| void
|
||||
> => {
|
||||
if ('chatwoot' in clientSideAction) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/nextjs",
|
||||
"version": "0.2.15",
|
||||
"version": "0.2.16",
|
||||
"description": "Convenient library to display typebots on your Next.js website",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/react",
|
||||
"version": "0.2.15",
|
||||
"version": "0.2.16",
|
||||
"description": "Convenient library to display typebots on your React app",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
16
packages/lib/mockedUser.ts
Normal file
16
packages/lib/mockedUser.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { User } from '@typebot.io/prisma'
|
||||
|
||||
export const mockedUser: User = {
|
||||
id: 'userId',
|
||||
name: 'John Doe',
|
||||
email: 'user@email.com',
|
||||
company: null,
|
||||
createdAt: new Date('2022-01-01'),
|
||||
emailVerified: null,
|
||||
graphNavigation: 'TRACKPAD',
|
||||
preferredAppAppearance: null,
|
||||
image: 'https://avatars.githubusercontent.com/u/16015833?v=4',
|
||||
lastActivityAt: new Date('2022-01-01'),
|
||||
onboardingCategories: [],
|
||||
updatedAt: new Date('2022-01-01'),
|
||||
}
|
123
packages/schemas/features/chat/legacy/schema.ts
Normal file
123
packages/schemas/features/chat/legacy/schema.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
chatLogSchema,
|
||||
chatMessageSchema,
|
||||
clientSideActionSchema,
|
||||
runtimeOptionsSchema,
|
||||
startTypebotSchema,
|
||||
} from '../schema'
|
||||
import { typebotV5Schema, typebotV6Schema } from '../../typebot'
|
||||
import { dynamicThemeSchema } from '../shared'
|
||||
import { inputBlockSchemas } from '../../blocks'
|
||||
|
||||
export const startElementIdSchema = z.union([
|
||||
z.object({
|
||||
startGroupId: z.string().describe('Start chat from a specific group.'),
|
||||
startEventId: z.never().optional(),
|
||||
}),
|
||||
z.object({
|
||||
startEventId: z.string().describe('Start chat from a specific event.'),
|
||||
startGroupId: z.never().optional(),
|
||||
}),
|
||||
z.object({}),
|
||||
])
|
||||
export type StartElementId = z.infer<typeof startElementIdSchema>
|
||||
|
||||
const startParamsSchema = z
|
||||
.object({
|
||||
typebot: startTypebotSchema
|
||||
.or(z.string())
|
||||
.describe(
|
||||
'Either a Typebot ID or a Typebot object. If you provide a Typebot object, it will be executed in preview mode. ([How can I find my typebot ID?](https://docs.typebot.io/api#how-to-find-my-typebotid)).'
|
||||
),
|
||||
isPreview: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
"If set to `true`, it will start a Preview session with the unpublished bot and it won't be saved in the Results tab. You need to be authenticated with a bearer token for this to work."
|
||||
),
|
||||
resultId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Provide it if you'd like to overwrite an existing result."),
|
||||
|
||||
prefilledVariables: z
|
||||
.record(z.unknown())
|
||||
.optional()
|
||||
.describe(
|
||||
'[More info about prefilled variables.](https://docs.typebot.io/editor/variables#prefilled-variables)'
|
||||
),
|
||||
isStreamEnabled: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Set this to `true` if you intend to stream OpenAI completions on a client.'
|
||||
),
|
||||
isOnlyRegistering: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'If set to `true`, it will only register the session and not start the chat. This is used for other chat platform integration as it can require a session to be registered before sending the first message.'
|
||||
),
|
||||
})
|
||||
.and(startElementIdSchema)
|
||||
|
||||
export const sendMessageInputSchema = z.object({
|
||||
message: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'The answer to the previous chat input. Do not provide it if you are starting a new chat.'
|
||||
),
|
||||
sessionId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Session ID that you get from the initial chat request to a bot. If not provided, it will create a new session.'
|
||||
),
|
||||
startParams: startParamsSchema.optional(),
|
||||
clientLogs: z
|
||||
.array(chatLogSchema)
|
||||
.optional()
|
||||
.describe('Logs while executing client side actions'),
|
||||
})
|
||||
|
||||
export const chatReplySchema = z.object({
|
||||
messages: z.array(chatMessageSchema),
|
||||
input: z
|
||||
.union([
|
||||
z.discriminatedUnion('type', [...inputBlockSchemas.v5]),
|
||||
z.discriminatedUnion('type', [...inputBlockSchemas.v6]),
|
||||
])
|
||||
.and(
|
||||
z.object({
|
||||
prefilledValue: z.string().optional(),
|
||||
runtimeOptions: runtimeOptionsSchema.optional(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
clientSideActions: z.array(clientSideActionSchema).optional(),
|
||||
sessionId: z.string().optional(),
|
||||
typebot: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
theme: z.union([
|
||||
typebotV5Schema._def.schema.shape.theme,
|
||||
typebotV6Schema.shape.theme,
|
||||
]),
|
||||
settings: z.union([
|
||||
typebotV5Schema._def.schema.shape.settings,
|
||||
typebotV6Schema.shape.settings,
|
||||
]),
|
||||
})
|
||||
.optional(),
|
||||
resultId: z.string().optional(),
|
||||
dynamicTheme: dynamicThemeSchema.optional(),
|
||||
logs: z.array(chatLogSchema).optional(),
|
||||
lastMessageNewFormat: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'The sent message is validated and formatted on the backend. This is set only if the message differs from the formatted version.'
|
||||
),
|
||||
})
|
@ -29,6 +29,7 @@ const chatSessionSchema = z.object({
|
||||
updatedAt: z.date(),
|
||||
state: sessionStateSchema,
|
||||
})
|
||||
export type ChatSession = z.infer<typeof chatSessionSchema>
|
||||
|
||||
const textMessageSchema = z.object({
|
||||
type: z.literal(BubbleBlockType.TEXT),
|
||||
@ -59,7 +60,7 @@ const embedMessageSchema = z.object({
|
||||
.merge(z.object({ height: z.number().optional() })),
|
||||
})
|
||||
|
||||
const chatMessageSchema = z
|
||||
export const chatMessageSchema = z
|
||||
.object({ id: z.string() })
|
||||
.and(
|
||||
z.discriminatedUnion('type', [
|
||||
@ -70,6 +71,7 @@ const chatMessageSchema = z
|
||||
embedMessageSchema,
|
||||
])
|
||||
)
|
||||
export type ChatMessage = z.infer<typeof chatMessageSchema>
|
||||
|
||||
const scriptToExecuteSchema = z.object({
|
||||
content: z.string(),
|
||||
@ -85,6 +87,7 @@ const scriptToExecuteSchema = z.object({
|
||||
})
|
||||
),
|
||||
})
|
||||
export type ScriptToExecute = z.infer<typeof scriptToExecuteSchema>
|
||||
|
||||
const startTypebotPick = {
|
||||
version: true,
|
||||
@ -103,87 +106,72 @@ export const startTypebotSchema = z.preprocess(
|
||||
typebotV6Schema.pick(startTypebotPick),
|
||||
])
|
||||
)
|
||||
export type StartTypebot = z.infer<typeof startTypebotSchema>
|
||||
|
||||
export const startElementIdSchema = z.union([
|
||||
z.object({
|
||||
startGroupId: z.string().describe('Start chat from a specific group.'),
|
||||
startEventId: z.never().optional(),
|
||||
}),
|
||||
z.object({
|
||||
startEventId: z.string().describe('Start chat from a specific event.'),
|
||||
startGroupId: z.never().optional(),
|
||||
}),
|
||||
z.object({}),
|
||||
])
|
||||
export type StartElementId = z.infer<typeof startElementIdSchema>
|
||||
|
||||
const startParamsSchema = z
|
||||
.object({
|
||||
typebot: startTypebotSchema
|
||||
.or(z.string())
|
||||
.describe(
|
||||
'Either a Typebot ID or a Typebot object. If you provide a Typebot object, it will be executed in preview mode. ([How can I find my typebot ID?](https://docs.typebot.io/api#how-to-find-my-typebotid)).'
|
||||
),
|
||||
isPreview: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
"If set to `true`, it will start a Preview session with the unpublished bot and it won't be saved in the Results tab. You need to be authenticated with a bearer token for this to work."
|
||||
),
|
||||
resultId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Provide it if you'd like to overwrite an existing result."),
|
||||
|
||||
prefilledVariables: z
|
||||
.record(z.unknown())
|
||||
.optional()
|
||||
.describe(
|
||||
'[More info about prefilled variables.](https://docs.typebot.io/editor/variables#prefilled-variables)'
|
||||
),
|
||||
isStreamEnabled: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Set this to `true` if you intend to stream OpenAI completions on a client.'
|
||||
),
|
||||
isOnlyRegistering: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'If set to `true`, it will only register the session and not start the chat. This is used for other chat platform integration as it can require a session to be registered before sending the first message.'
|
||||
),
|
||||
})
|
||||
.and(startElementIdSchema)
|
||||
|
||||
const replyLogSchema = logSchema
|
||||
export const chatLogSchema = logSchema
|
||||
.pick({
|
||||
status: true,
|
||||
description: true,
|
||||
})
|
||||
.merge(z.object({ details: z.unknown().optional() }))
|
||||
export type ChatLog = z.infer<typeof chatLogSchema>
|
||||
|
||||
export const sendMessageInputSchema = z.object({
|
||||
message: z
|
||||
export const startChatInputSchema = z.object({
|
||||
publicId: z.string(),
|
||||
isStreamEnabled: z.boolean().optional(),
|
||||
message: z.string().optional(),
|
||||
resultId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'The answer to the previous chat input. Do not provide it if you are starting a new chat.'
|
||||
),
|
||||
sessionId: z
|
||||
.string()
|
||||
.describe("Provide it if you'd like to overwrite an existing result."),
|
||||
isOnlyRegistering: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Session ID that you get from the initial chat request to a bot. If not provided, it will create a new session.'
|
||||
'If set to `true`, it will only register the session and not start the bot. This is used for 3rd party chat platforms as it can require a session to be registered before sending the first message.'
|
||||
),
|
||||
startParams: startParamsSchema.optional(),
|
||||
clientLogs: z
|
||||
.array(replyLogSchema)
|
||||
prefilledVariables: z
|
||||
.record(z.unknown())
|
||||
.optional()
|
||||
.describe('Logs while executing client side actions'),
|
||||
.describe(
|
||||
'[More info about prefilled variables.](https://docs.typebot.io/editor/variables#prefilled-variables)'
|
||||
),
|
||||
})
|
||||
export type StartChatInput = z.infer<typeof startChatInputSchema>
|
||||
|
||||
const runtimeOptionsSchema = paymentInputRuntimeOptionsSchema.optional()
|
||||
export const startFromSchema = z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal('group'),
|
||||
groupId: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('event'),
|
||||
eventId: z.string(),
|
||||
}),
|
||||
])
|
||||
export type StartFrom = z.infer<typeof startFromSchema>
|
||||
|
||||
export const startPreviewChatInputSchema = z.object({
|
||||
typebotId: z.string(),
|
||||
isStreamEnabled: z.boolean().optional(),
|
||||
message: z.string().optional(),
|
||||
isOnlyRegistering: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'If set to `true`, it will only register the session and not start the bot. This is used for 3rd party chat platforms as it can require a session to be registered before sending the first message.'
|
||||
),
|
||||
typebot: startTypebotSchema
|
||||
.optional()
|
||||
.describe(
|
||||
'If set, it will override the typebot that is used to start the chat.'
|
||||
),
|
||||
startFrom: startFromSchema.optional(),
|
||||
})
|
||||
export type StartPreviewChatInput = z.infer<typeof startPreviewChatInputSchema>
|
||||
|
||||
export const runtimeOptionsSchema = paymentInputRuntimeOptionsSchema.optional()
|
||||
export type RuntimeOptions = z.infer<typeof runtimeOptionsSchema>
|
||||
|
||||
const startPropsToInjectSchema = z.object({
|
||||
googleAnalyticsId: z.string().optional(),
|
||||
@ -191,8 +179,9 @@ const startPropsToInjectSchema = z.object({
|
||||
gtmId: z.string().optional(),
|
||||
customHeadCode: z.string().optional(),
|
||||
})
|
||||
export type StartPropsToInject = z.infer<typeof startPropsToInjectSchema>
|
||||
|
||||
const clientSideActionSchema = z
|
||||
export const clientSideActionSchema = z
|
||||
.object({
|
||||
lastBubbleBlockId: z.string().optional(),
|
||||
expectsDedicatedReply: z.boolean().optional(),
|
||||
@ -271,7 +260,14 @@ export const typebotInChatReply = z.preprocess(
|
||||
typebotV6Schema.pick(typebotInChatReplyPick),
|
||||
])
|
||||
)
|
||||
export const chatReplySchema = z.object({
|
||||
|
||||
const chatResponseBaseSchema = z.object({
|
||||
lastMessageNewFormat: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'The sent message is validated and formatted on the backend. This is set only if the message differs from the formatted version.'
|
||||
),
|
||||
messages: z.array(chatMessageSchema),
|
||||
input: z
|
||||
.union([
|
||||
@ -286,39 +282,30 @@ export const chatReplySchema = z.object({
|
||||
)
|
||||
.optional(),
|
||||
clientSideActions: z.array(clientSideActionSchema).optional(),
|
||||
sessionId: z.string().optional(),
|
||||
typebot: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
theme: z.union([
|
||||
typebotV5Schema._def.schema.shape.theme,
|
||||
typebotV6Schema.shape.theme,
|
||||
]),
|
||||
settings: z.union([
|
||||
typebotV5Schema._def.schema.shape.settings,
|
||||
typebotV6Schema.shape.settings,
|
||||
]),
|
||||
})
|
||||
.optional(),
|
||||
resultId: z.string().optional(),
|
||||
logs: z.array(chatLogSchema).optional(),
|
||||
dynamicTheme: dynamicThemeSchema.optional(),
|
||||
logs: z.array(replyLogSchema).optional(),
|
||||
lastMessageNewFormat: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'The sent message is validated and formatted on the backend. This is set only if the message differs from the formatted version.'
|
||||
),
|
||||
})
|
||||
|
||||
export type ChatSession = z.infer<typeof chatSessionSchema>
|
||||
export const startChatResponseSchema = chatResponseBaseSchema.extend({
|
||||
sessionId: z.string().optional(),
|
||||
typebot: z.object({
|
||||
id: z.string(),
|
||||
theme: z.union([
|
||||
typebotV5Schema._def.schema.shape.theme,
|
||||
typebotV6Schema.shape.theme,
|
||||
]),
|
||||
settings: z.union([
|
||||
typebotV5Schema._def.schema.shape.settings,
|
||||
typebotV6Schema.shape.settings,
|
||||
]),
|
||||
}),
|
||||
resultId: z.string().optional(),
|
||||
})
|
||||
export type StartChatResponse = z.infer<typeof startChatResponseSchema>
|
||||
|
||||
export type ChatReply = z.infer<typeof chatReplySchema>
|
||||
export type ChatMessage = z.infer<typeof chatMessageSchema>
|
||||
export type SendMessageInput = z.infer<typeof sendMessageInputSchema>
|
||||
export type ScriptToExecute = z.infer<typeof scriptToExecuteSchema>
|
||||
export type StartParams = z.infer<typeof startParamsSchema>
|
||||
export type RuntimeOptions = z.infer<typeof runtimeOptionsSchema>
|
||||
export type StartTypebot = z.infer<typeof startTypebotSchema>
|
||||
export type ReplyLog = z.infer<typeof replyLogSchema>
|
||||
export type StartPropsToInject = z.infer<typeof startPropsToInjectSchema>
|
||||
export const startPreviewChatResponseSchema = startChatResponseSchema.omit({
|
||||
resultId: true,
|
||||
})
|
||||
|
||||
export const continueChatResponseSchema = chatResponseBaseSchema
|
||||
export type ContinueChatResponse = z.infer<typeof continueChatResponseSchema>
|
||||
|
Reference in New Issue
Block a user