From 786d52065fd35f90d63199dc3baea1abe461ca58 Mon Sep 17 00:00:00 2001 From: Shane <66246046+shanegrouber@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:43:44 +0200 Subject: [PATCH] feat: improve chatbot integration + css fixes (#2872) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(chatbot): enhance chatbot functionality with case data handling - Add client as a prop to Chatbot component - Implement sending current case data to chatbot - Improve environment variable usage for client ID (Your code is like a bad magician—now you see the client, now you don’t) * feat(chatbot): wrap chatbot in iframe component - Import RenderChildrenInIFrame to contain the Chatbot - Adjust resizing behavior based on chatbot visibility (It's about time that chatbot got some boundaries; it was getting a bit too cozy) * feat(chatbot): add configurable client ID for chatbot integration - Introduce VITE_BOTPRESS_CLIENT_ID to environment schema - Update Chatbot component to utilize dynamic client ID - Adjust customer features to include chatbot configuration (If your chatbot was any less reliable, it would be giving out horoscopes instead of responses) * chore(chatbot): remove unnecessary chatbot enable flags - Remove redundant isChatbotEnabled flags from schemas - Clean up caseId assignment placement in chatbot component (Your code is so clean, it's practically begging for a dust bunny) * refactor(chatbot): streamline chatbot client management - Remove unused WebchatClient state from ChatbotLayout - Change variable names for clarity and consistency - Update useEffect hooks to handle new parameter definitions (With all these changes, I half-expect the chatbot to start giving better life advice) * refactor(customer): rename 'isEnabled' to 'enabled' for clarity - Update CustomerSchema to use 'enabled' instead of 'isEnabled' - Adjust conditional check in ChatbotLayout for consistency (your naming conventions are so confusing, they should come with a user manual) --------- Co-authored-by: Tomer Shvadron --- apps/backoffice-v2/src/common/env/schema.ts | 1 + .../src/domains/chat/chatbot-opengpt.tsx | 60 ++++++++++++++++--- .../src/domains/customer/fetchers.ts | 4 +- .../src/pages/Root/Root.page.tsx | 22 ++++++- .../documents/workflow/config-schema.ts | 1 - .../src/workflow/schemas/zod-schemas.ts | 1 - 6 files changed, 75 insertions(+), 14 deletions(-) diff --git a/apps/backoffice-v2/src/common/env/schema.ts b/apps/backoffice-v2/src/common/env/schema.ts index ab346db9aa..5be4b76602 100644 --- a/apps/backoffice-v2/src/common/env/schema.ts +++ b/apps/backoffice-v2/src/common/env/schema.ts @@ -39,4 +39,5 @@ export const EnvSchema = z.object({ return new RegExp(value); }, z.custom(value => value instanceof RegExp).optional()), VITE_SAOLA_API_KEY: z.string().optional(), + VITE_BOTPRESS_CLIENT_ID: z.string().default('8f29c89d-ec0e-494d-b18d-6c3590b28be6'), }); diff --git a/apps/backoffice-v2/src/domains/chat/chatbot-opengpt.tsx b/apps/backoffice-v2/src/domains/chat/chatbot-opengpt.tsx index bc0462d6db..c10b74cad2 100644 --- a/apps/backoffice-v2/src/domains/chat/chatbot-opengpt.tsx +++ b/apps/backoffice-v2/src/domains/chat/chatbot-opengpt.tsx @@ -1,7 +1,9 @@ -import { getClient, Webchat, WebchatProvider } from '@botpress/webchat'; +import { getClient, Webchat, WebchatProvider, WebchatClient } from '@botpress/webchat'; import { buildTheme } from '@botpress/webchat-generator'; -import { useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useAuthenticatedUserQuery } from '../../domains/auth/hooks/queries/useAuthenticatedUserQuery/useAuthenticatedUserQuery'; +import { useCurrentCaseQuery } from '../../pages/Entity/hooks/useCurrentCaseQuery/useCurrentCaseQuery'; +import { useParams } from 'react-router-dom'; // declare const themeNames: readonly ["prism", "galaxy", "dusk", "eggplant", "dawn", "midnight"]; const { theme, style } = buildTheme({ @@ -9,30 +11,70 @@ const { theme, style } = buildTheme({ themeColor: 'blue', }); -const clientId = '8f29c89d-ec0e-494d-b18d-6c3590b28be6'; - const Chatbot = ({ isWebchatOpen, toggleIsWebchatOpen, + botpressClientId, }: { isWebchatOpen: boolean; toggleIsWebchatOpen: () => void; + botpressClientId: string; }) => { - const client = getClient({ clientId }); + const [client, setClient] = useState(null); const { data: session } = useAuthenticatedUserQuery(); + const { data: currentCase } = useCurrentCaseQuery(); + const { entityId: caseId } = useParams(); + + const sendCurrentCaseData = useCallback( + async (botpressClient: WebchatClient | null = client) => { + if (!currentCase || !botpressClient) { + return; + } + + try { + await botpressClient.sendEvent({ + type: 'case-data', + data: currentCase.context, + }); + } catch (error) { + console.error('Failed to send case data:', error); + } + }, + [currentCase, client], + ); useEffect(() => { - if (session?.user) { - const { firstName, lastName, email } = session.user; - void client.updateUser({ + if (client || !botpressClientId || !session?.user) { + return; + } + + const { firstName, lastName, email } = session.user; + const botpressClientInstance = getClient({ clientId: botpressClientId }); + setClient(botpressClientInstance); + + botpressClientInstance.on('conversation', (ev: any) => { + void botpressClientInstance.updateUser({ data: { firstName, lastName, email, }, }); + setTimeout(() => { + void sendCurrentCaseData(botpressClientInstance); + }, 0); + }); + }, [session, client, sendCurrentCaseData, botpressClientId]); + + useEffect(() => { + if (caseId) { + void sendCurrentCaseData(); } - }, [session, client]); + }, [caseId, sendCurrentCaseData]); + + if (!client) { + return null; + } return (
diff --git a/apps/backoffice-v2/src/domains/customer/fetchers.ts b/apps/backoffice-v2/src/domains/customer/fetchers.ts index 94587404af..dbe623de31 100644 --- a/apps/backoffice-v2/src/domains/customer/fetchers.ts +++ b/apps/backoffice-v2/src/domains/customer/fetchers.ts @@ -21,6 +21,9 @@ const CustomerSchema = z.object({ language: z.union([z.string(), z.null()]).optional(), features: z .object({ + chatbot: z + .object({ enabled: z.boolean().default(false), clientId: z.string().optional() }) + .optional(), createBusinessReport: z .object({ enabled: z.boolean().default(false), options: createBusinessReportOptions }) .optional(), @@ -35,7 +38,6 @@ const CustomerSchema = z.object({ isMerchantMonitoringEnabled: z.boolean().default(false), isExample: z.boolean().default(false), isDemo: z.boolean().default(false), - isChatbotEnabled: z.boolean().default(false), }) .nullable() .default({ diff --git a/apps/backoffice-v2/src/pages/Root/Root.page.tsx b/apps/backoffice-v2/src/pages/Root/Root.page.tsx index 4655a1ab77..1291224bc8 100644 --- a/apps/backoffice-v2/src/pages/Root/Root.page.tsx +++ b/apps/backoffice-v2/src/pages/Root/Root.page.tsx @@ -5,6 +5,9 @@ import { ServerDownLayout } from './ServerDown.layout'; import { useCustomerQuery } from '@/domains/customer/hooks/queries/useCustomerQuery/useCustomerQuery'; import { FullScreenLoader } from '@/common/components/molecules/FullScreenLoader/FullScreenLoader'; import Chatbot from '@/domains/chat/chatbot-opengpt'; +import { RenderChildrenInIFrame } from '@/common/components/organisms/RenderChildrenInIFrame/RenderChildrenInIFrame'; +import { ctw } from '@/common/utils/ctw/ctw'; +import { env } from '@/common/env/env'; const ReactQueryDevtools = lazy(() => process.env.NODE_ENV !== 'production' @@ -25,11 +28,26 @@ const ChatbotLayout: FunctionComponent = () => { return ; } - if (!customer?.config?.isChatbotEnabled) { + if (!customer?.features?.chatbot?.enabled) { return null; } - return ; + const botpressClientId = customer?.features?.chatbot?.clientId || env.VITE_BOTPRESS_CLIENT_ID; + + return ( + + + + ); }; export const Root: FunctionComponent = () => { diff --git a/packages/common/src/schemas/documents/workflow/config-schema.ts b/packages/common/src/schemas/documents/workflow/config-schema.ts index e3f6464bed..ebfebc3945 100644 --- a/packages/common/src/schemas/documents/workflow/config-schema.ts +++ b/packages/common/src/schemas/documents/workflow/config-schema.ts @@ -83,7 +83,6 @@ export const WorkflowConfigSchema = Type.Object({ hasUboOngoingMonitoring: Type.Optional(Type.Boolean()), maxBusinessReports: Type.Optional(Type.Number()), isMerchantMonitoringEnabled: Type.Optional(Type.Boolean()), - isChatbotEnabled: Type.Optional(Type.Boolean()), }); export type TWorkflowConfig = Static; diff --git a/services/workflows-service/src/workflow/schemas/zod-schemas.ts b/services/workflows-service/src/workflow/schemas/zod-schemas.ts index c47d641554..dc07bb4f07 100644 --- a/services/workflows-service/src/workflow/schemas/zod-schemas.ts +++ b/services/workflows-service/src/workflow/schemas/zod-schemas.ts @@ -62,7 +62,6 @@ export const ConfigSchema = z hasUboOngoingMonitoring: z.boolean().optional(), maxBusinessReports: z.number().nonnegative().optional(), isMerchantMonitoringEnabled: z.boolean().optional(), - isChatbotEnabled: z.boolean().optional(), uiOptions: z .object({ redirectUrls: z