Skip to content

Commit 8ad77ea

Browse files
committed
Merge branch 'main' into DIAL-34-Add-training-model
2 parents 936bfe9 + cafc1b9 commit 8ad77ea

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+3063
-104
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/apis/flow.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { ENDPOINTS } from '@/constants';
2+
import http_client from '@/lib/http-client';
3+
import { TFlowInput } from '@/lib/schema/flow-input';
4+
import { TFLow } from '@/types/flow';
5+
import { TBaseQuery, TBaseResponse, TResPagination } from '@/types/share';
6+
7+
export class FlowApi {
8+
create(data: TFlowInput): Promise<TBaseResponse<TFLow>> {
9+
return http_client.post(ENDPOINTS.FLOW.INDEX, data);
10+
}
11+
12+
update(id: string, data: TFlowInput): Promise<TBaseResponse<TFLow>> {
13+
return http_client.put(`${ENDPOINTS.FLOW.INDEX}/${id}`, data);
14+
}
15+
16+
delete(id: string): Promise<TBaseResponse<null>> {
17+
return http_client.delete(`${ENDPOINTS.FLOW.INDEX}/${id}`);
18+
}
19+
20+
publish(id: string) {
21+
return http_client.post(`${ENDPOINTS.FLOW.PUBLISH}/${id}`);
22+
}
23+
24+
get(id: string) {
25+
return http_client.get(`${ENDPOINTS.FLOW.GET_ONE}/${id}`);
26+
}
27+
28+
getAll(
29+
q?: TBaseQuery
30+
): Promise<TResPagination<Pick<TFLow, 'id' | 'name' | 'publishAt'>>> {
31+
return http_client.get(ENDPOINTS.FLOW.INDEX, { params: q });
32+
}
33+
}
34+
35+
export const flowApi = new FlowApi();

client/src/app.tsx

Lines changed: 20 additions & 8 deletions
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,
@@ -27,6 +29,7 @@ import {
2729
articlesLoader,
2830
authLoader,
2931
channelsLoader,
32+
flowsLoader,
3033
settingLoader,
3134
} from './lib/loader';
3235
import HelpDetail from './pages/help-detail';
@@ -78,15 +81,10 @@ export const router = createBrowserRouter([
7881
},
7982
{
8083
path: ROUTES.PRIVATE.CHAT_BOT.INDEX,
81-
element: <div>a</div>,
82-
loader: () => {
83-
useAppLayoutStore
84-
.getState()
85-
.setTitle(i18n.t('common:chatbots'));
86-
87-
return null;
88-
},
84+
Component: Chatbots,
85+
loader: flowsLoader,
8986
},
87+
9088
{
9189
path: ROUTES.PRIVATE.CHANNEL.INDEX,
9290
Component: Channels,
@@ -134,6 +132,20 @@ export const router = createBrowserRouter([
134132
},
135133
],
136134
},
135+
{
136+
loader: appLoader,
137+
element: (
138+
<Suspense fallback={<PageLoading />}>
139+
<AppLayout showHeader={false} />
140+
</Suspense>
141+
),
142+
children: [
143+
{
144+
path: `${ROUTES.PRIVATE.CHAT_BOT.INDEX}/:id`,
145+
Component: ChatBotDetail,
146+
},
147+
],
148+
},
137149
],
138150
},
139151
]);
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { useForm } from 'react-hook-form';
2+
import {
3+
Form,
4+
FormControl,
5+
FormField,
6+
FormItem,
7+
FormLabel,
8+
FormMessage,
9+
Input,
10+
} from '../ui';
11+
import { useErrorsLngChange } from '@/hooks/use-errors-lng-change';
12+
import { useTranslation } from 'react-i18next';
13+
import { zodResolver } from '@hookform/resolvers/zod';
14+
import { TFlowInput, useFlowInputSchema } from '@/lib/schema/flow-input';
15+
16+
type Props = {
17+
id?: string;
18+
onSubmit?: (data: TFlowInput) => void;
19+
defaultValues?: TFlowInput;
20+
};
21+
22+
const FLowForm = ({ id = 'channel-form', onSubmit, defaultValues }: Props) => {
23+
const { t } = useTranslation('forms');
24+
const schema = useFlowInputSchema();
25+
const form = useForm<TFlowInput>({
26+
resolver: zodResolver(schema),
27+
mode: 'onChange',
28+
defaultValues,
29+
});
30+
31+
useErrorsLngChange(form);
32+
33+
const handleSubmit = (data: TFlowInput) => {
34+
if (onSubmit) {
35+
onSubmit(data);
36+
}
37+
};
38+
39+
return (
40+
<Form {...form}>
41+
<form
42+
className="space-y-3"
43+
id={id}
44+
onSubmit={form.handleSubmit(handleSubmit)}
45+
>
46+
<FormField
47+
name="name"
48+
control={form.control}
49+
render={({ field }) => (
50+
<FormItem>
51+
<FormLabel required>{t('name.label')}</FormLabel>
52+
<FormControl>
53+
<Input
54+
{...field}
55+
placeholder={t('name.placeholder')}
56+
autoComplete="off"
57+
/>
58+
</FormControl>
59+
<FormMessage />
60+
</FormItem>
61+
)}
62+
/>
63+
</form>
64+
</Form>
65+
);
66+
};
67+
68+
export default FLowForm;

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: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
export const Controls = () => {
10+
const { x, y, zoom } = useViewport();
11+
const { setViewport } = useReactFlow();
12+
13+
const zoomPercent = Math.round((zoom - 1) * 100);
14+
15+
const handleZoomIn = () => {
16+
setViewport({
17+
zoom: zoom >= MAX_ZOOM ? MAX_ZOOM : zoom + 0.25,
18+
x,
19+
y,
20+
});
21+
};
22+
23+
const handleZoomOut = () => {
24+
setViewport({
25+
zoom: zoom <= MIN_ZOOM ? MIN_ZOOM : zoom - 0.25,
26+
x,
27+
y,
28+
});
29+
};
30+
31+
const handleFitView = () => {
32+
setViewport({
33+
zoom: 2,
34+
x: 0,
35+
y: 0,
36+
});
37+
};
38+
39+
useLayoutEffect(() => {
40+
setViewport({
41+
zoom: 1.5,
42+
x: 0,
43+
y: 0,
44+
});
45+
}, [setViewport]);
46+
47+
return (
48+
<Panel position="bottom-left">
49+
<div className="flex h-12 items-center bg-card shadow px-2 select-none gap-2 rounded-md">
50+
<div className="flex items-center gap-2">
51+
<Button size="icon" variant="ghost" onClick={handleZoomIn}>
52+
<Plus className="w-4 h-4" />
53+
</Button>
54+
<span className="w-9 h-9 flex items-center justify-center">
55+
{zoomPercent > 100 ? '100%' : `${zoomPercent}%`}
56+
</span>
57+
<Button size="icon" variant="ghost" onClick={handleZoomOut}>
58+
<Minus className="w-4 h-4" />
59+
</Button>
60+
</div>
61+
<Button size="icon" variant="ghost" onClick={handleFitView}>
62+
<Maximize className="w-4 h-4" />
63+
</Button>
64+
</div>
65+
</Panel>
66+
);
67+
};
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';

0 commit comments

Comments
 (0)