Skip to content

Commit 8890879

Browse files
committed
feat: chatbots page
1 parent 9691fd3 commit 8890879

34 files changed

+1018
-21
lines changed

client/package-lock.json

Lines changed: 462 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@hookform/resolvers": "^3.3.4",
1414
"@mdx-js/react": "^3.0.1",
1515
"@mdx-js/rollup": "^3.0.1",
16+
"@paralleldrive/cuid2": "^2.2.2",
1617
"@radix-ui/react-accordion": "^1.1.2",
1718
"@radix-ui/react-alert-dialog": "^1.0.5",
1819
"@radix-ui/react-aspect-ratio": "^1.0.3",
@@ -70,6 +71,7 @@
7071
"react-i18next": "^14.0.5",
7172
"react-resizable-panels": "^2.0.9",
7273
"react-router-dom": "^6.22.2",
74+
"reactflow": "^11.10.4",
7375
"remark-mdx": "^3.0.1",
7476
"sonner": "^1.4.0",
7577
"tailwind-merge": "^2.2.1",

client/src/app.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { createBrowserRouter, redirect } from 'react-router-dom';
22
import i18n from './i18n';
33
import {
44
Channels,
5+
ChatBotDetail,
6+
Chatbots,
57
ForgotPassword,
68
Login,
79
Mail,
@@ -78,7 +80,7 @@ export const router = createBrowserRouter([
7880
},
7981
{
8082
path: ROUTES.PRIVATE.CHAT_BOT.INDEX,
81-
element: <div>a</div>,
83+
Component: Chatbots,
8284
loader: () => {
8385
useAppLayoutStore
8486
.getState()
@@ -87,6 +89,7 @@ export const router = createBrowserRouter([
8789
return null;
8890
},
8991
},
92+
9093
{
9194
path: ROUTES.PRIVATE.CHANNEL.INDEX,
9295
Component: Channels,
@@ -134,6 +137,20 @@ export const router = createBrowserRouter([
134137
},
135138
],
136139
},
140+
{
141+
loader: appLoader,
142+
element: (
143+
<Suspense fallback={<PageLoading />}>
144+
<AppLayout showHeader={false} />
145+
</Suspense>
146+
),
147+
children: [
148+
{
149+
path: `${ROUTES.PRIVATE.CHAT_BOT.INDEX}/:id`,
150+
Component: ChatBotDetail,
151+
},
152+
],
153+
},
137154
],
138155
},
139156
]);

client/src/components/layouts/app/layout.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
import Sidebar from './sidebar';
22
import { Header } from './header';
33
import { Outlet } from 'react-router-dom';
4+
import { cn } from '@/lib/utils';
45

