Skip to content

Commit 66076f1

Browse files
authored
Merge pull request #43 from Dialogue-Bot/DIAL-32-ui-chat-bot
Dial 32 UI chat bot
2 parents 06af48e + 2e9fd44 commit 66076f1

37 files changed

+2331
-84
lines changed

client/package-lock.json

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

client/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@
4949
"@tanstack/react-router": "^1.16.6",
5050
"@tanstack/react-table": "^8.12.0",
5151
"@tanstack/router-devtools": "^1.16.6",
52+
"@tiptap/extension-color": "^2.2.4",
53+
"@tiptap/extension-list-item": "^2.2.4",
54+
"@tiptap/extension-text-align": "^2.2.4",
55+
"@tiptap/extension-text-style": "^2.2.4",
56+
"@tiptap/extension-underline": "^2.2.4",
57+
"@tiptap/react": "^2.2.4",
58+
"@tiptap/starter-kit": "^2.2.4",
5259
"@xyflow/react": "^12.0.0-next.11",
5360
"axios": "^1.6.7",
5461
"class-variance-authority": "^0.7.0",

client/src/app.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { Outlet, createBrowserRouter, redirect } from 'react-router-dom'
2-
import i18n from './i18n'
31
import {
42
Channels,
53
ChatBotDetail,
@@ -11,17 +9,18 @@ import {
119
Register,
1210
SetPassword,
1311
} from '@/pages'
12+
import { Outlet, createBrowserRouter, redirect } from 'react-router-dom'
1413

1514
import {
1615
AppLayout,
1716
AuthLayout,
1817
PublishLayout,
1918
SettingLayout,
2019
} from '@/components/layouts'
21-
import { ROUTES } from './constants'
22-
import Help from './pages/help'
20+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
2321
import { Suspense } from 'react'
2422
import PageLoading from './components/page-loading'
23+
import { ROUTES } from './constants'
2524
import {
2625
appLoader,
2726
articleLoader,
@@ -32,8 +31,8 @@ import {
3231
flowsLoader,
3332
settingLoader,
3433
} from './lib/loader'
34+
import Help from './pages/help'
3535
import HelpDetail from './pages/help-detail'
36-
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
3736

3837
const App = () => {
3938
return (

client/src/components/forms/card.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ type Props = {
2828
defaultValue?: TCardInput
2929
}
3030

31-
export const CardForm = ({ defaultValue, id, onSubmit }: Props) => {
31+
export const CardForm = ({
32+
defaultValue,
33+
id = 'card-form',
34+
onSubmit,
35+
}: Props) => {
3236
const schema = useCardInputSchema()
3337
const { t } = useTranslation(['forms', 'flowDetail'])
3438
const form = useForm<TCardInput>({
@@ -71,7 +75,7 @@ export const CardForm = ({ defaultValue, id, onSubmit }: Props) => {
7175
<Form {...form}>
7276
<form
7377
className='space-y-3'
74-
id='card-form'
78+
id={id}
7579
onSubmit={form.handleSubmit(handleSubmit)}
7680
>
7781
<FormField

client/src/components/forms/variables-setting.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ const parseVariableValue = (value: string, type: string) => {
5454
return value
5555
}
5656

57+
const stringifyVariableValue = (value: any, type: string) => {
58+
if (type === 'array')
59+
return JSON.stringify(value)
60+
.replace('[', '')
61+
.replace(']', '')
62+
.replace(/"/g, '')
63+
64+
if (type === 'object') return JSON.stringify(value, null, 2)
65+
return JSON.stringify(value)
66+
}
67+
5768
export const VariablesSettingForm = ({
5869
id = 'variables-setting-form',
5970
onSubmit,
@@ -68,7 +79,7 @@ export const VariablesSettingForm = ({
6879
...defaultValues,
6980
variables: defaultValues?.variables?.map((variable) => ({
7081
...variable,
71-
value: JSON.stringify(variable.value).replace(/"/g, ''),
82+
value: stringifyVariableValue(variable.value, variable.type as string),
7283
})),
7384
},
7485
})

client/src/components/pages/flow-detail/condition-dialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
type Props = {}
22

3-
const ConditionDialog = (props: Props) => {
3+
const ConditionDialog = (_props: Props) => {
44
return <div>ConditionDialog</div>
55
}
66

client/src/components/pages/flow-detail/constant.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import i18n from '@/i18n'
2-
import { EActionTypes, EMessageTypes } from '@/types/flow'
2+
import { EActionTypes, EGrammarTypes, EMessageTypes } from '@/types/flow'
33
import {
44
Bolt,
55
GitPullRequest,
@@ -11,7 +11,14 @@ import {
1111
Workflow,
1212
} from 'lucide-react'
1313
import { useTranslation } from 'react-i18next'
14-
import { CheckVariablesContent, MessageDialogContent } from './node-dialog'
14+
import {
15+
CheckVariablesContent,
16+
HttpRequestDialogContent,
17+
MessageDialogContent,
18+
PromptAndCollectDialogContent,
19+
SendMailContent,
20+
SubFlowContent,
21+
} from './node-dialog'
1522

1623
export const MAP_ACTION_TO_LABEL: Record<EActionTypes, string> = {
1724
[EActionTypes.MESSAGE]: i18n.t('flowDetail:actions.items.message') as string,
@@ -36,6 +43,11 @@ export const MAP_ACTION_TO_LABEL: Record<EActionTypes, string> = {
3643
) as string,
3744
}
3845

46+
export const ACTIONS_TO_RENDER_LANG = [
47+
EActionTypes.MESSAGE,
48+
EActionTypes.PROMPT_AND_COLLECT,
49+
]
50+
3951
export const useMapActionToLabel = () => {
4052
const { t } = useTranslation('flowDetail')
4153

@@ -70,7 +82,7 @@ export const MAP_ACTION: Record<
7082
[EActionTypes.PROMPT_AND_COLLECT]: {
7183
icon: () => <HelpCircle className='w-4 h-4' />,
7284
label: 'Prompt and collect',
73-
dialogContent: () => <MessageDialogContent />,
85+
dialogContent: () => <PromptAndCollectDialogContent />,
7486
},
7587
[EActionTypes.CHECK_VARIABLES]: {
7688
icon: () => <Variable className='w-4 h-4' />,
@@ -80,12 +92,12 @@ export const MAP_ACTION: Record<
8092
[EActionTypes.HTTP_REQUEST]: {
8193
icon: () => <GitPullRequest className='w-4 h-4' />,
8294
label: 'Http request',
83-
dialogContent: () => <div>Http request</div>,
95+
dialogContent: () => <HttpRequestDialogContent />,
8496
},
8597
[EActionTypes.SEND_MAIL]: {
8698
icon: () => <Mail className='w-4 h-4' />,
8799
label: 'Send mail',
88-
dialogContent: () => <div>Send mail</div>,
100+
dialogContent: () => <SendMailContent />,
89101
},
90102
[EActionTypes.FALLBACK]: {
91103
icon: () => <Webhook className='w-4 h-4' />,
@@ -100,7 +112,7 @@ export const MAP_ACTION: Record<
100112
[EActionTypes.SUB_FLOW]: {
101113
icon: () => <Workflow className='w-4 h-4' />,
102114
label: 'Sub flow',
103-
dialogContent: () => <div>Sub flow</div>,
115+
dialogContent: () => <SubFlowContent />,
104116
},
105117
}
106118

@@ -136,3 +148,12 @@ export const CONDITIONAL_OPERATOR = [
136148
'in',
137149
'not_in',
138150
]
151+
152+
export const MAP_GRAMMAR_TYPE: Record<EGrammarTypes, string> = {
153+
[EGrammarTypes.INTENT]: i18n.t('flowDetail:grammars_type.intent'),
154+
[EGrammarTypes.NUMBER]: i18n.t('flowDetail:grammars_type.number'),
155+
[EGrammarTypes.EMAIL]: i18n.t('flowDetail:grammars_type.email'),
156+
[EGrammarTypes.PHONE_NUMBER]: i18n.t('flowDetail:grammars_type.phone_number'),
157+
[EGrammarTypes.TEXT]: i18n.t('flowDetail:grammars_type.text'),
158+
[EGrammarTypes.YES_NO]: i18n.t('flowDetail:grammars_type.yes_no'),
159+
}

client/src/components/pages/flow-detail/edge.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Button } from '@/components/ui'
22
import { X } from 'lucide-react'
3-
import { BaseEdge, EdgeProps, getBezierPath, useReactFlow } from 'reactflow'
3+
import { BaseEdge, EdgeProps, getSmoothStepPath, useReactFlow } from 'reactflow'
44

55
const foreignObjectSize = 16
66

@@ -22,7 +22,7 @@ export const Edge = ({
2222
deletable?: boolean
2323
}>) => {
2424
const { setEdges } = useReactFlow()
25-
const [edgePath, labelX, labelY] = getBezierPath({
25+
const [edgePath, labelX, labelY] = getSmoothStepPath({
2626
sourceX,
2727
sourceY,
2828
sourcePosition,

client/src/components/pages/flow-detail/flow-inside.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
import {
2+
AlertDialog,
3+
AlertDialogAction,
4+
AlertDialogCancel,
5+
AlertDialogContent,
6+
AlertDialogDescription,
7+
AlertDialogFooter,
8+
AlertDialogHeader,
9+
AlertDialogTitle,
10+
AlertDialogTrigger,
11+
} from '@/components/ui'
12+
import { useTranslation } from 'react-i18next'
13+
import { useBlocker } from 'react-router-dom'
114
import ReactFlow, { Background, BackgroundVariant } from 'reactflow'
215
import { Actions } from './actions'
316
import { Controls } from './controls'
@@ -19,10 +32,49 @@ export const FlowInside = () => {
1932
handleInit,
2033
handleDoubleClickNode,
2134
handleDoubleClickEdge,
35+
flow,
2236
} = useFlowCtx()
37+
const { t } = useTranslation('common')
38+
39+
const blocker = useBlocker(({ currentLocation, nextLocation }) => {
40+
return (
41+
(JSON.stringify(nodes) !== JSON.stringify(flow.nodes) ||
42+
JSON.stringify(edges) !== JSON.stringify(flow.edges)) &&
43+
currentLocation.pathname !== nextLocation.pathname
44+
)
45+
})
2346

2447
return (
2548
<div className='h-svh select-none'>
49+
<AlertDialog open={blocker.state === 'blocked'}>
50+
<AlertDialogTrigger asChild>
51+
<div className='hidden'></div>
52+
</AlertDialogTrigger>
53+
<AlertDialogContent>
54+
<AlertDialogHeader>
55+
<AlertDialogTitle>{t('leave_page_unsaved.title')}</AlertDialogTitle>
56+
<AlertDialogDescription>
57+
{t('leave_page_unsaved.desc')}
58+
</AlertDialogDescription>
59+
</AlertDialogHeader>
60+
<AlertDialogFooter>
61+
<AlertDialogCancel
62+
onClick={() => {
63+
blocker.reset?.()
64+
}}
65+
>
66+
{t('cancel')}
67+
</AlertDialogCancel>
68+
<AlertDialogAction
69+
onClick={() => {
70+
blocker.proceed?.()
71+
}}
72+
>
73+
{t('confirm')}
74+
</AlertDialogAction>
75+
</AlertDialogFooter>
76+
</AlertDialogContent>
77+
</AlertDialog>
2678
<ReactFlow
2779
nodeTypes={nodeTypes}
2880
nodes={nodes}

client/src/components/pages/flow-detail/flow-provider.tsx

Lines changed: 48 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useDidUpdate } from '@/hooks/use-did-update'
2-
import { usePrevious } from '@/hooks/use-prev'
32
import { TFlowInput } from '@/lib/schema/flow-input'
43
import { EActionTypes, EMessageTypes, TNode } from '@/types/flow'
54
import { createId } from '@paralleldrive/cuid2'
@@ -59,52 +58,59 @@ type Props = {
5958
export const FlowProvider = ({ children, flow }: Props) => {
6059
const [open, toggle] = useToggle()
6160
const actionToLabel = useMapActionToLabel()
62-
const [nodes, setNodes, onNodesChange] = useNodesState<any>([
63-
{
64-
id: EActionTypes.START,
65-
type: EActionTypes.START,
66-
position: { x: 100, y: 100 },
67-
data: {
68-
label: 'Start',
69-
action: EActionTypes.START,
70-
id: EActionTypes.START,
71-
name: 'Start',
72-
},
73-
deletable: false,
74-
draggable: false,
75-
},
76-
{
77-
id: EActionTypes.FALLBACK,
78-
type: EActionTypes.FALLBACK,
79-
position: { x: 190, y: 280 },
80-
data: {
81-
label: 'Fallback',
82-
id: EActionTypes.FALLBACK,
83-
action: EActionTypes.FALLBACK,
84-
name: 'Fallback',
85-
},
86-
deletable: false,
87-
draggable: false,
88-
},
89-
])
90-
const [edges, setEdges, onEdgesChange] = useEdgesState([
91-
{
92-
id: 'start-fallback',
93-
source: EActionTypes.START,
94-
target: EActionTypes.FALLBACK,
95-
type: 'custom',
96-
data: {
97-
deletable: false,
98-
},
99-
},
100-
])
61+
const [nodes, setNodes, onNodesChange] = useNodesState<any>(
62+
flow.nodes?.length
63+
? (flow.nodes as Node<any>[])
64+
: [
65+
{
66+
id: EActionTypes.START,
67+
type: EActionTypes.START,
68+
position: { x: 100, y: 100 },
69+
data: {
70+
label: 'Start',
71+
action: EActionTypes.START,
72+
id: EActionTypes.START,
73+
name: 'Start',
74+
},
75+
deletable: false,
76+
draggable: false,
77+
},
78+
{
79+
id: EActionTypes.FALLBACK,
80+
type: EActionTypes.FALLBACK,
81+
position: { x: 190, y: 280 },
82+
data: {
83+
label: 'Fallback',
84+
id: EActionTypes.FALLBACK,
85+
action: EActionTypes.FALLBACK,
86+
name: 'Fallback',
87+
},
88+
deletable: false,
89+
draggable: false,
90+
},
91+
],
92+
)
93+
const [edges, setEdges, onEdgesChange] = useEdgesState(
94+
flow.edges?.length
95+
? (flow.edges as Edge<any>[])
96+
: [
97+
{
98+
id: 'start-fallback',
99+
source: EActionTypes.START,
100+
target: EActionTypes.FALLBACK,
101+
type: 'custom',
102+
data: {
103+
deletable: false,
104+
},
105+
},
106+
],
107+
)
101108
const [selectedNode, setSelectedNode] = useState<Node<any> | null>(null)
102-
const [selectedEdge, setSelectedEdge] = useState<Edge<any> | null>(null)
109+
const [_selectedEdge, setSelectedEdge] = useState<Edge<any> | null>(null)
103110
const [currentLang, setCurrentLang] = useState(
104111
flow.settings?.find((setting) => setting.type === 'language')?.value ||
105112
'en',
106113
)
107-
const prevLang = usePrevious<string>(currentLang)
108114

109115
const [reactFlowInstance, setReactFlowInstance] = useState<ReactFlowInstance<
110116
any,

0 commit comments

Comments
 (0)