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'
|
||||
};
|
||||
|
||||
exports.Prisma.ChatFlowsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
flows: 'flows',
|
||||
defaultFlow: 'defaultFlow',
|
||||
defaultData: 'defaultData',
|
||||
active: 'active',
|
||||
flowUrl: 'flowUrl',
|
||||
flowToken: 'flowToken',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
@@ -195,7 +207,8 @@ exports.Prisma.ModelName = {
|
||||
User: 'User',
|
||||
ApiKey: 'ApiKey',
|
||||
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",
|
||||
"types": "index.d.ts",
|
||||
"browser": "default.js",
|
||||
|
||||
@@ -53,3 +53,15 @@ model WaHook {
|
||||
createdAt DateTime @default(now())
|
||||
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'
|
||||
};
|
||||
|
||||
exports.Prisma.ChatFlowsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
flows: 'flows',
|
||||
defaultFlow: 'defaultFlow',
|
||||
defaultData: 'defaultData',
|
||||
active: 'active',
|
||||
flowUrl: 'flowUrl',
|
||||
flowToken: 'flowToken',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
@@ -167,7 +179,8 @@ exports.Prisma.ModelName = {
|
||||
User: 'User',
|
||||
ApiKey: 'ApiKey',
|
||||
WebHook: 'WebHook',
|
||||
WaHook: 'WaHook'
|
||||
WaHook: 'WaHook',
|
||||
ChatFlows: 'ChatFlows'
|
||||
};
|
||||
/**
|
||||
* 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",
|
||||
"inlineSchemaHash": "f672201c199f0f043f3266324775af184d82ebd00379f5c23166510b25c889d0",
|
||||
"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": "853d49417068ff1819c3dbb60ac7aeea59288f307f7939669c83fdd63bb495e3",
|
||||
"copyEngine": true
|
||||
}
|
||||
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)
|
||||
config.engineWasm = {
|
||||
getRuntime: async () => require('./query_engine_bg.js'),
|
||||
|
||||
@@ -53,3 +53,15 @@ model WaHook {
|
||||
createdAt DateTime @default(now())
|
||||
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() {
|
||||
return (
|
||||
<MantineProvider>
|
||||
<MantineProvider defaultColorScheme="dark">
|
||||
<Notifications />
|
||||
<ModalsProvider>
|
||||
<AppRoutes />
|
||||
|
||||
@@ -7,6 +7,9 @@ import WebhookHome from "./pages/sq/dashboard/webhook/webhook_home";
|
||||
import WebhookLayout from "./pages/sq/dashboard/webhook/webhook_layout";
|
||||
import WajsHome from "./pages/sq/dashboard/wajs/wajs_home";
|
||||
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 DashboardPage from "./pages/sq/dashboard/dashboard_page";
|
||||
import DashboardLayout from "./pages/sq/dashboard/dashboard_layout";
|
||||
@@ -48,6 +51,19 @@ export default function AppRoutes() {
|
||||
element={<WajsHome />}
|
||||
/>
|
||||
</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
|
||||
path="/sq/dashboard/apikey/apikey"
|
||||
element={<ApikeyPage />}
|
||||
|
||||
@@ -9,6 +9,9 @@ const clientRoutes = {
|
||||
"/sq/dashboard/webhook/webhook-home": "/sq/dashboard/webhook/webhook-home",
|
||||
"/sq/dashboard/wajs": "/sq/dashboard/wajs",
|
||||
"/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/dashboard": "/sq/dashboard/dashboard",
|
||||
"/login": "/login",
|
||||
|
||||
@@ -10,6 +10,7 @@ import WaRoute from "./server/routes/wa_route";
|
||||
import WebhookRoute from "./server/routes/webhook_route";
|
||||
import cors from "@elysiajs/cors";
|
||||
import WaHookRoute from "./server/routes/wa_hook_route";
|
||||
import FlowRoute from "./server/routes/flow_route";
|
||||
|
||||
const Docs = new Elysia().use(
|
||||
Swagger({
|
||||
@@ -35,7 +36,7 @@ const Api = new Elysia({
|
||||
.use(ApiUser)
|
||||
.use(WaRoute)
|
||||
.use(WebhookRoute)
|
||||
|
||||
.use(FlowRoute);
|
||||
|
||||
const app = new Elysia()
|
||||
.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() {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div>
|
||||
<Container>
|
||||
<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 c="#9A9A9A">
|
||||
{apiKey.description || "—"}
|
||||
</Table.Td>
|
||||
<Table.Td c="#9A9A9A">{apiKey.description || "—"}</Table.Td>
|
||||
<Table.Td>
|
||||
{apiKey.expiredAt
|
||||
? new Date(apiKey.expiredAt)
|
||||
.toISOString()
|
||||
.split("T")[0]
|
||||
? new Date(apiKey.expiredAt).toISOString().split("T")[0]
|
||||
: "—"}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
{new Date(apiKey.createdAt)
|
||||
.toISOString()
|
||||
.split("T")[0]}
|
||||
{new Date(apiKey.createdAt).toISOString().split("T")[0]}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
{new Date(apiKey.updatedAt)
|
||||
.toISOString()
|
||||
.split("T")[0]}
|
||||
{new Date(apiKey.updatedAt).toISOString().split("T")[0]}
|
||||
</Table.Td>
|
||||
<Table.Td align="right">
|
||||
<Group gap={4} justify="right">
|
||||
@@ -301,7 +293,7 @@ function ListApiKey({ refresh }: { refresh: boolean }) {
|
||||
id: apiKey.id,
|
||||
});
|
||||
setApiKeys((prev) =>
|
||||
prev.filter((a) => a.id !== apiKey.id)
|
||||
prev.filter((a) => a.id !== apiKey.id),
|
||||
);
|
||||
showNotification({
|
||||
title: "Deleted",
|
||||
|
||||
@@ -258,6 +258,12 @@ function NavigationDashboard() {
|
||||
icon: <IconWebhook size={20} color="#00FFFF" />,
|
||||
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 (
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export default function WebhookCreate() {
|
||||
let headerObj: Record<string, string> = {};
|
||||
try {
|
||||
headerObj = JSON.parse(headers);
|
||||
} catch { }
|
||||
} catch {}
|
||||
if (apiToken) headerObj["Authorization"] = `Bearer ${apiToken}`;
|
||||
const prettyHeaders = safeJson(JSON.stringify(headerObj));
|
||||
const prettyPayload = safeJson(payload);
|
||||
@@ -141,7 +141,6 @@ export default function WebhookCreate() {
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
@@ -149,7 +148,6 @@ export default function WebhookCreate() {
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
@@ -157,7 +155,6 @@ export default function WebhookCreate() {
|
||||
placeholder="https://example.com/webhook"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
|
||||
/>
|
||||
|
||||
<Select
|
||||
@@ -169,7 +166,6 @@ export default function WebhookCreate() {
|
||||
value: v,
|
||||
label: v,
|
||||
}))}
|
||||
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
@@ -186,9 +182,8 @@ export default function WebhookCreate() {
|
||||
current["Authorization"] = `Bearer ${e.target.value}`;
|
||||
}
|
||||
setHeaders(JSON.stringify(current, null, 2));
|
||||
} catch { }
|
||||
} catch {}
|
||||
}}
|
||||
|
||||
/>
|
||||
|
||||
<Stack gap="xs">
|
||||
@@ -257,7 +252,6 @@ export default function WebhookCreate() {
|
||||
placeholder="Replay Key"
|
||||
value={replayKey}
|
||||
onChange={(e) => setReplayKey(e.target.value)}
|
||||
|
||||
/>
|
||||
|
||||
<Card
|
||||
|
||||
@@ -7,7 +7,18 @@ import { useMemo } from "react";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { IconCode, IconCheck, IconX } from "@tabler/icons-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 clientRoutes from "@/clientRoutes";
|
||||
import { useShallowEffect } from "@mantine/hooks";
|
||||
@@ -20,9 +31,16 @@ Available variables:
|
||||
export default function WebhookEdit() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const id = searchParams.get("id");
|
||||
const { data, error, isLoading, mutate } = useSWR("/", () => apiFetch.api.webhook.find({
|
||||
id: id!
|
||||
}).get(), {dedupingInterval: 3000})
|
||||
const { data, error, isLoading, mutate } = useSWR(
|
||||
"/",
|
||||
() =>
|
||||
apiFetch.api.webhook
|
||||
.find({
|
||||
id: id!,
|
||||
})
|
||||
.get(),
|
||||
{ dedupingInterval: 3000 },
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useShallowEffect(() => {
|
||||
@@ -35,29 +53,41 @@ export default function WebhookEdit() {
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
|
||||
<Group justify="space-between">
|
||||
<Title order={2}>Edit Webhook</Title>
|
||||
<Button variant="outline" onClick={() => {
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
modals.openConfirmModal({
|
||||
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" },
|
||||
labels: {
|
||||
cancel: "Cancel",
|
||||
confirm: "Remove",
|
||||
},
|
||||
onConfirm: () => {
|
||||
apiFetch.api.webhook.remove({
|
||||
id: id!
|
||||
}).delete()
|
||||
apiFetch.api.webhook
|
||||
.remove({
|
||||
id: id!,
|
||||
})
|
||||
.delete();
|
||||
navigate(clientRoutes["/sq/dashboard/webhook"]);
|
||||
},
|
||||
onCancel: () => {
|
||||
navigate(clientRoutes["/sq/dashboard/webhook/webhook-edit"] + "?id=" + id);
|
||||
navigate(
|
||||
clientRoutes["/sq/dashboard/webhook/webhook-edit"] +
|
||||
"?id=" +
|
||||
id,
|
||||
);
|
||||
},
|
||||
})
|
||||
}}>Remove</Button>
|
||||
});
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</Group>
|
||||
<EditView webhook={data.data?.webhook || null} />
|
||||
</Stack>
|
||||
@@ -98,7 +128,7 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
||||
let headerObj: Record<string, string> = {};
|
||||
try {
|
||||
headerObj = JSON.parse(headers);
|
||||
} catch { }
|
||||
} catch {}
|
||||
if (apiToken) headerObj["Authorization"] = `Bearer ${apiToken}`;
|
||||
const prettyHeaders = safeJson(JSON.stringify(headerObj));
|
||||
const prettyPayload = safeJson(payload);
|
||||
@@ -122,9 +152,11 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
||||
icon: <IconX />,
|
||||
});
|
||||
}
|
||||
const { data } = await apiFetch.api.webhook.update({
|
||||
const { data } = await apiFetch.api.webhook
|
||||
.update({
|
||||
id: webhook?.id,
|
||||
}).put({
|
||||
})
|
||||
.put({
|
||||
name,
|
||||
description,
|
||||
apiToken,
|
||||
@@ -184,7 +216,6 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
@@ -192,7 +223,6 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
@@ -200,7 +230,6 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
||||
placeholder="https://example.com/webhook"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
|
||||
/>
|
||||
|
||||
<Select
|
||||
@@ -212,7 +241,6 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
||||
value: v,
|
||||
label: v,
|
||||
}))}
|
||||
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
@@ -229,9 +257,8 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
||||
current["Authorization"] = `Bearer ${e.target.value}`;
|
||||
}
|
||||
setHeaders(JSON.stringify(current, null, 2));
|
||||
} catch { }
|
||||
} catch {}
|
||||
}}
|
||||
|
||||
/>
|
||||
|
||||
<Stack gap="xs">
|
||||
@@ -302,7 +329,6 @@ function EditView({ webhook }: { webhook: WebHook | null }) {
|
||||
placeholder="Replay Key"
|
||||
value={replayKey}
|
||||
onChange={(e) => setReplayKey(e.target.value)}
|
||||
|
||||
/>
|
||||
|
||||
<Card
|
||||
|
||||
@@ -35,7 +35,9 @@ export default function WebhookHome() {
|
||||
const navigate = useNavigate();
|
||||
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]);
|
||||
|
||||
@@ -43,9 +45,9 @@ export default function WebhookHome() {
|
||||
mutate();
|
||||
}, []);
|
||||
|
||||
|
||||
function ButtonCreate() {
|
||||
return <Tooltip label="Create new webhook" withArrow color="teal">
|
||||
return (
|
||||
<Tooltip label="Create new webhook" withArrow color="teal">
|
||||
<Button
|
||||
radius="xl"
|
||||
size="md"
|
||||
@@ -60,19 +62,18 @@ export default function WebhookHome() {
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(-2px)";
|
||||
e.currentTarget.style.boxShadow =
|
||||
"0 0 20px rgba(0,255,200,0.4)";
|
||||
e.currentTarget.style.boxShadow = "0 0 20px rgba(0,255,200,0.4)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(0)";
|
||||
e.currentTarget.style.boxShadow =
|
||||
"0 0 12px rgba(0,255,200,0.25)";
|
||||
e.currentTarget.style.boxShadow = "0 0 12px rgba(0,255,200,0.25)";
|
||||
}}
|
||||
onClick={() => navigate("/sq/dashboard/webhook/webhook-create")}
|
||||
>
|
||||
Create Webhook
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading)
|
||||
@@ -159,7 +160,11 @@ export default function WebhookHome() {
|
||||
variant="light"
|
||||
size="lg"
|
||||
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 />
|
||||
</ActionIcon>
|
||||
@@ -180,11 +185,16 @@ export default function WebhookHome() {
|
||||
>
|
||||
{webhook.enabled ? "Active" : "Disabled"}
|
||||
</Badge>
|
||||
<Badge bg={"teal"} leftSection={<IconMessageReply size={16} color="#00FFC8" />}>
|
||||
<Badge
|
||||
bg={"teal"}
|
||||
leftSection={<IconMessageReply size={16} color="#00FFC8" />}
|
||||
>
|
||||
{webhook.replay ? "Replay" : "Not Replay"}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Text c="#9A9A9A" size="sm">{webhook.description}</Text>
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
{webhook.description}
|
||||
</Text>
|
||||
</Stack>
|
||||
<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() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Outlet />
|
||||
);
|
||||
return <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'
|
||||
}
|
||||
}
|
||||
13
x.sh
13
x.sh
@@ -1,8 +1,7 @@
|
||||
TOKEN="EAALP22EWyC4BPnZCfcPQNmD5pGLKV6Ao3GIeWZCc81aPivDFc2FXGA1ZBgrRGcB60LaZCdAr1sbnfP1ufrH3dGthxQzpf18BTjDZBkgG3vBiYZAMpHa7MEZBiRIUZCBe4BDXe8KV0r7DsDmQHJqhA3yZBDKPOL1PKJPEqIq40tLxPwMqWYg4o7xf0sBmZCzx2wI1KtJL8I20MV1ggldngHZCIcnOKDL0uPzDAhc2LAQuI7ZBsgZDZD"
|
||||
MEDIA_ID="24893686766920074"
|
||||
BUSINESS_PHONE_NUMBER_ID="783866307805501"
|
||||
|
||||
#!/bin/bash
|
||||
TOKEN="EAALP22EWyC4BPv7XnK1xSaZCWccblEoJFbHzPZAf5mlp4678lSM7cqhQl1ExATf8abrOpinvvFF6U6ruK2FsJqIk8wg6DiUz2fc0NYfcwjon3ng7I3C5HSDQHecgTiJLUBxfZAcvE4IIlhks722jakXaJpojlByo8QJ0CEURtzwEU1guFq7YTX3Et0ZCkbhkdftZCOGmpUKFjL5w5nUdd26Nd58YrLVZCoT8NKhxpWFQZDZD"
|
||||
curl -i -X POST \
|
||||
https://graph.facebook.com/v22.0/838757782652201/messages \
|
||||
-H 'Authorization: Bearer $TOKEN' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{ "messaging_product": "whatsapp", "to": "6289505046093", "type": "template", "template": { "name": "hello_world", "language": { "code": "en_US" } } }'
|
||||
curl 'https://graph.facebook.com/v19.0/$MEDIA_ID?phone_number_id=$BUSINESS_PHONE_NUMBER_ID' \
|
||||
-H 'Authorization: Bearer $TOKEN' \
|
||||
-H 'Content-Type: application/json'
|
||||
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