5-
export const Layout = () => {
6+
type Props = {
7+
showHeader?: boolean;
8+
};
9+
10+
export const Layout = ({ showHeader = true }: Props) => {
611
return (
712
<div className="flex min-h-svh ">
8-
<Header />
13+
{showHeader && <Header />}
914
<Sidebar />
10-
<div className="ml-sidebar w-full mt-header">
15+
<div
16+
className={cn('ml-sidebar w-full', {
17+
'mt-header': showHeader,
18+
})}
19+
>
1120
<Outlet />
1221
</div>
1322
</div>

client/src/components/page-loading.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { Loader2 } from 'lucide-react';
2-
import { LogoImg } from './ui';
3-
1+
import { Bot, Loader2 } from 'lucide-react';
42
const PageLoading = () => {
53
return (
64
<div className="flex items-center justify-center h-svh bg-white">
75
<div className="flex flex-col gap-2 items-center">
8-
<LogoImg className="w-24 h-24" />
6+
<Bot size={56} className="text-primary" />
97
<Loader2 className="text-primary animate-spin" size={24} />
108
</div>
119
</div>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Button } from '@/components/ui';
2+
import { Maximize, Minus, Plus } from 'lucide-react';
3+
import { useLayoutEffect } from 'react';
4+
import { Panel, useViewport, useReactFlow } from 'reactflow';
5+
6+
const MAX_ZOOM = 2;
7+
const MIN_ZOOM = 0.5;
8+
9+
type Props = {
10+
onShare?: () => void;
11+
onPublish?: () => void;
12+
};
13+
14+
export const Controls = ({ onPublish, onShare }: Props) => {
15+
const { x, y, zoom } = useViewport();
16+
const { setViewport } = useReactFlow();
17+
18+
const zoomPercent = Math.round((zoom - 1) * 100);
19+
20+
const handleZoomIn = () => {
21+
setViewport({
22+
zoom: zoom >= MAX_ZOOM ? MAX_ZOOM : zoom + 0.25,
23+
x,
24+
y,
25+
});
26+
};
27+
28+
const handleZoomOut = () => {
29+
setViewport({
30+
zoom: zoom <= MIN_ZOOM ? MIN_ZOOM : zoom - 0.25,
31+
x,
32+
y,
33+
});
34+
};
35+
36+
const handleFitView = () => {
37+
setViewport({
38+
zoom: 2,
39+
x: 0,
40+
y: 0,
41+
});
42+
};
43+
44+
useLayoutEffect(() => {
45+
setViewport({
46+
zoom: 1.5,
47+
x: 0,
48+
y: 0,
49+
});
50+
}, [setViewport]);
51+
52+
return (
53+
<Panel position="top-center">
54+
<div className="flex h-12 items-center bg-card shadow px-2 select-none gap-2 rounded-md">
55+
<div className="flex items-center gap-2">
56+
<Button size="icon" variant="ghost" onClick={handleZoomIn}>
57+
<Plus className="w-4 h-4" />
58+
</Button>
59+
<span className="w-9 h-9 flex items-center justify-center">
60+
{zoomPercent > 100 ? '100%' : `${zoomPercent}%`}
61+
</span>
62+
<Button size="icon" variant="ghost" onClick={handleZoomOut}>
63+
<Minus className="w-4 h-4" />
64+
</Button>
65+
</div>
66+
<Button size="icon" variant="ghost" onClick={handleFitView}>
67+
<Maximize className="w-4 h-4" />
68+
</Button>
69+
<Button variant="ghost" onClick={onShare}>
70+
Share
71+
</Button>
72+
<Button variant="default" onClick={onPublish}>
73+
Publish
74+
</Button>
75+
</div>
76+
</Panel>
77+
);
78+
};

client/src/components/pages/chatbot-detail/customs.css

Whitespace-only changes.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Button } from '@/components/ui';
2+
import { X } from 'lucide-react';
3+
import {
4+
BaseEdge,
5+
EdgeLabelRenderer,
6+
EdgeProps,
7+
getSmoothStepPath,
8+
useReactFlow,
9+
} from 'reactflow';
10+
11+
export const Edge = ({
12+
id,
13+
sourceX,
14+
sourceY,
15+
targetX,
16+
targetY,
17+
sourcePosition,
18+
targetPosition,
19+
style = {},
20+
markerEnd,
21+
}: EdgeProps) => {
22+
const { setEdges } = useReactFlow();
23+
const [edgePath, labelX, labelY] = getSmoothStepPath({
24+
sourceX,
25+
sourceY,
26+
sourcePosition,
27+
targetX,
28+
targetY,
29+
targetPosition,
30+
});
31+
32+
const onEdgeClick = () => {
33+
setEdges((edges) => edges.filter((edge) => edge.id !== id));
34+
};
35+
36+
return (
37+
<>
38+
<BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />
39+
<EdgeLabelRenderer>
40+
<div
41+
style={{
42+
position: 'absolute',
43+
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
44+
fontSize: 12,
45+
pointerEvents: 'all',
46+
}}
47+
className="nodrag nopan"
48+
>
49+
<Button variant="outline" size="icon" className=" w-4 h-4 ">
50+
<X className="h-3 w-3 text-destructive" />
51+
</Button>
52+
</div>
53+
</EdgeLabelRenderer>
54+
</>
55+
);
56+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './controls';
2+
export * from './node-types';
3+
export * from './nodes';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { TNodeTypes } from '@/types/chatbot';
2+
import { MessageNode, StartNode } from './nodes';
3+
4+
/**
5+
* Represents the available node types.
6+
* @example
7+
* const nodeTypes: TNodeTypes = {
8+
* start: StartNodeComponent,
9+
* }
10+
*/
11+
export const nodeTypes: TNodeTypes = {
12+
start: StartNode,
13+
message: MessageNode,
14+
};

0 commit comments

Comments
 (0)