@ -4,8 +4,8 @@ import * as Sentry from '@sentry/nextjs'
|
|||||||
import { User } from '@typebot.io/prisma'
|
import { User } from '@typebot.io/prisma'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { getServerSession } from 'next-auth'
|
import { getServerSession } from 'next-auth'
|
||||||
import { mockedUser } from '../mockedUser'
|
|
||||||
import { env } from '@typebot.io/env'
|
import { env } from '@typebot.io/env'
|
||||||
|
import { mockedUser } from '@typebot.io/lib/mockedUser'
|
||||||
|
|
||||||
export const getAuthenticatedUser = async (
|
export const getAuthenticatedUser = async (
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
|
@ -12,8 +12,6 @@ export const AudioBubbleNode = ({ url }: Props) => {
|
|||||||
return isDefined(url) ? (
|
return isDefined(url) ? (
|
||||||
<audio src={url} controls />
|
<audio src={url} controls />
|
||||||
) : (
|
) : (
|
||||||
<Text color={'gray.500'}>
|
<Text color={'gray.500'}>{t('clickToEdit')}</Text>
|
||||||
{t('editor.blocks.bubbles.audio.node.clickToEdit.text')}
|
|
||||||
</Text>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,6 @@ type Props = {
|
|||||||
export const EmbedBubbleContent = ({ block }: Props) => {
|
export const EmbedBubbleContent = ({ block }: Props) => {
|
||||||
const { t } = useTranslate()
|
const { t } = useTranslate()
|
||||||
if (!block.content?.url)
|
if (!block.content?.url)
|
||||||
return (
|
return <Text color="gray.500">{t('clickToEdit')}</Text>
|
||||||
<Text color="gray.500">
|
|
||||||
{t('editor.blocks.bubbles.embed.node.clickToEdit.text')}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
return <Text>{t('editor.blocks.bubbles.embed.node.show.text')}</Text>
|
return <Text>{t('editor.blocks.bubbles.embed.node.show.text')}</Text>
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,7 @@ export const ImageBubbleContent = ({ block }: Props) => {
|
|||||||
const containsVariables =
|
const containsVariables =
|
||||||
block.content?.url?.includes('{{') && block.content.url.includes('}}')
|
block.content?.url?.includes('{{') && block.content.url.includes('}}')
|
||||||
return !block.content?.url ? (
|
return !block.content?.url ? (
|
||||||
<Text color={'gray.500'}>
|
<Text color={'gray.500'}>{t('clickToEdit')}</Text>
|
||||||
{t('editor.blocks.bubbles.image.node.clickToEdit.text')}
|
|
||||||
</Text>
|
|
||||||
) : (
|
) : (
|
||||||
<Box w="full">
|
<Box w="full">
|
||||||
<Image
|
<Image
|
||||||
|
@ -13,11 +13,7 @@ type Props = {
|
|||||||
export const VideoBubbleContent = ({ block }: Props) => {
|
export const VideoBubbleContent = ({ block }: Props) => {
|
||||||
const { t } = useTranslate()
|
const { t } = useTranslate()
|
||||||
if (!block.content?.url || !block.content.type)
|
if (!block.content?.url || !block.content.type)
|
||||||
return (
|
return <Text color="gray.500">{t('clickToEdit')}</Text>
|
||||||
<Text color="gray.500">
|
|
||||||
{t('editor.blocks.bubbles.video.node.clickToEdit.text')}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
const containsVariables =
|
const containsVariables =
|
||||||
block.content?.url?.includes('{{') && block.content.url.includes('}}')
|
block.content?.url?.includes('{{') && block.content.url.includes('}}')
|
||||||
switch (block.content.type) {
|
switch (block.content.type) {
|
||||||
|
@ -32,7 +32,7 @@ test('should be configurable', async ({ page }) => {
|
|||||||
await expect(page.getByTestId('selected-item-label').first()).toHaveText(
|
await expect(page.getByTestId('selected-item-label').first()).toHaveText(
|
||||||
'My link typebot 2'
|
'My link typebot 2'
|
||||||
)
|
)
|
||||||
await page.click('input[placeholder="Select a block"]')
|
await page.click('input[placeholder="Select a group"]')
|
||||||
await page.click('text=Group #2')
|
await page.click('text=Group #2')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
|
@ -4,7 +4,7 @@ import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
|||||||
import { useGraph } from '@/features/graph/providers/GraphProvider'
|
import { useGraph } from '@/features/graph/providers/GraphProvider'
|
||||||
import { useToast } from '@/hooks/useToast'
|
import { useToast } from '@/hooks/useToast'
|
||||||
import { Standard } from '@typebot.io/nextjs'
|
import { Standard } from '@typebot.io/nextjs'
|
||||||
import { ChatReply } from '@typebot.io/schemas'
|
import { ContinueChatResponse } from '@typebot.io/schemas'
|
||||||
|
|
||||||
export const WebPreview = () => {
|
export const WebPreview = () => {
|
||||||
const { typebot } = useTypebot()
|
const { typebot } = useTypebot()
|
||||||
@ -13,7 +13,7 @@ export const WebPreview = () => {
|
|||||||
|
|
||||||
const { showToast } = useToast()
|
const { showToast } = useToast()
|
||||||
|
|
||||||
const handleNewLogs = (logs: ChatReply['logs']) => {
|
const handleNewLogs = (logs: ContinueChatResponse['logs']) => {
|
||||||
logs?.forEach((log) => {
|
logs?.forEach((log) => {
|
||||||
showToast({
|
showToast({
|
||||||
icon: <WebhookIcon />,
|
icon: <WebhookIcon />,
|
||||||
@ -40,8 +40,13 @@ export const WebPreview = () => {
|
|||||||
<Standard
|
<Standard
|
||||||
key={`web-preview${startPreviewAtGroup ?? ''}`}
|
key={`web-preview${startPreviewAtGroup ?? ''}`}
|
||||||
typebot={typebot}
|
typebot={typebot}
|
||||||
startGroupId={startPreviewAtGroup}
|
startFrom={
|
||||||
startEventId={startPreviewAtEvent}
|
startPreviewAtGroup
|
||||||
|
? { type: 'group', groupId: startPreviewAtGroup }
|
||||||
|
: startPreviewAtEvent
|
||||||
|
? { type: 'event', eventId: startPreviewAtEvent }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
onNewInputBlock={(block) =>
|
onNewInputBlock={(block) =>
|
||||||
setPreviewingBlock({
|
setPreviewingBlock({
|
||||||
id: block.id,
|
id: block.id,
|
||||||
|
@ -24,7 +24,7 @@ import { BuoyIcon, ExternalLinkIcon } from '@/components/icons'
|
|||||||
|
|
||||||
export const WhatsAppPreviewInstructions = (props: StackProps) => {
|
export const WhatsAppPreviewInstructions = (props: StackProps) => {
|
||||||
const { typebot, save } = useTypebot()
|
const { typebot, save } = useTypebot()
|
||||||
const { startPreviewAtGroup } = useEditor()
|
const { startPreviewAtGroup, startPreviewAtEvent } = useEditor()
|
||||||
const [phoneNumber, setPhoneNumber] = useState(
|
const [phoneNumber, setPhoneNumber] = useState(
|
||||||
getPhoneNumberFromLocalStorage() ?? ''
|
getPhoneNumberFromLocalStorage() ?? ''
|
||||||
)
|
)
|
||||||
@ -56,7 +56,11 @@ export const WhatsAppPreviewInstructions = (props: StackProps) => {
|
|||||||
mutate({
|
mutate({
|
||||||
to: phoneNumber,
|
to: phoneNumber,
|
||||||
typebotId: typebot.id,
|
typebotId: typebot.id,
|
||||||
startGroupId: startPreviewAtGroup,
|
startFrom: startPreviewAtGroup
|
||||||
|
? { type: 'group', groupId: startPreviewAtGroup }
|
||||||
|
: startPreviewAtEvent
|
||||||
|
? { type: 'event', eventId: startPreviewAtEvent }
|
||||||
|
: undefined,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import { restartSession } from '@typebot.io/bot-engine/queries/restartSession'
|
|||||||
import { sendChatReplyToWhatsApp } from '@typebot.io/bot-engine/whatsapp/sendChatReplyToWhatsApp'
|
import { sendChatReplyToWhatsApp } from '@typebot.io/bot-engine/whatsapp/sendChatReplyToWhatsApp'
|
||||||
import { sendWhatsAppMessage } from '@typebot.io/bot-engine/whatsapp/sendWhatsAppMessage'
|
import { sendWhatsAppMessage } from '@typebot.io/bot-engine/whatsapp/sendWhatsAppMessage'
|
||||||
import { isReadTypebotForbidden } from '../typebot/helpers/isReadTypebotForbidden'
|
import { isReadTypebotForbidden } from '../typebot/helpers/isReadTypebotForbidden'
|
||||||
import { SessionState } from '@typebot.io/schemas'
|
import { SessionState, startFromSchema } from '@typebot.io/schemas'
|
||||||
|
|
||||||
export const startWhatsAppPreview = authenticatedProcedure
|
export const startWhatsAppPreview = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -31,7 +31,7 @@ export const startWhatsAppPreview = authenticatedProcedure
|
|||||||
value.replace(/\s/g, '').replace(/\+/g, '').replace(/-/g, '')
|
value.replace(/\s/g, '').replace(/\+/g, '').replace(/-/g, '')
|
||||||
),
|
),
|
||||||
typebotId: z.string(),
|
typebotId: z.string(),
|
||||||
startGroupId: z.string().optional(),
|
startFrom: startFromSchema.optional(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(
|
.output(
|
||||||
@ -39,135 +39,133 @@ export const startWhatsAppPreview = authenticatedProcedure
|
|||||||
message: z.string(),
|
message: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(
|
.mutation(async ({ input: { to, typebotId, startFrom }, ctx: { user } }) => {
|
||||||
async ({ input: { to, typebotId, startGroupId }, ctx: { user } }) => {
|
if (
|
||||||
if (
|
!env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID ||
|
||||||
!env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID ||
|
!env.META_SYSTEM_USER_TOKEN ||
|
||||||
!env.META_SYSTEM_USER_TOKEN ||
|
!env.WHATSAPP_PREVIEW_TEMPLATE_NAME
|
||||||
!env.WHATSAPP_PREVIEW_TEMPLATE_NAME
|
)
|
||||||
)
|
throw new TRPCError({
|
||||||
throw new TRPCError({
|
code: 'BAD_REQUEST',
|
||||||
code: 'BAD_REQUEST',
|
message:
|
||||||
message:
|
'Missing WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID or META_SYSTEM_USER_TOKEN or WHATSAPP_PREVIEW_TEMPLATE_NAME env variables',
|
||||||
'Missing WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID or META_SYSTEM_USER_TOKEN or WHATSAPP_PREVIEW_TEMPLATE_NAME env variables',
|
})
|
||||||
})
|
|
||||||
|
|
||||||
const existingTypebot = await prisma.typebot.findFirst({
|
const existingTypebot = await prisma.typebot.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: typebotId,
|
id: typebotId,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
workspaceId: true,
|
workspaceId: true,
|
||||||
collaborators: {
|
collaborators: {
|
||||||
select: {
|
select: {
|
||||||
userId: true,
|
userId: true,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
if (
|
})
|
||||||
!existingTypebot?.id ||
|
if (
|
||||||
(await isReadTypebotForbidden(existingTypebot, user))
|
!existingTypebot?.id ||
|
||||||
)
|
(await isReadTypebotForbidden(existingTypebot, user))
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
)
|
||||||
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
||||||
|
|
||||||
const sessionId = `wa-preview-${to}`
|
const sessionId = `wa-preview-${to}`
|
||||||
|
|
||||||
const existingSession = await prisma.chatSession.findFirst({
|
const existingSession = await prisma.chatSession.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
state: true,
|
state: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// For users that did not interact with the bot in the last 24 hours, we need to send a template message.
|
// For users that did not interact with the bot in the last 24 hours, we need to send a template message.
|
||||||
const canSendDirectMessagesToUser =
|
const canSendDirectMessagesToUser =
|
||||||
(existingSession?.updatedAt.getTime() ?? 0) >
|
(existingSession?.updatedAt.getTime() ?? 0) >
|
||||||
Date.now() - 24 * 60 * 60 * 1000
|
Date.now() - 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
const {
|
const {
|
||||||
newSessionState,
|
newSessionState,
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
clientSideActions,
|
||||||
|
logs,
|
||||||
|
visitedEdges,
|
||||||
|
} = await startSession({
|
||||||
|
version: 2,
|
||||||
|
message: undefined,
|
||||||
|
startParams: {
|
||||||
|
isOnlyRegistering: !canSendDirectMessagesToUser,
|
||||||
|
type: 'preview',
|
||||||
|
typebotId,
|
||||||
|
startFrom,
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
initialSessionState: {
|
||||||
|
whatsApp: (existingSession?.state as SessionState | undefined)
|
||||||
|
?.whatsApp,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (canSendDirectMessagesToUser) {
|
||||||
|
await sendChatReplyToWhatsApp({
|
||||||
|
to,
|
||||||
|
typingEmulation: newSessionState.typingEmulation,
|
||||||
messages,
|
messages,
|
||||||
input,
|
input,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
logs,
|
credentials: {
|
||||||
visitedEdges,
|
phoneNumberId: env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID,
|
||||||
} = await startSession({
|
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
|
||||||
version: 2,
|
|
||||||
message: undefined,
|
|
||||||
startParams: {
|
|
||||||
isOnlyRegistering: !canSendDirectMessagesToUser,
|
|
||||||
typebot: typebotId,
|
|
||||||
isPreview: true,
|
|
||||||
startGroupId,
|
|
||||||
},
|
|
||||||
userId: user.id,
|
|
||||||
initialSessionState: {
|
|
||||||
whatsApp: (existingSession?.state as SessionState | undefined)
|
|
||||||
?.whatsApp,
|
|
||||||
},
|
},
|
||||||
|
state: newSessionState,
|
||||||
})
|
})
|
||||||
|
await saveStateToDatabase({
|
||||||
if (canSendDirectMessagesToUser) {
|
clientSideActions: [],
|
||||||
await sendChatReplyToWhatsApp({
|
input,
|
||||||
|
logs,
|
||||||
|
session: {
|
||||||
|
id: sessionId,
|
||||||
|
state: newSessionState,
|
||||||
|
},
|
||||||
|
visitedEdges,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await restartSession({
|
||||||
|
state: newSessionState,
|
||||||
|
id: sessionId,
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
await sendWhatsAppMessage({
|
||||||
to,
|
to,
|
||||||
typingEmulation: newSessionState.typingEmulation,
|
message: {
|
||||||
messages,
|
type: 'template',
|
||||||
input,
|
template: {
|
||||||
clientSideActions,
|
language: {
|
||||||
|
code: env.WHATSAPP_PREVIEW_TEMPLATE_LANG,
|
||||||
|
},
|
||||||
|
name: env.WHATSAPP_PREVIEW_TEMPLATE_NAME,
|
||||||
|
},
|
||||||
|
},
|
||||||
credentials: {
|
credentials: {
|
||||||
phoneNumberId: env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID,
|
phoneNumberId: env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID,
|
||||||
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
|
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
|
||||||
},
|
},
|
||||||
state: newSessionState,
|
|
||||||
})
|
})
|
||||||
await saveStateToDatabase({
|
} catch (err) {
|
||||||
clientSideActions: [],
|
if (err instanceof HTTPError) console.log(err.response.body)
|
||||||
input,
|
throw new TRPCError({
|
||||||
logs,
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
session: {
|
message: 'Request to Meta to send preview message failed',
|
||||||
id: sessionId,
|
cause: err,
|
||||||
state: newSessionState,
|
|
||||||
},
|
|
||||||
visitedEdges,
|
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
await restartSession({
|
|
||||||
state: newSessionState,
|
|
||||||
id: sessionId,
|
|
||||||
})
|
|
||||||
try {
|
|
||||||
await sendWhatsAppMessage({
|
|
||||||
to,
|
|
||||||
message: {
|
|
||||||
type: 'template',
|
|
||||||
template: {
|
|
||||||
language: {
|
|
||||||
code: env.WHATSAPP_PREVIEW_TEMPLATE_LANG,
|
|
||||||
},
|
|
||||||
name: env.WHATSAPP_PREVIEW_TEMPLATE_NAME,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
credentials: {
|
|
||||||
phoneNumberId: env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID,
|
|
||||||
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof HTTPError) console.log(err.response.body)
|
|
||||||
throw new TRPCError({
|
|
||||||
code: 'INTERNAL_SERVER_ERROR',
|
|
||||||
message: 'Request to Meta to send preview message failed',
|
|
||||||
cause: err,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
message: 'success',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
return {
|
||||||
|
message: 'success',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ -11,7 +11,7 @@ import { NextApiRequest, NextApiResponse } from 'next'
|
|||||||
import { customAdapter } from '../../../features/auth/api/customAdapter'
|
import { customAdapter } from '../../../features/auth/api/customAdapter'
|
||||||
import { User } from '@typebot.io/prisma'
|
import { User } from '@typebot.io/prisma'
|
||||||
import { getAtPath, isDefined } from '@typebot.io/lib'
|
import { getAtPath, isDefined } from '@typebot.io/lib'
|
||||||
import { mockedUser } from '@/features/auth/mockedUser'
|
import { mockedUser } from '@typebot.io/lib/mockedUser'
|
||||||
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
|
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
|
||||||
import { sendVerificationRequest } from '@/features/auth/helpers/sendVerificationRequest'
|
import { sendVerificationRequest } from '@/features/auth/helpers/sendVerificationRequest'
|
||||||
import { Ratelimit } from '@upstash/ratelimit'
|
import { Ratelimit } from '@upstash/ratelimit'
|
||||||
|
@ -624,7 +624,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -634,6 +636,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -3929,7 +3937,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -3939,6 +3949,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -8007,7 +8023,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -8017,6 +8035,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -11708,7 +11732,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -11718,6 +11744,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -15497,7 +15529,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -15507,6 +15541,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -19195,7 +19235,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -19205,6 +19247,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -22918,7 +22966,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -22928,6 +22978,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -26647,7 +26703,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -26657,6 +26715,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -30203,7 +30267,6 @@
|
|||||||
"version",
|
"version",
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
"events",
|
|
||||||
"groups",
|
"groups",
|
||||||
"edges",
|
"edges",
|
||||||
"variables",
|
"variables",
|
||||||
@ -30425,7 +30488,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -30435,6 +30500,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -34213,7 +34284,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -34223,6 +34296,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -37878,7 +37957,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -37888,6 +37969,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -41678,7 +41765,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -41688,6 +41777,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -45166,10 +45261,6 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
"publicId": {
|
|
||||||
"type": "string",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"resultsTablePreferences": {
|
"resultsTablePreferences": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -45208,15 +45299,13 @@
|
|||||||
"required": [
|
"required": [
|
||||||
"version",
|
"version",
|
||||||
"name",
|
"name",
|
||||||
"events",
|
|
||||||
"groups",
|
"groups",
|
||||||
"edges",
|
"edges",
|
||||||
"variables",
|
"variables",
|
||||||
"theme",
|
"theme",
|
||||||
"settings",
|
"settings",
|
||||||
"icon",
|
"icon",
|
||||||
"folderId",
|
"folderId"
|
||||||
"publicId"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
@ -45418,7 +45507,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -45428,6 +45519,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -48816,10 +48913,6 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
"publicId": {
|
|
||||||
"type": "string",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"resultsTablePreferences": {
|
"resultsTablePreferences": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -48865,8 +48958,7 @@
|
|||||||
"theme",
|
"theme",
|
||||||
"settings",
|
"settings",
|
||||||
"icon",
|
"icon",
|
||||||
"folderId",
|
"folderId"
|
||||||
"publicId"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
@ -49092,7 +49184,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"url",
|
"url",
|
||||||
"youtube",
|
"youtube",
|
||||||
"vimeo"
|
"vimeo",
|
||||||
|
"tiktok",
|
||||||
|
"gumlet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
@ -49102,6 +49196,12 @@
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"aspectRatio": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -55954,8 +56054,47 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"startGroupId": {
|
"startFrom": {
|
||||||
"type": "string"
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"groupId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"groupId"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"event"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"eventId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"eventId"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
File diff suppressed because it is too large
Load Diff
79
apps/viewer/src/features/chat/api/continueChat.ts
Normal file
79
apps/viewer/src/features/chat/api/continueChat.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { publicProcedure } from '@/helpers/server/trpc'
|
||||||
|
import { continueChatResponseSchema } from '@typebot.io/schemas/features/chat/schema'
|
||||||
|
import { TRPCError } from '@trpc/server'
|
||||||
|
import { getSession } from '@typebot.io/bot-engine/queries/getSession'
|
||||||
|
import { saveStateToDatabase } from '@typebot.io/bot-engine/saveStateToDatabase'
|
||||||
|
import { continueBotFlow } from '@typebot.io/bot-engine/continueBotFlow'
|
||||||
|
import { parseDynamicTheme } from '@typebot.io/bot-engine/parseDynamicTheme'
|
||||||
|
import { isDefined } from '@typebot.io/lib/utils'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const continueChat = publicProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/v1/sessions/{sessionId}/continueChat',
|
||||||
|
summary: 'Continue chat',
|
||||||
|
description:
|
||||||
|
'To initiate a chat, do not provide a `sessionId` nor a `message`.\n\nContinue the conversation by providing the `sessionId` and the `message` that should answer the previous question.\n\nSet the `isPreview` option to `true` to chat with the non-published version of the typebot.',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
message: z.string().optional(),
|
||||||
|
sessionId: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(continueChatResponseSchema)
|
||||||
|
.mutation(async ({ input: { sessionId, message } }) => {
|
||||||
|
const session = await getSession(sessionId)
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'NOT_FOUND',
|
||||||
|
message: 'Session not found.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSessionExpired =
|
||||||
|
session &&
|
||||||
|
isDefined(session.state.expiryTimeout) &&
|
||||||
|
session.updatedAt.getTime() + session.state.expiryTimeout < Date.now()
|
||||||
|
|
||||||
|
if (isSessionExpired)
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'NOT_FOUND',
|
||||||
|
message: 'Session expired. You need to start a new session.',
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
clientSideActions,
|
||||||
|
newSessionState,
|
||||||
|
logs,
|
||||||
|
lastMessageNewFormat,
|
||||||
|
visitedEdges,
|
||||||
|
} = await continueBotFlow(message, { version: 2, state: session.state })
|
||||||
|
|
||||||
|
if (newSessionState)
|
||||||
|
await saveStateToDatabase({
|
||||||
|
session: {
|
||||||
|
id: session.id,
|
||||||
|
state: newSessionState,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
visitedEdges,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
clientSideActions,
|
||||||
|
dynamicTheme: parseDynamicTheme(newSessionState),
|
||||||
|
logs,
|
||||||
|
lastMessageNewFormat,
|
||||||
|
}
|
||||||
|
})
|
@ -1,8 +1,8 @@
|
|||||||
import { publicProcedure } from '@/helpers/server/trpc'
|
import { publicProcedure } from '@/helpers/server/trpc'
|
||||||
import {
|
import {
|
||||||
chatReplySchema,
|
|
||||||
sendMessageInputSchema,
|
sendMessageInputSchema,
|
||||||
} from '@typebot.io/schemas/features/chat/schema'
|
chatReplySchema,
|
||||||
|
} from '@typebot.io/schemas/features/chat/legacy/schema'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { getSession } from '@typebot.io/bot-engine/queries/getSession'
|
import { getSession } from '@typebot.io/bot-engine/queries/getSession'
|
||||||
import { startSession } from '@typebot.io/bot-engine/startSession'
|
import { startSession } from '@typebot.io/bot-engine/startSession'
|
||||||
@ -16,10 +16,12 @@ export const sendMessageV1 = publicProcedure
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/sendMessage',
|
path: '/v1/sendMessage',
|
||||||
summary: 'Send a message',
|
summary: 'Send a message',
|
||||||
description:
|
description:
|
||||||
'To initiate a chat, do not provide a `sessionId` nor a `message`.\n\nContinue the conversation by providing the `sessionId` and the `message` that should answer the previous question.\n\nSet the `isPreview` option to `true` to chat with the non-published version of the typebot.',
|
'To initiate a chat, do not provide a `sessionId` nor a `message`.\n\nContinue the conversation by providing the `sessionId` and the `message` that should answer the previous question.\n\nSet the `isPreview` option to `true` to chat with the non-published version of the typebot.',
|
||||||
|
tags: ['Deprecated'],
|
||||||
|
deprecated: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(sendMessageInputSchema)
|
.input(sendMessageInputSchema)
|
||||||
@ -60,8 +62,45 @@ export const sendMessageV1 = publicProcedure
|
|||||||
visitedEdges,
|
visitedEdges,
|
||||||
} = await startSession({
|
} = await startSession({
|
||||||
version: 1,
|
version: 1,
|
||||||
startParams,
|
startParams:
|
||||||
userId: user?.id,
|
startParams.isPreview || typeof startParams.typebot !== 'string'
|
||||||
|
? {
|
||||||
|
type: 'preview',
|
||||||
|
isOnlyRegistering: startParams.isOnlyRegistering ?? false,
|
||||||
|
isStreamEnabled: startParams.isStreamEnabled,
|
||||||
|
startFrom:
|
||||||
|
'startGroupId' in startParams && startParams.startGroupId
|
||||||
|
? {
|
||||||
|
type: 'group',
|
||||||
|
groupId: startParams.startGroupId,
|
||||||
|
}
|
||||||
|
: 'startEventId' in startParams &&
|
||||||
|
startParams.startEventId
|
||||||
|
? {
|
||||||
|
type: 'event',
|
||||||
|
eventId: startParams.startEventId,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
typebotId:
|
||||||
|
typeof startParams.typebot === 'string'
|
||||||
|
? startParams.typebot
|
||||||
|
: startParams.typebot.id,
|
||||||
|
typebot:
|
||||||
|
typeof startParams.typebot === 'string'
|
||||||
|
? undefined
|
||||||
|
: startParams.typebot,
|
||||||
|
message,
|
||||||
|
userId: parseUserId(user?.id),
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'live',
|
||||||
|
isOnlyRegistering: startParams.isOnlyRegistering ?? false,
|
||||||
|
isStreamEnabled: startParams.isStreamEnabled,
|
||||||
|
publicId: startParams.typebot,
|
||||||
|
prefilledVariables: startParams.prefilledVariables,
|
||||||
|
resultId: startParams.resultId,
|
||||||
|
message,
|
||||||
|
},
|
||||||
message,
|
message,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -133,3 +172,13 @@ export const sendMessageV1 = publicProcedure
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const parseUserId = (userId?: string): string => {
|
||||||
|
if (!userId)
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'UNAUTHORIZED',
|
||||||
|
message: 'You need to be authenticated to perform this action',
|
||||||
|
})
|
||||||
|
|
||||||
|
return userId
|
||||||
|
}
|
@ -1,8 +1,4 @@
|
|||||||
import { publicProcedure } from '@/helpers/server/trpc'
|
import { publicProcedure } from '@/helpers/server/trpc'
|
||||||
import {
|
|
||||||
chatReplySchema,
|
|
||||||
sendMessageInputSchema,
|
|
||||||
} from '@typebot.io/schemas/features/chat/schema'
|
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { getSession } from '@typebot.io/bot-engine/queries/getSession'
|
import { getSession } from '@typebot.io/bot-engine/queries/getSession'
|
||||||
import { startSession } from '@typebot.io/bot-engine/startSession'
|
import { startSession } from '@typebot.io/bot-engine/startSession'
|
||||||
@ -11,15 +7,21 @@ import { restartSession } from '@typebot.io/bot-engine/queries/restartSession'
|
|||||||
import { continueBotFlow } from '@typebot.io/bot-engine/continueBotFlow'
|
import { continueBotFlow } from '@typebot.io/bot-engine/continueBotFlow'
|
||||||
import { parseDynamicTheme } from '@typebot.io/bot-engine/parseDynamicTheme'
|
import { parseDynamicTheme } from '@typebot.io/bot-engine/parseDynamicTheme'
|
||||||
import { isDefined } from '@typebot.io/lib/utils'
|
import { isDefined } from '@typebot.io/lib/utils'
|
||||||
|
import {
|
||||||
|
chatReplySchema,
|
||||||
|
sendMessageInputSchema,
|
||||||
|
} from '@typebot.io/schemas/features/chat/legacy/schema'
|
||||||
|
|
||||||
export const sendMessageV2 = publicProcedure
|
export const sendMessageV2 = publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/sendMessage',
|
path: '/v2/sendMessage',
|
||||||
summary: 'Send a message',
|
summary: 'Send a message',
|
||||||
description:
|
description:
|
||||||
'To initiate a chat, do not provide a `sessionId` nor a `message`.\n\nContinue the conversation by providing the `sessionId` and the `message` that should answer the previous question.\n\nSet the `isPreview` option to `true` to chat with the non-published version of the typebot.',
|
'To initiate a chat, do not provide a `sessionId` nor a `message`.\n\nContinue the conversation by providing the `sessionId` and the `message` that should answer the previous question.\n\nSet the `isPreview` option to `true` to chat with the non-published version of the typebot.',
|
||||||
|
tags: ['Deprecated'],
|
||||||
|
deprecated: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(sendMessageInputSchema)
|
.input(sendMessageInputSchema)
|
||||||
@ -60,8 +62,45 @@ export const sendMessageV2 = publicProcedure
|
|||||||
visitedEdges,
|
visitedEdges,
|
||||||
} = await startSession({
|
} = await startSession({
|
||||||
version: 2,
|
version: 2,
|
||||||
startParams,
|
startParams:
|
||||||
userId: user?.id,
|
startParams.isPreview || typeof startParams.typebot !== 'string'
|
||||||
|
? {
|
||||||
|
type: 'preview',
|
||||||
|
isOnlyRegistering: startParams.isOnlyRegistering ?? false,
|
||||||
|
isStreamEnabled: startParams.isStreamEnabled,
|
||||||
|
startFrom:
|
||||||
|
'startGroupId' in startParams && startParams.startGroupId
|
||||||
|
? {
|
||||||
|
type: 'group',
|
||||||
|
groupId: startParams.startGroupId,
|
||||||
|
}
|
||||||
|
: 'startEventId' in startParams &&
|
||||||
|
startParams.startEventId
|
||||||
|
? {
|
||||||
|
type: 'event',
|
||||||
|
eventId: startParams.startEventId,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
typebotId:
|
||||||
|
typeof startParams.typebot === 'string'
|
||||||
|
? startParams.typebot
|
||||||
|
: startParams.typebot.id,
|
||||||
|
typebot:
|
||||||
|
typeof startParams.typebot === 'string'
|
||||||
|
? undefined
|
||||||
|
: startParams.typebot,
|
||||||
|
message,
|
||||||
|
userId: parseUserId(user?.id),
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'live',
|
||||||
|
isOnlyRegistering: startParams.isOnlyRegistering ?? false,
|
||||||
|
isStreamEnabled: startParams.isStreamEnabled,
|
||||||
|
publicId: startParams.typebot,
|
||||||
|
prefilledVariables: startParams.prefilledVariables,
|
||||||
|
resultId: startParams.resultId,
|
||||||
|
message,
|
||||||
|
},
|
||||||
message,
|
message,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -133,3 +172,13 @@ export const sendMessageV2 = publicProcedure
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const parseUserId = (userId?: string): string => {
|
||||||
|
if (!userId)
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'UNAUTHORIZED',
|
||||||
|
message: 'You need to be authenticated to perform this action',
|
||||||
|
})
|
||||||
|
|
||||||
|
return userId
|
||||||
|
}
|
63
apps/viewer/src/features/chat/api/saveClientLogs.ts
Normal file
63
apps/viewer/src/features/chat/api/saveClientLogs.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { publicProcedure } from '@/helpers/server/trpc'
|
||||||
|
import { chatLogSchema } from '@typebot.io/schemas/features/chat/schema'
|
||||||
|
import { TRPCError } from '@trpc/server'
|
||||||
|
import { getSession } from '@typebot.io/bot-engine/queries/getSession'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { saveLogs } from '@typebot.io/bot-engine/queries/saveLogs'
|
||||||
|
import { formatLogDetails } from '@typebot.io/bot-engine/logs/helpers/formatLogDetails'
|
||||||
|
import * as Sentry from '@sentry/nextjs'
|
||||||
|
|
||||||
|
export const saveClientLogs = publicProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/v1/sessions/{sessionId}/clientLogs',
|
||||||
|
summary: 'Save client logs',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
sessionId: z.string(),
|
||||||
|
clientLogs: z.array(chatLogSchema),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(z.object({ message: z.string() }))
|
||||||
|
.mutation(async ({ input: { sessionId, clientLogs } }) => {
|
||||||
|
const session = await getSession(sessionId)
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'NOT_FOUND',
|
||||||
|
message: 'Session not found.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultId = session.state.typebotsQueue[0].resultId
|
||||||
|
|
||||||
|
if (!resultId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'NOT_FOUND',
|
||||||
|
message: 'Result not found.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await saveLogs(
|
||||||
|
clientLogs.map((log) => ({
|
||||||
|
...log,
|
||||||
|
resultId,
|
||||||
|
details: formatLogDetails(log.details),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
message: 'Logs successfully saved.',
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to save logs', e)
|
||||||
|
Sentry.captureException(e)
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
|
message: 'Failed to save logs.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
83
apps/viewer/src/features/chat/api/startChat.ts
Normal file
83
apps/viewer/src/features/chat/api/startChat.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { publicProcedure } from '@/helpers/server/trpc'
|
||||||
|
import {
|
||||||
|
startChatInputSchema,
|
||||||
|
startChatResponseSchema,
|
||||||
|
} from '@typebot.io/schemas/features/chat/schema'
|
||||||
|
import { startSession } from '@typebot.io/bot-engine/startSession'
|
||||||
|
import { saveStateToDatabase } from '@typebot.io/bot-engine/saveStateToDatabase'
|
||||||
|
import { restartSession } from '@typebot.io/bot-engine/queries/restartSession'
|
||||||
|
|
||||||
|
export const startChat = publicProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/v1/typebots/{publicId}/startChat',
|
||||||
|
summary: 'Start chat',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(startChatInputSchema)
|
||||||
|
.output(startChatResponseSchema)
|
||||||
|
.mutation(
|
||||||
|
async ({
|
||||||
|
input: {
|
||||||
|
message,
|
||||||
|
isOnlyRegistering,
|
||||||
|
publicId,
|
||||||
|
isStreamEnabled,
|
||||||
|
prefilledVariables,
|
||||||
|
resultId: startResultId,
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
typebot,
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
resultId,
|
||||||
|
dynamicTheme,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
newSessionState,
|
||||||
|
visitedEdges,
|
||||||
|
} = await startSession({
|
||||||
|
version: 2,
|
||||||
|
startParams: {
|
||||||
|
type: 'live',
|
||||||
|
isOnlyRegistering,
|
||||||
|
isStreamEnabled,
|
||||||
|
publicId,
|
||||||
|
prefilledVariables,
|
||||||
|
resultId: startResultId,
|
||||||
|
},
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
|
||||||
|
const session = isOnlyRegistering
|
||||||
|
? await restartSession({
|
||||||
|
state: newSessionState,
|
||||||
|
})
|
||||||
|
: await saveStateToDatabase({
|
||||||
|
session: {
|
||||||
|
state: newSessionState,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
visitedEdges,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
sessionId: session.id,
|
||||||
|
typebot: {
|
||||||
|
id: typebot.id,
|
||||||
|
theme: typebot.theme,
|
||||||
|
settings: typebot.settings,
|
||||||
|
},
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
resultId,
|
||||||
|
dynamicTheme,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
83
apps/viewer/src/features/chat/api/startChatPreview.ts
Normal file
83
apps/viewer/src/features/chat/api/startChatPreview.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
|
import {
|
||||||
|
startPreviewChatInputSchema,
|
||||||
|
startPreviewChatResponseSchema,
|
||||||
|
} from '@typebot.io/schemas/features/chat/schema'
|
||||||
|
import { startSession } from '@typebot.io/bot-engine/startSession'
|
||||||
|
import { saveStateToDatabase } from '@typebot.io/bot-engine/saveStateToDatabase'
|
||||||
|
import { restartSession } from '@typebot.io/bot-engine/queries/restartSession'
|
||||||
|
|
||||||
|
export const startChatPreview = authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/v1/typebots/{typebotId}/preview/startChat',
|
||||||
|
summary: 'Start preview chat',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(startPreviewChatInputSchema)
|
||||||
|
.output(startPreviewChatResponseSchema)
|
||||||
|
.mutation(
|
||||||
|
async ({
|
||||||
|
input: {
|
||||||
|
message,
|
||||||
|
isOnlyRegistering,
|
||||||
|
isStreamEnabled,
|
||||||
|
startFrom,
|
||||||
|
typebotId,
|
||||||
|
typebot: startTypebot,
|
||||||
|
},
|
||||||
|
ctx: { user },
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
typebot,
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
dynamicTheme,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
newSessionState,
|
||||||
|
visitedEdges,
|
||||||
|
} = await startSession({
|
||||||
|
version: 2,
|
||||||
|
startParams: {
|
||||||
|
type: 'preview',
|
||||||
|
isOnlyRegistering,
|
||||||
|
isStreamEnabled,
|
||||||
|
startFrom,
|
||||||
|
typebotId,
|
||||||
|
typebot: startTypebot,
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
|
||||||
|
const session = isOnlyRegistering
|
||||||
|
? await restartSession({
|
||||||
|
state: newSessionState,
|
||||||
|
})
|
||||||
|
: await saveStateToDatabase({
|
||||||
|
session: {
|
||||||
|
state: newSessionState,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
visitedEdges,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
sessionId: session.id,
|
||||||
|
typebot: {
|
||||||
|
id: typebot.id,
|
||||||
|
theme: typebot.theme,
|
||||||
|
settings: typebot.settings,
|
||||||
|
},
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
dynamicTheme,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
@ -1,4 +1,3 @@
|
|||||||
import { publicProcedure } from '@/helpers/server/trpc'
|
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { getSession } from '@typebot.io/bot-engine/queries/getSession'
|
import { getSession } from '@typebot.io/bot-engine/queries/getSession'
|
||||||
@ -9,12 +8,13 @@ import {
|
|||||||
Variable,
|
Variable,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
|
|
||||||
export const updateTypebotInSession = publicProcedure
|
export const updateTypebotInSession = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/sessions/{sessionId}/updateTypebot',
|
path: '/v1/sessions/{sessionId}/updateTypebot',
|
||||||
summary: 'Update typebot in session',
|
summary: 'Update typebot in session',
|
||||||
description:
|
description:
|
||||||
'Update chat session with latest typebot modifications. This is useful when you want to update the typebot in an ongoing session after making changes to it.',
|
'Update chat session with latest typebot modifications. This is useful when you want to update the typebot in an ongoing session after making changes to it.',
|
||||||
@ -28,8 +28,6 @@ export const updateTypebotInSession = publicProcedure
|
|||||||
)
|
)
|
||||||
.output(z.object({ message: z.literal('success') }))
|
.output(z.object({ message: z.literal('success') }))
|
||||||
.mutation(async ({ input: { sessionId }, ctx: { user } }) => {
|
.mutation(async ({ input: { sessionId }, ctx: { user } }) => {
|
||||||
if (!user)
|
|
||||||
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Unauthorized' })
|
|
||||||
const session = await getSession(sessionId)
|
const session = await getSession(sessionId)
|
||||||
if (!session)
|
if (!session)
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Session not found' })
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Session not found' })
|
||||||
|
@ -19,10 +19,11 @@ export const getUploadUrl = publicProcedure
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/typebots/{typebotId}/blocks/{blockId}/storage/upload-url',
|
path: '/v1/typebots/{typebotId}/blocks/{blockId}/storage/upload-url',
|
||||||
summary: 'Get upload URL for a file',
|
summary: 'Get upload URL for a file',
|
||||||
description: 'Used for the web client to get the bucket upload file.',
|
description: 'Used for the web client to get the bucket upload file.',
|
||||||
deprecated: true,
|
deprecated: true,
|
||||||
|
tags: ['Deprecated'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(
|
.input(
|
||||||
|
@ -13,7 +13,7 @@ export const generateUploadUrl = publicProcedure
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/generate-upload-url',
|
path: '/v1/generate-upload-url',
|
||||||
summary: 'Generate upload URL',
|
summary: 'Generate upload URL',
|
||||||
description: 'Used to upload anything from the client to S3 bucket',
|
description: 'Used to upload anything from the client to S3 bucket',
|
||||||
},
|
},
|
||||||
|
@ -8,7 +8,7 @@ export const receiveMessage = publicProcedure
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/workspaces/{workspaceId}/whatsapp/{credentialsId}/webhook',
|
path: '/v1/workspaces/{workspaceId}/whatsapp/{credentialsId}/webhook',
|
||||||
summary: 'Message webhook',
|
summary: 'Message webhook',
|
||||||
tags: ['WhatsApp'],
|
tags: ['WhatsApp'],
|
||||||
},
|
},
|
||||||
|
@ -7,7 +7,7 @@ export const subscribeWebhook = publicProcedure
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/workspaces/{workspaceId}/whatsapp/{credentialsId}/webhook',
|
path: '/v1/workspaces/{workspaceId}/whatsapp/{credentialsId}/webhook',
|
||||||
summary: 'Subscribe webhook',
|
summary: 'Subscribe webhook',
|
||||||
tags: ['WhatsApp'],
|
tags: ['WhatsApp'],
|
||||||
protect: true,
|
protect: true,
|
||||||
|
26
apps/viewer/src/helpers/server/appRouter.ts
Normal file
26
apps/viewer/src/helpers/server/appRouter.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { sendMessageV1 } from '@/features/chat/api/legacy/sendMessageV1'
|
||||||
|
import { whatsAppRouter } from '@/features/whatsapp/api/router'
|
||||||
|
import { router } from './trpc'
|
||||||
|
import { updateTypebotInSession } from '@/features/chat/api/updateTypebotInSession'
|
||||||
|
import { getUploadUrl } from '@/features/fileUpload/api/deprecated/getUploadUrl'
|
||||||
|
import { generateUploadUrl } from '@/features/fileUpload/api/generateUploadUrl'
|
||||||
|
import { sendMessageV2 } from '@/features/chat/api/legacy/sendMessageV2'
|
||||||
|
import { continueChat } from '@/features/chat/api/continueChat'
|
||||||
|
import { saveClientLogs } from '@/features/chat/api/saveClientLogs'
|
||||||
|
import { startChat } from '@/features/chat/api/startChat'
|
||||||
|
import { startChatPreview } from '@/features/chat/api/startChatPreview'
|
||||||
|
|
||||||
|
export const appRouter = router({
|
||||||
|
sendMessageV1,
|
||||||
|
sendMessageV2,
|
||||||
|
startChat,
|
||||||
|
continueChat,
|
||||||
|
startChatPreview: startChatPreview,
|
||||||
|
getUploadUrl,
|
||||||
|
generateUploadUrl,
|
||||||
|
updateTypebotInSession,
|
||||||
|
whatsAppRouter,
|
||||||
|
saveClientLogs,
|
||||||
|
})
|
||||||
|
|
||||||
|
export type AppRouter = typeof appRouter
|
@ -3,6 +3,8 @@ import { inferAsyncReturnType } from '@trpc/server'
|
|||||||
import * as trpcNext from '@trpc/server/adapters/next'
|
import * as trpcNext from '@trpc/server/adapters/next'
|
||||||
import { User } from '@typebot.io/prisma'
|
import { User } from '@typebot.io/prisma'
|
||||||
import { NextApiRequest } from 'next'
|
import { NextApiRequest } from 'next'
|
||||||
|
import { mockedUser } from '@typebot.io/lib/mockedUser'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export async function createContext(opts: trpcNext.CreateNextContextOptions) {
|
export async function createContext(opts: trpcNext.CreateNextContextOptions) {
|
||||||
const user = await getAuthenticatedUser(opts.req)
|
const user = await getAuthenticatedUser(opts.req)
|
||||||
@ -15,6 +17,7 @@ export async function createContext(opts: trpcNext.CreateNextContextOptions) {
|
|||||||
const getAuthenticatedUser = async (
|
const getAuthenticatedUser = async (
|
||||||
req: NextApiRequest
|
req: NextApiRequest
|
||||||
): Promise<User | undefined> => {
|
): Promise<User | undefined> => {
|
||||||
|
if (env.NEXT_PUBLIC_E2E_TEST) return mockedUser
|
||||||
const bearerToken = extractBearerToken(req)
|
const bearerToken = extractBearerToken(req)
|
||||||
if (!bearerToken) return
|
if (!bearerToken) return
|
||||||
return authenticateByToken(bearerToken)
|
return authenticateByToken(bearerToken)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { generateOpenApiDocument } from 'trpc-openapi'
|
import { generateOpenApiDocument } from 'trpc-openapi'
|
||||||
import { writeFileSync } from 'fs'
|
import { writeFileSync } from 'fs'
|
||||||
import { appRouter } from './routers/appRouterV2'
|
import { appRouter } from './appRouter'
|
||||||
|
|
||||||
const openApiDocument = generateOpenApiDocument(appRouter, {
|
const openApiDocument = generateOpenApiDocument(appRouter, {
|
||||||
title: 'Chat API',
|
title: 'Chat API',
|
||||||
version: '2.0.0',
|
version: '3.0.0',
|
||||||
baseUrl: 'https://typebot.io/api/v2',
|
baseUrl: 'https://typebot.io/api',
|
||||||
docsUrl: 'https://docs.typebot.io/api',
|
docsUrl: 'https://docs.typebot.io/api',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
import { sendMessageV1 } from '@/features/chat/api/sendMessageV1'
|
|
||||||
import { whatsAppRouter } from '@/features/whatsapp/api/router'
|
|
||||||
import { router } from '../trpc'
|
|
||||||
import { updateTypebotInSession } from '@/features/chat/api/updateTypebotInSession'
|
|
||||||
import { getUploadUrl } from '@/features/fileUpload/api/deprecated/getUploadUrl'
|
|
||||||
import { generateUploadUrl } from '@/features/fileUpload/api/generateUploadUrl'
|
|
||||||
|
|
||||||
export const appRouter = router({
|
|
||||||
sendMessageV1,
|
|
||||||
getUploadUrl,
|
|
||||||
generateUploadUrl,
|
|
||||||
updateTypebotInSession,
|
|
||||||
whatsAppRouter,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter
|
|
@ -1,16 +0,0 @@
|
|||||||
import { sendMessageV2 } from '@/features/chat/api/sendMessageV2'
|
|
||||||
import { whatsAppRouter } from '@/features/whatsapp/api/router'
|
|
||||||
import { router } from '../trpc'
|
|
||||||
import { updateTypebotInSession } from '@/features/chat/api/updateTypebotInSession'
|
|
||||||
import { getUploadUrl } from '@/features/fileUpload/api/deprecated/getUploadUrl'
|
|
||||||
import { generateUploadUrl } from '@/features/fileUpload/api/generateUploadUrl'
|
|
||||||
|
|
||||||
export const appRouter = router({
|
|
||||||
sendMessageV2,
|
|
||||||
getUploadUrl,
|
|
||||||
generateUploadUrl,
|
|
||||||
updateTypebotInSession,
|
|
||||||
whatsAppRouter,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter
|
|
@ -1,4 +1,4 @@
|
|||||||
import { initTRPC } from '@trpc/server'
|
import { TRPCError, initTRPC } from '@trpc/server'
|
||||||
import { OpenApiMeta } from 'trpc-openapi'
|
import { OpenApiMeta } from 'trpc-openapi'
|
||||||
import superjson from 'superjson'
|
import superjson from 'superjson'
|
||||||
import { Context } from './context'
|
import { Context } from './context'
|
||||||
@ -8,13 +8,23 @@ const t = initTRPC.context<Context>().meta<OpenApiMeta>().create({
|
|||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const router = t.router
|
||||||
|
|
||||||
const sentryMiddleware = t.middleware(
|
const sentryMiddleware = t.middleware(
|
||||||
Sentry.Handlers.trpcMiddleware({
|
Sentry.Handlers.trpcMiddleware({
|
||||||
attachRpcInput: true,
|
attachRpcInput: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const injectUser = t.middleware(({ next, ctx }) => {
|
export const publicProcedure = t.procedure.use(sentryMiddleware)
|
||||||
|
|
||||||
|
const isAuthed = t.middleware(({ next, ctx }) => {
|
||||||
|
if (!ctx.user?.id) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'UNAUTHORIZED',
|
||||||
|
message: 'You need to be authenticated to perform this action',
|
||||||
|
})
|
||||||
|
}
|
||||||
return next({
|
return next({
|
||||||
ctx: {
|
ctx: {
|
||||||
user: ctx.user,
|
user: ctx.user,
|
||||||
@ -22,10 +32,6 @@ const injectUser = t.middleware(({ next, ctx }) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const finalMiddleware = sentryMiddleware.unstable_pipe(injectUser)
|
export const authenticatedProcedure = t.procedure.use(
|
||||||
|
sentryMiddleware.unstable_pipe(isAuthed)
|
||||||
export const middleware = t.middleware
|
)
|
||||||
|
|
||||||
export const router = t.router
|
|
||||||
|
|
||||||
export const publicProcedure = t.procedure.use(finalMiddleware)
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { appRouter } from '@/helpers/server/routers/appRouterV1'
|
import { appRouter } from '@/helpers/server/appRouter'
|
||||||
import * as Sentry from '@sentry/nextjs'
|
import * as Sentry from '@sentry/nextjs'
|
||||||
import { createOpenApiNextHandler } from 'trpc-openapi'
|
import { createOpenApiNextHandler } from 'trpc-openapi'
|
||||||
import cors from 'nextjs-cors'
|
import cors from 'nextjs-cors'
|
@ -1,23 +0,0 @@
|
|||||||
import { appRouter } from '@/helpers/server/routers/appRouterV2'
|
|
||||||
import * as Sentry from '@sentry/nextjs'
|
|
||||||
import { createOpenApiNextHandler } from 'trpc-openapi'
|
|
||||||
import cors from 'nextjs-cors'
|
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
|
||||||
import { createContext } from '@/helpers/server/context'
|
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
await cors(req, res)
|
|
||||||
|
|
||||||
return createOpenApiNextHandler({
|
|
||||||
router: appRouter,
|
|
||||||
createContext,
|
|
||||||
onError({ error }) {
|
|
||||||
if (error.code === 'INTERNAL_SERVER_ERROR') {
|
|
||||||
Sentry.captureException(error)
|
|
||||||
console.error('Something went wrong', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})(req, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default handler
|
|
@ -2,7 +2,6 @@ import { getTestAsset } from '@/test/utils/playwright'
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
import { SendMessageInput } from '@typebot.io/schemas'
|
|
||||||
import {
|
import {
|
||||||
createWebhook,
|
createWebhook,
|
||||||
deleteTypebots,
|
deleteTypebots,
|
||||||
@ -10,6 +9,7 @@ import {
|
|||||||
importTypebotInDatabase,
|
importTypebotInDatabase,
|
||||||
} from '@typebot.io/lib/playwright/databaseActions'
|
} from '@typebot.io/lib/playwright/databaseActions'
|
||||||
import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
|
import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
|
||||||
|
import { StartChatInput, StartPreviewChatInput } from '@typebot.io/schemas'
|
||||||
|
|
||||||
test.afterEach(async () => {
|
test.afterEach(async () => {
|
||||||
await deleteWebhooks(['chat-webhook-id'])
|
await deleteWebhooks(['chat-webhook-id'])
|
||||||
@ -40,17 +40,18 @@ test('API chat execution should work on preview bot', async ({ request }) => {
|
|||||||
url: 'https://api.chucknorris.io/jokes/random',
|
url: 'https://api.chucknorris.io/jokes/random',
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Start the chat', async () => {
|
let chatSessionId: string
|
||||||
|
|
||||||
|
await test.step('Can start and continue chat', async () => {
|
||||||
const { sessionId, messages, input, resultId } = await (
|
const { sessionId, messages, input, resultId } = await (
|
||||||
await request.post(`/api/v2/sendMessage`, {
|
await request.post(`/api/v1/typebots/${typebotId}/preview/startChat`, {
|
||||||
data: {
|
data: {
|
||||||
startParams: {
|
isOnlyRegistering: false,
|
||||||
typebot: typebotId,
|
isStreamEnabled: false,
|
||||||
isPreview: true,
|
} satisfies Omit<StartPreviewChatInput, 'typebotId'>,
|
||||||
},
|
|
||||||
} satisfies SendMessageInput,
|
|
||||||
})
|
})
|
||||||
).json()
|
).json()
|
||||||
|
chatSessionId = sessionId
|
||||||
expect(resultId).toBeUndefined()
|
expect(resultId).toBeUndefined()
|
||||||
expect(sessionId).toBeDefined()
|
expect(sessionId).toBeDefined()
|
||||||
expect(messages[0].content.richText).toStrictEqual([
|
expect(messages[0].content.richText).toStrictEqual([
|
||||||
@ -61,6 +62,38 @@ test('API chat execution should work on preview bot', async ({ request }) => {
|
|||||||
])
|
])
|
||||||
expect(input.type).toBe('text input')
|
expect(input.type).toBe('text input')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await test.step('Can answer Name question', async () => {
|
||||||
|
const { messages, input } = await (
|
||||||
|
await request.post(`/api/v1/sessions/${chatSessionId}/continueChat`, {
|
||||||
|
data: {
|
||||||
|
message: 'John',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).json()
|
||||||
|
expect(messages[0].content.richText).toStrictEqual([
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{ text: 'Nice to meet you ' },
|
||||||
|
{
|
||||||
|
type: 'inline-variable',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'p',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: 'John',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'p',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(input.type).toBe('number input')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('API chat execution should work on published bot', async ({ request }) => {
|
test('API chat execution should work on published bot', async ({ request }) => {
|
||||||
@ -83,12 +116,11 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Start the chat', async () => {
|
await test.step('Start the chat', async () => {
|
||||||
const { sessionId, messages, input, resultId } = await (
|
const { sessionId, messages, input, resultId } = await (
|
||||||
await request.post(`/api/v2/sendMessage`, {
|
await request.post(`/api/v1/typebots/${publicId}/startChat`, {
|
||||||
data: {
|
data: {
|
||||||
startParams: {
|
isOnlyRegistering: false,
|
||||||
typebot: publicId,
|
isStreamEnabled: false,
|
||||||
},
|
} satisfies Omit<StartChatInput, 'publicId'>,
|
||||||
} satisfies SendMessageInput,
|
|
||||||
})
|
})
|
||||||
).json()
|
).json()
|
||||||
chatSessionId = sessionId
|
chatSessionId = sessionId
|
||||||
@ -111,8 +143,8 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Answer Name question', async () => {
|
await test.step('Answer Name question', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v2/sendMessage`, {
|
await request.post(`/api/v1/sessions/${chatSessionId}/continueChat`, {
|
||||||
data: { message: 'John', sessionId: chatSessionId },
|
data: { message: 'John' },
|
||||||
})
|
})
|
||||||
).json()
|
).json()
|
||||||
expect(messages[0].content.richText).toStrictEqual([
|
expect(messages[0].content.richText).toStrictEqual([
|
||||||
@ -142,8 +174,8 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Answer Age question', async () => {
|
await test.step('Answer Age question', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v2/sendMessage`, {
|
await request.post(`/api/v1/sessions/${chatSessionId}/continueChat`, {
|
||||||
data: { message: '24', sessionId: chatSessionId },
|
data: { message: '24' },
|
||||||
})
|
})
|
||||||
).json()
|
).json()
|
||||||
expect(messages[0].content.richText).toStrictEqual([
|
expect(messages[0].content.richText).toStrictEqual([
|
||||||
@ -181,8 +213,8 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Answer Rating question', async () => {
|
await test.step('Answer Rating question', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v2/sendMessage`, {
|
await request.post(`/api/v1/sessions/${chatSessionId}/continueChat`, {
|
||||||
data: { message: '8', sessionId: chatSessionId },
|
data: { message: '8' },
|
||||||
})
|
})
|
||||||
).json()
|
).json()
|
||||||
expect(messages[0].content.richText).toStrictEqual([
|
expect(messages[0].content.richText).toStrictEqual([
|
||||||
@ -196,8 +228,8 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Answer Email question with wrong input', async () => {
|
await test.step('Answer Email question with wrong input', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v2/sendMessage`, {
|
await request.post(`/api/v1/sessions/${chatSessionId}/continueChat`, {
|
||||||
data: { message: 'invalid email', sessionId: chatSessionId },
|
data: { message: 'invalid email' },
|
||||||
})
|
})
|
||||||
).json()
|
).json()
|
||||||
expect(messages[0].content.richText).toStrictEqual([
|
expect(messages[0].content.richText).toStrictEqual([
|
||||||
@ -215,8 +247,8 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Answer Email question with valid input', async () => {
|
await test.step('Answer Email question with valid input', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v2/sendMessage`, {
|
await request.post(`/api/v1/sessions/${chatSessionId}/continueChat`, {
|
||||||
data: { message: 'typebot@email.com', sessionId: chatSessionId },
|
data: { message: 'typebot@email.com' },
|
||||||
})
|
})
|
||||||
).json()
|
).json()
|
||||||
expect(messages.length).toBe(0)
|
expect(messages.length).toBe(0)
|
||||||
@ -225,8 +257,8 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Answer URL question', async () => {
|
await test.step('Answer URL question', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v2/sendMessage`, {
|
await request.post(`/api/v1/sessions/${chatSessionId}/continueChat`, {
|
||||||
data: { message: 'https://typebot.io', sessionId: chatSessionId },
|
data: { message: 'https://typebot.io' },
|
||||||
})
|
})
|
||||||
).json()
|
).json()
|
||||||
expect(messages.length).toBe(0)
|
expect(messages.length).toBe(0)
|
||||||
@ -235,8 +267,8 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Answer Buttons question with invalid choice', async () => {
|
await test.step('Answer Buttons question with invalid choice', async () => {
|
||||||
const { messages } = await (
|
const { messages } = await (
|
||||||
await request.post(`/api/v2/sendMessage`, {
|
await request.post(`/api/v1/sessions/${chatSessionId}/continueChat`, {
|
||||||
data: { message: 'Yes', sessionId: chatSessionId },
|
data: { message: 'Yes' },
|
||||||
})
|
})
|
||||||
).json()
|
).json()
|
||||||
expect(messages[0].content.richText).toStrictEqual([
|
expect(messages[0].content.richText).toStrictEqual([
|
||||||
@ -263,14 +295,14 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
})
|
})
|
||||||
await test.step('Starting with a message when typebot starts with input should proceed', async () => {
|
await test.step('Starting with a message when typebot starts with input should proceed', async () => {
|
||||||
const { messages } = await (
|
const { messages } = await (
|
||||||
await request.post(`/api/v2/sendMessage`, {
|
await request.post(
|
||||||
data: {
|
`/api/v1/typebots/starting-with-input-public/startChat`,
|
||||||
message: 'Hey',
|
{
|
||||||
startParams: {
|
data: {
|
||||||
typebot: 'starting-with-input-public',
|
message: 'Hey',
|
||||||
},
|
} satisfies Omit<StartChatInput, 'publicId'>,
|
||||||
} satisfies SendMessageInput,
|
}
|
||||||
})
|
)
|
||||||
).json()
|
).json()
|
||||||
expect(messages[0].content.richText).toStrictEqual([
|
expect(messages[0].content.richText).toStrictEqual([
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ test('Result should be overwritten on page refresh', async ({ page }) => {
|
|||||||
|
|
||||||
const [, response] = await Promise.all([
|
const [, response] = await Promise.all([
|
||||||
page.goto(`/${typebotId}-public`),
|
page.goto(`/${typebotId}-public`),
|
||||||
page.waitForResponse(/sendMessage/),
|
page.waitForResponse(/startChat/),
|
||||||
])
|
])
|
||||||
const { resultId } = await response.json()
|
const { resultId } = await response.json()
|
||||||
expect(resultId).toBeDefined()
|
expect(resultId).toBeDefined()
|
||||||
@ -38,7 +38,7 @@ test('Result should be overwritten on page refresh', async ({ page }) => {
|
|||||||
|
|
||||||
const [, secondResponse] = await Promise.all([
|
const [, secondResponse] = await Promise.all([
|
||||||
page.reload(),
|
page.reload(),
|
||||||
page.waitForResponse(/sendMessage/),
|
page.waitForResponse(/startChat/),
|
||||||
])
|
])
|
||||||
const { resultId: secondResultId } = await secondResponse.json()
|
const { resultId: secondResultId } = await secondResponse.json()
|
||||||
expect(secondResultId).toBe(resultId)
|
expect(secondResultId).toBe(resultId)
|
||||||
@ -57,7 +57,7 @@ test.describe('Create result on page refresh enabled', () => {
|
|||||||
])
|
])
|
||||||
const [, response] = await Promise.all([
|
const [, response] = await Promise.all([
|
||||||
page.goto(`/${typebotId}-public`),
|
page.goto(`/${typebotId}-public`),
|
||||||
page.waitForResponse(/sendMessage/),
|
page.waitForResponse(/startChat/),
|
||||||
])
|
])
|
||||||
const { resultId } = await response.json()
|
const { resultId } = await response.json()
|
||||||
expect(resultId).toBeDefined()
|
expect(resultId).toBeDefined()
|
||||||
@ -65,7 +65,7 @@ test.describe('Create result on page refresh enabled', () => {
|
|||||||
await expect(page.getByRole('textbox')).toBeVisible()
|
await expect(page.getByRole('textbox')).toBeVisible()
|
||||||
const [, secondResponse] = await Promise.all([
|
const [, secondResponse] = await Promise.all([
|
||||||
page.reload(),
|
page.reload(),
|
||||||
page.waitForResponse(/sendMessage/),
|
page.waitForResponse(/startChat/),
|
||||||
])
|
])
|
||||||
const { resultId: secondResultId } = await secondResponse.json()
|
const { resultId: secondResultId } = await secondResponse.json()
|
||||||
expect(secondResultId).not.toBe(resultId)
|
expect(secondResultId).not.toBe(resultId)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { generateOpenApiDocument } from 'trpc-openapi'
|
import { generateOpenApiDocument } from 'trpc-openapi'
|
||||||
import { writeFileSync } from 'fs'
|
import { writeFileSync } from 'fs'
|
||||||
import { appRouter } from '@/helpers/server/routers/appRouterV2'
|
import { appRouter } from '@/helpers/server/appRouter'
|
||||||
|
|
||||||
const openApiDocument = generateOpenApiDocument(appRouter, {
|
const openApiDocument = generateOpenApiDocument(appRouter, {
|
||||||
title: 'Chat API',
|
title: 'Chat API',
|
||||||
version: '2.0.0',
|
version: '3.0.0',
|
||||||
baseUrl: 'https://typebot.io/api/v2',
|
baseUrl: 'https://typebot.io/api',
|
||||||
docsUrl: 'https://docs.typebot.io/api',
|
docsUrl: 'https://docs.typebot.io/api',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import {
|
|||||||
SessionState,
|
SessionState,
|
||||||
GoogleSheetsGetOptions,
|
GoogleSheetsGetOptions,
|
||||||
VariableWithValue,
|
VariableWithValue,
|
||||||
ReplyLog,
|
ChatLog,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { isNotEmpty, byId, isDefined } from '@typebot.io/lib'
|
import { isNotEmpty, byId, isDefined } from '@typebot.io/lib'
|
||||||
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
||||||
@ -18,7 +18,7 @@ export const getRow = async (
|
|||||||
options,
|
options,
|
||||||
}: { outgoingEdgeId?: string; options: GoogleSheetsGetOptions }
|
}: { outgoingEdgeId?: string; options: GoogleSheetsGetOptions }
|
||||||
): Promise<ExecuteIntegrationResponse> => {
|
): Promise<ExecuteIntegrationResponse> => {
|
||||||
const logs: ReplyLog[] = []
|
const logs: ChatLog[] = []
|
||||||
const { variables } = state.typebotsQueue[0].typebot
|
const { variables } = state.typebotsQueue[0].typebot
|
||||||
const { sheetId, cellsToExtract, filter, ...parsedOptions } =
|
const { sheetId, cellsToExtract, filter, ...parsedOptions } =
|
||||||
deepParseVariables(variables)(options)
|
deepParseVariables(variables)(options)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
SessionState,
|
SessionState,
|
||||||
GoogleSheetsInsertRowOptions,
|
GoogleSheetsInsertRowOptions,
|
||||||
ReplyLog,
|
ChatLog,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { parseCellValues } from './helpers/parseCellValues'
|
import { parseCellValues } from './helpers/parseCellValues'
|
||||||
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
||||||
@ -17,7 +17,7 @@ export const insertRow = async (
|
|||||||
const { variables } = state.typebotsQueue[0].typebot
|
const { variables } = state.typebotsQueue[0].typebot
|
||||||
if (!options.cellsToInsert || !options.sheetId) return { outgoingEdgeId }
|
if (!options.cellsToInsert || !options.sheetId) return { outgoingEdgeId }
|
||||||
|
|
||||||
const logs: ReplyLog[] = []
|
const logs: ChatLog[] = []
|
||||||
|
|
||||||
const doc = await getAuthenticatedGoogleDoc({
|
const doc = await getAuthenticatedGoogleDoc({
|
||||||
credentialsId: options.credentialsId,
|
credentialsId: options.credentialsId,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
SessionState,
|
SessionState,
|
||||||
GoogleSheetsUpdateRowOptions,
|
GoogleSheetsUpdateRowOptions,
|
||||||
ReplyLog,
|
ChatLog,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { parseCellValues } from './helpers/parseCellValues'
|
import { parseCellValues } from './helpers/parseCellValues'
|
||||||
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
|
||||||
@ -28,7 +28,7 @@ export const updateRow = async (
|
|||||||
if (!options.cellsToUpsert || !sheetId || (!referenceCell && !filter))
|
if (!options.cellsToUpsert || !sheetId || (!referenceCell && !filter))
|
||||||
return { outgoingEdgeId }
|
return { outgoingEdgeId }
|
||||||
|
|
||||||
const logs: ReplyLog[] = []
|
const logs: ChatLog[] = []
|
||||||
|
|
||||||
const doc = await getAuthenticatedGoogleDoc({
|
const doc = await getAuthenticatedGoogleDoc({
|
||||||
credentialsId: options.credentialsId,
|
credentialsId: options.credentialsId,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { isNotEmpty } from '@typebot.io/lib/utils'
|
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 { OpenAIBlock } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
||||||
import { HTTPError } from 'got'
|
import { HTTPError } from 'got'
|
||||||
import { ClientOptions, OpenAI } from 'openai'
|
import { ClientOptions, OpenAI } from 'openai'
|
||||||
@ -10,7 +10,7 @@ type Props = Pick<
|
|||||||
> & {
|
> & {
|
||||||
apiKey: string
|
apiKey: string
|
||||||
temperature: number | undefined
|
temperature: number | undefined
|
||||||
currentLogs?: ChatReply['logs']
|
currentLogs?: ContinueChatResponse['logs']
|
||||||
isRetrying?: boolean
|
isRetrying?: boolean
|
||||||
} & Pick<NonNullable<OpenAIBlock['options']>, 'apiVersion' | 'baseUrl'>
|
} & Pick<NonNullable<OpenAIBlock['options']>, 'apiVersion' | 'baseUrl'>
|
||||||
|
|
||||||
@ -25,9 +25,9 @@ export const executeChatCompletionOpenAIRequest = async ({
|
|||||||
currentLogs = [],
|
currentLogs = [],
|
||||||
}: Props): Promise<{
|
}: Props): Promise<{
|
||||||
chatCompletion?: OpenAI.Chat.Completions.ChatCompletion
|
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 }
|
if (messages.length === 0) return { logs }
|
||||||
try {
|
try {
|
||||||
const config = {
|
const config = {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { byId, isDefined } from '@typebot.io/lib'
|
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 { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
||||||
import { VariableWithUnknowValue } from '@typebot.io/schemas/features/typebot/variable'
|
import { VariableWithUnknowValue } from '@typebot.io/schemas/features/typebot/variable'
|
||||||
import { updateVariablesInSession } from '../../../variables/updateVariablesInSession'
|
import { updateVariablesInSession } from '../../../variables/updateVariablesInSession'
|
||||||
@ -14,7 +14,7 @@ export const resumeChatCompletion =
|
|||||||
}: {
|
}: {
|
||||||
outgoingEdgeId?: string
|
outgoingEdgeId?: string
|
||||||
options: ChatCompletionOpenAIOptions
|
options: ChatCompletionOpenAIOptions
|
||||||
logs?: ChatReply['logs']
|
logs?: ContinueChatResponse['logs']
|
||||||
}
|
}
|
||||||
) =>
|
) =>
|
||||||
async (message: string, totalTokens?: number) => {
|
async (message: string, totalTokens?: number) => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { DefaultBotNotificationEmail, render } from '@typebot.io/emails'
|
import { DefaultBotNotificationEmail, render } from '@typebot.io/emails'
|
||||||
import {
|
import {
|
||||||
AnswerInSessionState,
|
AnswerInSessionState,
|
||||||
ReplyLog,
|
ChatLog,
|
||||||
SendEmailBlock,
|
SendEmailBlock,
|
||||||
SessionState,
|
SessionState,
|
||||||
SmtpCredentials,
|
SmtpCredentials,
|
||||||
@ -25,7 +25,7 @@ export const executeSendEmailBlock = async (
|
|||||||
state: SessionState,
|
state: SessionState,
|
||||||
block: SendEmailBlock
|
block: SendEmailBlock
|
||||||
): Promise<ExecuteIntegrationResponse> => {
|
): Promise<ExecuteIntegrationResponse> => {
|
||||||
const logs: ReplyLog[] = []
|
const logs: ChatLog[] = []
|
||||||
const { options } = block
|
const { options } = block
|
||||||
const { typebot, resultId, answers } = state.typebotsQueue[0]
|
const { typebot, resultId, answers } = state.typebotsQueue[0]
|
||||||
const isPreview = !resultId
|
const isPreview = !resultId
|
||||||
@ -114,8 +114,8 @@ const sendEmail = async ({
|
|||||||
typebot: TypebotInSession
|
typebot: TypebotInSession
|
||||||
answers: AnswerInSessionState[]
|
answers: AnswerInSessionState[]
|
||||||
fileUrls?: string | string[]
|
fileUrls?: string | string[]
|
||||||
}): Promise<ReplyLog[] | undefined> => {
|
}): Promise<ChatLog[] | undefined> => {
|
||||||
const logs: ReplyLog[] = []
|
const logs: ChatLog[] = []
|
||||||
const { name: replyToName } = parseEmailRecipient(replyTo)
|
const { name: replyToName } = parseEmailRecipient(replyTo)
|
||||||
|
|
||||||
const { host, port, isTlsEnabled, username, password, from } =
|
const { host, port, isTlsEnabled, username, password, from } =
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
Variable,
|
Variable,
|
||||||
WebhookResponse,
|
WebhookResponse,
|
||||||
KeyValue,
|
KeyValue,
|
||||||
ReplyLog,
|
ChatLog,
|
||||||
ExecutableWebhook,
|
ExecutableWebhook,
|
||||||
AnswerInSessionState,
|
AnswerInSessionState,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
@ -34,7 +34,7 @@ export const executeWebhookBlock = async (
|
|||||||
state: SessionState,
|
state: SessionState,
|
||||||
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
||||||
): Promise<ExecuteIntegrationResponse> => {
|
): Promise<ExecuteIntegrationResponse> => {
|
||||||
const logs: ReplyLog[] = []
|
const logs: ChatLog[] = []
|
||||||
const webhook =
|
const webhook =
|
||||||
block.options?.webhook ??
|
block.options?.webhook ??
|
||||||
('webhookId' in block
|
('webhookId' in block
|
||||||
@ -142,8 +142,8 @@ const parseWebhookAttributes =
|
|||||||
|
|
||||||
export const executeWebhook = async (
|
export const executeWebhook = async (
|
||||||
webhook: ParsedWebhook
|
webhook: ParsedWebhook
|
||||||
): Promise<{ response: WebhookResponse; logs?: ReplyLog[] }> => {
|
): Promise<{ response: WebhookResponse; logs?: ChatLog[] }> => {
|
||||||
const logs: ReplyLog[] = []
|
const logs: ChatLog[] = []
|
||||||
const { headers, url, method, basicAuth, body, isJson } = webhook
|
const { headers, url, method, basicAuth, body, isJson } = webhook
|
||||||
const contentType = headers ? headers['Content-Type'] : undefined
|
const contentType = headers ? headers['Content-Type'] : undefined
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { byId } from '@typebot.io/lib'
|
|||||||
import {
|
import {
|
||||||
MakeComBlock,
|
MakeComBlock,
|
||||||
PabblyConnectBlock,
|
PabblyConnectBlock,
|
||||||
ReplyLog,
|
ChatLog,
|
||||||
VariableWithUnknowValue,
|
VariableWithUnknowValue,
|
||||||
WebhookBlock,
|
WebhookBlock,
|
||||||
ZapierBlock,
|
ZapierBlock,
|
||||||
@ -15,7 +15,7 @@ import { updateVariablesInSession } from '../../../variables/updateVariablesInSe
|
|||||||
type Props = {
|
type Props = {
|
||||||
state: SessionState
|
state: SessionState
|
||||||
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
|
||||||
logs?: ReplyLog[]
|
logs?: ChatLog[]
|
||||||
response: {
|
response: {
|
||||||
statusCode: number
|
statusCode: number
|
||||||
data?: unknown
|
data?: unknown
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
TypebotLinkBlock,
|
TypebotLinkBlock,
|
||||||
SessionState,
|
SessionState,
|
||||||
Variable,
|
Variable,
|
||||||
ReplyLog,
|
ChatLog,
|
||||||
Edge,
|
Edge,
|
||||||
typebotInSessionStateSchema,
|
typebotInSessionStateSchema,
|
||||||
TypebotInSession,
|
TypebotInSession,
|
||||||
@ -19,7 +19,7 @@ export const executeTypebotLink = async (
|
|||||||
state: SessionState,
|
state: SessionState,
|
||||||
block: TypebotLinkBlock
|
block: TypebotLinkBlock
|
||||||
): Promise<ExecuteLogicResponse> => {
|
): Promise<ExecuteLogicResponse> => {
|
||||||
const logs: ReplyLog[] = []
|
const logs: ChatLog[] = []
|
||||||
const typebotId = block.options?.typebotId
|
const typebotId = block.options?.typebotId
|
||||||
if (!typebotId) {
|
if (!typebotId) {
|
||||||
logs.push({
|
logs.push({
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AnswerInSessionState,
|
AnswerInSessionState,
|
||||||
Block,
|
Block,
|
||||||
ChatReply,
|
ContinueChatResponse,
|
||||||
Group,
|
Group,
|
||||||
InputBlock,
|
InputBlock,
|
||||||
SessionState,
|
SessionState,
|
||||||
@ -46,7 +46,10 @@ export const continueBotFlow = async (
|
|||||||
reply: string | undefined,
|
reply: string | undefined,
|
||||||
{ state, version }: Params
|
{ state, version }: Params
|
||||||
): Promise<
|
): Promise<
|
||||||
ChatReply & { newSessionState: SessionState; visitedEdges: VisitedEdge[] }
|
ContinueChatResponse & {
|
||||||
|
newSessionState: SessionState
|
||||||
|
visitedEdges: VisitedEdge[]
|
||||||
|
}
|
||||||
> => {
|
> => {
|
||||||
let firstBubbleWasStreamed = false
|
let firstBubbleWasStreamed = false
|
||||||
let newSessionState = { ...state }
|
let newSessionState = { ...state }
|
||||||
@ -202,7 +205,9 @@ const saveVariableValueIfAny =
|
|||||||
|
|
||||||
const parseRetryMessage =
|
const parseRetryMessage =
|
||||||
(state: SessionState) =>
|
(state: SessionState) =>
|
||||||
async (block: InputBlock): Promise<Pick<ChatReply, 'messages' | 'input'>> => {
|
async (
|
||||||
|
block: InputBlock
|
||||||
|
): Promise<Pick<ContinueChatResponse, 'messages' | 'input'>> => {
|
||||||
const retryMessage =
|
const retryMessage =
|
||||||
block.options &&
|
block.options &&
|
||||||
'retryMessageContent' in block.options &&
|
'retryMessageContent' in block.options &&
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
ChatReply,
|
ContinueChatResponse,
|
||||||
Group,
|
Group,
|
||||||
InputBlock,
|
InputBlock,
|
||||||
RuntimeOptions,
|
RuntimeOptions,
|
||||||
@ -31,7 +31,7 @@ import { VisitedEdge } from '@typebot.io/prisma'
|
|||||||
type ContextProps = {
|
type ContextProps = {
|
||||||
version: 1 | 2
|
version: 1 | 2
|
||||||
state: SessionState
|
state: SessionState
|
||||||
currentReply?: ChatReply
|
currentReply?: ContinueChatResponse
|
||||||
currentLastBubbleId?: string
|
currentLastBubbleId?: string
|
||||||
firstBubbleWasStreamed?: boolean
|
firstBubbleWasStreamed?: boolean
|
||||||
visitedEdges: VisitedEdge[]
|
visitedEdges: VisitedEdge[]
|
||||||
@ -48,12 +48,16 @@ export const executeGroup = async (
|
|||||||
firstBubbleWasStreamed,
|
firstBubbleWasStreamed,
|
||||||
}: ContextProps
|
}: ContextProps
|
||||||
): Promise<
|
): Promise<
|
||||||
ChatReply & { newSessionState: SessionState; visitedEdges: VisitedEdge[] }
|
ContinueChatResponse & {
|
||||||
|
newSessionState: SessionState
|
||||||
|
visitedEdges: VisitedEdge[]
|
||||||
|
}
|
||||||
> => {
|
> => {
|
||||||
const messages: ChatReply['messages'] = currentReply?.messages ?? []
|
const messages: ContinueChatResponse['messages'] =
|
||||||
let clientSideActions: ChatReply['clientSideActions'] =
|
currentReply?.messages ?? []
|
||||||
|
let clientSideActions: ContinueChatResponse['clientSideActions'] =
|
||||||
currentReply?.clientSideActions
|
currentReply?.clientSideActions
|
||||||
let logs: ChatReply['logs'] = currentReply?.logs
|
let logs: ContinueChatResponse['logs'] = currentReply?.logs
|
||||||
let nextEdgeId = null
|
let nextEdgeId = null
|
||||||
let lastBubbleBlockId: string | undefined = currentLastBubbleId
|
let lastBubbleBlockId: string | undefined = currentLastBubbleId
|
||||||
|
|
||||||
@ -173,7 +177,7 @@ const computeRuntimeOptions =
|
|||||||
|
|
||||||
export const parseInput =
|
export const parseInput =
|
||||||
(state: SessionState) =>
|
(state: SessionState) =>
|
||||||
async (block: InputBlock): Promise<ChatReply['input']> => {
|
async (block: InputBlock): Promise<ContinueChatResponse['input']> => {
|
||||||
switch (block.type) {
|
switch (block.type) {
|
||||||
case InputBlockType.CHOICE: {
|
case InputBlockType.CHOICE: {
|
||||||
return injectVariableValuesInButtonsInputBlock(state)(block)
|
return injectVariableValuesInButtonsInputBlock(state)(block)
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { parseVideoUrl } from '@typebot.io/lib/parseVideoUrl'
|
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 { deepParseVariables } from './variables/deepParseVariables'
|
||||||
import { isEmpty, isNotEmpty } from '@typebot.io/lib/utils'
|
import { isEmpty, isNotEmpty } from '@typebot.io/lib/utils'
|
||||||
import {
|
import {
|
||||||
@ -27,7 +32,7 @@ export type BubbleBlockWithDefinedContent = BubbleBlock & {
|
|||||||
export const parseBubbleBlock = (
|
export const parseBubbleBlock = (
|
||||||
block: BubbleBlockWithDefinedContent,
|
block: BubbleBlockWithDefinedContent,
|
||||||
{ version, variables, typebotVersion }: Params
|
{ version, variables, typebotVersion }: Params
|
||||||
): ChatReply['messages'][0] => {
|
): ContinueChatResponse['messages'][0] => {
|
||||||
switch (block.type) {
|
switch (block.type) {
|
||||||
case BubbleBlockType.TEXT: {
|
case BubbleBlockType.TEXT: {
|
||||||
if (version === 1)
|
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'
|
import { parseVariables } from './variables/parseVariables'
|
||||||
|
|
||||||
export const parseDynamicTheme = (
|
export const parseDynamicTheme = (
|
||||||
state: SessionState | undefined
|
state: SessionState | undefined
|
||||||
): ChatReply['dynamicTheme'] => {
|
): ContinueChatResponse['dynamicTheme'] => {
|
||||||
if (!state?.dynamicTheme) return
|
if (!state?.dynamicTheme) return
|
||||||
return {
|
return {
|
||||||
hostAvatarUrl: parseVariables(state?.typebotsQueue[0].typebot.variables)(
|
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 { upsertResult } from './queries/upsertResult'
|
||||||
import { saveLogs } from './queries/saveLogs'
|
import { saveLogs } from './queries/saveLogs'
|
||||||
import { updateSession } from './queries/updateSession'
|
import { updateSession } from './queries/updateSession'
|
||||||
@ -11,9 +11,9 @@ import { VisitedEdge } from '@typebot.io/prisma'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
session: Pick<ChatSession, 'state'> & { id?: string }
|
session: Pick<ChatSession, 'state'> & { id?: string }
|
||||||
input: ChatReply['input']
|
input: ContinueChatResponse['input']
|
||||||
logs: ChatReply['logs']
|
logs: ContinueChatResponse['logs']
|
||||||
clientSideActions: ChatReply['clientSideActions']
|
clientSideActions: ContinueChatResponse['clientSideActions']
|
||||||
visitedEdges: VisitedEdge[]
|
visitedEdges: VisitedEdge[]
|
||||||
forceCreateSession?: boolean
|
forceCreateSession?: boolean
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { TRPCError } from '@trpc/server'
|
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 { executeGroup } from './executeGroup'
|
||||||
import { getNextGroup } from './getNextGroup'
|
import { getNextGroup } from './getNextGroup'
|
||||||
import { VisitedEdge } from '@typebot.io/prisma'
|
import { VisitedEdge } from '@typebot.io/prisma'
|
||||||
@ -7,20 +11,24 @@ import { VisitedEdge } from '@typebot.io/prisma'
|
|||||||
type Props = {
|
type Props = {
|
||||||
version: 1 | 2
|
version: 1 | 2
|
||||||
state: SessionState
|
state: SessionState
|
||||||
} & StartElementId
|
startFrom?: StartFrom
|
||||||
|
}
|
||||||
|
|
||||||
export const startBotFlow = async ({
|
export const startBotFlow = async ({
|
||||||
version,
|
version,
|
||||||
state,
|
state,
|
||||||
...props
|
startFrom,
|
||||||
}: Props): Promise<
|
}: Props): Promise<
|
||||||
ChatReply & { newSessionState: SessionState; visitedEdges: VisitedEdge[] }
|
ContinueChatResponse & {
|
||||||
|
newSessionState: SessionState
|
||||||
|
visitedEdges: VisitedEdge[]
|
||||||
|
}
|
||||||
> => {
|
> => {
|
||||||
let newSessionState = state
|
let newSessionState = state
|
||||||
const visitedEdges: VisitedEdge[] = []
|
const visitedEdges: VisitedEdge[] = []
|
||||||
if ('startGroupId' in props) {
|
if (startFrom?.type === 'group') {
|
||||||
const group = state.typebotsQueue[0].typebot.groups.find(
|
const group = state.typebotsQueue[0].typebot.groups.find(
|
||||||
(group) => group.id === props.startGroupId
|
(group) => group.id === startFrom.groupId
|
||||||
)
|
)
|
||||||
if (!group)
|
if (!group)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@ -35,7 +43,7 @@ export const startBotFlow = async ({
|
|||||||
}
|
}
|
||||||
const firstEdgeId = getFirstEdgeId({
|
const firstEdgeId = getFirstEdgeId({
|
||||||
state: newSessionState,
|
state: newSessionState,
|
||||||
startEventId: 'startEventId' in props ? props.startEventId : undefined,
|
startEventId: startFrom?.type === 'event' ? startFrom.eventId : undefined,
|
||||||
})
|
})
|
||||||
if (!firstEdgeId) return { messages: [], newSessionState, visitedEdges: [] }
|
if (!firstEdgeId) return { messages: [], newSessionState, visitedEdges: [] }
|
||||||
const nextGroup = await getNextGroup(newSessionState)(firstEdgeId)
|
const nextGroup = await getNextGroup(newSessionState)(firstEdgeId)
|
||||||
|
@ -12,13 +12,13 @@ import {
|
|||||||
Block,
|
Block,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import {
|
import {
|
||||||
ChatReply,
|
StartChatInput,
|
||||||
StartParams,
|
StartChatResponse,
|
||||||
|
StartPreviewChatInput,
|
||||||
StartTypebot,
|
StartTypebot,
|
||||||
startTypebotSchema,
|
startTypebotSchema,
|
||||||
} from '@typebot.io/schemas/features/chat/schema'
|
} from '@typebot.io/schemas/features/chat/schema'
|
||||||
import parse, { NodeType } from 'node-html-parser'
|
import parse, { NodeType } from 'node-html-parser'
|
||||||
import { env } from '@typebot.io/env'
|
|
||||||
import { parseDynamicTheme } from './parseDynamicTheme'
|
import { parseDynamicTheme } from './parseDynamicTheme'
|
||||||
import { findTypebot } from './queries/findTypebot'
|
import { findTypebot } from './queries/findTypebot'
|
||||||
import { findPublicTypebot } from './queries/findPublicTypebot'
|
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 { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
import { VisitedEdge } from '@typebot.io/prisma'
|
import { VisitedEdge } from '@typebot.io/prisma'
|
||||||
|
|
||||||
|
type StartParams =
|
||||||
|
| ({
|
||||||
|
type: 'preview'
|
||||||
|
userId: string
|
||||||
|
} & StartPreviewChatInput)
|
||||||
|
| ({
|
||||||
|
type: 'live'
|
||||||
|
} & StartChatInput)
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
version: 1 | 2
|
version: 1 | 2
|
||||||
message: string | undefined
|
message: string | undefined
|
||||||
startParams: StartParams
|
startParams: StartParams
|
||||||
userId: string | undefined
|
|
||||||
initialSessionState?: Pick<SessionState, 'whatsApp' | 'expiryTimeout'>
|
initialSessionState?: Pick<SessionState, 'whatsApp' | 'expiryTimeout'>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,26 +56,24 @@ export const startSession = async ({
|
|||||||
version,
|
version,
|
||||||
message,
|
message,
|
||||||
startParams,
|
startParams,
|
||||||
userId,
|
|
||||||
initialSessionState,
|
initialSessionState,
|
||||||
}: Props): Promise<
|
}: Props): Promise<
|
||||||
ChatReply & { newSessionState: SessionState; visitedEdges: VisitedEdge[] }
|
Omit<StartChatResponse, 'resultId'> & {
|
||||||
|
newSessionState: SessionState
|
||||||
|
visitedEdges: VisitedEdge[]
|
||||||
|
resultId?: string
|
||||||
|
}
|
||||||
> => {
|
> => {
|
||||||
if (!startParams)
|
const typebot = await getTypebot(startParams)
|
||||||
throw new TRPCError({
|
|
||||||
code: 'BAD_REQUEST',
|
|
||||||
message: 'StartParams are missing',
|
|
||||||
})
|
|
||||||
|
|
||||||
const typebot = await getTypebot(startParams, userId)
|
const prefilledVariables =
|
||||||
|
startParams.type === 'live' && startParams.prefilledVariables
|
||||||
const prefilledVariables = startParams.prefilledVariables
|
? prefillVariables(typebot.variables, startParams.prefilledVariables)
|
||||||
? prefillVariables(typebot.variables, startParams.prefilledVariables)
|
: typebot.variables
|
||||||
: typebot.variables
|
|
||||||
|
|
||||||
const result = await getResult({
|
const result = await getResult({
|
||||||
...startParams,
|
resultId: startParams.type === 'live' ? startParams.resultId : undefined,
|
||||||
isPreview: startParams.isPreview || typeof startParams.typebot !== 'string',
|
isPreview: startParams.type === 'preview',
|
||||||
typebotId: typebot.id,
|
typebotId: typebot.id,
|
||||||
prefilledVariables,
|
prefilledVariables,
|
||||||
isRememberUserEnabled:
|
isRememberUserEnabled:
|
||||||
@ -148,11 +154,8 @@ export const startSession = async ({
|
|||||||
let chatReply = await startBotFlow({
|
let chatReply = await startBotFlow({
|
||||||
version,
|
version,
|
||||||
state: initialState,
|
state: initialState,
|
||||||
...('startGroupId' in startParams
|
startFrom:
|
||||||
? { startGroupId: startParams.startGroupId }
|
startParams.type === 'preview' ? startParams.startFrom : undefined,
|
||||||
: 'startEventId' in startParams
|
|
||||||
? { startEventId: startParams.startEventId }
|
|
||||||
: {}),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// If params has message and first block is an input block, we can directly continue the bot flow
|
// 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 (
|
const getTypebot = async (startParams: StartParams): Promise<StartTypebot> => {
|
||||||
{ typebot, isPreview }: Pick<StartParams, 'typebot' | 'isPreview'>,
|
if (startParams.type === 'preview' && startParams.typebot)
|
||||||
userId?: string
|
return startParams.typebot
|
||||||
): Promise<StartTypebot> => {
|
const typebotQuery =
|
||||||
if (typeof typebot !== 'string') return typebot
|
startParams.type === 'preview'
|
||||||
if (isPreview && !userId && !env.NEXT_PUBLIC_E2E_TEST)
|
? await findTypebot({
|
||||||
throw new TRPCError({
|
id: startParams.typebotId,
|
||||||
code: 'UNAUTHORIZED',
|
userId: startParams.userId,
|
||||||
message:
|
})
|
||||||
'You need to authenticate the request to start a bot in preview mode.',
|
: await findPublicTypebot({ publicId: startParams.publicId })
|
||||||
})
|
|
||||||
const typebotQuery = isPreview
|
|
||||||
? await findTypebot({ id: typebot, userId })
|
|
||||||
: await findPublicTypebot({ publicId: typebot })
|
|
||||||
|
|
||||||
const parsedTypebot =
|
const parsedTypebot =
|
||||||
typebotQuery && 'typebot' in typebotQuery
|
typebotQuery && 'typebot' in typebotQuery
|
||||||
@ -319,7 +318,9 @@ const getResult = async ({
|
|||||||
resultId,
|
resultId,
|
||||||
prefilledVariables,
|
prefilledVariables,
|
||||||
isRememberUserEnabled,
|
isRememberUserEnabled,
|
||||||
}: Pick<StartParams, 'isPreview' | 'resultId'> & {
|
}: {
|
||||||
|
resultId: string | undefined
|
||||||
|
isPreview: boolean
|
||||||
typebotId: string
|
typebotId: string
|
||||||
prefilledVariables: Variable[]
|
prefilledVariables: Variable[]
|
||||||
isRememberUserEnabled: boolean
|
isRememberUserEnabled: boolean
|
||||||
@ -375,7 +376,7 @@ const parseDynamicThemeInState = (theme: Theme) => {
|
|||||||
|
|
||||||
const parseStartClientSideAction = (
|
const parseStartClientSideAction = (
|
||||||
typebot: StartTypebot
|
typebot: StartTypebot
|
||||||
): NonNullable<ChatReply['clientSideActions']>[number] | undefined => {
|
): NonNullable<StartChatResponse['clientSideActions']>[number] | undefined => {
|
||||||
const blocks = typebot.groups.flatMap<Block>((group) => group.blocks)
|
const blocks = typebot.groups.flatMap<Block>((group) => group.blocks)
|
||||||
const pixelBlocks = (
|
const pixelBlocks = (
|
||||||
blocks.filter(
|
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 EdgeId = string
|
||||||
|
|
||||||
export type ExecuteLogicResponse = {
|
export type ExecuteLogicResponse = {
|
||||||
outgoingEdgeId: EdgeId | undefined
|
outgoingEdgeId: EdgeId | undefined
|
||||||
newSessionState?: SessionState
|
newSessionState?: SessionState
|
||||||
} & Pick<ChatReply, 'clientSideActions' | 'logs'>
|
} & Pick<ContinueChatResponse, 'clientSideActions' | 'logs'>
|
||||||
|
|
||||||
export type ExecuteIntegrationResponse = {
|
export type ExecuteIntegrationResponse = {
|
||||||
outgoingEdgeId: EdgeId | undefined
|
outgoingEdgeId: EdgeId | undefined
|
||||||
newSessionState?: SessionState
|
newSessionState?: SessionState
|
||||||
} & Pick<ChatReply, 'clientSideActions' | 'logs'>
|
} & Pick<ContinueChatResponse, 'clientSideActions' | 'logs'>
|
||||||
|
|
||||||
export type ParsedReply =
|
export type ParsedReply =
|
||||||
| { status: 'success'; reply: string }
|
| { status: 'success'; reply: string }
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { safeStringify } from '@typebot.io/lib/safeStringify'
|
import { safeStringify } from '@typebot.io/lib/safeStringify'
|
||||||
import { StartParams, Variable } from '@typebot.io/schemas'
|
import { StartChatInput, Variable } from '@typebot.io/schemas'
|
||||||
|
|
||||||
export const prefillVariables = (
|
export const prefillVariables = (
|
||||||
variables: Variable[],
|
variables: Variable[],
|
||||||
prefilledVariables: NonNullable<StartParams['prefilledVariables']>
|
prefilledVariables: NonNullable<StartChatInput['prefilledVariables']>
|
||||||
): Variable[] =>
|
): Variable[] =>
|
||||||
variables.map((variable) => {
|
variables.map((variable) => {
|
||||||
const prefilledVariable = prefilledVariables[variable.name]
|
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 { WhatsAppSendingMessage } from '@typebot.io/schemas/features/whatsapp'
|
||||||
import { convertRichTextToWhatsAppText } from './convertRichTextToWhatsAppText'
|
import { convertRichTextToWhatsAppText } from './convertRichTextToWhatsAppText'
|
||||||
import { isDefined, isEmpty } from '@typebot.io/lib/utils'
|
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'
|
import { defaultChoiceInputOptions } from '@typebot.io/schemas/features/blocks/inputs/choice/constants'
|
||||||
|
|
||||||
export const convertInputToWhatsAppMessages = (
|
export const convertInputToWhatsAppMessages = (
|
||||||
input: NonNullable<ChatReply['input']>,
|
input: NonNullable<ContinueChatResponse['input']>,
|
||||||
lastMessage: ChatReply['messages'][number] | undefined
|
lastMessage: ContinueChatResponse['messages'][number] | undefined
|
||||||
): WhatsAppSendingMessage[] => {
|
): WhatsAppSendingMessage[] => {
|
||||||
const lastMessageText =
|
const lastMessageText =
|
||||||
lastMessage?.type === BubbleBlockType.TEXT
|
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 { WhatsAppSendingMessage } from '@typebot.io/schemas/features/whatsapp'
|
||||||
import { convertRichTextToWhatsAppText } from './convertRichTextToWhatsAppText'
|
import { convertRichTextToWhatsAppText } from './convertRichTextToWhatsAppText'
|
||||||
import { isSvgSrc } from '@typebot.io/lib/utils'
|
import { isSvgSrc } from '@typebot.io/lib/utils'
|
||||||
@ -8,7 +8,7 @@ import { VideoBubbleContentType } from '@typebot.io/schemas/features/blocks/bubb
|
|||||||
const mp4HttpsUrlRegex = /^https:\/\/.*\.mp4$/
|
const mp4HttpsUrlRegex = /^https:\/\/.*\.mp4$/
|
||||||
|
|
||||||
export const convertMessageToWhatsAppMessage = (
|
export const convertMessageToWhatsAppMessage = (
|
||||||
message: ChatReply['messages'][number]
|
message: ContinueChatResponse['messages'][number]
|
||||||
): WhatsAppSendingMessage | undefined => {
|
): WhatsAppSendingMessage | undefined => {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case BubbleBlockType.TEXT: {
|
case BubbleBlockType.TEXT: {
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import { ChatReply, SessionState, Settings } from '@typebot.io/schemas'
|
import {
|
||||||
|
ContinueChatResponse,
|
||||||
|
SessionState,
|
||||||
|
Settings,
|
||||||
|
} from '@typebot.io/schemas'
|
||||||
import {
|
import {
|
||||||
WhatsAppCredentials,
|
WhatsAppCredentials,
|
||||||
WhatsAppSendingMessage,
|
WhatsAppSendingMessage,
|
||||||
@ -21,7 +25,7 @@ type Props = {
|
|||||||
typingEmulation: SessionState['typingEmulation']
|
typingEmulation: SessionState['typingEmulation']
|
||||||
credentials: WhatsAppCredentials['data']
|
credentials: WhatsAppCredentials['data']
|
||||||
state: SessionState
|
state: SessionState
|
||||||
} & Pick<ChatReply, 'messages' | 'input' | 'clientSideActions'>
|
} & Pick<ContinueChatResponse, 'messages' | 'input' | 'clientSideActions'>
|
||||||
|
|
||||||
export const sendChatReplyToWhatsApp = async ({
|
export const sendChatReplyToWhatsApp = async ({
|
||||||
to,
|
to,
|
||||||
@ -171,7 +175,9 @@ const getTypingDuration = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLastMessageIncludedInInput = (input: ChatReply['input']): boolean => {
|
const isLastMessageIncludedInInput = (
|
||||||
|
input: ContinueChatResponse['input']
|
||||||
|
): boolean => {
|
||||||
if (isNotDefined(input)) return false
|
if (isNotDefined(input)) return false
|
||||||
return input.type === InputBlockType.CHOICE
|
return input.type === InputBlockType.CHOICE
|
||||||
}
|
}
|
||||||
@ -179,7 +185,9 @@ const isLastMessageIncludedInInput = (input: ChatReply['input']): boolean => {
|
|||||||
const executeClientSideAction =
|
const executeClientSideAction =
|
||||||
(context: { to: string; credentials: WhatsAppCredentials['data'] }) =>
|
(context: { to: string; credentials: WhatsAppCredentials['data'] }) =>
|
||||||
async (
|
async (
|
||||||
clientSideAction: NonNullable<ChatReply['clientSideActions']>[number]
|
clientSideAction: NonNullable<
|
||||||
|
ContinueChatResponse['clientSideActions']
|
||||||
|
>[number]
|
||||||
): Promise<{ replyToSend: string | undefined } | void> => {
|
): Promise<{ replyToSend: string | undefined } | void> => {
|
||||||
if ('wait' in clientSideAction) {
|
if ('wait' in clientSideAction) {
|
||||||
await new Promise((resolve) =>
|
await new Promise((resolve) =>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
import {
|
import {
|
||||||
ChatReply,
|
ContinueChatResponse,
|
||||||
PublicTypebot,
|
PublicTypebot,
|
||||||
SessionState,
|
SessionState,
|
||||||
Settings,
|
Settings,
|
||||||
@ -20,7 +20,7 @@ import { VisitedEdge } from '@typebot.io/prisma'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
incomingMessage?: string
|
incomingMessage?: string
|
||||||
workspaceId?: string
|
workspaceId: string
|
||||||
credentials: WhatsAppCredentials['data'] & Pick<WhatsAppCredentials, 'id'>
|
credentials: WhatsAppCredentials['data'] & Pick<WhatsAppCredentials, 'id'>
|
||||||
contact: NonNullable<SessionState['whatsApp']>['contact']
|
contact: NonNullable<SessionState['whatsApp']>['contact']
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@ export const startWhatsAppSession = async ({
|
|||||||
credentials,
|
credentials,
|
||||||
contact,
|
contact,
|
||||||
}: Props): Promise<
|
}: Props): Promise<
|
||||||
| (ChatReply & {
|
| (ContinueChatResponse & {
|
||||||
newSessionState: SessionState
|
newSessionState: SessionState
|
||||||
visitedEdges: VisitedEdge[]
|
visitedEdges: VisitedEdge[]
|
||||||
})
|
})
|
||||||
@ -89,9 +89,10 @@ export const startWhatsAppSession = async ({
|
|||||||
version: 2,
|
version: 2,
|
||||||
message: incomingMessage,
|
message: incomingMessage,
|
||||||
startParams: {
|
startParams: {
|
||||||
typebot: publicTypebot.typebot.publicId as string,
|
type: 'live',
|
||||||
|
publicId: publicTypebot.typebot.publicId as string,
|
||||||
|
isOnlyRegistering: false,
|
||||||
},
|
},
|
||||||
userId: undefined,
|
|
||||||
initialSessionState: {
|
initialSessionState: {
|
||||||
whatsApp: {
|
whatsApp: {
|
||||||
contact,
|
contact,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/js",
|
"name": "@typebot.io/js",
|
||||||
"version": "0.2.15",
|
"version": "0.2.16",
|
||||||
"description": "Javascript library to display typebots on your website",
|
"description": "Javascript library to display typebots on your website",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { LiteBadge } from './LiteBadge'
|
import { LiteBadge } from './LiteBadge'
|
||||||
import { createEffect, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
import { createEffect, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||||
import { isNotDefined, isNotEmpty } from '@typebot.io/lib'
|
import { isNotDefined, isNotEmpty } from '@typebot.io/lib'
|
||||||
import { getInitialChatReplyQuery } from '@/queries/getInitialChatReplyQuery'
|
import { startChatQuery } from '@/queries/startChatQuery'
|
||||||
import { ConversationContainer } from './ConversationContainer'
|
import { ConversationContainer } from './ConversationContainer'
|
||||||
import { setIsMobile } from '@/utils/isMobileSignal'
|
import { setIsMobile } from '@/utils/isMobileSignal'
|
||||||
import { BotContext, InitialChatReply, OutgoingLog } from '@/types'
|
import { BotContext, InitialChatReply, OutgoingLog } from '@/types'
|
||||||
@ -12,7 +12,8 @@ import {
|
|||||||
} from '@/utils/storage'
|
} from '@/utils/storage'
|
||||||
import { setCssVariablesValue } from '@/utils/setCssVariablesValue'
|
import { setCssVariablesValue } from '@/utils/setCssVariablesValue'
|
||||||
import immutableCss from '../assets/immutable.css'
|
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'
|
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
export type BotProps = {
|
export type BotProps = {
|
||||||
@ -27,7 +28,8 @@ export type BotProps = {
|
|||||||
onInit?: () => void
|
onInit?: () => void
|
||||||
onEnd?: () => void
|
onEnd?: () => void
|
||||||
onNewLogs?: (logs: OutgoingLog[]) => void
|
onNewLogs?: (logs: OutgoingLog[]) => void
|
||||||
} & StartElementId
|
startFrom?: StartFrom
|
||||||
|
}
|
||||||
|
|
||||||
export const Bot = (props: BotProps & { class?: string }) => {
|
export const Bot = (props: BotProps & { class?: string }) => {
|
||||||
const [initialChatReply, setInitialChatReply] = createSignal<
|
const [initialChatReply, setInitialChatReply] = createSignal<
|
||||||
@ -47,11 +49,13 @@ export const Bot = (props: BotProps & { class?: string }) => {
|
|||||||
})
|
})
|
||||||
const typebotIdFromProps =
|
const typebotIdFromProps =
|
||||||
typeof props.typebot === 'string' ? props.typebot : undefined
|
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,
|
stripeRedirectStatus: urlParams.get('redirect_status') ?? undefined,
|
||||||
typebot: props.typebot,
|
typebot: props.typebot,
|
||||||
apiHost: props.apiHost,
|
apiHost: props.apiHost,
|
||||||
isPreview: props.isPreview ?? false,
|
isPreview,
|
||||||
resultId: isNotEmpty(props.resultId)
|
resultId: isNotEmpty(props.resultId)
|
||||||
? props.resultId
|
? props.resultId
|
||||||
: getExistingResultIdFromStorage(typebotIdFromProps),
|
: getExistingResultIdFromStorage(typebotIdFromProps),
|
||||||
@ -59,14 +63,10 @@ export const Bot = (props: BotProps & { class?: string }) => {
|
|||||||
...prefilledVariables,
|
...prefilledVariables,
|
||||||
...props.prefilledVariables,
|
...props.prefilledVariables,
|
||||||
},
|
},
|
||||||
...('startGroupId' in props
|
startFrom: props.startFrom,
|
||||||
? { startGroupId: props.startGroupId }
|
|
||||||
: 'startEventId' in props
|
|
||||||
? { startEventId: props.startEventId }
|
|
||||||
: {}),
|
|
||||||
})
|
})
|
||||||
if (error && 'code' in error && typeof error.code === 'string') {
|
if (error && 'code' in error && typeof error.code === 'string') {
|
||||||
if (typeof props.typebot !== 'string' || (props.isPreview ?? false)) {
|
if (isPreview) {
|
||||||
return setError(
|
return setError(
|
||||||
new Error('An error occurred while loading the bot.', {
|
new Error('An error occurred while loading the bot.', {
|
||||||
cause: error.message,
|
cause: error.message,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BotContext, ChatChunk as ChatChunkType } from '@/types'
|
import { BotContext, ChatChunk as ChatChunkType } from '@/types'
|
||||||
import { isMobile } from '@/utils/isMobileSignal'
|
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 { createSignal, For, onMount, Show } from 'solid-js'
|
||||||
import { HostBubble } from '../bubbles/HostBubble'
|
import { HostBubble } from '../bubbles/HostBubble'
|
||||||
import { InputChatBlock } from '../InputChatBlock'
|
import { InputChatBlock } from '../InputChatBlock'
|
||||||
@ -9,7 +9,7 @@ import { StreamingBubble } from '../bubbles/StreamingBubble'
|
|||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/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
|
theme: Theme
|
||||||
settings: Settings
|
settings: Settings
|
||||||
inputIndex: number
|
inputIndex: number
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
ChatReply,
|
ContinueChatResponse,
|
||||||
InputBlock,
|
InputBlock,
|
||||||
SendMessageInput,
|
|
||||||
Theme,
|
Theme,
|
||||||
|
ChatLog,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import {
|
import {
|
||||||
createEffect,
|
createEffect,
|
||||||
@ -12,7 +12,7 @@ import {
|
|||||||
onMount,
|
onMount,
|
||||||
Show,
|
Show,
|
||||||
} from 'solid-js'
|
} from 'solid-js'
|
||||||
import { sendMessageQuery } from '@/queries/sendMessageQuery'
|
import { continueChatQuery } from '@/queries/continueChatQuery'
|
||||||
import { ChatChunk } from './ChatChunk'
|
import { ChatChunk } from './ChatChunk'
|
||||||
import {
|
import {
|
||||||
BotContext,
|
BotContext,
|
||||||
@ -30,10 +30,11 @@ import {
|
|||||||
setFormattedMessages,
|
setFormattedMessages,
|
||||||
} from '@/utils/formattedMessagesSignal'
|
} from '@/utils/formattedMessagesSignal'
|
||||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||||
|
import { saveClientLogsQuery } from '@/queries/saveClientLogsQuery'
|
||||||
|
|
||||||
const parseDynamicTheme = (
|
const parseDynamicTheme = (
|
||||||
initialTheme: Theme,
|
initialTheme: Theme,
|
||||||
dynamicTheme: ChatReply['dynamicTheme']
|
dynamicTheme: ContinueChatResponse['dynamicTheme']
|
||||||
): Theme => ({
|
): Theme => ({
|
||||||
...initialTheme,
|
...initialTheme,
|
||||||
chat: {
|
chat: {
|
||||||
@ -74,7 +75,7 @@ export const ConversationContainer = (props: Props) => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
const [dynamicTheme, setDynamicTheme] = createSignal<
|
const [dynamicTheme, setDynamicTheme] = createSignal<
|
||||||
ChatReply['dynamicTheme']
|
ContinueChatResponse['dynamicTheme']
|
||||||
>(props.initialChatReply.dynamicTheme)
|
>(props.initialChatReply.dynamicTheme)
|
||||||
const [theme, setTheme] = createSignal(props.initialChatReply.typebot.theme)
|
const [theme, setTheme] = createSignal(props.initialChatReply.typebot.theme)
|
||||||
const [isSending, setIsSending] = createSignal(false)
|
const [isSending, setIsSending] = createSignal(false)
|
||||||
@ -136,9 +137,16 @@ export const ConversationContainer = (props: Props) => {
|
|||||||
|
|
||||||
const sendMessage = async (
|
const sendMessage = async (
|
||||||
message: string | undefined,
|
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)
|
setHasError(false)
|
||||||
const currentInputBlock = [...chatChunks()].pop()?.input
|
const currentInputBlock = [...chatChunks()].pop()?.input
|
||||||
if (currentInputBlock?.id && props.onAnswer && message)
|
if (currentInputBlock?.id && props.onAnswer && message)
|
||||||
@ -153,11 +161,10 @@ export const ConversationContainer = (props: Props) => {
|
|||||||
const longRequest = setTimeout(() => {
|
const longRequest = setTimeout(() => {
|
||||||
setIsSending(true)
|
setIsSending(true)
|
||||||
}, 1000)
|
}, 1000)
|
||||||
const { data, error } = await sendMessageQuery({
|
const { data, error } = await continueChatQuery({
|
||||||
apiHost: props.context.apiHost,
|
apiHost: props.context.apiHost,
|
||||||
sessionId: props.initialChatReply.sessionId,
|
sessionId: props.initialChatReply.sessionId,
|
||||||
message,
|
message,
|
||||||
clientLogs,
|
|
||||||
})
|
})
|
||||||
clearTimeout(longRequest)
|
clearTimeout(longRequest)
|
||||||
setIsSending(false)
|
setIsSending(false)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
ChatReply,
|
ContinueChatResponse,
|
||||||
ChoiceInputBlock,
|
ChoiceInputBlock,
|
||||||
EmailInputBlock,
|
EmailInputBlock,
|
||||||
FileInputBlock,
|
FileInputBlock,
|
||||||
@ -39,7 +39,7 @@ import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constan
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
ref: HTMLDivElement | undefined
|
ref: HTMLDivElement | undefined
|
||||||
block: NonNullable<ChatReply['input']>
|
block: NonNullable<ContinueChatResponse['input']>
|
||||||
hasHostAvatar: boolean
|
hasHostAvatar: boolean
|
||||||
guestAvatar?: NonNullable<Theme['chat']>['guestAvatar']
|
guestAvatar?: NonNullable<Theme['chat']>['guestAvatar']
|
||||||
inputIndex: number
|
inputIndex: number
|
||||||
@ -113,7 +113,7 @@ export const InputChatBlock = (props: Props) => {
|
|||||||
|
|
||||||
const Input = (props: {
|
const Input = (props: {
|
||||||
context: BotContext
|
context: BotContext
|
||||||
block: NonNullable<ChatReply['input']>
|
block: NonNullable<ContinueChatResponse['input']>
|
||||||
inputIndex: number
|
inputIndex: number
|
||||||
isInputPrefillEnabled: boolean
|
isInputPrefillEnabled: boolean
|
||||||
onSubmit: (answer: InputSubmitContent) => void
|
onSubmit: (answer: InputSubmitContent) => void
|
||||||
@ -252,11 +252,11 @@ const Input = (props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isButtonsBlock = (
|
const isButtonsBlock = (
|
||||||
block: ChatReply['input']
|
block: ContinueChatResponse['input']
|
||||||
): ChoiceInputBlock | undefined =>
|
): ChoiceInputBlock | undefined =>
|
||||||
block?.type === InputBlockType.CHOICE ? block : undefined
|
block?.type === InputBlockType.CHOICE ? block : undefined
|
||||||
|
|
||||||
const isPictureChoiceBlock = (
|
const isPictureChoiceBlock = (
|
||||||
block: ChatReply['input']
|
block: ContinueChatResponse['input']
|
||||||
): PictureChoiceBlock | undefined =>
|
): PictureChoiceBlock | undefined =>
|
||||||
block?.type === InputBlockType.PICTURE_CHOICE ? block : undefined
|
block?.type === InputBlockType.PICTURE_CHOICE ? block : undefined
|
||||||
|
@ -10,7 +10,7 @@ export const defaultBotProps: BotProps = {
|
|||||||
onInit: undefined,
|
onInit: undefined,
|
||||||
onNewLogs: undefined,
|
onNewLogs: undefined,
|
||||||
isPreview: undefined,
|
isPreview: undefined,
|
||||||
startGroupId: undefined,
|
startFrom: undefined,
|
||||||
prefilledVariables: undefined,
|
prefilledVariables: undefined,
|
||||||
apiHost: undefined,
|
apiHost: undefined,
|
||||||
resultId: 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 = {
|
export type InputSubmitContent = {
|
||||||
label?: string
|
label?: string
|
||||||
@ -13,9 +13,9 @@ export type BotContext = {
|
|||||||
sessionId: string
|
sessionId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InitialChatReply = ChatReply & {
|
export type InitialChatReply = StartChatResponse & {
|
||||||
typebot: NonNullable<ChatReply['typebot']>
|
typebot: NonNullable<StartChatResponse['typebot']>
|
||||||
sessionId: NonNullable<ChatReply['sessionId']>
|
sessionId: NonNullable<StartChatResponse['sessionId']>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OutgoingLog = {
|
export type OutgoingLog = {
|
||||||
@ -30,7 +30,7 @@ export type ClientSideActionContext = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ChatChunk = Pick<
|
export type ChatChunk = Pick<
|
||||||
ChatReply,
|
ContinueChatResponse,
|
||||||
'messages' | 'input' | 'clientSideActions'
|
'messages' | 'input' | 'clientSideActions'
|
||||||
> & {
|
> & {
|
||||||
streamingMessageId?: string
|
streamingMessageId?: string
|
||||||
|
@ -8,11 +8,11 @@ import { executeWait } from '@/features/blocks/logic/wait/utils/executeWait'
|
|||||||
import { executeWebhook } from '@/features/blocks/integrations/webhook/executeWebhook'
|
import { executeWebhook } from '@/features/blocks/integrations/webhook/executeWebhook'
|
||||||
import { executePixel } from '@/features/blocks/integrations/pixel/executePixel'
|
import { executePixel } from '@/features/blocks/integrations/pixel/executePixel'
|
||||||
import { ClientSideActionContext } from '@/types'
|
import { ClientSideActionContext } from '@/types'
|
||||||
import type { ChatReply, ReplyLog } from '@typebot.io/schemas'
|
import type { ContinueChatResponse, ChatLog } from '@typebot.io/schemas'
|
||||||
import { injectStartProps } from './injectStartProps'
|
import { injectStartProps } from './injectStartProps'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
clientSideAction: NonNullable<ChatReply['clientSideActions']>[0]
|
clientSideAction: NonNullable<ContinueChatResponse['clientSideActions']>[0]
|
||||||
context: ClientSideActionContext
|
context: ClientSideActionContext
|
||||||
onMessageStream?: (props: { id: string; message: string }) => void
|
onMessageStream?: (props: { id: string; message: string }) => void
|
||||||
}
|
}
|
||||||
@ -23,7 +23,7 @@ export const executeClientSideAction = async ({
|
|||||||
onMessageStream,
|
onMessageStream,
|
||||||
}: Props): Promise<
|
}: Props): Promise<
|
||||||
| { blockedPopupUrl: string }
|
| { blockedPopupUrl: string }
|
||||||
| { replyToSend: string | undefined; logs?: ReplyLog[] }
|
| { replyToSend: string | undefined; logs?: ChatLog[] }
|
||||||
| void
|
| void
|
||||||
> => {
|
> => {
|
||||||
if ('chatwoot' in clientSideAction) {
|
if ('chatwoot' in clientSideAction) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/nextjs",
|
"name": "@typebot.io/nextjs",
|
||||||
"version": "0.2.15",
|
"version": "0.2.16",
|
||||||
"description": "Convenient library to display typebots on your Next.js website",
|
"description": "Convenient library to display typebots on your Next.js website",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/react",
|
"name": "@typebot.io/react",
|
||||||
"version": "0.2.15",
|
"version": "0.2.16",
|
||||||
"description": "Convenient library to display typebots on your React app",
|
"description": "Convenient library to display typebots on your React app",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
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(),
|
updatedAt: z.date(),
|
||||||
state: sessionStateSchema,
|
state: sessionStateSchema,
|
||||||
})
|
})
|
||||||
|
export type ChatSession = z.infer<typeof chatSessionSchema>
|
||||||
|
|
||||||
const textMessageSchema = z.object({
|
const textMessageSchema = z.object({
|
||||||
type: z.literal(BubbleBlockType.TEXT),
|
type: z.literal(BubbleBlockType.TEXT),
|
||||||
@ -59,7 +60,7 @@ const embedMessageSchema = z.object({
|
|||||||
.merge(z.object({ height: z.number().optional() })),
|
.merge(z.object({ height: z.number().optional() })),
|
||||||
})
|
})
|
||||||
|
|
||||||
const chatMessageSchema = z
|
export const chatMessageSchema = z
|
||||||
.object({ id: z.string() })
|
.object({ id: z.string() })
|
||||||
.and(
|
.and(
|
||||||
z.discriminatedUnion('type', [
|
z.discriminatedUnion('type', [
|
||||||
@ -70,6 +71,7 @@ const chatMessageSchema = z
|
|||||||
embedMessageSchema,
|
embedMessageSchema,
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
export type ChatMessage = z.infer<typeof chatMessageSchema>
|
||||||
|
|
||||||
const scriptToExecuteSchema = z.object({
|
const scriptToExecuteSchema = z.object({
|
||||||
content: z.string(),
|
content: z.string(),
|
||||||
@ -85,6 +87,7 @@ const scriptToExecuteSchema = z.object({
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
export type ScriptToExecute = z.infer<typeof scriptToExecuteSchema>
|
||||||
|
|
||||||
const startTypebotPick = {
|
const startTypebotPick = {
|
||||||
version: true,
|
version: true,
|
||||||
@ -103,87 +106,72 @@ export const startTypebotSchema = z.preprocess(
|
|||||||
typebotV6Schema.pick(startTypebotPick),
|
typebotV6Schema.pick(startTypebotPick),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
export type StartTypebot = z.infer<typeof startTypebotSchema>
|
||||||
|
|
||||||
export const startElementIdSchema = z.union([
|
export const chatLogSchema = logSchema
|
||||||
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
|
|
||||||
.pick({
|
.pick({
|
||||||
status: true,
|
status: true,
|
||||||
description: true,
|
description: true,
|
||||||
})
|
})
|
||||||
.merge(z.object({ details: z.unknown().optional() }))
|
.merge(z.object({ details: z.unknown().optional() }))
|
||||||
|
export type ChatLog = z.infer<typeof chatLogSchema>
|
||||||
|
|
||||||
export const sendMessageInputSchema = z.object({
|
export const startChatInputSchema = z.object({
|
||||||
message: z
|
publicId: z.string(),
|
||||||
|
isStreamEnabled: z.boolean().optional(),
|
||||||
|
message: z.string().optional(),
|
||||||
|
resultId: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.describe("Provide it if you'd like to overwrite an existing result."),
|
||||||
'The answer to the previous chat input. Do not provide it if you are starting a new chat.'
|
isOnlyRegistering: z
|
||||||
),
|
.boolean()
|
||||||
sessionId: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.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(),
|
prefilledVariables: z
|
||||||
clientLogs: z
|
.record(z.unknown())
|
||||||
.array(replyLogSchema)
|
|
||||||
.optional()
|
.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({
|
const startPropsToInjectSchema = z.object({
|
||||||
googleAnalyticsId: z.string().optional(),
|
googleAnalyticsId: z.string().optional(),
|
||||||
@ -191,8 +179,9 @@ const startPropsToInjectSchema = z.object({
|
|||||||
gtmId: z.string().optional(),
|
gtmId: z.string().optional(),
|
||||||
customHeadCode: z.string().optional(),
|
customHeadCode: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
export type StartPropsToInject = z.infer<typeof startPropsToInjectSchema>
|
||||||
|
|
||||||
const clientSideActionSchema = z
|
export const clientSideActionSchema = z
|
||||||
.object({
|
.object({
|
||||||
lastBubbleBlockId: z.string().optional(),
|
lastBubbleBlockId: z.string().optional(),
|
||||||
expectsDedicatedReply: z.boolean().optional(),
|
expectsDedicatedReply: z.boolean().optional(),
|
||||||
@ -271,7 +260,14 @@ export const typebotInChatReply = z.preprocess(
|
|||||||
typebotV6Schema.pick(typebotInChatReplyPick),
|
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),
|
messages: z.array(chatMessageSchema),
|
||||||
input: z
|
input: z
|
||||||
.union([
|
.union([
|
||||||
@ -286,39 +282,30 @@ export const chatReplySchema = z.object({
|
|||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
clientSideActions: z.array(clientSideActionSchema).optional(),
|
clientSideActions: z.array(clientSideActionSchema).optional(),
|
||||||
sessionId: z.string().optional(),
|
logs: z.array(chatLogSchema).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(),
|
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 const startPreviewChatResponseSchema = startChatResponseSchema.omit({
|
||||||
export type ChatMessage = z.infer<typeof chatMessageSchema>
|
resultId: true,
|
||||||
export type SendMessageInput = z.infer<typeof sendMessageInputSchema>
|
})
|
||||||
export type ScriptToExecute = z.infer<typeof scriptToExecuteSchema>
|
|
||||||
export type StartParams = z.infer<typeof startParamsSchema>
|
export const continueChatResponseSchema = chatResponseBaseSchema
|
||||||
export type RuntimeOptions = z.infer<typeof runtimeOptionsSchema>
|
export type ContinueChatResponse = z.infer<typeof continueChatResponseSchema>
|
||||||
export type StartTypebot = z.infer<typeof startTypebotSchema>
|
|
||||||
export type ReplyLog = z.infer<typeof replyLogSchema>
|
|
||||||
export type StartPropsToInject = z.infer<typeof startPropsToInjectSchema>
|
|
||||||
|
Reference in New Issue
Block a user