From 04529830f5c132dc7cb7aea1fa0ae9004effec53 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sat, 14 Oct 2023 20:27:42 +0530 Subject: [PATCH] support for bot api --- app/ui/package.json | 2 +- app/ui/public/providers/api.svg | 1 + app/ui/src/@types/bot.ts | 9 ++ .../API/ApiPlaygroundComponent.tsx | 24 +++ .../components/Bot/Integration/API/Index.tsx | 18 +++ .../Bot/Integration/API/NoApiKeyComponent.tsx | 52 +++++++ .../Bot/Integration/IntegrationGrid.tsx | 33 +++- app/ui/src/main.tsx | 9 ++ app/ui/src/routes/bot/api.tsx | 39 +++++ package.json | 2 +- .../bot/integration/handlers/api.handler.ts | 146 ++++++++++++++++++ .../api/v1/bot/integration/handlers/type.ts | 7 + .../routes/api/v1/bot/integration/index.ts | 27 ++++ .../api/v1/bot/integration/schema/index.ts | 110 ++++++++----- .../bot/playground/handlers/post.handler.ts | 4 +- 15 files changed, 439 insertions(+), 44 deletions(-) create mode 100644 app/ui/public/providers/api.svg create mode 100644 app/ui/src/components/Bot/Integration/API/ApiPlaygroundComponent.tsx create mode 100644 app/ui/src/components/Bot/Integration/API/Index.tsx create mode 100644 app/ui/src/components/Bot/Integration/API/NoApiKeyComponent.tsx create mode 100644 app/ui/src/routes/bot/api.tsx create mode 100644 server/src/routes/api/v1/bot/integration/handlers/api.handler.ts diff --git a/app/ui/package.json b/app/ui/package.json index 04ec6130..dc79f343 100644 --- a/app/ui/package.json +++ b/app/ui/package.json @@ -1,7 +1,7 @@ { "name": "app", "private": true, - "version": "1.1.0", + "version": "1.1.1", "type": "module", "scripts": { "dev": "vite", diff --git a/app/ui/public/providers/api.svg b/app/ui/public/providers/api.svg new file mode 100644 index 00000000..e6144a48 --- /dev/null +++ b/app/ui/public/providers/api.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/ui/src/@types/bot.ts b/app/ui/src/@types/bot.ts index 8dda624e..398bb09e 100644 --- a/app/ui/src/@types/bot.ts +++ b/app/ui/src/@types/bot.ts @@ -13,3 +13,12 @@ export type BotSettings = { bot_protect: boolean; use_rag: boolean; }; + + +export type BotIntegrationAPI = { + is_api_enabled: boolean; + data: { + public_url: string | null; + api_key: string | null; + }; +} \ No newline at end of file diff --git a/app/ui/src/components/Bot/Integration/API/ApiPlaygroundComponent.tsx b/app/ui/src/components/Bot/Integration/API/ApiPlaygroundComponent.tsx new file mode 100644 index 00000000..69e79038 --- /dev/null +++ b/app/ui/src/components/Bot/Integration/API/ApiPlaygroundComponent.tsx @@ -0,0 +1,24 @@ +const ApiPlaygroundComponent: React.FC = () => { + return ( +
+
+

POST /chat/completions

+ +
+
+

LEFT PANEL

+ {/* Content of left panel */} +
+ +
+

RIGHT PANEL

+ {/* Content of right panel */} +
+
+
+
+ ) +} + + +export default ApiPlaygroundComponent; \ No newline at end of file diff --git a/app/ui/src/components/Bot/Integration/API/Index.tsx b/app/ui/src/components/Bot/Integration/API/Index.tsx new file mode 100644 index 00000000..e0e6cf1c --- /dev/null +++ b/app/ui/src/components/Bot/Integration/API/Index.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import NoApiKeyComponent from "./NoApiKeyComponent"; +import { BotIntegrationAPI } from "../../../../@types/bot"; +import ApiPlaygroundComponent from "./ApiPlaygroundComponent"; + +const IntegrationAPIBody: React.FC = ({ + is_api_enabled, +}) => { + return ( +
+

API Integration

+ {!is_api_enabled && } + {is_api_enabled && } +
+ ); +}; + +export default IntegrationAPIBody; diff --git a/app/ui/src/components/Bot/Integration/API/NoApiKeyComponent.tsx b/app/ui/src/components/Bot/Integration/API/NoApiKeyComponent.tsx new file mode 100644 index 00000000..a6824fac --- /dev/null +++ b/app/ui/src/components/Bot/Integration/API/NoApiKeyComponent.tsx @@ -0,0 +1,52 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { notification } from "antd"; +import React from "react"; +import api from "../../../../services/api"; +import { useParams } from "react-router-dom"; + +const NoApiKeyComponent: React.FC = () => { + const client = useQueryClient(); + const param = useParams<{ id: string }>(); + const { mutate: generateAPIKey, isLoading: isGeneratingAPIKey } = useMutation( + async () => { + const response = await api.post(`/bot/integration/${param.id}/api`); + return response.data; + }, + { + onSuccess: () => { + notification.success({ + message: "Success", + description: "API key generated successfully.", + }); + client.invalidateQueries(["getBotIntegrationAPI"]); + }, + onError: (e) => { + console.log(e); + notification.error({ + message: "Error", + description: "Something went wrong while generating the API key.", + }); + }, + } + ); + + return ( +
+
+

No API Key Found

+

You need to generate an API key to get started.

+ +
+
+ ); +}; + +export default NoApiKeyComponent; diff --git a/app/ui/src/components/Bot/Integration/IntegrationGrid.tsx b/app/ui/src/components/Bot/Integration/IntegrationGrid.tsx index 37be9afc..89900643 100644 --- a/app/ui/src/components/Bot/Integration/IntegrationGrid.tsx +++ b/app/ui/src/components/Bot/Integration/IntegrationGrid.tsx @@ -115,8 +115,9 @@ export const IntegrationGrid: React.FC = ({ data }) => { ))} + to={`/bot/${param.id}/embed`} + className="relative group bg-white p-6 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-500 rounded-lg overflow-hidden border hover:shadow-lg transition-shadow duration-300 ease-in-out cursor-pointer" + >
@@ -139,6 +140,34 @@ export const IntegrationGrid: React.FC = ({ data }) => {
+ + +
+
+
+ API +
+
+
+
+

+ API +

+

+ Customize your integration using our robust API. Connect and + expand the capabilities of your chatbot across platforms. +

+
+
+
+
{/* MODAL */} diff --git a/app/ui/src/main.tsx b/app/ui/src/main.tsx index 53d75f70..e822d99b 100644 --- a/app/ui/src/main.tsx +++ b/app/ui/src/main.tsx @@ -24,6 +24,7 @@ import RegisterRoot from "./routes/register"; import { QueryBoundaries } from "./components/Common/QueryBoundaries"; import SettingsApplicationRoot from "./routes/settings/application"; import SettingsTeamsRoot from "./routes/settings/teams"; +import BotIntegrationAPIRoot from "./routes/bot/api"; const router = createHashRouter([ { @@ -106,6 +107,14 @@ const router = createHashRouter([ ), }, + { + path: "/bot/:id/integrations/api", + element: ( + + + + ), + }, { path: "/bot/:id/appearance", element: ( diff --git a/app/ui/src/routes/bot/api.tsx b/app/ui/src/routes/bot/api.tsx new file mode 100644 index 00000000..3bc996b5 --- /dev/null +++ b/app/ui/src/routes/bot/api.tsx @@ -0,0 +1,39 @@ +import { useQuery } from "@tanstack/react-query"; +import React from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import api from "../../services/api"; +import { SkeletonLoading } from "../../components/Common/SkeletonLoading"; +import IntegrationAPIBody from "../../components/Bot/Integration/API/Index"; +import { BotIntegrationAPI } from "../../@types/bot"; + +export default function BotIntegrationAPIRoot() { + const param = useParams<{ id: string }>(); + const navigate = useNavigate(); + const { data, status } = useQuery( + ["getBotIntegrationAPI", param.id], + async () => { + const response = await api.get(`/bot/integration/${param.id}/api`); + return response.data as BotIntegrationAPI; + }, + { + enabled: !!param.id, + } + ); + + React.useEffect(() => { + if (status === "error") { + navigate("/"); + } + }, [status]); + + return ( +
+ {status === "loading" && + + + + } + {status === "success" && } +
+ ); +} diff --git a/package.json b/package.json index 117f2df9..3595569f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dialoqbase", - "version": "1.1.0", + "version": "1.1.1", "description": "Create chatbots with ease", "scripts": { "ui:dev": "pnpm run --filter ui dev", diff --git a/server/src/routes/api/v1/bot/integration/handlers/api.handler.ts b/server/src/routes/api/v1/bot/integration/handlers/api.handler.ts new file mode 100644 index 00000000..986ddecd --- /dev/null +++ b/server/src/routes/api/v1/bot/integration/handlers/api.handler.ts @@ -0,0 +1,146 @@ +import { FastifyReply, FastifyRequest } from "fastify"; +import { GetAPIIntergationRequest } from "./type"; +import { randomBytes } from "crypto"; + +const generateAPIKey = (length = 32) => { + const charset = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + const bytes = randomBytes(length); + let result = ""; + + for (let i = 0; i < length; i++) { + result += charset.charAt(bytes[i] % charset.length); + } + + return result; +}; + +export const getAPIIntegrationHandler = async ( + request: FastifyRequest, + reply: FastifyReply, +) => { + try { + const prisma = request.server.prisma; + const id = request.params.id; + + const bot = await prisma.bot.findFirst({ + where: { + id, + user_id: request.user.user_id, + }, + }); + + if (!bot) { + return reply.status(404).send({ + message: "Bot not found", + }); + } + + if (!bot.bot_api_key) { + return { + is_api_enabled: false, + data: { + public_url: null, + api_key: null, + }, + }; + } + + return { + is_api_enabled: true, + data: { + public_url: bot.publicId, + api_key: bot.bot_api_key, + }, + }; + } catch (e) { + console.log(e); + return reply.status(500).send({ message: "Internal Server Error" }); + } +}; + +export const generateAPIKeyHandler = async ( + request: FastifyRequest, + reply: FastifyReply, +) => { + try { + const prisma = request.server.prisma; + const id = request.params.id; + + const bot = await prisma.bot.findFirst({ + where: { + id, + user_id: request.user.user_id, + }, + }); + + if (!bot) { + return reply.status(404).send({ + message: "Bot not found", + }); + } + + const bot_api_key = generateAPIKey(); + + await prisma.bot.update({ + where: { + id, + }, + data: { + bot_api_key, + }, + }); + + return { + data: { + bot_api_key, + }, + }; + } catch (e) { + console.log(e); + return reply.status(500).send({ message: "Internal Server Error" }); + } +}; + +export const regenerateAPIKeyHandler = async ( + request: FastifyRequest, + reply: FastifyReply, +) => { + try { + const prisma = request.server.prisma; + const id = request.params.id; + + const bot = await prisma.bot.findFirst({ + where: { + id, + user_id: request.user.user_id, + }, + }); + + if (!bot) { + return reply.status(404).send({ + message: "Bot not found", + }); + } + + const bot_api_key = generateAPIKey(); + + await prisma.bot.update({ + where: { + id, + }, + data: { + bot_api_key, + }, + }); + + return { + data: { + bot_api_key, + }, + }; + } catch (e) { + console.log(e); + return reply.status(500).send({ message: "Internal Server Error" }); + } +}; diff --git a/server/src/routes/api/v1/bot/integration/handlers/type.ts b/server/src/routes/api/v1/bot/integration/handlers/type.ts index cb632808..540754f5 100644 --- a/server/src/routes/api/v1/bot/integration/handlers/type.ts +++ b/server/src/routes/api/v1/bot/integration/handlers/type.ts @@ -76,4 +76,11 @@ export type WhatsAppIntergationBodyType = { "x-hub-signature": string; } Body: Record; +} + + +export type GetAPIIntergationRequest = { + Params: { + id: string; + }; } \ No newline at end of file diff --git a/server/src/routes/api/v1/bot/integration/index.ts b/server/src/routes/api/v1/bot/integration/index.ts index 91ee6bd4..89e89d51 100644 --- a/server/src/routes/api/v1/bot/integration/index.ts +++ b/server/src/routes/api/v1/bot/integration/index.ts @@ -7,8 +7,17 @@ import { } from "./handlers/post.handler"; import { createIntergationSchema, + generateAPIKeySchema, + getAPIIntegrationSchema, pauseOrResumeIntergationSchema, + regenerateAPIKeySchema, } from "./schema"; + +import { + generateAPIKeyHandler, + getAPIIntegrationHandler, + regenerateAPIKeyHandler, +} from "./handlers/api.handler"; import { getChannelsByProvider } from "./handlers/get.handler"; const root: FastifyPluginAsync = async (fastify, _): Promise => { @@ -31,6 +40,24 @@ const root: FastifyPluginAsync = async (fastify, _): Promise => { // whatsapp integration fastify.get("/:id/whatsapp", {}, whatsappIntergationHandler); fastify.post("/:id/whatsapp", {}, whatsappIntergationHandlerPost); + + // api key integration + fastify.get("/:id/api", { + schema: getAPIIntegrationSchema, + onRequest: [fastify.authenticate], + }, getAPIIntegrationHandler); + + // generate api key + fastify.post("/:id/api", { + schema: generateAPIKeySchema, + onRequest: [fastify.authenticate], + }, generateAPIKeyHandler); + + // regenerate api key + fastify.put("/:id/api", { + schema: regenerateAPIKeySchema, + onRequest: [fastify.authenticate], + }, regenerateAPIKeyHandler); }; export default root; diff --git a/server/src/routes/api/v1/bot/integration/schema/index.ts b/server/src/routes/api/v1/bot/integration/schema/index.ts index 0f918b30..e062f6d5 100644 --- a/server/src/routes/api/v1/bot/integration/schema/index.ts +++ b/server/src/routes/api/v1/bot/integration/schema/index.ts @@ -1,51 +1,85 @@ import { FastifySchema } from "fastify"; import { CHANNELS } from "../../../../../../utils/intergation"; - export const createIntergationSchema: FastifySchema = { - params: { - type: "object", - required: ["id"], - properties: { - id: { - type: "string", - }, - }, + params: { + type: "object", + required: ["id"], + properties: { + id: { + type: "string", + }, }, - body: { + }, + body: { + type: "object", + required: ["provider", "value"], + properties: { + provider: { + type: "string", + enum: CHANNELS, + }, + value: { type: "object", - required: ["provider", "value"], - properties: { - provider: { - type: "string", - enum: CHANNELS, - }, - value: { - type: "object", - }, - }, + }, }, + }, }; - export const pauseOrResumeIntergationSchema: FastifySchema = { - params: { - type: "object", - required: ["id"], - properties: { - id: { - type: "string", - }, - }, + params: { + type: "object", + required: ["id"], + properties: { + id: { + type: "string", + }, }, - body: { - type: "object", - required: ["provider"], - properties: { - provider: { - type: "string", - enum: CHANNELS, - }, - }, + }, + body: { + type: "object", + required: ["provider"], + properties: { + provider: { + type: "string", + enum: CHANNELS, + }, + }, + }, +}; + +export const generateAPIKeySchema: FastifySchema = { + params: { + type: "object", + required: ["id"], + properties: { + id: { + type: "string", + }, + }, + }, +}; + +export const regenerateAPIKeySchema: FastifySchema = { + params: { + type: "object", + required: ["id"], + properties: { + id: { + type: "string", + }, + }, + }, +}; + +export const getAPIIntegrationSchema: FastifySchema = { + params: { + type: "object", + required: ["id"], + properties: { + id: { + type: "string", + }, }, + }, }; diff --git a/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts b/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts index 9173fcbc..138e16df 100644 --- a/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts +++ b/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts @@ -547,7 +547,7 @@ export const chatRequestStreamHandler = async ( let historyId = history_id; const documents = await documentPromise; - console.log(response); + console.log(response, historyId); if (!historyId) { const newHistory = await prisma.botPlayground.create({ @@ -558,7 +558,7 @@ export const chatRequestStreamHandler = async ( }); historyId = newHistory.id; } - + await prisma.botPlaygroundMessage.create({ data: { type: "human",