tambahan
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -164,6 +164,18 @@ exports.Prisma.WaHookScalarFieldEnum = {
|
|||||||
updatedAt: 'updatedAt'
|
updatedAt: 'updatedAt'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.Prisma.ChatFlowsScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
flows: 'flows',
|
||||||
|
defaultFlow: 'defaultFlow',
|
||||||
|
defaultData: 'defaultData',
|
||||||
|
active: 'active',
|
||||||
|
flowUrl: 'flowUrl',
|
||||||
|
flowToken: 'flowToken',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
exports.Prisma.SortOrder = {
|
exports.Prisma.SortOrder = {
|
||||||
asc: 'asc',
|
asc: 'asc',
|
||||||
desc: 'desc'
|
desc: 'desc'
|
||||||
@@ -195,7 +207,8 @@ exports.Prisma.ModelName = {
|
|||||||
User: 'User',
|
User: 'User',
|
||||||
ApiKey: 'ApiKey',
|
ApiKey: 'ApiKey',
|
||||||
WebHook: 'WebHook',
|
WebHook: 'WebHook',
|
||||||
WaHook: 'WaHook'
|
WaHook: 'WaHook',
|
||||||
|
ChatFlows: 'ChatFlows'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
1337
generated/prisma/index.d.ts
vendored
1337
generated/prisma/index.d.ts
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "prisma-client-b7159a5a3af13766d165b3bf5d09869b273a4102920455b6b9bd965ee512be7e",
|
"name": "prisma-client-0c787597671925c5dbd90480bfc5d45c3aa9b494d0bff63e2482cd62c5107b7b",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"browser": "default.js",
|
"browser": "default.js",
|
||||||
|
|||||||
@@ -53,3 +53,15 @@ model WaHook {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model ChatFlows {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
flows Json?
|
||||||
|
defaultFlow String?
|
||||||
|
defaultData Json?
|
||||||
|
active Boolean @default(true)
|
||||||
|
flowUrl String? @unique
|
||||||
|
flowToken String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|||||||
@@ -136,6 +136,18 @@ exports.Prisma.WaHookScalarFieldEnum = {
|
|||||||
updatedAt: 'updatedAt'
|
updatedAt: 'updatedAt'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.Prisma.ChatFlowsScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
flows: 'flows',
|
||||||
|
defaultFlow: 'defaultFlow',
|
||||||
|
defaultData: 'defaultData',
|
||||||
|
active: 'active',
|
||||||
|
flowUrl: 'flowUrl',
|
||||||
|
flowToken: 'flowToken',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
exports.Prisma.SortOrder = {
|
exports.Prisma.SortOrder = {
|
||||||
asc: 'asc',
|
asc: 'asc',
|
||||||
desc: 'desc'
|
desc: 'desc'
|
||||||
@@ -167,7 +179,8 @@ exports.Prisma.ModelName = {
|
|||||||
User: 'User',
|
User: 'User',
|
||||||
ApiKey: 'ApiKey',
|
ApiKey: 'ApiKey',
|
||||||
WebHook: 'WebHook',
|
WebHook: 'WebHook',
|
||||||
WaHook: 'WaHook'
|
WaHook: 'WaHook',
|
||||||
|
ChatFlows: 'ChatFlows'
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Create the Client
|
* Create the Client
|
||||||
@@ -216,13 +229,13 @@ const config = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n\nmodel User {\n id String @id @default(cuid())\n name String?\n email String? @unique\n password String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n ApiKey ApiKey[]\n}\n\nmodel ApiKey {\n id String @id @default(cuid())\n User User? @relation(fields: [userId], references: [id])\n userId String\n name String\n key String @unique @db.Text\n description String?\n expiredAt DateTime?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel WebHook {\n id String @id @default(cuid())\n name String?\n description String?\n url String\n payload String? @default(\"{}\")\n method String @default(\"POST\")\n headers String? @default(\"{}\")\n apiToken String?\n retries Int? @default(3)\n enabled Boolean @default(true)\n replay Boolean @default(false)\n replayKey String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel WaHook {\n id String @id @default(cuid())\n data Json? @db.Json\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n",
|
"inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n\nmodel User {\n id String @id @default(cuid())\n name String?\n email String? @unique\n password String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n ApiKey ApiKey[]\n}\n\nmodel ApiKey {\n id String @id @default(cuid())\n User User? @relation(fields: [userId], references: [id])\n userId String\n name String\n key String @unique @db.Text\n description String?\n expiredAt DateTime?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel WebHook {\n id String @id @default(cuid())\n name String?\n description String?\n url String\n payload String? @default(\"{}\")\n method String @default(\"POST\")\n headers String? @default(\"{}\")\n apiToken String?\n retries Int? @default(3)\n enabled Boolean @default(true)\n replay Boolean @default(false)\n replayKey String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel WaHook {\n id String @id @default(cuid())\n data Json? @db.Json\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel ChatFlows {\n id String @id @default(cuid())\n flows Json?\n defaultFlow String?\n defaultData Json?\n active Boolean @default(true)\n flowUrl String? @unique\n flowToken String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n",
|
||||||
"inlineSchemaHash": "f672201c199f0f043f3266324775af184d82ebd00379f5c23166510b25c889d0",
|
"inlineSchemaHash": "853d49417068ff1819c3dbb60ac7aeea59288f307f7939669c83fdd63bb495e3",
|
||||||
"copyEngine": true
|
"copyEngine": true
|
||||||
}
|
}
|
||||||
config.dirname = '/'
|
config.dirname = '/'
|
||||||
|
|
||||||
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"ApiKey\",\"kind\":\"object\",\"type\":\"ApiKey\",\"relationName\":\"ApiKeyToUser\"}],\"dbName\":null},\"ApiKey\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"User\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ApiKeyToUser\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiredAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"WebHook\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"payload\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"method\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"headers\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"apiToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"retries\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"enabled\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"replay\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"replayKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"WaHook\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"ApiKey\",\"kind\":\"object\",\"type\":\"ApiKey\",\"relationName\":\"ApiKeyToUser\"}],\"dbName\":null},\"ApiKey\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"User\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ApiKeyToUser\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiredAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"WebHook\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"payload\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"method\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"headers\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"apiToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"retries\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"enabled\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"replay\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"replayKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"WaHook\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ChatFlows\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"flows\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"defaultFlow\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"defaultData\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"active\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"flowUrl\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"flowToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
||||||
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
|
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
|
||||||
config.engineWasm = {
|
config.engineWasm = {
|
||||||
getRuntime: async () => require('./query_engine_bg.js'),
|
getRuntime: async () => require('./query_engine_bg.js'),
|
||||||
|
|||||||
@@ -53,3 +53,15 @@ model WaHook {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model ChatFlows {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
flows Json?
|
||||||
|
defaultFlow String?
|
||||||
|
defaultData Json?
|
||||||
|
active Boolean @default(true)
|
||||||
|
flowUrl String? @unique
|
||||||
|
flowToken String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import AppRoutes from "./AppRoutes";
|
|||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
<MantineProvider>
|
<MantineProvider defaultColorScheme="dark">
|
||||||
<Notifications />
|
<Notifications />
|
||||||
<ModalsProvider>
|
<ModalsProvider>
|
||||||
<AppRoutes />
|
<AppRoutes />
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import WebhookHome from "./pages/sq/dashboard/webhook/webhook_home";
|
|||||||
import WebhookLayout from "./pages/sq/dashboard/webhook/webhook_layout";
|
import WebhookLayout from "./pages/sq/dashboard/webhook/webhook_layout";
|
||||||
import WajsHome from "./pages/sq/dashboard/wajs/wajs_home";
|
import WajsHome from "./pages/sq/dashboard/wajs/wajs_home";
|
||||||
import WajsLayout from "./pages/sq/dashboard/wajs/wajs_layout";
|
import WajsLayout from "./pages/sq/dashboard/wajs/wajs_layout";
|
||||||
|
import WaHookHome from "./pages/sq/dashboard/wa-hook/wa_hook_home";
|
||||||
|
import FlowWaHook from "./pages/sq/dashboard/wa-hook/flow_wa_hook";
|
||||||
|
import WaHookLayout from "./pages/sq/dashboard/wa-hook/wa_hook_layout";
|
||||||
import ApikeyPage from "./pages/sq/dashboard/apikey/apikey_page";
|
import ApikeyPage from "./pages/sq/dashboard/apikey/apikey_page";
|
||||||
import DashboardPage from "./pages/sq/dashboard/dashboard_page";
|
import DashboardPage from "./pages/sq/dashboard/dashboard_page";
|
||||||
import DashboardLayout from "./pages/sq/dashboard/dashboard_layout";
|
import DashboardLayout from "./pages/sq/dashboard/dashboard_layout";
|
||||||
@@ -48,6 +51,19 @@ export default function AppRoutes() {
|
|||||||
element={<WajsHome />}
|
element={<WajsHome />}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/sq/dashboard/wa-hook" element={<WaHookLayout />}>
|
||||||
|
<Route index element={<WaHookHome />} />
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/sq/dashboard/wa-hook/wa-hook-home"
|
||||||
|
element={<WaHookHome />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/sq/dashboard/wa-hook/flow-wa-hook"
|
||||||
|
element={<FlowWaHook />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
path="/sq/dashboard/apikey/apikey"
|
path="/sq/dashboard/apikey/apikey"
|
||||||
element={<ApikeyPage />}
|
element={<ApikeyPage />}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ const clientRoutes = {
|
|||||||
"/sq/dashboard/webhook/webhook-home": "/sq/dashboard/webhook/webhook-home",
|
"/sq/dashboard/webhook/webhook-home": "/sq/dashboard/webhook/webhook-home",
|
||||||
"/sq/dashboard/wajs": "/sq/dashboard/wajs",
|
"/sq/dashboard/wajs": "/sq/dashboard/wajs",
|
||||||
"/sq/dashboard/wajs/wajs-home": "/sq/dashboard/wajs/wajs-home",
|
"/sq/dashboard/wajs/wajs-home": "/sq/dashboard/wajs/wajs-home",
|
||||||
|
"/sq/dashboard/wa-hook": "/sq/dashboard/wa-hook",
|
||||||
|
"/sq/dashboard/wa-hook/wa-hook-home": "/sq/dashboard/wa-hook/wa-hook-home",
|
||||||
|
"/sq/dashboard/wa-hook/flow-wa-hook": "/sq/dashboard/wa-hook/flow-wa-hook",
|
||||||
"/sq/dashboard/apikey/apikey": "/sq/dashboard/apikey/apikey",
|
"/sq/dashboard/apikey/apikey": "/sq/dashboard/apikey/apikey",
|
||||||
"/sq/dashboard/dashboard": "/sq/dashboard/dashboard",
|
"/sq/dashboard/dashboard": "/sq/dashboard/dashboard",
|
||||||
"/login": "/login",
|
"/login": "/login",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import WaRoute from "./server/routes/wa_route";
|
|||||||
import WebhookRoute from "./server/routes/webhook_route";
|
import WebhookRoute from "./server/routes/webhook_route";
|
||||||
import cors from "@elysiajs/cors";
|
import cors from "@elysiajs/cors";
|
||||||
import WaHookRoute from "./server/routes/wa_hook_route";
|
import WaHookRoute from "./server/routes/wa_hook_route";
|
||||||
|
import FlowRoute from "./server/routes/flow_route";
|
||||||
|
|
||||||
const Docs = new Elysia().use(
|
const Docs = new Elysia().use(
|
||||||
Swagger({
|
Swagger({
|
||||||
@@ -35,7 +36,7 @@ const Api = new Elysia({
|
|||||||
.use(ApiUser)
|
.use(ApiUser)
|
||||||
.use(WaRoute)
|
.use(WaRoute)
|
||||||
.use(WebhookRoute)
|
.use(WebhookRoute)
|
||||||
|
.use(FlowRoute);
|
||||||
|
|
||||||
const app = new Elysia()
|
const app = new Elysia()
|
||||||
.use(cors())
|
.use(cors())
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
|
import clientRoutes from "@/clientRoutes";
|
||||||
|
import { Button, Container } from "@mantine/core";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<div>
|
<Container>
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
</div>
|
<Button onClick={() => navigate(clientRoutes["/sq/dashboard"])}>
|
||||||
|
Go to SQ
|
||||||
|
</Button>
|
||||||
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,25 +254,17 @@ function ListApiKey({ refresh }: { refresh: boolean }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Table.Td>{apiKey.name}</Table.Td>
|
<Table.Td>{apiKey.name}</Table.Td>
|
||||||
<Table.Td c="#9A9A9A">
|
<Table.Td c="#9A9A9A">{apiKey.description || "—"}</Table.Td>
|
||||||
{apiKey.description || "—"}
|
|
||||||
</Table.Td>
|
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
{apiKey.expiredAt
|
{apiKey.expiredAt
|
||||||
? new Date(apiKey.expiredAt)
|
? new Date(apiKey.expiredAt).toISOString().split("T")[0]
|
||||||
.toISOString()
|
|
||||||
.split("T")[0]
|
|
||||||
: "—"}
|
: "—"}
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
{new Date(apiKey.createdAt)
|
{new Date(apiKey.createdAt).toISOString().split("T")[0]}
|
||||||
.toISOString()
|
|
||||||
.split("T")[0]}
|
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
{new Date(apiKey.updatedAt)
|
{new Date(apiKey.updatedAt).toISOString().split("T")[0]}
|
||||||
.toISOString()
|
|
||||||
.split("T")[0]}
|
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td align="right">
|
<Table.Td align="right">
|
||||||
<Group gap={4} justify="right">
|
<Group gap={4} justify="right">
|
||||||
@@ -301,7 +293,7 @@ function ListApiKey({ refresh }: { refresh: boolean }) {
|
|||||||
id: apiKey.id,
|
id: apiKey.id,
|
||||||
});
|
});
|
||||||
setApiKeys((prev) =>
|
setApiKeys((prev) =>
|
||||||
prev.filter((a) => a.id !== apiKey.id)
|
prev.filter((a) => a.id !== apiKey.id),
|
||||||
);
|
);
|
||||||
showNotification({
|
showNotification({
|
||||||
title: "Deleted",
|
title: "Deleted",
|
||||||
|
|||||||
@@ -258,6 +258,12 @@ function NavigationDashboard() {
|
|||||||
icon: <IconWebhook size={20} color="#00FFFF" />,
|
icon: <IconWebhook size={20} color="#00FFFF" />,
|
||||||
desc: "Incoming and outgoing event handlers",
|
desc: "Incoming and outgoing event handlers",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: clientRoutes["/sq/dashboard/wa-hook/wa-hook-home"],
|
||||||
|
label: "WA Hook",
|
||||||
|
icon: <IconWebhook size={20} color="#00FFFF" />,
|
||||||
|
desc: "WA Hook",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
195
src/pages/sq/dashboard/wa-hook/flow_wa_hook.tsx
Normal file
195
src/pages/sq/dashboard/wa-hook/flow_wa_hook.tsx
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import { useState, useCallback } from "react";
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Stack,
|
||||||
|
Card,
|
||||||
|
Group,
|
||||||
|
Button,
|
||||||
|
Table,
|
||||||
|
TextInput,
|
||||||
|
PasswordInput,
|
||||||
|
ActionIcon,
|
||||||
|
Checkbox,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Flex,
|
||||||
|
Loader,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconReload, IconCheck, IconCopy } from "@tabler/icons-react";
|
||||||
|
import { showNotification } from "@mantine/notifications";
|
||||||
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
|
import apiFetch from "@/lib/apiFetch";
|
||||||
|
|
||||||
|
export default function FlowWaHook() {
|
||||||
|
return (
|
||||||
|
<Container size="xl" px="md">
|
||||||
|
<Stack gap="xl">
|
||||||
|
<FlowWaHookForm />
|
||||||
|
<FlowWaHookList />
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FlowWaHookList() {
|
||||||
|
const [flows, setFlows] = useState<{ id: string; name: string; type: string }[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [defaultFlow, setDefaultFlow] = useState("");
|
||||||
|
|
||||||
|
const loadFlows = useCallback(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const { data, error } = await apiFetch.api.chatflows.find.get();
|
||||||
|
if (error) {
|
||||||
|
showNotification({ title: "Error", message: "Failed to load flows", color: "red" });
|
||||||
|
} else {
|
||||||
|
setFlows(data?.flows || []);
|
||||||
|
setDefaultFlow(data?.defaultFlow || "");
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
loadFlows();
|
||||||
|
}, [loadFlows]);
|
||||||
|
|
||||||
|
const syncFlows = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const { error } = await apiFetch.api.chatflows.sync.get();
|
||||||
|
if (error) {
|
||||||
|
showNotification({ title: "Error", message: "Sync failed", color: "red" });
|
||||||
|
} else {
|
||||||
|
await loadFlows();
|
||||||
|
showNotification({ title: "Success", message: "Flows synchronized", color: "green" });
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setAsDefault = async (id: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
const { error } = await apiFetch.api.chatflows.default.put({ id, defaultData: {} });
|
||||||
|
if (error) {
|
||||||
|
showNotification({ title: "Error", message: "Failed to set default flow", color: "red" });
|
||||||
|
} else {
|
||||||
|
await loadFlows();
|
||||||
|
showNotification({ title: "Success", message: "Default flow updated", color: "green" });
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card radius="md" p="lg" withBorder>
|
||||||
|
<Stack gap="lg">
|
||||||
|
<Flex justify="space-between" align="center">
|
||||||
|
<Title order={3}>Flow Management</Title>
|
||||||
|
<Button leftSection={<IconReload size={16} />} onClick={syncFlows} loading={loading}>
|
||||||
|
Sync Flows
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<Table striped highlightOnHover withTableBorder withColumnBorders>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th>Name</Table.Th>
|
||||||
|
<Table.Th>Type</Table.Th>
|
||||||
|
<Table.Th>Default</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{flows.map((flow) => (
|
||||||
|
<Table.Tr key={flow.id} bg={defaultFlow === flow.id ? "dark.4" : undefined}>
|
||||||
|
<Table.Td>{flow.name}</Table.Td>
|
||||||
|
<Table.Td>{flow.type}</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Checkbox checked={defaultFlow === flow.id} onChange={() => setAsDefault(flow.id)} />
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
{!loading && flows.length === 0 && (
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td colSpan={3}>
|
||||||
|
<Text ta="center" c="dimmed">
|
||||||
|
No flows available
|
||||||
|
</Text>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
)}
|
||||||
|
{loading && (
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td colSpan={3}>
|
||||||
|
<Flex justify="center" align="center" py="md">
|
||||||
|
<Loader size="sm" />
|
||||||
|
</Flex>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
)}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FlowWaHookForm() {
|
||||||
|
const [flowUrl, setFlowUrl] = useState("");
|
||||||
|
const [flowToken, setFlowToken] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
const loadCredentials = async () => {
|
||||||
|
const { data, error } = await apiFetch.api.chatflows["url-token"].get();
|
||||||
|
if (error) {
|
||||||
|
showNotification({ title: "Error", message: "Failed to load credentials", color: "red" });
|
||||||
|
} else {
|
||||||
|
setFlowUrl(data?.data?.flowUrl || "");
|
||||||
|
setFlowToken(data?.data?.flowToken || "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadCredentials();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const saveCredentials = async () => {
|
||||||
|
if (!flowUrl || !flowToken) {
|
||||||
|
showNotification({ title: "Error", message: "URL and token are required", color: "red" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
const { error } = await apiFetch.api.chatflows["url-token"].put({ flowUrl, flowToken });
|
||||||
|
if (error) {
|
||||||
|
showNotification({ title: "Error", message: "Failed to update credentials", color: "red" });
|
||||||
|
} else {
|
||||||
|
showNotification({ title: "Success", message: "Credentials updated", color: "green" });
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyToken = () => {
|
||||||
|
navigator.clipboard.writeText(flowToken);
|
||||||
|
showNotification({ title: "Copied", message: "Token copied to clipboard", color: "green" });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card radius="md" p="lg" withBorder>
|
||||||
|
<Stack gap="lg">
|
||||||
|
<Title order={3}>Flow Credentials</Title>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput label="Flow URL" placeholder="Enter flow URL" value={flowUrl} onChange={(e) => setFlowUrl(e.currentTarget.value)} />
|
||||||
|
<PasswordInput
|
||||||
|
label="Flow Token"
|
||||||
|
placeholder="Enter flow token"
|
||||||
|
value={flowToken}
|
||||||
|
onChange={(e) => setFlowToken(e.currentTarget.value)}
|
||||||
|
rightSection={
|
||||||
|
<ActionIcon onClick={copyToken}>
|
||||||
|
<IconCopy size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Group justify="flex-end">
|
||||||
|
<Button leftSection={<IconCheck size={16} />} onClick={saveCredentials} loading={loading}>
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
29
src/pages/sq/dashboard/wa-hook/wa_hook_home.tsx
Normal file
29
src/pages/sq/dashboard/wa-hook/wa_hook_home.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import apiFetch from "@/lib/apiFetch";
|
||||||
|
import { Skeleton, Stack, Text, Title } from "@mantine/core";
|
||||||
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
export default function WaHookHome() {
|
||||||
|
const { data, error, isLoading, mutate } = useSWR("/wa-hook", apiFetch["wa-hook"].list.get,{
|
||||||
|
refreshInterval: 3000,
|
||||||
|
revalidateOnFocus: true,
|
||||||
|
revalidateOnReconnect: true,
|
||||||
|
revalidateIfStale: true,
|
||||||
|
refreshWhenHidden: true,
|
||||||
|
refreshWhenOffline: true,
|
||||||
|
dedupingInterval: 3000,
|
||||||
|
})
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
mutate()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (isLoading) return <Skeleton height={500} />
|
||||||
|
if (error) return <div>Error: {error.message}</div>
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Title order={2}>WaHookHome</Title>
|
||||||
|
<pre>{JSON.stringify(data?.data?.list, null, 2)}</pre>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
src/pages/sq/dashboard/wa-hook/wa_hook_layout.tsx
Normal file
23
src/pages/sq/dashboard/wa-hook/wa_hook_layout.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import clientRoutes from "@/clientRoutes";
|
||||||
|
import { Button, Container, Group, Stack } from "@mantine/core";
|
||||||
|
import { Outlet, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function WaHookLayout() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<Container size="xl" w={"100%"}>
|
||||||
|
<Group justify="flex-start" p={"md"}>
|
||||||
|
<Button
|
||||||
|
size="compact-xs"
|
||||||
|
radius={"lg"}
|
||||||
|
onClick={() =>
|
||||||
|
navigate(clientRoutes["/sq/dashboard/wa-hook/flow-wa-hook"])
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Flow WA Hook
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
<Outlet />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -141,7 +141,6 @@ export default function WebhookCreate() {
|
|||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -149,7 +148,6 @@ export default function WebhookCreate() {
|
|||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -157,7 +155,6 @@ export default function WebhookCreate() {
|
|||||||
placeholder="https://example.com/webhook"
|
placeholder="https://example.com/webhook"
|
||||||
value={url}
|
value={url}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
@@ -169,7 +166,6 @@ export default function WebhookCreate() {
|
|||||||
value: v,
|
value: v,
|
||||||
label: v,
|
label: v,
|
||||||
}))}
|
}))}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -188,7 +184,6 @@ export default function WebhookCreate() {
|
|||||||
setHeaders(JSON.stringify(current, null, 2));
|
setHeaders(JSON.stringify(current, null, 2));
|
||||||
} catch {}
|
} catch {}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
@@ -257,7 +252,6 @@ export default function WebhookCreate() {
|
|||||||
placeholder="Replay Key"
|
placeholder="Replay Key"
|
||||||
value={replayKey}
|
value={replayKey}
|
||||||
onChange={(e) => setReplayKey(e.target.value)}
|
onChange={(e) => setReplayKey(e.target.value)}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
@@ -7,7 +7,18 @@ import { useMemo } from "react";
|
|||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import { IconCode, IconCheck, IconX } from "@tabler/icons-react";
|
import { IconCode, IconCheck, IconX } from "@tabler/icons-react";
|
||||||
import Editor from "@monaco-editor/react";
|
import Editor from "@monaco-editor/react";
|
||||||
import { Stack, Group, Title, Divider, TextInput, Select, Checkbox, Card, Button, Text } from "@mantine/core";
|
import {
|
||||||
|
Stack,
|
||||||
|
Group,
|
||||||
|
Title,
|
||||||
|
Divider,
|
||||||
|
TextInput,
|
||||||
|
Select,
|
||||||
|
Checkbox,
|
||||||
|
Card,
|
||||||
|
Button,
|
||||||
|
Text,
|
||||||
|
} from "@mantine/core";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import clientRoutes from "@/clientRoutes";
|
import clientRoutes from "@/clientRoutes";
|
||||||
import { useShallowEffect } from "@mantine/hooks";
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
@@ -20,9 +31,16 @@ Available variables:
|
|||||||
export default function WebhookEdit() {
|
export default function WebhookEdit() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const id = searchParams.get("id");
|
const id = searchParams.get("id");
|
||||||
const { data, error, isLoading, mutate } = useSWR("/", () => apiFetch.api.webhook.find({
|
const { data, error, isLoading, mutate } = useSWR(
|
||||||
id: id!
|
"/",
|
||||||
}).get(), {dedupingInterval: 3000})
|
() =>
|
||||||
|
apiFetch.api.webhook
|
||||||
|
.find({
|
||||||
|
id: id!,
|
||||||
|
})
|
||||||
|
.get(),
|
||||||
|
{ dedupingInterval: 3000 },
|
||||||
|
);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
@@ -35,29 +53,41 @@ export default function WebhookEdit() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
|
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Title order={2}>Edit Webhook</Title>
|
<Title order={2}>Edit Webhook</Title>
|
||||||
<Button variant="outline" onClick={() => {
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
modals.openConfirmModal({
|
modals.openConfirmModal({
|
||||||
title: "Remove Webhook",
|
title: "Remove Webhook",
|
||||||
children: <Text>Are you sure you want to remove this webhook?</Text>,
|
children: (
|
||||||
|
<Text>Are you sure you want to remove this webhook?</Text>
|
||||||
|
),
|
||||||
confirmProps: { color: "red" },
|
confirmProps: { color: "red" },
|
||||||
labels: {
|
labels: {
|
||||||
cancel: "Cancel",
|
cancel: "Cancel",
|
||||||
confirm: "Remove",
|
confirm: "Remove",
|
||||||
},
|
},
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
apiFetch.api.webhook.remove({
|
apiFetch.api.webhook
|
||||||
id: id!
|
.remove({
|
||||||
}).delete()
|
id: id!,
|
||||||
|
})
|
||||||
|
.delete();
|
||||||
navigate(clientRoutes["/sq/dashboard/webhook"]);
|
navigate(clientRoutes["/sq/dashboard/webhook"]);
|
||||||
},
|
},
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
navigate(clientRoutes["/sq/dashboard/webhook/webhook-edit"] + "?id=" + id);
|
navigate(
|
||||||
|
clientRoutes["/sq/dashboard/webhook/webhook-edit"] +
|
||||||
|
"?id=" +
|
||||||
|
id,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}}>Remove</Button>
|
}}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
<EditView webhook={data.data?.webhook || null} />
|
<EditView webhook={data.data?.webhook || null} />
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -122,9 +152,11 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
|||||||
icon: <IconX />,
|
icon: <IconX />,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { data } = await apiFetch.api.webhook.update({
|
const { data } = await apiFetch.api.webhook
|
||||||
|
.update({
|
||||||
id: webhook?.id,
|
id: webhook?.id,
|
||||||
}).put({
|
})
|
||||||
|
.put({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
apiToken,
|
apiToken,
|
||||||
@@ -184,7 +216,6 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
|||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -192,7 +223,6 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
|||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -200,7 +230,6 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
|||||||
placeholder="https://example.com/webhook"
|
placeholder="https://example.com/webhook"
|
||||||
value={url}
|
value={url}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
@@ -212,7 +241,6 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
|||||||
value: v,
|
value: v,
|
||||||
label: v,
|
label: v,
|
||||||
}))}
|
}))}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -231,7 +259,6 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
|||||||
setHeaders(JSON.stringify(current, null, 2));
|
setHeaders(JSON.stringify(current, null, 2));
|
||||||
} catch {}
|
} catch {}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
@@ -302,7 +329,6 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
|||||||
placeholder="Replay Key"
|
placeholder="Replay Key"
|
||||||
value={replayKey}
|
value={replayKey}
|
||||||
onChange={(e) => setReplayKey(e.target.value)}
|
onChange={(e) => setReplayKey(e.target.value)}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ export default function WebhookHome() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data, error, isLoading, mutate } = useSWR(
|
const { data, error, isLoading, mutate } = useSWR(
|
||||||
"/",
|
"/",
|
||||||
apiFetch.api.webhook.list.get, { dedupingInterval: 3000, refreshInterval: 3000 });
|
apiFetch.api.webhook.list.get,
|
||||||
|
{ dedupingInterval: 3000, refreshInterval: 3000 },
|
||||||
|
);
|
||||||
|
|
||||||
const webhooks = useMemo(() => data?.data?.list ?? [], [data]);
|
const webhooks = useMemo(() => data?.data?.list ?? [], [data]);
|
||||||
|
|
||||||
@@ -43,9 +45,9 @@ export default function WebhookHome() {
|
|||||||
mutate();
|
mutate();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
function ButtonCreate() {
|
function ButtonCreate() {
|
||||||
return <Tooltip label="Create new webhook" withArrow color="teal">
|
return (
|
||||||
|
<Tooltip label="Create new webhook" withArrow color="teal">
|
||||||
<Button
|
<Button
|
||||||
radius="xl"
|
radius="xl"
|
||||||
size="md"
|
size="md"
|
||||||
@@ -60,19 +62,18 @@ export default function WebhookHome() {
|
|||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.transform = "translateY(-2px)";
|
e.currentTarget.style.transform = "translateY(-2px)";
|
||||||
e.currentTarget.style.boxShadow =
|
e.currentTarget.style.boxShadow = "0 0 20px rgba(0,255,200,0.4)";
|
||||||
"0 0 20px rgba(0,255,200,0.4)";
|
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.transform = "translateY(0)";
|
e.currentTarget.style.transform = "translateY(0)";
|
||||||
e.currentTarget.style.boxShadow =
|
e.currentTarget.style.boxShadow = "0 0 12px rgba(0,255,200,0.25)";
|
||||||
"0 0 12px rgba(0,255,200,0.25)";
|
|
||||||
}}
|
}}
|
||||||
onClick={() => navigate("/sq/dashboard/webhook/webhook-create")}
|
onClick={() => navigate("/sq/dashboard/webhook/webhook-create")}
|
||||||
>
|
>
|
||||||
Create Webhook
|
Create Webhook
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
@@ -159,7 +160,11 @@ export default function WebhookHome() {
|
|||||||
variant="light"
|
variant="light"
|
||||||
size="lg"
|
size="lg"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
onClick={() => navigate(`${clientRoutes["/sq/dashboard/webhook/webhook-edit"]}?id=${webhook.id}`)}
|
onClick={() =>
|
||||||
|
navigate(
|
||||||
|
`${clientRoutes["/sq/dashboard/webhook/webhook-edit"]}?id=${webhook.id}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<IconEdit />
|
<IconEdit />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
@@ -180,11 +185,16 @@ export default function WebhookHome() {
|
|||||||
>
|
>
|
||||||
{webhook.enabled ? "Active" : "Disabled"}
|
{webhook.enabled ? "Active" : "Disabled"}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge bg={"teal"} leftSection={<IconMessageReply size={16} color="#00FFC8" />}>
|
<Badge
|
||||||
|
bg={"teal"}
|
||||||
|
leftSection={<IconMessageReply size={16} color="#00FFC8" />}
|
||||||
|
>
|
||||||
{webhook.replay ? "Replay" : "Not Replay"}
|
{webhook.replay ? "Replay" : "Not Replay"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Group>
|
</Group>
|
||||||
<Text c="#9A9A9A" size="sm">{webhook.description}</Text>
|
<Text c="#9A9A9A" size="sm">
|
||||||
|
{webhook.description}
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Divider color="rgba(0,255,200,0.2)" my="sm" />
|
<Divider color="rgba(0,255,200,0.2)" my="sm" />
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,5 @@ import { useNavigate, Outlet } from "react-router-dom";
|
|||||||
export default function WebhookLayout() {
|
export default function WebhookLayout() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return <Outlet />;
|
||||||
<Outlet />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
247
src/server/routes/flow_route.ts
Normal file
247
src/server/routes/flow_route.ts
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import Elysia, { t } from 'elysia'
|
||||||
|
import { prisma } from '../lib/prisma'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
const getUrlToken = async () => await prisma.chatFlows.findUnique({ where: { id: "1" }, select: { flowUrl: true, flowToken: true } })
|
||||||
|
|
||||||
|
const FlowRoute = new Elysia({
|
||||||
|
prefix: '/chatflows',
|
||||||
|
detail: { tags: ['chatflows'] },
|
||||||
|
})
|
||||||
|
.get('/sync', async ctx => {
|
||||||
|
const result = await getUrlToken()
|
||||||
|
if (!result) {
|
||||||
|
return { error: 'Flow URL and Token not found' }
|
||||||
|
}
|
||||||
|
const { flowUrl, flowToken } = result
|
||||||
|
const response = await fetch(flowUrl + '/chatflows', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + flowToken,
|
||||||
|
Accept: '*/*',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return { error: 'Failed to fetch flows' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
const chatflows = await prisma.chatFlows.upsert({
|
||||||
|
where: {
|
||||||
|
id: "1",
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
flows: data,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
flows: data,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return { data: chatflows }
|
||||||
|
}, {
|
||||||
|
detail: {
|
||||||
|
summary: "Sync chatflows",
|
||||||
|
description: "Sync chatflows",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get('/find', async ctx => {
|
||||||
|
const result = await prisma.chatFlows.findUnique({
|
||||||
|
where: { id: "1" },
|
||||||
|
})
|
||||||
|
if (!result) {
|
||||||
|
return { flows: [], defaultFlow: null, flowUrl: null, flowToken: null }
|
||||||
|
}
|
||||||
|
const flows = _.orderBy(result?.flows as any[], ['type'], ['asc'])
|
||||||
|
const defaultFlow = result?.defaultFlow
|
||||||
|
const flowUrl = result?.flowUrl
|
||||||
|
const flowToken = result?.flowToken
|
||||||
|
return { flows, defaultFlow, flowUrl, flowToken }
|
||||||
|
}, {
|
||||||
|
detail: {
|
||||||
|
summary: "Find chatflows",
|
||||||
|
description: "Find chatflows",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get("/default", async ctx => {
|
||||||
|
const result = await prisma.chatFlows.findUnique({
|
||||||
|
where: { id: "1" },
|
||||||
|
})
|
||||||
|
if (!result) {
|
||||||
|
return { defaultFlow: null, defaultData: null }
|
||||||
|
}
|
||||||
|
const defaultFlow = result?.defaultFlow
|
||||||
|
const defaultData = result?.defaultData
|
||||||
|
return { defaultFlow, defaultData }
|
||||||
|
}, {
|
||||||
|
detail: {
|
||||||
|
summary: "Get default chatflows",
|
||||||
|
description: "Get default chatflows",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.put(
|
||||||
|
'/default',
|
||||||
|
async ctx => {
|
||||||
|
const { id, defaultData } = ctx.body
|
||||||
|
|
||||||
|
const result = await prisma.chatFlows.update({
|
||||||
|
where: {
|
||||||
|
id: "1",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
defaultFlow: id,
|
||||||
|
defaultData: defaultData,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return { data: result }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
defaultData: t.Optional(t.Any()),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Update default chatflows",
|
||||||
|
description: "Update default chatflows",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.post(
|
||||||
|
'/query',
|
||||||
|
async ctx => {
|
||||||
|
const { flowId, question } = ctx.body
|
||||||
|
const result = await chatFlowQuery({ flowId, question })
|
||||||
|
return { data: result }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
flowId: t.String(),
|
||||||
|
question: t.String(),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Query chatflows",
|
||||||
|
description: "Query chatflows",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.put(
|
||||||
|
'/flow-active',
|
||||||
|
async ctx => {
|
||||||
|
const { active } = ctx.body
|
||||||
|
const result = await prisma.chatFlows.upsert({
|
||||||
|
where: {
|
||||||
|
id: "1",
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
active: active,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
active: active,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return { data: result }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
active: t.Boolean(),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Update flow active",
|
||||||
|
description: "Update flow active",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.get('/url-token', async ctx => {
|
||||||
|
const result = await prisma.chatFlows.findUnique({
|
||||||
|
where: { id: "1" },
|
||||||
|
select: {
|
||||||
|
flowUrl: true,
|
||||||
|
flowToken: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (!result) {
|
||||||
|
return { data: { flowUrl: null, flowToken: null } }
|
||||||
|
}
|
||||||
|
return { data: { flowUrl: result.flowUrl, flowToken: result.flowToken } }
|
||||||
|
}, {
|
||||||
|
detail: {
|
||||||
|
summary: "Get flow url and token",
|
||||||
|
description: "Get flow url and token",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.put(
|
||||||
|
'/url-token',
|
||||||
|
async ctx => {
|
||||||
|
const { flowUrl, flowToken } = ctx.body
|
||||||
|
const result = await prisma.chatFlows.upsert({
|
||||||
|
where: {
|
||||||
|
id: "1",
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
flowUrl: flowUrl,
|
||||||
|
flowToken: flowToken,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: "1",
|
||||||
|
flowUrl: flowUrl,
|
||||||
|
flowToken: flowToken,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return { data: result }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
flowUrl: t.String(),
|
||||||
|
flowToken: t.String(),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Update flow url and token",
|
||||||
|
description: "Update flow url and token",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.on('error', ctx => {
|
||||||
|
console.log(ctx.error)
|
||||||
|
return { error: ctx.error }
|
||||||
|
})
|
||||||
|
|
||||||
|
export default FlowRoute
|
||||||
|
|
||||||
|
async function chatFlowQuery({
|
||||||
|
flowId,
|
||||||
|
question,
|
||||||
|
}: {
|
||||||
|
flowId: string
|
||||||
|
question: string
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
const resultUrlToken = await prisma.chatFlows.findUnique({ where: { id: "1" }, select: { flowUrl: true, flowToken: true } })
|
||||||
|
if (!resultUrlToken) {
|
||||||
|
return { error: 'Flow URL and Token not found' }
|
||||||
|
}
|
||||||
|
const { flowUrl, flowToken } = resultUrlToken
|
||||||
|
if (!flowUrl || !flowToken) {
|
||||||
|
return { error: 'Flow URL and Token not found' }
|
||||||
|
}
|
||||||
|
const response = await fetch(`${flowUrl}/prediction/${flowId}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${flowToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
question,
|
||||||
|
overrideConfig: {
|
||||||
|
sessionId: "1",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
const result = await response.text()
|
||||||
|
return JSON.parse(result).text
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
return 'Failed to fetch response'
|
||||||
|
}
|
||||||
|
}
|
||||||
11
x.sh
11
x.sh
@@ -1,8 +1,7 @@
|
|||||||
|
TOKEN="EAALP22EWyC4BPnZCfcPQNmD5pGLKV6Ao3GIeWZCc81aPivDFc2FXGA1ZBgrRGcB60LaZCdAr1sbnfP1ufrH3dGthxQzpf18BTjDZBkgG3vBiYZAMpHa7MEZBiRIUZCBe4BDXe8KV0r7DsDmQHJqhA3yZBDKPOL1PKJPEqIq40tLxPwMqWYg4o7xf0sBmZCzx2wI1KtJL8I20MV1ggldngHZCIcnOKDL0uPzDAhc2LAQuI7ZBsgZDZD"
|
||||||
|
MEDIA_ID="24893686766920074"
|
||||||
|
BUSINESS_PHONE_NUMBER_ID="783866307805501"
|
||||||
|
|
||||||
#!/bin/bash
|
curl 'https://graph.facebook.com/v19.0/$MEDIA_ID?phone_number_id=$BUSINESS_PHONE_NUMBER_ID' \
|
||||||
TOKEN="EAALP22EWyC4BPv7XnK1xSaZCWccblEoJFbHzPZAf5mlp4678lSM7cqhQl1ExATf8abrOpinvvFF6U6ruK2FsJqIk8wg6DiUz2fc0NYfcwjon3ng7I3C5HSDQHecgTiJLUBxfZAcvE4IIlhks722jakXaJpojlByo8QJ0CEURtzwEU1guFq7YTX3Et0ZCkbhkdftZCOGmpUKFjL5w5nUdd26Nd58YrLVZCoT8NKhxpWFQZDZD"
|
|
||||||
curl -i -X POST \
|
|
||||||
https://graph.facebook.com/v22.0/838757782652201/messages \
|
|
||||||
-H 'Authorization: Bearer $TOKEN' \
|
-H 'Authorization: Bearer $TOKEN' \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json'
|
||||||
-d '{ "messaging_product": "whatsapp", "to": "6289505046093", "type": "template", "template": { "name": "hello_world", "language": { "code": "en_US" } } }'
|
|
||||||
24
xx.ts
Normal file
24
xx.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import fetch from "node-fetch";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
const token = "EAALP22EWyC4BPrjshjjYBbPVKWp4Gp2ljkb7hCmgpZArLigB8XNmRoXBomDJm6aWnjpKpqehdVatbfFAHeGaQftGkNBp4Oyds9apr4lOQjG2YWYEzZC05ZAo7MARnfXn7FVua0iaeNMh2gunMZBd6pO58wjAUP3gqLiUrwASeOnJu5pW3tKg6fHubALBlQZDZD"; // dari Meta Developer > App > Access Token
|
||||||
|
const mediaId = "838467435201133"; // dari webhook
|
||||||
|
|
||||||
|
// 1. Dapatkan URL file asli
|
||||||
|
const mediaInfo = await fetch(
|
||||||
|
`https://graph.facebook.com/v19.0/${mediaId}?access_token=${token}`
|
||||||
|
).then(res => res.json()) as any;
|
||||||
|
|
||||||
|
// mediaInfo.url berisi link unduhan sementara
|
||||||
|
const fileUrl = mediaInfo.url;
|
||||||
|
|
||||||
|
const fileResponse = await fetch(fileUrl, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`, // wajib!
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const buffer = await fileResponse.arrayBuffer();
|
||||||
|
fs.writeFileSync("sticker.webp", Buffer.from(buffer));
|
||||||
|
|
||||||
|
console.log("Sticker berhasil diunduh!");
|
||||||
Reference in New Issue
Block a user