⚡ Introduce a new high-performing standalone chat API (#1200)
Closes #1154 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added authentication functionality for user sessions in chat API. - Introduced chat-related API endpoints for starting, previewing, and continuing chat sessions, and streaming messages. - Implemented WhatsApp API webhook handling for receiving and processing messages. - Added environment variable `NEXT_PUBLIC_CHAT_API_URL` for chat API URL configuration. - **Bug Fixes** - Adjusted file upload logic to correctly determine the API host. - Fixed message streaming URL in chat integration with OpenAI. - **Documentation** - Updated guides for creating blocks, local installation, self-hosting, and deployment to use `bun` instead of `pnpm`. - **Refactor** - Refactored chat API functionalities to use modular architecture. - Simplified client log saving and session update functionalities by using external functions. - Transitioned package management and workflow commands to use `bun`. - **Chores** - Switched to `bun` for package management in Dockerfiles and GitHub workflows. - Added new Dockerfile for chat API service setup with Bun framework. - Updated `.prettierignore` and documentation with new commands. - **Style** - No visible changes to end-users. - **Tests** - No visible changes to end-users. - **Revert** - No reverts in this release. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@ -1,3 +1,3 @@
|
|||||||
emojiList.json
|
emojiList.json
|
||||||
iconNames.ts
|
iconNames.ts
|
||||||
reporters
|
reporters
|
||||||
|
@ -21,6 +21,12 @@ const injectViewerUrlIfVercelPreview = (val) => {
|
|||||||
process.env.VERCEL_BUILDER_PROJECT_NAME,
|
process.env.VERCEL_BUILDER_PROJECT_NAME,
|
||||||
process.env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME
|
process.env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME
|
||||||
)
|
)
|
||||||
|
if (process.env.NEXT_PUBLIC_CHAT_API_URL.includes('{{pr_id}}'))
|
||||||
|
process.env.NEXT_PUBLIC_CHAT_API_URL =
|
||||||
|
process.env.NEXT_PUBLIC_CHAT_API_URL.replace(
|
||||||
|
'{{pr_id}}',
|
||||||
|
process.env.VERCEL_GIT_PULL_REQUEST_ID
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
injectViewerUrlIfVercelPreview(process.env.NEXT_PUBLIC_VIEWER_URL)
|
injectViewerUrlIfVercelPreview(process.env.NEXT_PUBLIC_VIEWER_URL)
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
"next": "14.1.0",
|
"next": "14.1.0",
|
||||||
"next-auth": "4.22.1",
|
"next-auth": "4.22.1",
|
||||||
"nextjs-cors": "2.1.2",
|
"nextjs-cors": "2.1.2",
|
||||||
"nodemailer": "6.9.3",
|
"nodemailer": "6.9.8",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"openai": "4.28.4",
|
"openai": "4.28.4",
|
||||||
"papaparse": "5.4.1",
|
"papaparse": "5.4.1",
|
||||||
@ -114,7 +114,7 @@
|
|||||||
"@types/jsonwebtoken": "9.0.2",
|
"@types/jsonwebtoken": "9.0.2",
|
||||||
"@types/micro-cors": "0.1.3",
|
"@types/micro-cors": "0.1.3",
|
||||||
"@types/node": "20.4.2",
|
"@types/node": "20.4.2",
|
||||||
"@types/nodemailer": "6.4.8",
|
"@types/nodemailer": "6.4.14",
|
||||||
"@types/nprogress": "0.2.0",
|
"@types/nprogress": "0.2.0",
|
||||||
"@types/papaparse": "5.3.7",
|
"@types/papaparse": "5.3.7",
|
||||||
"@types/prettier": "2.7.3",
|
"@types/prettier": "2.7.3",
|
||||||
|
@ -114,6 +114,7 @@ export const startWhatsAppPreview = authenticatedProcedure
|
|||||||
typebotId,
|
typebotId,
|
||||||
startFrom,
|
startFrom,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
isStreamEnabled: false,
|
||||||
},
|
},
|
||||||
initialSessionState: {
|
initialSessionState: {
|
||||||
whatsApp: (existingSession?.state as SessionState | undefined)
|
whatsApp: (existingSession?.state as SessionState | undefined)
|
||||||
|
36
apps/chat-api/package.json
Normal file
36
apps/chat-api/package.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "chat-api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "dotenv -e ./.env -e ../../.env -- bun --hot src/index.ts",
|
||||||
|
"build": "dotenv -e ./.env -e ../../.env -- bun build --target=bun ./src/index.ts --outdir ./dist",
|
||||||
|
"start": "bun src/index.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hono/prometheus": "1.0.0",
|
||||||
|
"@hono/sentry": "1.0.1",
|
||||||
|
"@hono/typebox-validator": "0.2.2",
|
||||||
|
"@sinclair/typebox": "0.32.5",
|
||||||
|
"@trpc/server": "10.40.0",
|
||||||
|
"@typebot.io/bot-engine": "workspace:*",
|
||||||
|
"@typebot.io/env": "workspace:*",
|
||||||
|
"@typebot.io/forge": "workspace:*",
|
||||||
|
"@typebot.io/forge-repository": "workspace:*",
|
||||||
|
"@typebot.io/lib": "workspace:*",
|
||||||
|
"@typebot.io/prisma": "workspace:*",
|
||||||
|
"@typebot.io/schemas": "workspace:*",
|
||||||
|
"@typebot.io/variables": "workspace:*",
|
||||||
|
"ai": "3.0.12",
|
||||||
|
"hono": "4.0.5",
|
||||||
|
"openai": "4.28.4",
|
||||||
|
"prom-client": "15.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"dotenv-cli": "7.2.1",
|
||||||
|
"@typebot.io/tsconfig": "workspace:*",
|
||||||
|
"@types/react": "18.2.15",
|
||||||
|
"react": "18.2.0"
|
||||||
|
}
|
||||||
|
}
|
30
apps/chat-api/src/auth.ts
Normal file
30
apps/chat-api/src/auth.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
import { mockedUser } from '@typebot.io/lib/mockedUser'
|
||||||
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
|
|
||||||
|
export const getAuthenticatedUserId = async (
|
||||||
|
authorizationHeaderValue: string | undefined
|
||||||
|
): Promise<string | undefined> => {
|
||||||
|
if (env.NEXT_PUBLIC_E2E_TEST) return mockedUser.id
|
||||||
|
const bearerToken = extractBearerToken(authorizationHeaderValue)
|
||||||
|
if (!bearerToken) return
|
||||||
|
return authenticateByToken(bearerToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
const authenticateByToken = async (
|
||||||
|
token: string
|
||||||
|
): Promise<string | undefined> => {
|
||||||
|
if (typeof window !== 'undefined') return
|
||||||
|
const apiToken = await prisma.apiToken.findFirst({
|
||||||
|
where: {
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
ownerId: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return apiToken?.ownerId
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractBearerToken = (authorizationHeaderValue: string | undefined) =>
|
||||||
|
authorizationHeaderValue?.slice(7)
|
30
apps/chat-api/src/index.ts
Normal file
30
apps/chat-api/src/index.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Hono } from 'hono'
|
||||||
|
import { webRuntime } from './runtimes/web'
|
||||||
|
import { whatsAppRuntime } from './runtimes/whatsapp'
|
||||||
|
import { prometheus } from '@hono/prometheus'
|
||||||
|
import { sentry } from '@hono/sentry'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
'*',
|
||||||
|
sentry({
|
||||||
|
environment: env.NODE_ENV,
|
||||||
|
dsn: env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
|
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA + '-chat-api',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const { printMetrics, registerMetrics } = prometheus()
|
||||||
|
app.use('*', registerMetrics)
|
||||||
|
app.get('/metrics', printMetrics)
|
||||||
|
|
||||||
|
app.get('/ping', (c) => c.json({ status: 'ok' }, 200))
|
||||||
|
app.route('/', webRuntime)
|
||||||
|
app.route('/', whatsAppRuntime)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
port: process.env.PORT ?? 3002,
|
||||||
|
fetch: app.fetch,
|
||||||
|
}
|
132
apps/chat-api/src/runtimes/web.ts
Normal file
132
apps/chat-api/src/runtimes/web.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { startChat } from '@typebot.io/bot-engine/apiHandlers/startChat'
|
||||||
|
import { continueChat } from '@typebot.io/bot-engine/apiHandlers/continueChat'
|
||||||
|
import { startChatPreview } from '@typebot.io/bot-engine/apiHandlers/startChatPreview'
|
||||||
|
import { getMessageStream } from '@typebot.io/bot-engine/apiHandlers/getMessageStream'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { cors } from 'hono/cors'
|
||||||
|
import { tbValidator } from '@hono/typebox-validator'
|
||||||
|
import { Type as t } from '@sinclair/typebox'
|
||||||
|
import { getAuthenticatedUserId } from '../auth'
|
||||||
|
|
||||||
|
export const webRuntime = new Hono()
|
||||||
|
|
||||||
|
webRuntime.use('*', cors())
|
||||||
|
|
||||||
|
webRuntime.post(
|
||||||
|
'/api/v1/typebots/:publicId/startChat',
|
||||||
|
tbValidator(
|
||||||
|
'json',
|
||||||
|
t.Object({
|
||||||
|
message: t.Optional(t.String()),
|
||||||
|
isStreamEnabled: t.Optional(t.Boolean()),
|
||||||
|
resultId: t.Optional(t.String()),
|
||||||
|
isOnlyRegistering: t.Optional(t.Boolean()),
|
||||||
|
prefilledVariables: t.Optional(t.Record(t.String(), t.Unknown())),
|
||||||
|
}),
|
||||||
|
(result, c) => {
|
||||||
|
if (!result.success) return c.json({ message: 'Invalid input' }, 400)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const data = c.req.valid('json')
|
||||||
|
const { corsOrigin, ...response } = await startChat({
|
||||||
|
...data,
|
||||||
|
publicId: c.req.param('publicId'),
|
||||||
|
isStreamEnabled: data.isStreamEnabled ?? true,
|
||||||
|
isOnlyRegistering: data.isOnlyRegistering ?? false,
|
||||||
|
origin: c.req.header('origin'),
|
||||||
|
})
|
||||||
|
if (corsOrigin) c.res.headers.set('Access-Control-Allow-Origin', corsOrigin)
|
||||||
|
return c.json(response)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
webRuntime.post(
|
||||||
|
'/api/v1/typebots/:id/preview/startChat',
|
||||||
|
tbValidator(
|
||||||
|
'json',
|
||||||
|
t.Object({
|
||||||
|
message: t.Optional(t.String()),
|
||||||
|
isStreamEnabled: t.Optional(t.Boolean()),
|
||||||
|
resultId: t.Optional(t.String()),
|
||||||
|
isOnlyRegistering: t.Optional(t.Boolean()),
|
||||||
|
prefilledVariables: t.Optional(t.Record(t.String(), t.Unknown())),
|
||||||
|
startFrom: t.Optional(
|
||||||
|
t.Union([
|
||||||
|
t.Object({
|
||||||
|
type: t.Literal('group'),
|
||||||
|
groupId: t.String(),
|
||||||
|
}),
|
||||||
|
t.Object({
|
||||||
|
type: t.Literal('event'),
|
||||||
|
eventId: t.String(),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
typebot: t.Optional(t.Any()),
|
||||||
|
}),
|
||||||
|
(result, c) => {
|
||||||
|
if (!result.success) return c.json({ message: 'Invalid input' }, 400)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const data = c.req.valid('json')
|
||||||
|
const userId = !data.typebot
|
||||||
|
? await getAuthenticatedUserId(c.req.header('Authorization'))
|
||||||
|
: undefined
|
||||||
|
return c.json(
|
||||||
|
await startChatPreview({
|
||||||
|
...data,
|
||||||
|
typebotId: c.req.param('id'),
|
||||||
|
userId,
|
||||||
|
isStreamEnabled: data.isStreamEnabled ?? true,
|
||||||
|
isOnlyRegistering: data.isOnlyRegistering ?? false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
webRuntime.post(
|
||||||
|
'/api/v1/sessions/:sessionId/continueChat',
|
||||||
|
tbValidator(
|
||||||
|
'json',
|
||||||
|
t.Object({
|
||||||
|
message: t.Optional(t.String()),
|
||||||
|
}),
|
||||||
|
(result, c) => {
|
||||||
|
if (!result.success) return c.json({ message: 'Invalid input' }, 400)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const data = c.req.valid('json')
|
||||||
|
const { corsOrigin, ...response } = await continueChat({
|
||||||
|
...data,
|
||||||
|
sessionId: c.req.param('sessionId'),
|
||||||
|
origin: c.req.header('origin'),
|
||||||
|
})
|
||||||
|
if (corsOrigin) c.res.headers.set('Access-Control-Allow-Origin', corsOrigin)
|
||||||
|
return c.json(response)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
webRuntime.post(
|
||||||
|
'/api/v1/sessions/:sessionId/streamMessage',
|
||||||
|
tbValidator(
|
||||||
|
'json',
|
||||||
|
t.Object({
|
||||||
|
messages: t.Optional(t.Array(t.Any())),
|
||||||
|
}),
|
||||||
|
(result, c) => {
|
||||||
|
if (!result.success) return c.json({ message: 'Invalid input' }, 400)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const data = c.req.valid('json')
|
||||||
|
const { stream, status, message } = await getMessageStream({
|
||||||
|
sessionId: c.req.param('sessionId'),
|
||||||
|
messages: data.messages,
|
||||||
|
})
|
||||||
|
if (!stream) return c.json({ message }, (status ?? 400) as ResponseInit)
|
||||||
|
return new Response(stream)
|
||||||
|
}
|
||||||
|
)
|
51
apps/chat-api/src/runtimes/whatsapp.ts
Normal file
51
apps/chat-api/src/runtimes/whatsapp.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { receiveMessage } from '@typebot.io/bot-engine/apiHandlers/receiveMessage'
|
||||||
|
import { receiveMessagePreview } from '@typebot.io/bot-engine/apiHandlers/receiveMessagePreview'
|
||||||
|
import { tbValidator } from '@hono/typebox-validator'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { Type as t } from '@sinclair/typebox'
|
||||||
|
|
||||||
|
export const whatsAppRuntime = new Hono()
|
||||||
|
|
||||||
|
whatsAppRuntime.post(
|
||||||
|
'/api/v1/workspaces/:workspaceId/whatsapp/:credentialsId/webhook',
|
||||||
|
tbValidator(
|
||||||
|
'json',
|
||||||
|
t.Object({
|
||||||
|
object: t.String(),
|
||||||
|
entry: t.Any(),
|
||||||
|
}),
|
||||||
|
(result, c) => {
|
||||||
|
if (!result.success) return c.json({ message: 'Invalid input' }, 400)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const data = c.req.valid('json')
|
||||||
|
receiveMessage({
|
||||||
|
workspaceId: c.req.param('workspaceId'),
|
||||||
|
credentialsId: c.req.param('credentialsId'),
|
||||||
|
...data,
|
||||||
|
})
|
||||||
|
return c.json({ message: 'Webhook received' }, 200)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
whatsAppRuntime.post(
|
||||||
|
'/api/v1/whatsapp/preview/webhook',
|
||||||
|
tbValidator(
|
||||||
|
'json',
|
||||||
|
t.Object({
|
||||||
|
object: t.String(),
|
||||||
|
entry: t.Any(),
|
||||||
|
}),
|
||||||
|
(result, c) => {
|
||||||
|
if (!result.success) return c.json({ message: 'Invalid input' }, 400)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const data = c.req.valid('json')
|
||||||
|
receiveMessagePreview({
|
||||||
|
...data,
|
||||||
|
})
|
||||||
|
return c.json({ message: 'Webhook received' }, 200)
|
||||||
|
}
|
||||||
|
)
|
8
apps/chat-api/tsconfig.json
Normal file
8
apps/chat-api/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "@typebot.io/tsconfig/base.json",
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react"
|
||||||
|
}
|
||||||
|
}
|
@ -1772,14 +1772,16 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"isStreamEnabled": {
|
"isStreamEnabled": {
|
||||||
"type": "boolean"
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
},
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"isOnlyRegistering": {
|
"isOnlyRegistering": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "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."
|
"description": "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.",
|
||||||
|
"default": false
|
||||||
},
|
},
|
||||||
"typebot": {
|
"typebot": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "landing-page",
|
"name": "landing-page",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "dotenv -e ./.env -e ../../.env -- next dev -p 3002",
|
"dev": "dotenv -e ./.env -e ../../.env -- next dev -p 3003",
|
||||||
"start": "dotenv -e ./.env -e ../../.env -- next start",
|
"start": "dotenv -e ./.env -e ../../.env -- next start",
|
||||||
"build": "dotenv -e ./.env -e ../../.env -- next build",
|
"build": "dotenv -e ./.env -e ../../.env -- next build",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
|
@ -17,6 +17,12 @@ const injectViewerUrlIfVercelPreview = (val) => {
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL = `https://${process.env.VERCEL_BRANCH_URL}`
|
process.env.NEXT_PUBLIC_VIEWER_URL = `https://${process.env.VERCEL_BRANCH_URL}`
|
||||||
|
if (process.env.NEXT_PUBLIC_CHAT_API_URL.includes('{{pr_id}}'))
|
||||||
|
process.env.NEXT_PUBLIC_CHAT_API_URL =
|
||||||
|
process.env.NEXT_PUBLIC_CHAT_API_URL.replace(
|
||||||
|
'{{pr_id}}',
|
||||||
|
process.env.VERCEL_GIT_PULL_REQUEST_ID
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
injectViewerUrlIfVercelPreview(process.env.NEXT_PUBLIC_VIEWER_URL)
|
injectViewerUrlIfVercelPreview(process.env.NEXT_PUBLIC_VIEWER_URL)
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
"got": "12.6.0",
|
"got": "12.6.0",
|
||||||
"next": "14.1.0",
|
"next": "14.1.0",
|
||||||
"nextjs-cors": "2.1.2",
|
"nextjs-cors": "2.1.2",
|
||||||
"nodemailer": "6.9.3",
|
"nodemailer": "6.9.8",
|
||||||
"openai": "4.28.4",
|
"openai": "4.28.4",
|
||||||
"qs": "6.11.2",
|
"qs": "6.11.2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"@typebot.io/variables": "workspace:*",
|
"@typebot.io/variables": "workspace:*",
|
||||||
"@types/cors": "2.8.13",
|
"@types/cors": "2.8.13",
|
||||||
"@types/node": "20.4.2",
|
"@types/node": "20.4.2",
|
||||||
"@types/nodemailer": "6.4.8",
|
"@types/nodemailer": "6.4.14",
|
||||||
"@types/papaparse": "5.3.7",
|
"@types/papaparse": "5.3.7",
|
||||||
"@types/qs": "6.9.7",
|
"@types/qs": "6.9.7",
|
||||||
"@types/react": "18.2.15",
|
"@types/react": "18.2.15",
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
import { getMessageStream } from '@typebot.io/bot-engine/apiHandlers/getMessageStream'
|
||||||
|
import { StreamingTextResponse } from 'ai'
|
||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
|
const responseHeaders = {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||||
|
'Access-Control-Expose-Headers': 'Content-Length, X-JSON',
|
||||||
|
'Access-Control-Allow-Headers': '*',
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function OPTIONS() {
|
||||||
|
return new Response('ok', {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST',
|
||||||
|
'Access-Control-Expose-Headers': 'Content-Length, X-JSON',
|
||||||
|
'Access-Control-Allow-Headers': '*',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(
|
||||||
|
req: Request,
|
||||||
|
{ params }: { params: { sessionId: string } }
|
||||||
|
) {
|
||||||
|
if (process.env.VERCEL_ENV)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Can't get streaming if hosted on Vercel" },
|
||||||
|
{ status: 400, headers: responseHeaders }
|
||||||
|
)
|
||||||
|
const messages =
|
||||||
|
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
||||||
|
const { stream, status, message } = await getMessageStream({
|
||||||
|
sessionId: params.sessionId,
|
||||||
|
messages,
|
||||||
|
})
|
||||||
|
if (!stream)
|
||||||
|
return NextResponse.json({ message }, { status, headers: responseHeaders })
|
||||||
|
return new StreamingTextResponse(stream, {
|
||||||
|
headers: responseHeaders,
|
||||||
|
})
|
||||||
|
}
|
@ -1,14 +1,7 @@
|
|||||||
import { publicProcedure } from '@/helpers/server/trpc'
|
import { publicProcedure } from '@/helpers/server/trpc'
|
||||||
import { continueChatResponseSchema } from '@typebot.io/schemas/features/chat/schema'
|
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, isNotDefined } from '@typebot.io/lib/utils'
|
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { filterPotentiallySensitiveLogs } from '@typebot.io/bot-engine/logs/filterPotentiallySensitiveLogs'
|
import { continueChat as continueChatFn } from '@typebot.io/bot-engine/apiHandlers/continueChat'
|
||||||
import { computeCurrentProgress } from '@typebot.io/bot-engine/computeCurrentProgress'
|
|
||||||
|
|
||||||
export const continueChat = publicProcedure
|
export const continueChat = publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -29,92 +22,12 @@ export const continueChat = publicProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(continueChatResponseSchema)
|
.output(continueChatResponseSchema)
|
||||||
.mutation(async ({ input: { sessionId, message }, ctx: { res, origin } }) => {
|
.mutation(async ({ input: { sessionId, message }, ctx: { origin, res } }) => {
|
||||||
const session = await getSession(sessionId)
|
const { corsOrigin, ...response } = await continueChatFn({
|
||||||
|
origin,
|
||||||
if (!session) {
|
sessionId,
|
||||||
throw new TRPCError({
|
message,
|
||||||
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.',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (
|
|
||||||
session?.state.allowedOrigins &&
|
|
||||||
session.state.allowedOrigins.length > 0
|
|
||||||
) {
|
|
||||||
if (origin && session.state.allowedOrigins.includes(origin))
|
|
||||||
res.setHeader('Access-Control-Allow-Origin', origin)
|
|
||||||
else
|
|
||||||
res.setHeader(
|
|
||||||
'Access-Control-Allow-Origin',
|
|
||||||
session.state.allowedOrigins[0]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
messages,
|
|
||||||
input,
|
|
||||||
clientSideActions,
|
|
||||||
newSessionState,
|
|
||||||
logs,
|
|
||||||
lastMessageNewFormat,
|
|
||||||
visitedEdges,
|
|
||||||
} = await continueBotFlow(message, {
|
|
||||||
version: 2,
|
|
||||||
state: session.state,
|
|
||||||
startTime: Date.now(),
|
|
||||||
})
|
})
|
||||||
|
if (corsOrigin) res.setHeader('Access-Control-Allow-Origin', corsOrigin)
|
||||||
if (newSessionState)
|
return response
|
||||||
await saveStateToDatabase({
|
|
||||||
session: {
|
|
||||||
id: session.id,
|
|
||||||
state: newSessionState,
|
|
||||||
},
|
|
||||||
input,
|
|
||||||
logs,
|
|
||||||
clientSideActions,
|
|
||||||
visitedEdges,
|
|
||||||
hasCustomEmbedBubble: messages.some(
|
|
||||||
(message) => message.type === 'custom-embed'
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
const isPreview = isNotDefined(session.state.typebotsQueue[0].resultId)
|
|
||||||
|
|
||||||
const isEnded =
|
|
||||||
newSessionState.progressMetadata &&
|
|
||||||
!input?.id &&
|
|
||||||
(clientSideActions?.filter((c) => c.expectsDedicatedReply).length ??
|
|
||||||
0) === 0
|
|
||||||
|
|
||||||
return {
|
|
||||||
messages,
|
|
||||||
input,
|
|
||||||
clientSideActions,
|
|
||||||
dynamicTheme: parseDynamicTheme(newSessionState),
|
|
||||||
logs: isPreview ? logs : logs?.filter(filterPotentiallySensitiveLogs),
|
|
||||||
lastMessageNewFormat,
|
|
||||||
progress: newSessionState.progressMetadata
|
|
||||||
? isEnded
|
|
||||||
? 100
|
|
||||||
: computeCurrentProgress({
|
|
||||||
typebotsQueue: newSessionState.typebotsQueue,
|
|
||||||
progressMetadata: newSessionState.progressMetadata,
|
|
||||||
currentInputBlockId: input?.id,
|
|
||||||
})
|
|
||||||
: undefined,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import { publicProcedure } from '@/helpers/server/trpc'
|
import { publicProcedure } from '@/helpers/server/trpc'
|
||||||
import { chatLogSchema } from '@typebot.io/schemas/features/chat/schema'
|
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 { z } from 'zod'
|
||||||
import { saveLogs } from '@typebot.io/bot-engine/queries/saveLogs'
|
import { saveClientLogs as saveClientLogsFn } from '@typebot.io/bot-engine/apiHandlers/saveClientLogs'
|
||||||
import { formatLogDetails } from '@typebot.io/bot-engine/logs/helpers/formatLogDetails'
|
|
||||||
import * as Sentry from '@sentry/nextjs'
|
|
||||||
|
|
||||||
export const saveClientLogs = publicProcedure
|
export const saveClientLogs = publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -22,42 +18,6 @@ export const saveClientLogs = publicProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(z.object({ message: z.string() }))
|
.output(z.object({ message: z.string() }))
|
||||||
.mutation(async ({ input: { sessionId, clientLogs } }) => {
|
.mutation(({ input: { sessionId, clientLogs } }) =>
|
||||||
const session = await getSession(sessionId)
|
saveClientLogsFn({ sessionId, clientLogs })
|
||||||
|
)
|
||||||
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.',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
@ -3,11 +3,7 @@ import {
|
|||||||
startChatInputSchema,
|
startChatInputSchema,
|
||||||
startChatResponseSchema,
|
startChatResponseSchema,
|
||||||
} from '@typebot.io/schemas/features/chat/schema'
|
} from '@typebot.io/schemas/features/chat/schema'
|
||||||
import { startSession } from '@typebot.io/bot-engine/startSession'
|
import { startChat as startChatFn } from '@typebot.io/bot-engine/apiHandlers/startChat'
|
||||||
import { saveStateToDatabase } from '@typebot.io/bot-engine/saveStateToDatabase'
|
|
||||||
import { restartSession } from '@typebot.io/bot-engine/queries/restartSession'
|
|
||||||
import { filterPotentiallySensitiveLogs } from '@typebot.io/bot-engine/logs/filterPotentiallySensitiveLogs'
|
|
||||||
import { computeCurrentProgress } from '@typebot.io/bot-engine/computeCurrentProgress'
|
|
||||||
|
|
||||||
export const startChat = publicProcedure
|
export const startChat = publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -19,99 +15,11 @@ export const startChat = publicProcedure
|
|||||||
})
|
})
|
||||||
.input(startChatInputSchema)
|
.input(startChatInputSchema)
|
||||||
.output(startChatResponseSchema)
|
.output(startChatResponseSchema)
|
||||||
.mutation(
|
.mutation(async ({ input, ctx: { origin, res } }) => {
|
||||||
async ({
|
const { corsOrigin, ...response } = await startChatFn({
|
||||||
input: {
|
...input,
|
||||||
message,
|
origin,
|
||||||
isOnlyRegistering,
|
})
|
||||||
publicId,
|
if (corsOrigin) res.setHeader('Access-Control-Allow-Origin', corsOrigin)
|
||||||
isStreamEnabled,
|
return response
|
||||||
prefilledVariables,
|
})
|
||||||
resultId: startResultId,
|
|
||||||
},
|
|
||||||
ctx: { origin, res },
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
typebot,
|
|
||||||
messages,
|
|
||||||
input,
|
|
||||||
resultId,
|
|
||||||
dynamicTheme,
|
|
||||||
logs,
|
|
||||||
clientSideActions,
|
|
||||||
newSessionState,
|
|
||||||
visitedEdges,
|
|
||||||
} = await startSession({
|
|
||||||
version: 2,
|
|
||||||
startParams: {
|
|
||||||
type: 'live',
|
|
||||||
isOnlyRegistering,
|
|
||||||
isStreamEnabled,
|
|
||||||
publicId,
|
|
||||||
prefilledVariables,
|
|
||||||
resultId: startResultId,
|
|
||||||
},
|
|
||||||
message,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (
|
|
||||||
newSessionState.allowedOrigins &&
|
|
||||||
newSessionState.allowedOrigins.length > 0
|
|
||||||
) {
|
|
||||||
if (origin && newSessionState.allowedOrigins.includes(origin))
|
|
||||||
res.setHeader('Access-Control-Allow-Origin', origin)
|
|
||||||
else
|
|
||||||
res.setHeader(
|
|
||||||
'Access-Control-Allow-Origin',
|
|
||||||
newSessionState.allowedOrigins[0]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = isOnlyRegistering
|
|
||||||
? await restartSession({
|
|
||||||
state: newSessionState,
|
|
||||||
})
|
|
||||||
: await saveStateToDatabase({
|
|
||||||
session: {
|
|
||||||
state: newSessionState,
|
|
||||||
},
|
|
||||||
input,
|
|
||||||
logs,
|
|
||||||
clientSideActions,
|
|
||||||
visitedEdges,
|
|
||||||
hasCustomEmbedBubble: messages.some(
|
|
||||||
(message) => message.type === 'custom-embed'
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
const isEnded =
|
|
||||||
newSessionState.progressMetadata &&
|
|
||||||
!input?.id &&
|
|
||||||
(clientSideActions?.filter((c) => c.expectsDedicatedReply).length ??
|
|
||||||
0) === 0
|
|
||||||
|
|
||||||
return {
|
|
||||||
sessionId: session.id,
|
|
||||||
typebot: {
|
|
||||||
id: typebot.id,
|
|
||||||
theme: typebot.theme,
|
|
||||||
settings: typebot.settings,
|
|
||||||
},
|
|
||||||
messages,
|
|
||||||
input,
|
|
||||||
resultId,
|
|
||||||
dynamicTheme,
|
|
||||||
logs: logs?.filter(filterPotentiallySensitiveLogs),
|
|
||||||
clientSideActions,
|
|
||||||
progress: newSessionState.progressMetadata
|
|
||||||
? isEnded
|
|
||||||
? 100
|
|
||||||
: computeCurrentProgress({
|
|
||||||
typebotsQueue: newSessionState.typebotsQueue,
|
|
||||||
progressMetadata: newSessionState.progressMetadata,
|
|
||||||
currentInputBlockId: input?.id,
|
|
||||||
})
|
|
||||||
: undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
@ -2,11 +2,8 @@ import {
|
|||||||
startPreviewChatInputSchema,
|
startPreviewChatInputSchema,
|
||||||
startPreviewChatResponseSchema,
|
startPreviewChatResponseSchema,
|
||||||
} from '@typebot.io/schemas/features/chat/schema'
|
} 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'
|
|
||||||
import { publicProcedure } from '@/helpers/server/trpc'
|
import { publicProcedure } from '@/helpers/server/trpc'
|
||||||
import { computeCurrentProgress } from '@typebot.io/bot-engine/computeCurrentProgress'
|
import { startChatPreview as startChatPreviewFn } from '@typebot.io/bot-engine/apiHandlers/startChatPreview'
|
||||||
|
|
||||||
export const startChatPreview = publicProcedure
|
export const startChatPreview = publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -32,75 +29,15 @@ export const startChatPreview = publicProcedure
|
|||||||
prefilledVariables,
|
prefilledVariables,
|
||||||
},
|
},
|
||||||
ctx: { user },
|
ctx: { user },
|
||||||
}) => {
|
}) =>
|
||||||
const {
|
startChatPreviewFn({
|
||||||
typebot,
|
|
||||||
messages,
|
|
||||||
input,
|
|
||||||
dynamicTheme,
|
|
||||||
logs,
|
|
||||||
clientSideActions,
|
|
||||||
newSessionState,
|
|
||||||
visitedEdges,
|
|
||||||
} = await startSession({
|
|
||||||
version: 2,
|
|
||||||
startParams: {
|
|
||||||
type: 'preview',
|
|
||||||
isOnlyRegistering,
|
|
||||||
isStreamEnabled,
|
|
||||||
startFrom,
|
|
||||||
typebotId,
|
|
||||||
typebot: startTypebot,
|
|
||||||
userId: user?.id,
|
|
||||||
prefilledVariables,
|
|
||||||
},
|
|
||||||
message,
|
message,
|
||||||
|
isOnlyRegistering,
|
||||||
|
isStreamEnabled,
|
||||||
|
startFrom,
|
||||||
|
typebotId,
|
||||||
|
typebot: startTypebot,
|
||||||
|
userId: user?.id,
|
||||||
|
prefilledVariables,
|
||||||
})
|
})
|
||||||
|
|
||||||
const session = isOnlyRegistering
|
|
||||||
? await restartSession({
|
|
||||||
state: newSessionState,
|
|
||||||
})
|
|
||||||
: await saveStateToDatabase({
|
|
||||||
session: {
|
|
||||||
state: newSessionState,
|
|
||||||
},
|
|
||||||
input,
|
|
||||||
logs,
|
|
||||||
clientSideActions,
|
|
||||||
visitedEdges,
|
|
||||||
hasCustomEmbedBubble: messages.some(
|
|
||||||
(message) => message.type === 'custom-embed'
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
const isEnded =
|
|
||||||
newSessionState.progressMetadata &&
|
|
||||||
!input?.id &&
|
|
||||||
(clientSideActions?.filter((c) => c.expectsDedicatedReply).length ??
|
|
||||||
0) === 0
|
|
||||||
|
|
||||||
return {
|
|
||||||
sessionId: session.id,
|
|
||||||
typebot: {
|
|
||||||
id: typebot.id,
|
|
||||||
theme: typebot.theme,
|
|
||||||
settings: typebot.settings,
|
|
||||||
},
|
|
||||||
messages,
|
|
||||||
input,
|
|
||||||
dynamicTheme,
|
|
||||||
logs,
|
|
||||||
clientSideActions,
|
|
||||||
progress: newSessionState.progressMetadata
|
|
||||||
? isEnded
|
|
||||||
? 100
|
|
||||||
: computeCurrentProgress({
|
|
||||||
typebotsQueue: newSessionState.typebotsQueue,
|
|
||||||
progressMetadata: newSessionState.progressMetadata,
|
|
||||||
currentInputBlockId: input?.id,
|
|
||||||
})
|
|
||||||
: undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
@ -1,14 +1,6 @@
|
|||||||
import { publicProcedure } from '@/helpers/server/trpc'
|
import { publicProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { getSession } from '@typebot.io/bot-engine/queries/getSession'
|
import { updateTypebotInSession as updateTypebotInSessionFn } from '@typebot.io/bot-engine/apiHandlers/updateTypebotInSession'
|
||||||
import {
|
|
||||||
PublicTypebot,
|
|
||||||
SessionState,
|
|
||||||
Typebot,
|
|
||||||
Variable,
|
|
||||||
} from '@typebot.io/schemas'
|
|
||||||
import prisma from '@typebot.io/lib/prisma'
|
|
||||||
|
|
||||||
export const updateTypebotInSession = publicProcedure
|
export const updateTypebotInSession = publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -27,85 +19,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(({ input: { sessionId }, ctx: { user } }) =>
|
||||||
if (!user)
|
updateTypebotInSessionFn({ user, sessionId })
|
||||||
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Unauthorized' })
|
)
|
||||||
const session = await getSession(sessionId)
|
|
||||||
if (!session)
|
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Session not found' })
|
|
||||||
|
|
||||||
const publicTypebot = (await prisma.publicTypebot.findFirst({
|
|
||||||
where: {
|
|
||||||
typebot: {
|
|
||||||
id: session.state.typebotsQueue[0].typebot.id,
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
workspace: {
|
|
||||||
members: {
|
|
||||||
some: { userId: user.id, role: { in: ['ADMIN', 'MEMBER'] } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
collaborators: {
|
|
||||||
some: { userId: user.id, type: { in: ['WRITE'] } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
edges: true,
|
|
||||||
groups: true,
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
})) as Pick<PublicTypebot, 'edges' | 'variables' | 'groups'> | null
|
|
||||||
|
|
||||||
if (!publicTypebot)
|
|
||||||
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Unauthorized' })
|
|
||||||
|
|
||||||
const newSessionState = updateSessionState(session.state, publicTypebot)
|
|
||||||
|
|
||||||
await prisma.chatSession.updateMany({
|
|
||||||
where: { id: session.id },
|
|
||||||
data: { state: newSessionState },
|
|
||||||
})
|
|
||||||
|
|
||||||
return { message: 'success' }
|
|
||||||
})
|
|
||||||
|
|
||||||
const updateSessionState = (
|
|
||||||
currentState: SessionState,
|
|
||||||
newTypebot: Pick<PublicTypebot, 'edges' | 'variables' | 'groups'>
|
|
||||||
): SessionState => ({
|
|
||||||
...currentState,
|
|
||||||
typebotsQueue: currentState.typebotsQueue.map((typebotInQueue, index) =>
|
|
||||||
index === 0
|
|
||||||
? {
|
|
||||||
...typebotInQueue,
|
|
||||||
typebot: {
|
|
||||||
...typebotInQueue.typebot,
|
|
||||||
edges: newTypebot.edges,
|
|
||||||
groups: newTypebot.groups,
|
|
||||||
variables: updateVariablesInSession(
|
|
||||||
typebotInQueue.typebot.variables,
|
|
||||||
newTypebot.variables
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: typebotInQueue
|
|
||||||
) as SessionState['typebotsQueue'],
|
|
||||||
})
|
|
||||||
|
|
||||||
const updateVariablesInSession = (
|
|
||||||
currentVariables: Variable[],
|
|
||||||
newVariables: Typebot['variables']
|
|
||||||
): Variable[] => [
|
|
||||||
...currentVariables,
|
|
||||||
...newVariables.filter(
|
|
||||||
(newVariable) =>
|
|
||||||
!currentVariables.find(
|
|
||||||
(currentVariable) => currentVariable.id === newVariable.id
|
|
||||||
)
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
21
chatApi.Dockerfile
Normal file
21
chatApi.Dockerfile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
FROM oven/bun
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN apt-get -qy update && apt-get -qy --no-install-recommends install openssl ca-certificates git -y && update-ca-certificates
|
||||||
|
|
||||||
|
RUN bun install
|
||||||
|
|
||||||
|
# Need Node for Prisma
|
||||||
|
COPY --from=node:18 /usr/local/bin/node /usr/local/bin/node
|
||||||
|
RUN bun /app/packages/prisma/scripts/db-exec.ts "bunx prisma generate"
|
||||||
|
|
||||||
|
RUN rm -rf /usr/local/bin/node
|
||||||
|
RUN rm -rf /app/apps/builder
|
||||||
|
RUN rm -rf /app/apps/viewer
|
||||||
|
|
||||||
|
ENV PORT=3000
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["bun", "run", "apps/chat-api/src/index.ts"]
|
10
package.json
10
package.json
@ -3,12 +3,20 @@
|
|||||||
"name": "typebot-os",
|
"name": "typebot-os",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*",
|
||||||
|
"packages/deprecated/*",
|
||||||
|
"packages/embeds/*",
|
||||||
|
"packages/forge/*",
|
||||||
|
"packages/forge/blocks/*",
|
||||||
|
"apps/*"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"docker:up": "docker compose -f docker-compose.dev.yml up -d && node -e \"setTimeout(() => {}, 5000)\"",
|
"docker:up": "docker compose -f docker-compose.dev.yml up -d && node -e \"setTimeout(() => {}, 5000)\"",
|
||||||
"docker:nuke": "docker compose -f docker-compose.dev.yml down --volumes --remove-orphans",
|
"docker:nuke": "docker compose -f docker-compose.dev.yml down --volumes --remove-orphans",
|
||||||
"lint": "turbo run lint",
|
"lint": "turbo run lint",
|
||||||
"dev": "pnpm docker:up && turbo build --filter=@typebot.io/nextjs... && turbo run dev --filter=builder... --filter=viewer... --parallel --no-cache",
|
"dev": "pnpm docker:up && turbo build --filter=@typebot.io/nextjs... && turbo run dev --filter=builder... --filter=viewer... --filter=chat-api... --parallel --no-cache",
|
||||||
"build": "pnpm docker:up && turbo run build",
|
"build": "pnpm docker:up && turbo run build",
|
||||||
"build:apps": "turbo run build --filter=builder... --filter=viewer...",
|
"build:apps": "turbo run build --filter=builder... --filter=viewer...",
|
||||||
"db:migrate": "cd packages/prisma && pnpm run db:migrate",
|
"db:migrate": "cd packages/prisma && pnpm run db:migrate",
|
||||||
|
102
packages/bot-engine/apiHandlers/continueChat.ts
Normal file
102
packages/bot-engine/apiHandlers/continueChat.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { TRPCError } from '@trpc/server'
|
||||||
|
import { isDefined, isNotDefined } from '@typebot.io/lib/utils'
|
||||||
|
import { getSession } from '../queries/getSession'
|
||||||
|
import { continueBotFlow } from '../continueBotFlow'
|
||||||
|
import { filterPotentiallySensitiveLogs } from '../logs/filterPotentiallySensitiveLogs'
|
||||||
|
import { parseDynamicTheme } from '../parseDynamicTheme'
|
||||||
|
import { saveStateToDatabase } from '../saveStateToDatabase'
|
||||||
|
import { computeCurrentProgress } from '../computeCurrentProgress'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
origin: string | undefined
|
||||||
|
message?: string
|
||||||
|
sessionId: string
|
||||||
|
}
|
||||||
|
export const continueChat = async ({ origin, sessionId, message }: Props) => {
|
||||||
|
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.',
|
||||||
|
})
|
||||||
|
|
||||||
|
let corsOrigin
|
||||||
|
|
||||||
|
if (
|
||||||
|
session?.state.allowedOrigins &&
|
||||||
|
session.state.allowedOrigins.length > 0
|
||||||
|
) {
|
||||||
|
if (origin && session.state.allowedOrigins.includes(origin))
|
||||||
|
corsOrigin = origin
|
||||||
|
else corsOrigin = session.state.allowedOrigins[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
clientSideActions,
|
||||||
|
newSessionState,
|
||||||
|
logs,
|
||||||
|
lastMessageNewFormat,
|
||||||
|
visitedEdges,
|
||||||
|
} = await continueBotFlow(message, {
|
||||||
|
version: 2,
|
||||||
|
state: session.state,
|
||||||
|
startTime: Date.now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (newSessionState)
|
||||||
|
await saveStateToDatabase({
|
||||||
|
session: {
|
||||||
|
id: session.id,
|
||||||
|
state: newSessionState,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
visitedEdges,
|
||||||
|
hasCustomEmbedBubble: messages.some(
|
||||||
|
(message) => message.type === 'custom-embed'
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
const isPreview = isNotDefined(session.state.typebotsQueue[0].resultId)
|
||||||
|
|
||||||
|
const isEnded =
|
||||||
|
newSessionState.progressMetadata &&
|
||||||
|
!input?.id &&
|
||||||
|
(clientSideActions?.filter((c) => c.expectsDedicatedReply).length ?? 0) ===
|
||||||
|
0
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
clientSideActions,
|
||||||
|
dynamicTheme: parseDynamicTheme(newSessionState),
|
||||||
|
logs: isPreview ? logs : logs?.filter(filterPotentiallySensitiveLogs),
|
||||||
|
lastMessageNewFormat,
|
||||||
|
corsOrigin,
|
||||||
|
progress: newSessionState.progressMetadata
|
||||||
|
? isEnded
|
||||||
|
? 100
|
||||||
|
: computeCurrentProgress({
|
||||||
|
typebotsQueue: newSessionState.typebotsQueue,
|
||||||
|
progressMetadata: newSessionState.progressMetadata,
|
||||||
|
currentInputBlockId: input?.id,
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
}
|
130
packages/bot-engine/apiHandlers/getMessageStream.ts
Normal file
130
packages/bot-engine/apiHandlers/getMessageStream.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||||
|
import { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
||||||
|
import { OpenAI } from 'openai'
|
||||||
|
import { decryptV2 } from '@typebot.io/lib/api/encryption/decryptV2'
|
||||||
|
import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
|
||||||
|
import { ReadOnlyVariableStore } from '@typebot.io/forge'
|
||||||
|
import {
|
||||||
|
ParseVariablesOptions,
|
||||||
|
parseVariables,
|
||||||
|
} from '@typebot.io/variables/parseVariables'
|
||||||
|
import { getOpenAIChatCompletionStream } from './legacy/getOpenAIChatCompletionStream'
|
||||||
|
import { getCredentials } from '../queries/getCredentials'
|
||||||
|
import { getSession } from '../queries/getSession'
|
||||||
|
import { getBlockById } from '@typebot.io/schemas/helpers'
|
||||||
|
import { isForgedBlockType } from '@typebot.io/schemas/features/blocks/forged/helpers'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
sessionId: string
|
||||||
|
messages: OpenAI.Chat.ChatCompletionMessage[] | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMessageStream = async ({ sessionId, messages }: Props) => {
|
||||||
|
const session = await getSession(sessionId)
|
||||||
|
|
||||||
|
if (!session?.state || !session.state.currentBlockId)
|
||||||
|
return { status: 404, message: 'Could not find session' }
|
||||||
|
|
||||||
|
const { group, block } = getBlockById(
|
||||||
|
session.state.currentBlockId,
|
||||||
|
session.state.typebotsQueue[0].typebot.groups
|
||||||
|
)
|
||||||
|
if (!block || !group)
|
||||||
|
return {
|
||||||
|
status: 404,
|
||||||
|
message: 'Could not find block or group',
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('options' in block))
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
message: 'This block does not have options',
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.type === IntegrationBlockType.OPEN_AI && messages) {
|
||||||
|
try {
|
||||||
|
const stream = await getOpenAIChatCompletionStream(
|
||||||
|
session.state,
|
||||||
|
block.options as ChatCompletionOpenAIOptions,
|
||||||
|
messages
|
||||||
|
)
|
||||||
|
if (!stream)
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
message: 'Could not create stream',
|
||||||
|
}
|
||||||
|
|
||||||
|
return { stream }
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof OpenAI.APIError) {
|
||||||
|
const { message } = error
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isForgedBlockType(block.type))
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
message: 'This block does not have a stream function',
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockDef = forgedBlocks[block.type]
|
||||||
|
const action = blockDef?.actions.find((a) => a.name === block.options?.action)
|
||||||
|
|
||||||
|
if (!action || !action.run?.stream)
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
message: 'This block does not have a stream function',
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!block.options.credentialsId)
|
||||||
|
return { status: 404, message: 'Could not find credentials' }
|
||||||
|
const credentials = await getCredentials(block.options.credentialsId)
|
||||||
|
if (!credentials)
|
||||||
|
return { status: 404, message: 'Could not find credentials' }
|
||||||
|
const decryptedCredentials = await decryptV2(
|
||||||
|
credentials.data,
|
||||||
|
credentials.iv
|
||||||
|
)
|
||||||
|
const variables: ReadOnlyVariableStore = {
|
||||||
|
list: () => session.state.typebotsQueue[0].typebot.variables,
|
||||||
|
get: (id: string) => {
|
||||||
|
const variable = session.state.typebotsQueue[0].typebot.variables.find(
|
||||||
|
(variable) => variable.id === id
|
||||||
|
)
|
||||||
|
return variable?.value
|
||||||
|
},
|
||||||
|
parse: (text: string, params?: ParseVariablesOptions) =>
|
||||||
|
parseVariables(
|
||||||
|
session.state.typebotsQueue[0].typebot.variables,
|
||||||
|
params
|
||||||
|
)(text),
|
||||||
|
}
|
||||||
|
const stream = await action.run.stream.run({
|
||||||
|
credentials: decryptedCredentials,
|
||||||
|
options: block.options,
|
||||||
|
variables,
|
||||||
|
})
|
||||||
|
if (!stream) return { status: 500, message: 'Could not create stream' }
|
||||||
|
|
||||||
|
return { stream }
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof OpenAI.APIError) {
|
||||||
|
const { message } = error
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
message: 'Could not create stream',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
import { decryptV2 } from '@typebot.io/lib/api/encryption/decryptV2'
|
||||||
|
import { isNotEmpty } from '@typebot.io/lib/utils'
|
||||||
|
import {
|
||||||
|
ChatCompletionOpenAIOptions,
|
||||||
|
OpenAICredentials,
|
||||||
|
} from '@typebot.io/schemas/features/blocks/integrations/openai'
|
||||||
|
import { SessionState } from '@typebot.io/schemas/features/chat/sessionState'
|
||||||
|
import { OpenAIStream } from 'ai'
|
||||||
|
import { parseVariableNumber } from '@typebot.io/variables/parseVariableNumber'
|
||||||
|
import { ClientOptions, OpenAI } from 'openai'
|
||||||
|
import { defaultOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai/constants'
|
||||||
|
import { getCredentials } from '../../queries/getCredentials'
|
||||||
|
|
||||||
|
export const getOpenAIChatCompletionStream = async (
|
||||||
|
state: SessionState,
|
||||||
|
options: ChatCompletionOpenAIOptions,
|
||||||
|
messages: OpenAI.Chat.ChatCompletionMessageParam[]
|
||||||
|
) => {
|
||||||
|
if (!options.credentialsId) return
|
||||||
|
const credentials = await getCredentials(options.credentialsId)
|
||||||
|
if (!credentials) {
|
||||||
|
console.error('Could not find credentials in database')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { apiKey } = (await decryptV2(
|
||||||
|
credentials.data,
|
||||||
|
credentials.iv
|
||||||
|
)) as OpenAICredentials['data']
|
||||||
|
|
||||||
|
const { typebot } = state.typebotsQueue[0]
|
||||||
|
const temperature = parseVariableNumber(typebot.variables)(
|
||||||
|
options.advancedSettings?.temperature
|
||||||
|
)
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
apiKey,
|
||||||
|
baseURL: options.baseUrl,
|
||||||
|
defaultHeaders: {
|
||||||
|
'api-key': apiKey,
|
||||||
|
},
|
||||||
|
defaultQuery: isNotEmpty(options.apiVersion)
|
||||||
|
? {
|
||||||
|
'api-version': options.apiVersion,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
} satisfies ClientOptions
|
||||||
|
|
||||||
|
const openai = new OpenAI(config)
|
||||||
|
|
||||||
|
const response = await openai.chat.completions.create({
|
||||||
|
model: options.model ?? defaultOpenAIOptions.model,
|
||||||
|
temperature,
|
||||||
|
stream: true,
|
||||||
|
messages,
|
||||||
|
})
|
||||||
|
|
||||||
|
return OpenAIStream(response)
|
||||||
|
}
|
35
packages/bot-engine/apiHandlers/receiveMessage.ts
Normal file
35
packages/bot-engine/apiHandlers/receiveMessage.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { WhatsAppWebhookRequestBody } from '@typebot.io/schemas/features/whatsapp'
|
||||||
|
import { isNotDefined } from '@typebot.io/lib'
|
||||||
|
import { resumeWhatsAppFlow } from '../whatsapp/resumeWhatsAppFlow'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
entry: WhatsAppWebhookRequestBody['entry']
|
||||||
|
credentialsId: string
|
||||||
|
workspaceId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const receiveMessage = async ({
|
||||||
|
entry,
|
||||||
|
credentialsId,
|
||||||
|
workspaceId,
|
||||||
|
}: Props) => {
|
||||||
|
const receivedMessage = entry.at(0)?.changes.at(0)?.value.messages?.at(0)
|
||||||
|
if (isNotDefined(receivedMessage)) return { message: 'No message found' }
|
||||||
|
const contactName =
|
||||||
|
entry.at(0)?.changes.at(0)?.value?.contacts?.at(0)?.profile?.name ?? ''
|
||||||
|
const contactPhoneNumber =
|
||||||
|
entry.at(0)?.changes.at(0)?.value?.messages?.at(0)?.from ?? ''
|
||||||
|
const phoneNumberId = entry.at(0)?.changes.at(0)?.value
|
||||||
|
.metadata.phone_number_id
|
||||||
|
if (!phoneNumberId) return { message: 'No phone number id found' }
|
||||||
|
return resumeWhatsAppFlow({
|
||||||
|
receivedMessage,
|
||||||
|
sessionId: `wa-${phoneNumberId}-${receivedMessage.from}`,
|
||||||
|
credentialsId,
|
||||||
|
workspaceId,
|
||||||
|
contact: {
|
||||||
|
name: contactName,
|
||||||
|
phoneNumber: contactPhoneNumber,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
31
packages/bot-engine/apiHandlers/receiveMessagePreview.ts
Normal file
31
packages/bot-engine/apiHandlers/receiveMessagePreview.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { WhatsAppWebhookRequestBody } from '@typebot.io/schemas/features/whatsapp'
|
||||||
|
import { isNotDefined } from '@typebot.io/lib'
|
||||||
|
import { TRPCError } from '@trpc/server'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
import { resumeWhatsAppFlow } from '../whatsapp/resumeWhatsAppFlow'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
entry: WhatsAppWebhookRequestBody['entry']
|
||||||
|
}
|
||||||
|
export const receiveMessagePreview = ({ entry }: Props) => {
|
||||||
|
if (!env.WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID)
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
|
message: 'WHATSAPP_PREVIEW_FROM_PHONE_NUMBER_ID is not defined',
|
||||||
|
})
|
||||||
|
const receivedMessage = entry.at(0)?.changes.at(0)?.value.messages?.at(0)
|
||||||
|
if (isNotDefined(receivedMessage)) return { message: 'No message found' }
|
||||||
|
const contactName =
|
||||||
|
entry.at(0)?.changes.at(0)?.value?.contacts?.at(0)?.profile?.name ?? ''
|
||||||
|
const contactPhoneNumber =
|
||||||
|
entry.at(0)?.changes.at(0)?.value?.messages?.at(0)?.from ?? ''
|
||||||
|
|
||||||
|
return resumeWhatsAppFlow({
|
||||||
|
receivedMessage,
|
||||||
|
sessionId: `wa-preview-${receivedMessage.from}`,
|
||||||
|
contact: {
|
||||||
|
name: contactName,
|
||||||
|
phoneNumber: contactPhoneNumber,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
49
packages/bot-engine/apiHandlers/saveClientLogs.ts
Normal file
49
packages/bot-engine/apiHandlers/saveClientLogs.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { TRPCError } from '@trpc/server'
|
||||||
|
import { ChatLog } from '@typebot.io/schemas'
|
||||||
|
import { formatLogDetails } from '../logs/helpers/formatLogDetails'
|
||||||
|
import { getSession } from '../queries/getSession'
|
||||||
|
import { saveLogs } from '../queries/saveLogs'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
sessionId: string
|
||||||
|
clientLogs: ChatLog[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveClientLogs = async ({ sessionId, clientLogs }: Props) => {
|
||||||
|
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)
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
|
message: 'Failed to save logs.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
107
packages/bot-engine/apiHandlers/startChat.ts
Normal file
107
packages/bot-engine/apiHandlers/startChat.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { computeCurrentProgress } from '../computeCurrentProgress'
|
||||||
|
import { filterPotentiallySensitiveLogs } from '../logs/filterPotentiallySensitiveLogs'
|
||||||
|
import { restartSession } from '../queries/restartSession'
|
||||||
|
import { saveStateToDatabase } from '../saveStateToDatabase'
|
||||||
|
import { startSession } from '../startSession'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
origin: string | undefined
|
||||||
|
message?: string
|
||||||
|
isOnlyRegistering: boolean
|
||||||
|
publicId: string
|
||||||
|
isStreamEnabled: boolean
|
||||||
|
prefilledVariables?: Record<string, unknown>
|
||||||
|
resultId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startChat = async ({
|
||||||
|
origin,
|
||||||
|
message,
|
||||||
|
isOnlyRegistering,
|
||||||
|
publicId,
|
||||||
|
isStreamEnabled,
|
||||||
|
prefilledVariables,
|
||||||
|
resultId: startResultId,
|
||||||
|
}: Props) => {
|
||||||
|
const {
|
||||||
|
typebot,
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
resultId,
|
||||||
|
dynamicTheme,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
newSessionState,
|
||||||
|
visitedEdges,
|
||||||
|
} = await startSession({
|
||||||
|
version: 2,
|
||||||
|
startParams: {
|
||||||
|
type: 'live',
|
||||||
|
isOnlyRegistering,
|
||||||
|
isStreamEnabled,
|
||||||
|
publicId,
|
||||||
|
prefilledVariables,
|
||||||
|
resultId: startResultId,
|
||||||
|
},
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
|
||||||
|
let corsOrigin
|
||||||
|
|
||||||
|
if (
|
||||||
|
newSessionState.allowedOrigins &&
|
||||||
|
newSessionState.allowedOrigins.length > 0
|
||||||
|
) {
|
||||||
|
if (origin && newSessionState.allowedOrigins.includes(origin))
|
||||||
|
corsOrigin = origin
|
||||||
|
else corsOrigin = newSessionState.allowedOrigins[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = isOnlyRegistering
|
||||||
|
? await restartSession({
|
||||||
|
state: newSessionState,
|
||||||
|
})
|
||||||
|
: await saveStateToDatabase({
|
||||||
|
session: {
|
||||||
|
state: newSessionState,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
visitedEdges,
|
||||||
|
hasCustomEmbedBubble: messages.some(
|
||||||
|
(message) => message.type === 'custom-embed'
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
const isEnded =
|
||||||
|
newSessionState.progressMetadata &&
|
||||||
|
!input?.id &&
|
||||||
|
(clientSideActions?.filter((c) => c.expectsDedicatedReply).length ?? 0) ===
|
||||||
|
0
|
||||||
|
|
||||||
|
return {
|
||||||
|
sessionId: session.id,
|
||||||
|
typebot: {
|
||||||
|
id: typebot.id,
|
||||||
|
theme: typebot.theme,
|
||||||
|
settings: typebot.settings,
|
||||||
|
},
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
resultId,
|
||||||
|
dynamicTheme,
|
||||||
|
logs: logs?.filter(filterPotentiallySensitiveLogs),
|
||||||
|
clientSideActions,
|
||||||
|
corsOrigin,
|
||||||
|
progress: newSessionState.progressMetadata
|
||||||
|
? isEnded
|
||||||
|
? 100
|
||||||
|
: computeCurrentProgress({
|
||||||
|
typebotsQueue: newSessionState.typebotsQueue,
|
||||||
|
progressMetadata: newSessionState.progressMetadata,
|
||||||
|
currentInputBlockId: input?.id,
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
}
|
97
packages/bot-engine/apiHandlers/startChatPreview.ts
Normal file
97
packages/bot-engine/apiHandlers/startChatPreview.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { StartFrom, StartTypebot } from '@typebot.io/schemas'
|
||||||
|
import { restartSession } from '../queries/restartSession'
|
||||||
|
import { saveStateToDatabase } from '../saveStateToDatabase'
|
||||||
|
import { startSession } from '../startSession'
|
||||||
|
import { computeCurrentProgress } from '../computeCurrentProgress'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
message?: string
|
||||||
|
isOnlyRegistering: boolean
|
||||||
|
isStreamEnabled: boolean
|
||||||
|
startFrom?: StartFrom
|
||||||
|
typebotId: string
|
||||||
|
typebot?: StartTypebot
|
||||||
|
userId?: string
|
||||||
|
prefilledVariables?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startChatPreview = async ({
|
||||||
|
message,
|
||||||
|
isOnlyRegistering,
|
||||||
|
isStreamEnabled,
|
||||||
|
startFrom,
|
||||||
|
typebotId,
|
||||||
|
typebot: startTypebot,
|
||||||
|
userId,
|
||||||
|
prefilledVariables,
|
||||||
|
}: Props) => {
|
||||||
|
const {
|
||||||
|
typebot,
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
dynamicTheme,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
newSessionState,
|
||||||
|
visitedEdges,
|
||||||
|
} = await startSession({
|
||||||
|
version: 2,
|
||||||
|
startParams: {
|
||||||
|
type: 'preview',
|
||||||
|
isOnlyRegistering,
|
||||||
|
isStreamEnabled,
|
||||||
|
startFrom,
|
||||||
|
typebotId,
|
||||||
|
typebot: startTypebot,
|
||||||
|
userId,
|
||||||
|
prefilledVariables,
|
||||||
|
},
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
|
||||||
|
const session = isOnlyRegistering
|
||||||
|
? await restartSession({
|
||||||
|
state: newSessionState,
|
||||||
|
})
|
||||||
|
: await saveStateToDatabase({
|
||||||
|
session: {
|
||||||
|
state: newSessionState,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
visitedEdges,
|
||||||
|
hasCustomEmbedBubble: messages.some(
|
||||||
|
(message) => message.type === 'custom-embed'
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
const isEnded =
|
||||||
|
newSessionState.progressMetadata &&
|
||||||
|
!input?.id &&
|
||||||
|
(clientSideActions?.filter((c) => c.expectsDedicatedReply).length ?? 0) ===
|
||||||
|
0
|
||||||
|
|
||||||
|
return {
|
||||||
|
sessionId: session.id,
|
||||||
|
typebot: {
|
||||||
|
id: typebot.id,
|
||||||
|
theme: typebot.theme,
|
||||||
|
settings: typebot.settings,
|
||||||
|
},
|
||||||
|
messages,
|
||||||
|
input,
|
||||||
|
dynamicTheme,
|
||||||
|
logs,
|
||||||
|
clientSideActions,
|
||||||
|
progress: newSessionState.progressMetadata
|
||||||
|
? isEnded
|
||||||
|
? 100
|
||||||
|
: computeCurrentProgress({
|
||||||
|
typebotsQueue: newSessionState.typebotsQueue,
|
||||||
|
progressMetadata: newSessionState.progressMetadata,
|
||||||
|
currentInputBlockId: input?.id,
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
}
|
97
packages/bot-engine/apiHandlers/updateTypebotInSession.ts
Normal file
97
packages/bot-engine/apiHandlers/updateTypebotInSession.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { TRPCError } from '@trpc/server'
|
||||||
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
|
import {
|
||||||
|
SessionState,
|
||||||
|
Variable,
|
||||||
|
PublicTypebot,
|
||||||
|
Typebot,
|
||||||
|
} from '@typebot.io/schemas'
|
||||||
|
import { getSession } from '../queries/getSession'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
user?: { id: string }
|
||||||
|
sessionId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateTypebotInSession = async ({ user, sessionId }: Props) => {
|
||||||
|
if (!user)
|
||||||
|
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Unauthorized' })
|
||||||
|
const session = await getSession(sessionId)
|
||||||
|
if (!session)
|
||||||
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Session not found' })
|
||||||
|
|
||||||
|
const publicTypebot = (await prisma.publicTypebot.findFirst({
|
||||||
|
where: {
|
||||||
|
typebot: {
|
||||||
|
id: session.state.typebotsQueue[0].typebot.id,
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
workspace: {
|
||||||
|
members: {
|
||||||
|
some: { userId: user.id, role: { in: ['ADMIN', 'MEMBER'] } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
collaborators: {
|
||||||
|
some: { userId: user.id, type: { in: ['WRITE'] } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
edges: true,
|
||||||
|
groups: true,
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
})) as Pick<PublicTypebot, 'edges' | 'variables' | 'groups'> | null
|
||||||
|
|
||||||
|
if (!publicTypebot)
|
||||||
|
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Unauthorized' })
|
||||||
|
|
||||||
|
const newSessionState = updateSessionState(session.state, publicTypebot)
|
||||||
|
|
||||||
|
await prisma.chatSession.updateMany({
|
||||||
|
where: { id: session.id },
|
||||||
|
data: { state: newSessionState },
|
||||||
|
})
|
||||||
|
|
||||||
|
return { message: 'success' } as const
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSessionState = (
|
||||||
|
currentState: SessionState,
|
||||||
|
newTypebot: Pick<PublicTypebot, 'edges' | 'variables' | 'groups'>
|
||||||
|
): SessionState => ({
|
||||||
|
...currentState,
|
||||||
|
typebotsQueue: currentState.typebotsQueue.map((typebotInQueue, index) =>
|
||||||
|
index === 0
|
||||||
|
? {
|
||||||
|
...typebotInQueue,
|
||||||
|
typebot: {
|
||||||
|
...typebotInQueue.typebot,
|
||||||
|
edges: newTypebot.edges,
|
||||||
|
groups: newTypebot.groups,
|
||||||
|
variables: updateVariablesInSession(
|
||||||
|
typebotInQueue.typebot.variables,
|
||||||
|
newTypebot.variables
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: typebotInQueue
|
||||||
|
) as SessionState['typebotsQueue'],
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateVariablesInSession = (
|
||||||
|
currentVariables: Variable[],
|
||||||
|
newVariables: Typebot['variables']
|
||||||
|
): Variable[] => [
|
||||||
|
...currentVariables,
|
||||||
|
...newVariables.filter(
|
||||||
|
(newVariable) =>
|
||||||
|
!currentVariables.find(
|
||||||
|
(currentVariable) => currentVariable.id === newVariable.id
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
@ -84,14 +84,13 @@ export const createChatCompletionOpenAI = async (
|
|||||||
)?.name
|
)?.name
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isPlaneteScale() &&
|
|
||||||
isCredentialsV2(credentials) &&
|
|
||||||
newSessionState.isStreamEnabled &&
|
newSessionState.isStreamEnabled &&
|
||||||
!newSessionState.whatsApp &&
|
!newSessionState.whatsApp &&
|
||||||
isNextBubbleMessageWithAssistantMessage(typebot)(
|
isNextBubbleMessageWithAssistantMessage(typebot)(
|
||||||
blockId,
|
blockId,
|
||||||
assistantMessageVariableName
|
assistantMessageVariableName
|
||||||
)
|
) &&
|
||||||
|
!process.env.VERCEL_ENV
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
clientSideActions: [
|
clientSideActions: [
|
||||||
|
@ -7,9 +7,9 @@ import {
|
|||||||
import got from 'got'
|
import got from 'got'
|
||||||
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
|
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
|
||||||
import { byId, isDefined, isEmpty } from '@typebot.io/lib'
|
import { byId, isDefined, isEmpty } from '@typebot.io/lib'
|
||||||
import prisma from '@typebot.io/lib/prisma'
|
|
||||||
import { ExecuteIntegrationResponse } from '../../../types'
|
import { ExecuteIntegrationResponse } from '../../../types'
|
||||||
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
|
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
|
||||||
|
import { getCredentials } from '../../../queries/getCredentials'
|
||||||
import { parseAnswers } from '@typebot.io/results/parseAnswers'
|
import { parseAnswers } from '@typebot.io/results/parseAnswers'
|
||||||
|
|
||||||
const URL = 'https://api.zemantic.ai/v1/search-documents'
|
const URL = 'https://api.zemantic.ai/v1/search-documents'
|
||||||
@ -25,11 +25,7 @@ export const executeZemanticAiBlock = async (
|
|||||||
outgoingEdgeId: block.outgoingEdgeId,
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = await prisma.credentials.findUnique({
|
const credentials = await getCredentials(block.options.credentialsId)
|
||||||
where: {
|
|
||||||
id: block.options?.credentialsId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
return {
|
return {
|
||||||
|
@ -19,6 +19,8 @@ import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesI
|
|||||||
import { ExecuteIntegrationResponse } from '../types'
|
import { ExecuteIntegrationResponse } from '../types'
|
||||||
import { byId } from '@typebot.io/lib'
|
import { byId } from '@typebot.io/lib'
|
||||||
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
import { getCredentials } from '../queries/getCredentials'
|
||||||
|
|
||||||
export const executeForgedBlock = async (
|
export const executeForgedBlock = async (
|
||||||
state: SessionState,
|
state: SessionState,
|
||||||
@ -40,11 +42,7 @@ export const executeForgedBlock = async (
|
|||||||
logs: [noCredentialsError],
|
logs: [noCredentialsError],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
credentials = await prisma.credentials.findUnique({
|
credentials = await getCredentials(block.options.credentialsId)
|
||||||
where: {
|
|
||||||
id: block.options.credentialsId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
console.error('Could not find credentials in database')
|
console.error('Could not find credentials in database')
|
||||||
return {
|
return {
|
||||||
@ -57,15 +55,13 @@ export const executeForgedBlock = async (
|
|||||||
const typebot = state.typebotsQueue[0].typebot
|
const typebot = state.typebotsQueue[0].typebot
|
||||||
if (
|
if (
|
||||||
action?.run?.stream &&
|
action?.run?.stream &&
|
||||||
isPlaneteScale() &&
|
|
||||||
credentials &&
|
|
||||||
isCredentialsV2(credentials) &&
|
|
||||||
state.isStreamEnabled &&
|
|
||||||
!state.whatsApp &&
|
|
||||||
isNextBubbleTextWithStreamingVar(typebot)(
|
isNextBubbleTextWithStreamingVar(typebot)(
|
||||||
block.id,
|
block.id,
|
||||||
action.run.stream.getStreamVariableId(block.options)
|
action.run.stream.getStreamVariableId(block.options)
|
||||||
)
|
) &&
|
||||||
|
state.isStreamEnabled &&
|
||||||
|
!state.whatsApp &&
|
||||||
|
!process.env.VERCEL_ENV
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
outgoingEdgeId: block.outgoingEdgeId,
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
|
@ -25,9 +25,10 @@
|
|||||||
"google-auth-library": "8.9.0",
|
"google-auth-library": "8.9.0",
|
||||||
"google-spreadsheet": "4.1.1",
|
"google-spreadsheet": "4.1.1",
|
||||||
"got": "12.6.0",
|
"got": "12.6.0",
|
||||||
|
"ky": "^1.1.3",
|
||||||
"libphonenumber-js": "1.10.37",
|
"libphonenumber-js": "1.10.37",
|
||||||
"node-html-parser": "6.1.5",
|
"node-html-parser": "6.1.5",
|
||||||
"nodemailer": "6.9.3",
|
"nodemailer": "6.9.8",
|
||||||
"openai": "4.28.4",
|
"openai": "4.28.4",
|
||||||
"qs": "6.11.2",
|
"qs": "6.11.2",
|
||||||
"stripe": "12.13.0",
|
"stripe": "12.13.0",
|
||||||
@ -36,7 +37,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typebot.io/forge": "workspace:*",
|
"@typebot.io/forge": "workspace:*",
|
||||||
"@typebot.io/forge-repository": "workspace:*",
|
"@typebot.io/forge-repository": "workspace:*",
|
||||||
"@types/nodemailer": "6.4.8",
|
"@types/nodemailer": "6.4.14",
|
||||||
"@types/qs": "6.9.7"
|
"@types/qs": "6.9.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
packages/bot-engine/queries/getCredentials.ts
Normal file
6
packages/bot-engine/queries/getCredentials.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
|
|
||||||
|
export const getCredentials = async (credentialsId: string) =>
|
||||||
|
prisma.credentials.findUnique({
|
||||||
|
where: { id: credentialsId },
|
||||||
|
})
|
@ -10,7 +10,7 @@ import {
|
|||||||
import { convertMessageToWhatsAppMessage } from './convertMessageToWhatsAppMessage'
|
import { convertMessageToWhatsAppMessage } from './convertMessageToWhatsAppMessage'
|
||||||
import { sendWhatsAppMessage } from './sendWhatsAppMessage'
|
import { sendWhatsAppMessage } from './sendWhatsAppMessage'
|
||||||
import * as Sentry from '@sentry/nextjs'
|
import * as Sentry from '@sentry/nextjs'
|
||||||
import { HTTPError } from 'got'
|
import { HTTPError } from 'ky'
|
||||||
import { convertInputToWhatsAppMessages } from './convertInputToWhatsAppMessage'
|
import { convertInputToWhatsAppMessages } from './convertInputToWhatsAppMessage'
|
||||||
import { isNotDefined } from '@typebot.io/lib/utils'
|
import { isNotDefined } from '@typebot.io/lib/utils'
|
||||||
import { computeTypingDuration } from '../computeTypingDuration'
|
import { computeTypingDuration } from '../computeTypingDuration'
|
||||||
@ -141,7 +141,7 @@ export const sendChatReplyToWhatsApp = async ({
|
|||||||
Sentry.captureException(err, { extra: { message } })
|
Sentry.captureException(err, { extra: { message } })
|
||||||
console.log('Failed to send message:', JSON.stringify(message, null, 2))
|
console.log('Failed to send message:', JSON.stringify(message, null, 2))
|
||||||
if (err instanceof HTTPError)
|
if (err instanceof HTTPError)
|
||||||
console.log('HTTPError', err.response.statusCode, err.response.body)
|
console.log('HTTPError', err.response.status, err.response.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ export const sendChatReplyToWhatsApp = async ({
|
|||||||
Sentry.captureException(err, { extra: { message } })
|
Sentry.captureException(err, { extra: { message } })
|
||||||
console.log('Failed to send message:', JSON.stringify(message, null, 2))
|
console.log('Failed to send message:', JSON.stringify(message, null, 2))
|
||||||
if (err instanceof HTTPError)
|
if (err instanceof HTTPError)
|
||||||
console.log('HTTPError', err.response.statusCode, err.response.body)
|
console.log('HTTPError', err.response.status, err.response.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,7 +253,7 @@ const executeClientSideAction =
|
|||||||
Sentry.captureException(err, { extra: { message } })
|
Sentry.captureException(err, { extra: { message } })
|
||||||
console.log('Failed to send message:', JSON.stringify(message, null, 2))
|
console.log('Failed to send message:', JSON.stringify(message, null, 2))
|
||||||
if (err instanceof HTTPError)
|
if (err instanceof HTTPError)
|
||||||
console.log('HTTPError', err.response.statusCode, err.response.body)
|
console.log('HTTPError', err.response.status, err.response.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import got from 'got'
|
import ky from 'ky'
|
||||||
import {
|
import {
|
||||||
WhatsAppCredentials,
|
WhatsAppCredentials,
|
||||||
WhatsAppSendingMessage,
|
WhatsAppSendingMessage,
|
||||||
@ -16,14 +16,16 @@ export const sendWhatsAppMessage = async ({
|
|||||||
message,
|
message,
|
||||||
credentials,
|
credentials,
|
||||||
}: Props) =>
|
}: Props) =>
|
||||||
got.post({
|
ky.post(
|
||||||
url: `${env.WHATSAPP_CLOUD_API_URL}/v17.0/${credentials.phoneNumberId}/messages`,
|
`${env.WHATSAPP_CLOUD_API_URL}/v17.0/${credentials.phoneNumberId}/messages`,
|
||||||
headers: {
|
{
|
||||||
Authorization: `Bearer ${credentials.systemUserAccessToken}`,
|
headers: {
|
||||||
},
|
Authorization: `Bearer ${credentials.systemUserAccessToken}`,
|
||||||
json: {
|
},
|
||||||
messaging_product: 'whatsapp',
|
json: {
|
||||||
to,
|
messaging_product: 'whatsapp',
|
||||||
...message,
|
to,
|
||||||
},
|
...message,
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -17,11 +17,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@faire/mjml-react": "3.3.0",
|
"@faire/mjml-react": "3.3.0",
|
||||||
"@types/node": "20.4.2",
|
"@types/node": "20.4.2",
|
||||||
"@types/nodemailer": "6.4.8",
|
"@types/nodemailer": "6.4.14",
|
||||||
"@types/react": "18.2.15",
|
"@types/react": "18.2.15",
|
||||||
"concurrently": "8.2.0",
|
"concurrently": "8.2.0",
|
||||||
"http-server": "14.1.1",
|
"http-server": "14.1.1",
|
||||||
"nodemailer": "6.9.3",
|
"nodemailer": "6.9.8",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"tsx": "3.12.7",
|
"tsx": "3.12.7",
|
||||||
"@typebot.io/lib": "workspace:*",
|
"@typebot.io/lib": "workspace:*",
|
||||||
|
@ -73,7 +73,8 @@ export const FileUploadForm = (props: Props) => {
|
|||||||
})
|
})
|
||||||
setIsUploading(true)
|
setIsUploading(true)
|
||||||
const urls = await uploadFiles({
|
const urls = await uploadFiles({
|
||||||
apiHost: props.context.apiHost ?? guessApiHost(),
|
apiHost:
|
||||||
|
props.context.apiHost ?? guessApiHost({ ignoreChatApiUrl: true }),
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
file,
|
file,
|
||||||
@ -112,7 +113,8 @@ export const FileUploadForm = (props: Props) => {
|
|||||||
})
|
})
|
||||||
setIsUploading(true)
|
setIsUploading(true)
|
||||||
const urls = await uploadFiles({
|
const urls = await uploadFiles({
|
||||||
apiHost: props.context.apiHost ?? guessApiHost(),
|
apiHost:
|
||||||
|
props.context.apiHost ?? guessApiHost({ ignoreChatApiUrl: true }),
|
||||||
files: files.map((file) => ({
|
files: files.map((file) => ({
|
||||||
file: file,
|
file: file,
|
||||||
input: {
|
input: {
|
||||||
|
@ -25,16 +25,15 @@ export const streamChat =
|
|||||||
const apiHost = context.apiHost
|
const apiHost = context.apiHost
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${
|
`${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/sessions/${
|
||||||
isNotEmpty(apiHost) ? apiHost : guessApiHost()
|
context.sessionId
|
||||||
}/api/integrations/openai/streamer`,
|
}/streamMessage`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
sessionId: context.sessionId,
|
|
||||||
messages,
|
messages,
|
||||||
}),
|
}),
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
|
@ -83,7 +83,10 @@ export async function startChatQuery({
|
|||||||
startFrom,
|
startFrom,
|
||||||
typebot,
|
typebot,
|
||||||
prefilledVariables,
|
prefilledVariables,
|
||||||
} satisfies Omit<StartPreviewChatInput, 'typebotId'>,
|
} satisfies Omit<
|
||||||
|
StartPreviewChatInput,
|
||||||
|
'typebotId' | 'isOnlyRegistering'
|
||||||
|
>,
|
||||||
timeout: false,
|
timeout: false,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,29 @@
|
|||||||
import { getRuntimeVariable } from '@typebot.io/env/getRuntimeVariable'
|
import { getRuntimeVariable } from '@typebot.io/env/getRuntimeVariable'
|
||||||
|
|
||||||
const cloudViewerUrl = 'https://typebot.io'
|
const chatApiCloudFallbackHost = 'https://chat.typebot.io'
|
||||||
|
|
||||||
export const guessApiHost = () =>
|
type Params = {
|
||||||
getRuntimeVariable('NEXT_PUBLIC_VIEWER_URL')?.split(',')[0] ?? cloudViewerUrl
|
ignoreChatApiUrl?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const guessApiHost = (
|
||||||
|
{ ignoreChatApiUrl }: Params = { ignoreChatApiUrl: false }
|
||||||
|
) => {
|
||||||
|
const chatApiUrl = getRuntimeVariable('NEXT_PUBLIC_CHAT_API_URL')
|
||||||
|
const newChatApiOnUrls = getRuntimeVariable(
|
||||||
|
'NEXT_PUBLIC_USE_EXPERIMENTAL_CHAT_API_ON'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
!ignoreChatApiUrl &&
|
||||||
|
chatApiUrl &&
|
||||||
|
(!newChatApiOnUrls || newChatApiOnUrls.includes(window.location.href))
|
||||||
|
) {
|
||||||
|
return chatApiUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
getRuntimeVariable('NEXT_PUBLIC_VIEWER_URL')?.split(',')[0] ??
|
||||||
|
chatApiCloudFallbackHost
|
||||||
|
)
|
||||||
|
}
|
||||||
|
17
packages/env/env.ts
vendored
17
packages/env/env.ts
vendored
@ -46,7 +46,9 @@ const boolean = z.enum(['true', 'false']).transform((value) => value === 'true')
|
|||||||
|
|
||||||
const baseEnv = {
|
const baseEnv = {
|
||||||
server: {
|
server: {
|
||||||
NODE_ENV: z.enum(['development', 'production', 'test']).optional(),
|
NODE_ENV: z
|
||||||
|
.enum(['development', 'staging', 'production', 'test'])
|
||||||
|
.optional(),
|
||||||
DATABASE_URL: z
|
DATABASE_URL: z
|
||||||
.string()
|
.string()
|
||||||
.url()
|
.url()
|
||||||
@ -99,6 +101,15 @@ const baseEnv = {
|
|||||||
),
|
),
|
||||||
NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID: z.string().min(1).optional(),
|
NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID: z.string().min(1).optional(),
|
||||||
NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE: z.coerce.number().optional(),
|
NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE: z.coerce.number().optional(),
|
||||||
|
NEXT_PUBLIC_CHAT_API_URL: z.string().url().optional(),
|
||||||
|
// To remove to deploy chat API for all typebots
|
||||||
|
NEXT_PUBLIC_USE_EXPERIMENTAL_CHAT_API_ON: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.transform((val) =>
|
||||||
|
val.split('/').map((s) => s.split(',').map((s) => s.split('|')))
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
NEXT_PUBLIC_VIEWER_404_TITLE: z.string().optional().default('404'),
|
NEXT_PUBLIC_VIEWER_404_TITLE: z.string().optional().default('404'),
|
||||||
NEXT_PUBLIC_VIEWER_404_SUBTITLE: z
|
NEXT_PUBLIC_VIEWER_404_SUBTITLE: z
|
||||||
.string()
|
.string()
|
||||||
@ -114,6 +125,10 @@ const baseEnv = {
|
|||||||
NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE: getRuntimeVariable(
|
NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE: getRuntimeVariable(
|
||||||
'NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE'
|
'NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE'
|
||||||
),
|
),
|
||||||
|
NEXT_PUBLIC_CHAT_API_URL: getRuntimeVariable('NEXT_PUBLIC_CHAT_API_URL'),
|
||||||
|
NEXT_PUBLIC_USE_EXPERIMENTAL_CHAT_API_ON: getRuntimeVariable(
|
||||||
|
'NEXT_PUBLIC_USE_EXPERIMENTAL_CHAT_API_ON'
|
||||||
|
),
|
||||||
NEXT_PUBLIC_VIEWER_404_TITLE: getRuntimeVariable(
|
NEXT_PUBLIC_VIEWER_404_TITLE: getRuntimeVariable(
|
||||||
'NEXT_PUBLIC_VIEWER_404_TITLE'
|
'NEXT_PUBLIC_VIEWER_404_TITLE'
|
||||||
),
|
),
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"@types/nodemailer": "6.4.8",
|
"@types/nodemailer": "6.4.8",
|
||||||
"@types/validator": "13.11.9",
|
"@types/validator": "13.11.9",
|
||||||
"next": "14.1.0",
|
"next": "14.1.0",
|
||||||
"nodemailer": "6.9.3",
|
"nodemailer": "6.9.8",
|
||||||
"tslib": "2.6.0",
|
"tslib": "2.6.0",
|
||||||
"typescript": "5.3.2"
|
"typescript": "5.3.2"
|
||||||
},
|
},
|
||||||
|
@ -4,11 +4,15 @@ import { PrismaClient } from '@typebot.io/prisma'
|
|||||||
declare const global: { prisma: PrismaClient }
|
declare const global: { prisma: PrismaClient }
|
||||||
let prisma: PrismaClient
|
let prisma: PrismaClient
|
||||||
|
|
||||||
if (env.NODE_ENV === 'production') {
|
if (env.NODE_ENV === 'production' && !process.versions.bun) {
|
||||||
prisma = new PrismaClient()
|
prisma = new PrismaClient({
|
||||||
|
log: ['info', 'warn', 'error'],
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
if (!global.prisma) {
|
if (!global.prisma) {
|
||||||
global.prisma = new PrismaClient()
|
global.prisma = new PrismaClient({
|
||||||
|
log: ['info', 'warn', 'error'],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
prisma = global.prisma
|
prisma = global.prisma
|
||||||
}
|
}
|
||||||
|
7
packages/prisma/scripts/db-exec.ts
Normal file
7
packages/prisma/scripts/db-exec.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { executePrismaCommand } from './executeCommand'
|
||||||
|
|
||||||
|
const commandToExecute = process.argv.pop()
|
||||||
|
|
||||||
|
if (!commandToExecute) process.exit(1)
|
||||||
|
|
||||||
|
executePrismaCommand(commandToExecute, { force: true })
|
@ -225,14 +225,15 @@ export const startPreviewChatInputSchema = z.object({
|
|||||||
.describe(
|
.describe(
|
||||||
"[Where to find my bot's ID?](../how-to#how-to-find-my-typebotid)"
|
"[Where to find my bot's ID?](../how-to#how-to-find-my-typebotid)"
|
||||||
),
|
),
|
||||||
isStreamEnabled: z.boolean().optional(),
|
isStreamEnabled: z.boolean().optional().default(false),
|
||||||
message: z.string().optional(),
|
message: z.string().optional(),
|
||||||
isOnlyRegistering: z
|
isOnlyRegistering: z
|
||||||
.boolean()
|
.boolean()
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.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.'
|
'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.'
|
||||||
),
|
)
|
||||||
|
.default(false),
|
||||||
typebot: startTypebotSchema
|
typebot: startTypebotSchema
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
|
@ -174,6 +174,10 @@ export const whatsAppWebhookRequestBodySchema = z.object({
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export type WhatsAppWebhookRequestBody = z.infer<
|
||||||
|
typeof whatsAppWebhookRequestBodySchema
|
||||||
|
>
|
||||||
|
|
||||||
export const whatsAppCredentialsSchema = z
|
export const whatsAppCredentialsSchema = z
|
||||||
.object({
|
.object({
|
||||||
type: z.literal('whatsApp'),
|
type: z.literal('whatsApp'),
|
||||||
|
227
pnpm-lock.yaml
generated
227
pnpm-lock.yaml
generated
@ -211,13 +211,13 @@ importers:
|
|||||||
version: 14.1.0(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0)
|
version: 14.1.0(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0)
|
||||||
next-auth:
|
next-auth:
|
||||||
specifier: 4.22.1
|
specifier: 4.22.1
|
||||||
version: 4.22.1(next@14.1.0)(nodemailer@6.9.3)(react-dom@18.2.0)(react@18.2.0)
|
version: 4.22.1(next@14.1.0)(nodemailer@6.9.8)(react-dom@18.2.0)(react@18.2.0)
|
||||||
nextjs-cors:
|
nextjs-cors:
|
||||||
specifier: 2.1.2
|
specifier: 2.1.2
|
||||||
version: 2.1.2(next@14.1.0)
|
version: 2.1.2(next@14.1.0)
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: 6.9.3
|
specifier: 6.9.8
|
||||||
version: 6.9.3
|
version: 6.9.8
|
||||||
nprogress:
|
nprogress:
|
||||||
specifier: 0.2.0
|
specifier: 0.2.0
|
||||||
version: 0.2.0
|
version: 0.2.0
|
||||||
@ -328,8 +328,8 @@ importers:
|
|||||||
specifier: 20.4.2
|
specifier: 20.4.2
|
||||||
version: 20.4.2
|
version: 20.4.2
|
||||||
'@types/nodemailer':
|
'@types/nodemailer':
|
||||||
specifier: 6.4.8
|
specifier: 6.4.14
|
||||||
version: 6.4.8
|
version: 6.4.14
|
||||||
'@types/nprogress':
|
'@types/nprogress':
|
||||||
specifier: 0.2.0
|
specifier: 0.2.0
|
||||||
version: 0.2.0
|
version: 0.2.0
|
||||||
@ -370,6 +370,73 @@ importers:
|
|||||||
specifier: 3.22.4
|
specifier: 3.22.4
|
||||||
version: 3.22.4
|
version: 3.22.4
|
||||||
|
|
||||||
|
apps/chat-api:
|
||||||
|
dependencies:
|
||||||
|
'@hono/prometheus':
|
||||||
|
specifier: 1.0.0
|
||||||
|
version: 1.0.0(hono@4.0.5)(prom-client@15.1.0)
|
||||||
|
'@hono/sentry':
|
||||||
|
specifier: 1.0.1
|
||||||
|
version: 1.0.1(hono@4.0.5)
|
||||||
|
'@hono/typebox-validator':
|
||||||
|
specifier: 0.2.2
|
||||||
|
version: 0.2.2(@sinclair/typebox@0.32.5)(hono@4.0.5)
|
||||||
|
'@sinclair/typebox':
|
||||||
|
specifier: 0.32.5
|
||||||
|
version: 0.32.5
|
||||||
|
'@trpc/server':
|
||||||
|
specifier: 10.40.0
|
||||||
|
version: 10.40.0
|
||||||
|
'@typebot.io/bot-engine':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/bot-engine
|
||||||
|
'@typebot.io/env':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/env
|
||||||
|
'@typebot.io/forge':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/forge/core
|
||||||
|
'@typebot.io/forge-repository':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/forge/repository
|
||||||
|
'@typebot.io/lib':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/lib
|
||||||
|
'@typebot.io/prisma':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/prisma
|
||||||
|
'@typebot.io/schemas':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/schemas
|
||||||
|
'@typebot.io/variables':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/variables
|
||||||
|
ai:
|
||||||
|
specifier: 3.0.12
|
||||||
|
version: 3.0.12(react@18.2.0)(solid-js@1.7.8)(svelte@4.2.12)(vue@3.4.21)(zod@3.22.4)
|
||||||
|
hono:
|
||||||
|
specifier: 4.0.5
|
||||||
|
version: 4.0.5
|
||||||
|
openai:
|
||||||
|
specifier: 4.28.4
|
||||||
|
version: 4.28.4
|
||||||
|
prom-client:
|
||||||
|
specifier: 15.1.0
|
||||||
|
version: 15.1.0
|
||||||
|
devDependencies:
|
||||||
|
'@typebot.io/tsconfig':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/tsconfig
|
||||||
|
'@types/react':
|
||||||
|
specifier: 18.2.15
|
||||||
|
version: 18.2.15
|
||||||
|
dotenv-cli:
|
||||||
|
specifier: 7.2.1
|
||||||
|
version: 7.2.1
|
||||||
|
react:
|
||||||
|
specifier: 18.2.0
|
||||||
|
version: 18.2.0
|
||||||
|
|
||||||
apps/docs:
|
apps/docs:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
dotenv-cli:
|
dotenv-cli:
|
||||||
@ -530,8 +597,8 @@ importers:
|
|||||||
specifier: 2.1.2
|
specifier: 2.1.2
|
||||||
version: 2.1.2(next@14.1.0)
|
version: 2.1.2(next@14.1.0)
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: 6.9.3
|
specifier: 6.9.8
|
||||||
version: 6.9.3
|
version: 6.9.8
|
||||||
openai:
|
openai:
|
||||||
specifier: 4.28.4
|
specifier: 4.28.4
|
||||||
version: 4.28.4
|
version: 4.28.4
|
||||||
@ -594,8 +661,8 @@ importers:
|
|||||||
specifier: 20.4.2
|
specifier: 20.4.2
|
||||||
version: 20.4.2
|
version: 20.4.2
|
||||||
'@types/nodemailer':
|
'@types/nodemailer':
|
||||||
specifier: 6.4.8
|
specifier: 6.4.14
|
||||||
version: 6.4.8
|
version: 6.4.14
|
||||||
'@types/papaparse':
|
'@types/papaparse':
|
||||||
specifier: 5.3.7
|
specifier: 5.3.7
|
||||||
version: 5.3.7
|
version: 5.3.7
|
||||||
@ -714,6 +781,9 @@ importers:
|
|||||||
got:
|
got:
|
||||||
specifier: 12.6.0
|
specifier: 12.6.0
|
||||||
version: 12.6.0
|
version: 12.6.0
|
||||||
|
ky:
|
||||||
|
specifier: ^1.1.3
|
||||||
|
version: 1.2.0
|
||||||
libphonenumber-js:
|
libphonenumber-js:
|
||||||
specifier: 1.10.37
|
specifier: 1.10.37
|
||||||
version: 1.10.37
|
version: 1.10.37
|
||||||
@ -721,8 +791,8 @@ importers:
|
|||||||
specifier: 6.1.5
|
specifier: 6.1.5
|
||||||
version: 6.1.5
|
version: 6.1.5
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: 6.9.3
|
specifier: 6.9.8
|
||||||
version: 6.9.3
|
version: 6.9.8
|
||||||
openai:
|
openai:
|
||||||
specifier: 4.28.4
|
specifier: 4.28.4
|
||||||
version: 4.28.4
|
version: 4.28.4
|
||||||
@ -740,8 +810,8 @@ importers:
|
|||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../forge/repository
|
version: link:../forge/repository
|
||||||
'@types/nodemailer':
|
'@types/nodemailer':
|
||||||
specifier: 6.4.8
|
specifier: 6.4.14
|
||||||
version: 6.4.8
|
version: 6.4.14
|
||||||
'@types/qs':
|
'@types/qs':
|
||||||
specifier: 6.9.7
|
specifier: 6.9.7
|
||||||
version: 6.9.7
|
version: 6.9.7
|
||||||
@ -894,8 +964,8 @@ importers:
|
|||||||
specifier: 20.4.2
|
specifier: 20.4.2
|
||||||
version: 20.4.2
|
version: 20.4.2
|
||||||
'@types/nodemailer':
|
'@types/nodemailer':
|
||||||
specifier: 6.4.8
|
specifier: 6.4.14
|
||||||
version: 6.4.8
|
version: 6.4.14
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: 18.2.15
|
specifier: 18.2.15
|
||||||
version: 18.2.15
|
version: 18.2.15
|
||||||
@ -912,8 +982,8 @@ importers:
|
|||||||
specifier: 14.1.1
|
specifier: 14.1.1
|
||||||
version: 14.1.1
|
version: 14.1.1
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: 6.9.3
|
specifier: 6.9.8
|
||||||
version: 6.9.3
|
version: 6.9.8
|
||||||
react:
|
react:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
@ -1625,8 +1695,8 @@ importers:
|
|||||||
specifier: 14.1.0
|
specifier: 14.1.0
|
||||||
version: 14.1.0(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0)
|
version: 14.1.0(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0)
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: 6.9.3
|
specifier: 6.9.8
|
||||||
version: 6.9.3
|
version: 6.9.8
|
||||||
tslib:
|
tslib:
|
||||||
specifier: 2.6.0
|
specifier: 2.6.0
|
||||||
version: 2.6.0
|
version: 2.6.0
|
||||||
@ -6495,6 +6565,35 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@hono/prometheus@1.0.0(hono@4.0.5)(prom-client@15.1.0):
|
||||||
|
resolution: {integrity: sha512-cB4TklEw3CVqOdgLYenl6g4TM1YwYimrE044azQmVLLq7arfJfnWupvzev42LM1+ntlhsAAwS0TfpjSdnQBggw==}
|
||||||
|
peerDependencies:
|
||||||
|
hono: ^3.12.0
|
||||||
|
prom-client: ^15.0.0
|
||||||
|
dependencies:
|
||||||
|
hono: 4.0.5
|
||||||
|
prom-client: 15.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@hono/sentry@1.0.1(hono@4.0.5):
|
||||||
|
resolution: {integrity: sha512-4JgwdyasCQIoH3lhl4yLNxrP4/SElfK01ZV3JUaMvexVJnyAOPuXDhtJasl9Gssg7qDNt8ZIDDjqmVrOwS+AIw==}
|
||||||
|
peerDependencies:
|
||||||
|
hono: '>=3.*'
|
||||||
|
dependencies:
|
||||||
|
hono: 4.0.5
|
||||||
|
toucan-js: 3.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@hono/typebox-validator@0.2.2(@sinclair/typebox@0.32.5)(hono@4.0.5):
|
||||||
|
resolution: {integrity: sha512-6hLnF9Pe+nOWSvX5SIhobZ9Wt7vjTT3sjqDZ5lKALf+9J9XMtNzDyMSjpM5SQv8gg+fOPHu/ZSYxxmUhl65QOg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@sinclair/typebox': ^0.31.15
|
||||||
|
hono: '>=3.9.0'
|
||||||
|
dependencies:
|
||||||
|
'@sinclair/typebox': 0.32.5
|
||||||
|
hono: 4.0.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@humanwhocodes/config-array@0.11.14:
|
/@humanwhocodes/config-array@0.11.14:
|
||||||
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
|
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
|
||||||
engines: {node: '>=10.10.0'}
|
engines: {node: '>=10.10.0'}
|
||||||
@ -7691,6 +7790,11 @@ packages:
|
|||||||
/@one-ini/wasm@0.1.1:
|
/@one-ini/wasm@0.1.1:
|
||||||
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
|
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
|
||||||
|
|
||||||
|
/@opentelemetry/api@1.8.0:
|
||||||
|
resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@panva/hkdf@1.1.1:
|
/@panva/hkdf@1.1.1:
|
||||||
resolution: {integrity: sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==}
|
resolution: {integrity: sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -8803,6 +8907,14 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@sentry/core@7.76.0:
|
||||||
|
resolution: {integrity: sha512-M+ptkCTeCNf6fn7p2MmEb1Wd9/JXUWxIT/0QEc+t11DNR4FYy1ZP2O9Zb3Zp2XacO7ORrlL3Yc+VIfl5JTgjfw==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
dependencies:
|
||||||
|
'@sentry/types': 7.76.0
|
||||||
|
'@sentry/utils': 7.76.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@sentry/core@7.77.0:
|
/@sentry/core@7.77.0:
|
||||||
resolution: {integrity: sha512-Tj8oTYFZ/ZD+xW8IGIsU6gcFXD/gfE+FUxUaeSosd9KHwBQNOLhZSsYo/tTVf/rnQI/dQnsd4onPZLiL+27aTg==}
|
resolution: {integrity: sha512-Tj8oTYFZ/ZD+xW8IGIsU6gcFXD/gfE+FUxUaeSosd9KHwBQNOLhZSsYo/tTVf/rnQI/dQnsd4onPZLiL+27aTg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -8811,6 +8923,16 @@ packages:
|
|||||||
'@sentry/utils': 7.77.0
|
'@sentry/utils': 7.77.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@sentry/integrations@7.76.0:
|
||||||
|
resolution: {integrity: sha512-4ea0PNZrGN9wKuE/8bBCRrxxw4Cq5T710y8rhdKHAlSUpbLqr/atRF53h8qH3Fi+ec0m38PB+MivKem9zUwlwA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
dependencies:
|
||||||
|
'@sentry/core': 7.76.0
|
||||||
|
'@sentry/types': 7.76.0
|
||||||
|
'@sentry/utils': 7.76.0
|
||||||
|
localforage: 1.10.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@sentry/integrations@7.77.0:
|
/@sentry/integrations@7.77.0:
|
||||||
resolution: {integrity: sha512-P055qXgBHeZNKnnVEs5eZYLdy6P49Zr77A1aWJuNih/EenzMy922GOeGy2mF6XYrn1YJSjEwsNMNsQkcvMTK8Q==}
|
resolution: {integrity: sha512-P055qXgBHeZNKnnVEs5eZYLdy6P49Zr77A1aWJuNih/EenzMy922GOeGy2mF6XYrn1YJSjEwsNMNsQkcvMTK8Q==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -8888,11 +9010,23 @@ packages:
|
|||||||
'@sentry/utils': 7.77.0
|
'@sentry/utils': 7.77.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@sentry/types@7.76.0:
|
||||||
|
resolution: {integrity: sha512-vj6z+EAbVrKAXmJPxSv/clpwS9QjPqzkraMFk2hIdE/kii8s8kwnkBwTSpIrNc8GnzV3qYC4r3qD+BXDxAGPaw==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@sentry/types@7.77.0:
|
/@sentry/types@7.77.0:
|
||||||
resolution: {integrity: sha512-nfb00XRJVi0QpDHg+JkqrmEBHsqBnxJu191Ded+Cs1OJ5oPXEW6F59LVcBScGvMqe+WEk1a73eH8XezwfgrTsA==}
|
resolution: {integrity: sha512-nfb00XRJVi0QpDHg+JkqrmEBHsqBnxJu191Ded+Cs1OJ5oPXEW6F59LVcBScGvMqe+WEk1a73eH8XezwfgrTsA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@sentry/utils@7.76.0:
|
||||||
|
resolution: {integrity: sha512-40jFD+yfQaKpFYINghdhovzec4IEpB7aAuyH/GtE7E0gLpcqnC72r55krEIVILfqIR2Mlr5OKUzyeoCyWAU/yw==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
dependencies:
|
||||||
|
'@sentry/types': 7.76.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@sentry/utils@7.77.0:
|
/@sentry/utils@7.77.0:
|
||||||
resolution: {integrity: sha512-NmM2kDOqVchrey3N5WSzdQoCsyDkQkiRxExPaNI2oKQ/jMWHs9yt0tSy7otPBcXs0AP59ihl75Bvm1tDRcsp5g==}
|
resolution: {integrity: sha512-NmM2kDOqVchrey3N5WSzdQoCsyDkQkiRxExPaNI2oKQ/jMWHs9yt0tSy7otPBcXs0AP59ihl75Bvm1tDRcsp5g==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -8924,6 +9058,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@sinclair/typebox@0.32.5:
|
||||||
|
resolution: {integrity: sha512-0M6FyxZwIEu/Ly6W+l7iYqiZQYJ8khLOJGzg+cxivNKRKqk9hctcuDC0UYI7B9vNgycExA8w40m4M3yDKW37RA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@sindresorhus/is@5.6.0:
|
/@sindresorhus/is@5.6.0:
|
||||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
@ -9550,6 +9688,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-8e2HYcg7ohnTUbHk8focoklEQYvemQmu9M/f43DZVx43kHn0tE3BY/6gSDxS7k0SprtS0NHvj+L80cGLnoOUcQ==}
|
resolution: {integrity: sha512-8e2HYcg7ohnTUbHk8focoklEQYvemQmu9M/f43DZVx43kHn0tE3BY/6gSDxS7k0SprtS0NHvj+L80cGLnoOUcQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/nodemailer@6.4.14:
|
||||||
|
resolution: {integrity: sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.11.26
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/nodemailer@6.4.8:
|
/@types/nodemailer@6.4.8:
|
||||||
resolution: {integrity: sha512-oVsJSCkqViCn8/pEu2hfjwVO+Gb3e+eTWjg3PcjeFKRItfKpKwHphQqbYmPQrlMk+op7pNNWPbsJIEthpFN/OQ==}
|
resolution: {integrity: sha512-oVsJSCkqViCn8/pEu2hfjwVO+Gb3e+eTWjg3PcjeFKRItfKpKwHphQqbYmPQrlMk+op7pNNWPbsJIEthpFN/OQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -11461,6 +11605,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
/bintrees@1.0.2:
|
||||||
|
resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/bl@4.1.0:
|
/bl@4.1.0:
|
||||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -14934,6 +15082,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-KZFBHenkVuyyG4uaqRSXqWJr3HTxcaPguM7rU1BlH/mtbDlzaXNSXTa9AhV+fXEjrNemHu9vtLRIaM8/8OW0xA==}
|
resolution: {integrity: sha512-KZFBHenkVuyyG4uaqRSXqWJr3HTxcaPguM7rU1BlH/mtbDlzaXNSXTa9AhV+fXEjrNemHu9vtLRIaM8/8OW0xA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/hono@4.0.5:
|
||||||
|
resolution: {integrity: sha512-6LEGL1Pf3+dLjVA0NJxAB/3FJ6S3W5qxd/XOG7Wl9YOrpMRZT9lt83R4Ojs8dO6GbAUSutI7zTyjStnSn9sbEg==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/hosted-git-info@2.8.9:
|
/hosted-git-info@2.8.9:
|
||||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||||
|
|
||||||
@ -16498,6 +16651,11 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/ky@1.2.0:
|
||||||
|
resolution: {integrity: sha512-dnPW+T78MuJ9tLAiF/apJV7bP7RRRCARXQxsCmsWiKLXqGtMBOgDVOFRYzCAfNe/OrRyFyor5ESgvvC+QWEqOA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/language-subtag-registry@0.3.22:
|
/language-subtag-registry@0.3.22:
|
||||||
resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
|
resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -18206,7 +18364,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/next-auth@4.22.1(next@14.1.0)(nodemailer@6.9.3)(react-dom@18.2.0)(react@18.2.0):
|
/next-auth@4.22.1(next@14.1.0)(nodemailer@6.9.8)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-NTR3f6W7/AWXKw8GSsgSyQcDW6jkslZLH8AiZa5PQ09w1kR8uHtR9rez/E9gAq/o17+p0JYHE8QjF3RoniiObA==}
|
resolution: {integrity: sha512-NTR3f6W7/AWXKw8GSsgSyQcDW6jkslZLH8AiZa5PQ09w1kR8uHtR9rez/E9gAq/o17+p0JYHE8QjF3RoniiObA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: ^12.2.5 || ^13
|
next: ^12.2.5 || ^13
|
||||||
@ -18222,7 +18380,7 @@ packages:
|
|||||||
cookie: 0.5.0
|
cookie: 0.5.0
|
||||||
jose: 4.15.5
|
jose: 4.15.5
|
||||||
next: 14.1.0(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0)
|
next: 14.1.0(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0)
|
||||||
nodemailer: 6.9.3
|
nodemailer: 6.9.8
|
||||||
oauth: 0.9.15
|
oauth: 0.9.15
|
||||||
openid-client: 5.6.5
|
openid-client: 5.6.5
|
||||||
preact: 10.19.6
|
preact: 10.19.6
|
||||||
@ -18391,8 +18549,8 @@ packages:
|
|||||||
/node-releases@2.0.14:
|
/node-releases@2.0.14:
|
||||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||||
|
|
||||||
/nodemailer@6.9.3:
|
/nodemailer@6.9.8:
|
||||||
resolution: {integrity: sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==}
|
resolution: {integrity: sha512-cfrYUk16e67Ks051i4CntM9kshRYei1/o/Gi8K1d+R34OIs21xdFnW7Pt7EucmVKA0LKtqUGNcjMZ7ehjl49mQ==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
/nopt@7.2.0:
|
/nopt@7.2.0:
|
||||||
@ -19539,6 +19697,14 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/prom-client@15.1.0:
|
||||||
|
resolution: {integrity: sha512-cCD7jLTqyPdjEPBo/Xk4Iu8jxjuZgZJ3e/oET3L+ZwOuap/7Cw3dH/TJSsZKs1TQLZ2IHpIlRAKw82ef06kmMw==}
|
||||||
|
engines: {node: ^16 || ^18 || >=20}
|
||||||
|
dependencies:
|
||||||
|
'@opentelemetry/api': 1.8.0
|
||||||
|
tdigest: 0.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/promise.series@0.2.0:
|
/promise.series@0.2.0:
|
||||||
resolution: {integrity: sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==}
|
resolution: {integrity: sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==}
|
||||||
engines: {node: '>=0.12'}
|
engines: {node: '>=0.12'}
|
||||||
@ -21542,6 +21708,12 @@ packages:
|
|||||||
yallist: 4.0.0
|
yallist: 4.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/tdigest@0.1.2:
|
||||||
|
resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
|
||||||
|
dependencies:
|
||||||
|
bintrees: 1.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/terser-webpack-plugin@5.3.10(@swc/core@1.3.101)(esbuild@0.19.11)(webpack@5.90.3):
|
/terser-webpack-plugin@5.3.10(@swc/core@1.3.101)(esbuild@0.19.11)(webpack@5.90.3):
|
||||||
resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==}
|
resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==}
|
||||||
engines: {node: '>= 10.13.0'}
|
engines: {node: '>= 10.13.0'}
|
||||||
@ -21671,6 +21843,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
|
/toucan-js@3.3.1:
|
||||||
|
resolution: {integrity: sha512-9BpkHb/Pzsrtl1ItNq9OEQPnuUHwzce0nV2uG+DYFiQ4fPyiA6mKTBcDwQzcvNkfSER038U+8TzvdkCev+Maww==}
|
||||||
|
dependencies:
|
||||||
|
'@sentry/core': 7.76.0
|
||||||
|
'@sentry/integrations': 7.76.0
|
||||||
|
'@sentry/types': 7.76.0
|
||||||
|
'@sentry/utils': 7.76.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tough-cookie@4.1.3:
|
/tough-cookie@4.1.3:
|
||||||
resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==}
|
resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
Reference in New Issue
Block a user