feat: s
This commit is contained in:
parent
cd269b82b3
commit
6a0362e18f
@ -18,6 +18,7 @@
|
||||
"@types/node": "^16.18.95",
|
||||
"@types/react": "^18.2.74",
|
||||
"@types/react-dom": "^18.2.24",
|
||||
"axios": "^1.6.8",
|
||||
"babel-jest": "^27.4.2",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-named-asset-import": "^0.3.8",
|
||||
@ -52,6 +53,7 @@
|
||||
"postcss-normalize": "^10.0.1",
|
||||
"postcss-preset-env": "^7.0.1",
|
||||
"prompts": "^2.4.2",
|
||||
"qs": "^6.12.0",
|
||||
"react": "^18.2.0",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
@ -102,6 +104,7 @@
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash": "^4.17.0",
|
||||
"@types/qs": "^6.9.14",
|
||||
"monaco-editor-webpack-plugin": "^7.1.0"
|
||||
},
|
||||
"jest": {
|
||||
@ -159,5 +162,6 @@
|
||||
"presets": [
|
||||
"react-app"
|
||||
]
|
||||
}
|
||||
},
|
||||
"proxy": "http://rcw-mock-lab02.lab.nordigy.ru:8080"
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
* @Date : 2024-04-09 14:46:24
|
||||
* @LastEditTime : 2024-04-09 14:46:24
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
import React, { FC } from "react";
|
||||
import { styled } from '@mui/material/styles';
|
||||
@ -11,10 +11,12 @@ import ListItem from '@mui/material/ListItem';
|
||||
import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
|
||||
import Box from '@mui/material/Box';
|
||||
import FaceIcon from '@mui/icons-material/Face';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
|
||||
interface Props {
|
||||
isMe: boolean;
|
||||
message: string;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@ -43,8 +45,9 @@ const ChatMessageWrap = styled(Box)<{ isMe: boolean }>(({ isMe }) => ({
|
||||
|
||||
const ChatMessage = styled(Box)(() => ({
|
||||
maxWidth: '80%',
|
||||
whiteSpace: 'pre-line'
|
||||
}));
|
||||
export const ChatItem: FC<Props> = ({ isMe, message }) => {
|
||||
export const ChatItem: FC<Props> = ({ isMe, message, loading }) => {
|
||||
return (
|
||||
<ChatWrap isMe={isMe} alignItems="flex-start">
|
||||
<ChatAvatar>
|
||||
@ -53,7 +56,8 @@ export const ChatItem: FC<Props> = ({ isMe, message }) => {
|
||||
</ChatAvatar>
|
||||
<ChatMessageWrap isMe={isMe}>
|
||||
<ChatMessage sx={{ borderRadius: 2, p: 2, bgcolor: isMe ? 'primary.light' : 'secondary.light' }}>
|
||||
{message}
|
||||
{loading && <CircularProgress color="secondary" size={20}/>}
|
||||
{!loading && message}
|
||||
</ChatMessage>
|
||||
</ChatMessageWrap>
|
||||
</ChatWrap>
|
||||
|
138
src/Create.tsx
138
src/Create.tsx
@ -3,9 +3,9 @@
|
||||
* @Date : 2024-04-09 10:36:44
|
||||
* @LastEditTime : 2024-04-09 10:36:44
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
import React, { ChangeEvent, KeyboardEvent, useEffect, useRef, useState } from "react";
|
||||
import React, { ChangeEvent, KeyboardEvent, useEffect, useMemo, useRef, useState } from "react";
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
@ -19,21 +19,72 @@ import { ChatItem } from "./ChatItem";
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { uniqueId } from 'lodash';
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { Editor } from "./Editor";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import axios from 'axios';
|
||||
import { Definition, DSL2Json, Json2Preview } from "./loadYml";
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
message: string;
|
||||
isMe: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const SupportedBlock = ['BotMessage', 'Chat', 'EmailSender', 'Script'];
|
||||
const SupportedBlock = ['BotMessage', 'Chat', 'GlipSender', 'Script'];
|
||||
|
||||
export const Create = () => {
|
||||
const [chatList, setChatList] = useState<Message[]>([]);
|
||||
const [workflowName, setWorkFlowName] = useState('My workflow');
|
||||
const location = useLocation();
|
||||
const id = useMemo(() => new URLSearchParams(location.search).get('id'), [location]);
|
||||
const [formJson, setFormJson] = useState(() => {
|
||||
let value = {
|
||||
id: 'abc',
|
||||
description: '',
|
||||
name: 'My workflow',
|
||||
versionId: '',
|
||||
};
|
||||
try {
|
||||
value = JSON.parse(`create-${id}`);
|
||||
} catch (e) {
|
||||
}
|
||||
return value;
|
||||
});
|
||||
const [chatList, setChatList] = useState<Message[]>([{
|
||||
id: uniqueId('r'),
|
||||
message: [
|
||||
'Hello! Please tell me what kind of workflow you want to generate!\n',
|
||||
'Currently the node types I support are: Chat, BotMessage, GlipSender, Script'].join('\n'),
|
||||
isMe: false
|
||||
}]);
|
||||
const [chatInput, setChatInput] = useState('');
|
||||
const [workflowContent, setWorkflowContent] = useState('');
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
const flowDefinition = useMemo(() => DSL2Json(workflowContent), [workflowContent]);
|
||||
const previewImg = useMemo(() => Json2Preview(flowDefinition), [flowDefinition]);
|
||||
const chatContentRef = useRef<HTMLUListElement>(null);
|
||||
const hasEmailSender = useMemo(() => workflowContent.includes('"EmailSender"'), [workflowContent]);
|
||||
const hasBotAddin = useMemo(() => workflowContent.includes('"Chat"'), [workflowContent]);
|
||||
const hasScript = useMemo(() => workflowContent.includes('"Script"'), [workflowContent]);
|
||||
|
||||
const [emailData, setEmailData] = useState({
|
||||
to: '',
|
||||
subject: '',
|
||||
content: `${content}`
|
||||
});
|
||||
useEffect(() => {
|
||||
console.log(workflowContent);
|
||||
console.log(flowDefinition);
|
||||
}, [flowDefinition]);
|
||||
// useEffect(() => {
|
||||
// axios.post('/chat/start', {
|
||||
// accountId: 10086,
|
||||
// channelId: "10000",
|
||||
// dialogId: "1",
|
||||
// segmentId: "1"
|
||||
// });
|
||||
// }, []);
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setChatInput(e.target.value);
|
||||
};
|
||||
@ -41,13 +92,35 @@ export const Create = () => {
|
||||
const handleClickChip = (text: string) => () => {
|
||||
setChatInput(s => s + text);
|
||||
};
|
||||
const handleSend = () => {
|
||||
const handleSend = async () => {
|
||||
const rId = uniqueId('r');
|
||||
|
||||
setChatList([...chatList, {
|
||||
id: uniqueId(),
|
||||
id: uniqueId('m'),
|
||||
message: chatInput,
|
||||
isMe: true
|
||||
}, {
|
||||
id: rId,
|
||||
message: '',
|
||||
isMe: false,
|
||||
loading: true
|
||||
}]);
|
||||
setChatInput('');
|
||||
try {
|
||||
const { data } = await axios.post<{ answer: string; workflowContent: string }>(`/bot/workflow/${id}/messages`, {
|
||||
"question": chatInput
|
||||
});
|
||||
setWorkflowContent(data.workflowContent);
|
||||
setChatList(list => list.map(l => {
|
||||
if (l.id === rId) {
|
||||
l.loading = false;
|
||||
l.message = data.answer;
|
||||
}
|
||||
return l;
|
||||
}));
|
||||
} catch (e) {
|
||||
setChatList(list => list.filter(l => l.id !== rId));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -56,25 +129,54 @@ export const Create = () => {
|
||||
}
|
||||
}, [chatList]);
|
||||
|
||||
const handleRefresh = async () => {
|
||||
await axios.delete(`/bot/workflow/${id}/messages`);
|
||||
setChatList([{
|
||||
id: uniqueId('r'),
|
||||
message: [
|
||||
'Hello! Please tell me what kind of workflow you want to generate!\n',
|
||||
'Currently the node types I support are: Chat, BotMessage, GlipSender, Script'].join('\n'),
|
||||
isMe: false
|
||||
}]);
|
||||
setChatInput('');
|
||||
setWorkflowContent('');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<TextField value={workflowName} focused fullWidth variant="outlined" label="Workflow Name"
|
||||
<TextField value={formJson.name} fullWidth variant="outlined" label="Workflow Name"
|
||||
placeholder="Please name your workflow" onChange={e => {
|
||||
setWorkFlowName(e.target.value);
|
||||
setFormJson(json => ({
|
||||
...json,
|
||||
name: e.target.value
|
||||
}));
|
||||
}}/>
|
||||
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs>
|
||||
<Box sx={{ border: 1, height: 600, borderRadius: 1, mt: 4, borderColor: 'rgba(0,0,0,0.23)' }}>
|
||||
<Box sx={{
|
||||
border: 1,
|
||||
height: 600,
|
||||
borderRadius: 1,
|
||||
mt: 4,
|
||||
borderColor: 'rgba(255,255,255,0.23)',
|
||||
position: 'relative'
|
||||
}}>
|
||||
<Tooltip title="Refresh AI">
|
||||
<IconButton onClick={handleRefresh} style={{ position: 'absolute', right: 10, top: 10, zIndex: 5 }}>
|
||||
<RefreshIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<List ref={chatContentRef}
|
||||
style={{ flex: '1 1 auto', overflowY: 'auto', overflowX: 'hidden' }}
|
||||
sx={{ p: 2 }}>
|
||||
{chatList.map(chat => <ChatItem isMe={chat.isMe} message={chat.message} key={chat.id}/>)}
|
||||
{chatList.map(chat => <ChatItem isMe={chat.isMe} message={chat.message} key={chat.id}
|
||||
loading={chat.loading}/>)}
|
||||
</List>
|
||||
<Box sx={{ display: 'flex', flex: '0 0 auto', p: 2, borderTop: 1, borderColor: 'divider' }}>
|
||||
<Grid container spacing={2}>
|
||||
@ -84,7 +186,7 @@ export const Create = () => {
|
||||
Supported Action Types:
|
||||
</Typography>
|
||||
{SupportedBlock.map(label => (
|
||||
<Chip label={label} key={label} size="small" clickable
|
||||
<Chip label={label} key={label} size="small" clickable color="primary"
|
||||
onClick={handleClickChip(label)}/>))}
|
||||
</Stack>
|
||||
</Grid>
|
||||
@ -96,6 +198,7 @@ export const Create = () => {
|
||||
handleSend();
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
multiline maxRows={3}
|
||||
value={chatInput}/>
|
||||
</Grid>
|
||||
@ -110,14 +213,14 @@ export const Create = () => {
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Box sx={{ border: 1, height: 600, borderRadius: 1, p: 2, mt: 4, borderColor: 'rgba(0,0,0,0.23)' }}>
|
||||
Preview
|
||||
<Box sx={{ border: 1, height: 600, borderRadius: 1, p: 2, mt: 4, borderColor: 'rgba(255,255,255,0.23)' }}>
|
||||
{!previewImg ? 'Workflow Preview...' :
|
||||
<img src={previewImg} style={{ width: '100%', height: '100%' }} alt="preview"/>}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider sx={{ mt: 4, mb: 4 }}/>
|
||||
<Editor/>
|
||||
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Box
|
||||
@ -129,6 +232,7 @@ export const Create = () => {
|
||||
p: 2,
|
||||
boxShadow: 2,
|
||||
borderTop: 1,
|
||||
bgcolor: 'background.default',
|
||||
borderColor: 'divider',
|
||||
display: { xs: 'flex' }, justifyContent: "flex-end"
|
||||
}}>
|
||||
|
@ -3,7 +3,7 @@
|
||||
* @Date : 2024-04-09 09:27:12
|
||||
* @LastEditTime : 2024-04-09 09:27:12
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
import React from "react";
|
||||
import { Outlet } from "react-router-dom";
|
||||
@ -15,6 +15,7 @@ import Paper from '@mui/material/Paper';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import AccountCircle from '@mui/icons-material/AccountCircle';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { RCIcon } from './RCIcon';
|
||||
|
||||
const Layout = styled(Box)(({ theme }) => (
|
||||
{
|
||||
@ -36,7 +37,7 @@ const Content = styled(Box)(({ theme }) => ({
|
||||
export const Home = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<AppBar position="static">
|
||||
<AppBar position="static" enableColorOnDark>
|
||||
<Toolbar>
|
||||
<Typography
|
||||
variant="h6"
|
||||
@ -44,6 +45,7 @@ export const Home = () => {
|
||||
component="div"
|
||||
sx={{ flexGrow: 1, display: { xs: 'block' } }}
|
||||
>
|
||||
<RCIcon style={{ marginRight: 4, verticalAlign: 'middle' }}/>
|
||||
RingCentral AI Workflow Hub
|
||||
</Typography>
|
||||
<Box sx={{ display: { xs: 'block' } }}>
|
||||
|
112
src/List.tsx
112
src/List.tsx
@ -3,68 +3,132 @@
|
||||
* @Date : 2024-04-09 09:38:20
|
||||
* @LastEditTime : 2024-04-09 09:38:20
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Button from '@mui/material/Button';
|
||||
import Box from '@mui/material/Box';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import CardActionArea from '@mui/material/CardActionArea';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardActions from '@mui/material/CardActions';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import { RCIcon } from './RCIcon';
|
||||
import axios from "axios";
|
||||
import Container from '@mui/material/Container';
|
||||
|
||||
|
||||
interface ListItem {
|
||||
id: string;
|
||||
active: boolean;
|
||||
title: string;
|
||||
}
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import TextField from '@mui/material/TextField';
|
||||
|
||||
const Flex = styled(Box)(() => ({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap'
|
||||
}));
|
||||
|
||||
interface Workflow {
|
||||
description: string;
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const List = () => {
|
||||
const [list] = useState<ListItem[]>([{
|
||||
id: 'a',
|
||||
title: 'Workflow 1',
|
||||
active: true
|
||||
}]);
|
||||
const [list, setList] = useState<Workflow[]>([]);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
axios.get<{
|
||||
total: number
|
||||
workflows: Workflow[]
|
||||
}>('/api/public/wf/config/v1/account/10086/workflows').then(({ data }) => {
|
||||
setList(data.workflows);
|
||||
});
|
||||
}, []);
|
||||
const handleOpen = () => {
|
||||
setShowModal(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const formJson = Object.fromEntries((formData as any).entries());
|
||||
axios.post('/bot/workflow', { name: formJson.name }).then(({ data }) => {
|
||||
sessionStorage.setItem(`create-${data.visibleId}`, JSON.stringify({ ...data, name: formJson.name }));
|
||||
handleClose();
|
||||
navigate(`/create?id=${data.visibleId}`);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Container maxWidth="lg">
|
||||
<Flex style={{ justifyContent: 'flex-end' }}>
|
||||
<Button startIcon={<AddIcon/>} variant="contained" href={`/create`}>Create Workflow</Button>
|
||||
<Button startIcon={<AddIcon/>} variant="contained" onClick={handleOpen}>Create Workflow</Button>
|
||||
</Flex>
|
||||
<Flex sx={{ gap: 2 }}>
|
||||
<Flex sx={{ gap: 4, mt: 4 }}>
|
||||
{list.map(item => (
|
||||
<Link
|
||||
key={item.id}
|
||||
to={`/detail/${item.id}`}
|
||||
>
|
||||
<Card sx={{ width: 280, borderRadius: 2, boxShadow: 2 }}>
|
||||
<Card sx={{ width: 263, borderRadius: 2, boxShadow: 2 }}>
|
||||
<CardActionArea>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant="h5" component="div">
|
||||
{item.title}
|
||||
{item.name || 'My Workflow'}
|
||||
</Typography>
|
||||
<Box sx={{ minHeight: 60 }}>
|
||||
<img style={{ width: 20, height: 20 }}
|
||||
src="https://netstorage.ringcentral.com/appext/gallery-page/images/25e9f677-5605-4092-a6c3-69de29a81d8d.svg"
|
||||
alt="rc"/>
|
||||
<RCIcon/>
|
||||
</Box>
|
||||
</CardContent>
|
||||
<CardActions sx={{ display: { xs: 'flex' }, justifyContent: "flex-end" }}>
|
||||
<CardActions
|
||||
sx={{ display: { xs: 'flex' }, justifyContent: "flex-end", borderTop: 1, borderColor: 'divider' }}>
|
||||
<Switch defaultChecked/>
|
||||
</CardActions>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</Link>))}
|
||||
</Flex>
|
||||
</>
|
||||
<Dialog
|
||||
open={showModal}
|
||||
onClose={handleClose}
|
||||
PaperProps={{
|
||||
component: 'form',
|
||||
onSubmit: handleSubmit,
|
||||
}}
|
||||
>
|
||||
<DialogTitle>Create Workflow</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
To create a workflow, please name your workflow first.
|
||||
</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
required
|
||||
margin="dense"
|
||||
id="name"
|
||||
name="name"
|
||||
defaultValue="My workflow"
|
||||
label="Workflow Name"
|
||||
fullWidth
|
||||
focused
|
||||
variant="standard"
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button type="submit">Create</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
16
src/RCIcon.tsx
Normal file
16
src/RCIcon.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* @Author : Eleanor Mao
|
||||
* @Date : 2024-04-11 10:41:51
|
||||
* @LastEditTime : 2024-04-11 10:41:51
|
||||
*
|
||||
* Copyright © RingCentral. All rights reserved.
|
||||
*/
|
||||
import React, { CSSProperties, FC } from "react";
|
||||
|
||||
export const RCIcon: FC<{ size?: number; style?: CSSProperties }> = ({ size = 20, style }) => {
|
||||
return (
|
||||
<img style={{ width: size, height: size, ...style }}
|
||||
src="https://netstorage.ringcentral.com/appext/gallery-page/images/25e9f677-5605-4092-a6c3-69de29a81d8d.svg"
|
||||
alt="rc"/>
|
||||
);
|
||||
};
|
127
src/ajax.ts
Normal file
127
src/ajax.ts
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* @Author : Eleanor Mao
|
||||
* @Date : 2024-04-11 10:14:35
|
||||
* @LastEditTime : 2024-04-11 10:14:35
|
||||
*
|
||||
* Copyright © RingCentral. All rights reserved.
|
||||
*/
|
||||
import axios from 'axios';
|
||||
import type { AxiosHeaders, AxiosError } from 'axios';
|
||||
import qs from 'qs';
|
||||
import { isFunction } from 'lodash';
|
||||
|
||||
type Params = {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
urlParams?: {
|
||||
[key: string]: string
|
||||
};
|
||||
paramsSerializer?: (params: Params) => string;
|
||||
timeoutCallback?: (error: any) => void;
|
||||
cancelCallback?: (error: any) => void;
|
||||
arrayFormat?: 'repeat' | 'brackets' | 'comma' | 'indices';
|
||||
allowDots?: boolean;
|
||||
headers?: AxiosHeaders;
|
||||
}
|
||||
|
||||
type IFunc = (url: string, data?: any, options?: Props) => Promise<any>
|
||||
|
||||
export interface Ajax {
|
||||
post?: IFunc;
|
||||
get?: IFunc;
|
||||
put?: IFunc;
|
||||
delete?: IFunc;
|
||||
patch?: IFunc;
|
||||
}
|
||||
|
||||
type METHODS = ['post', 'get', 'put', 'delete', 'patch']
|
||||
|
||||
const methods: METHODS = ['post', 'get', 'put', 'delete', 'patch'];
|
||||
const ins: Ajax = {};
|
||||
|
||||
|
||||
axios.defaults.headers.post['Content-Type'] = 'application/json';
|
||||
axios.defaults.headers.get.Accept = 'application/json';
|
||||
axios.defaults.timeout = 9500;
|
||||
|
||||
const format = function (
|
||||
url: string,
|
||||
obj: {
|
||||
[key: string]: string
|
||||
}
|
||||
) {
|
||||
let ret = url;
|
||||
for (const key of Object.keys(obj)) {
|
||||
ret = ret.replace('{' + key + '}', obj[key]);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
methods.forEach(method => {
|
||||
ins[method] = (url, data, options = {}) => {
|
||||
data = data || {};
|
||||
|
||||
|
||||
if (options.urlParams) {
|
||||
url = format(url, options.urlParams);
|
||||
}
|
||||
|
||||
if (method === 'get') {
|
||||
data = { params: data };
|
||||
} else {
|
||||
if (
|
||||
!(data instanceof FormData) &&
|
||||
options.headers &&
|
||||
options.headers['Content-Type'] === 'application/x-www-form-urlencoded'
|
||||
) {
|
||||
data = qs.stringify(data, {
|
||||
arrayFormat: options.arrayFormat ? options.arrayFormat : 'repeat',
|
||||
allowDots: options.allowDots
|
||||
});
|
||||
}
|
||||
data = { data };
|
||||
}
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
return axios({
|
||||
url,
|
||||
method,
|
||||
...data,
|
||||
withCredentials: true,
|
||||
timeout: 9500,
|
||||
...options
|
||||
})
|
||||
.then(response => {
|
||||
return response.data;
|
||||
})
|
||||
.then(response => {
|
||||
if (response instanceof Blob) {
|
||||
resolve(response);
|
||||
}
|
||||
if (response.code === 0 || response.code === 200) {
|
||||
resolve(response.data);
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
})
|
||||
.catch((error: AxiosError) => {
|
||||
if (axios.isCancel(error)) {
|
||||
return isFunction(options.cancelCallback) ? options.cancelCallback(error) : undefined;
|
||||
}
|
||||
|
||||
// https://github.com/axios/axios/issues/660
|
||||
// @ts-ignore
|
||||
if (error.code === 'ECONNABORTED' && isFunction(options.timeoutCallback)) {
|
||||
return options.timeoutCallback(error);
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
return promise;
|
||||
};
|
||||
});
|
||||
|
@ -7,30 +7,33 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import {} from '@mui/material/colors';
|
||||
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
mode: 'light',
|
||||
primary: {
|
||||
main: '#3f51b5',
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: {
|
||||
main: '#3f51b5',
|
||||
},
|
||||
tonalOffset: 0.6,
|
||||
secondary: {
|
||||
main: '#f50057',
|
||||
},
|
||||
background: {
|
||||
paper: '#404041',
|
||||
default: '#242425',
|
||||
},
|
||||
},
|
||||
tonalOffset: 0.6,
|
||||
secondary: {
|
||||
main: '#f50057',
|
||||
},
|
||||
},
|
||||
});
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline/>
|
||||
<App/>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
<React.StrictMode>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline/>
|
||||
<App/>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
@ -23,8 +23,59 @@ function compress(s: string, pre: string): string {
|
||||
return "http://www.plantuml.com/plantuml" + pre + encode64_(compressed);
|
||||
}
|
||||
|
||||
export function loadYml(input: string): string {
|
||||
const definition = jsYaml.load(input);
|
||||
let outputValue = transformDSL(definition);
|
||||
return compress(outputValue, '/svg/');
|
||||
export interface EventHandlerAction {
|
||||
actionType: string;
|
||||
targetState?: string;
|
||||
scriptSource?: string;
|
||||
transitionName?:string
|
||||
}
|
||||
|
||||
export interface Action {
|
||||
actionType: string;
|
||||
scriptSource?: string;
|
||||
id?: string;
|
||||
question?: string;
|
||||
score?: null;
|
||||
token?: string;
|
||||
type?: string;
|
||||
content?:string;
|
||||
subject?:string;
|
||||
to?:string
|
||||
}
|
||||
|
||||
export interface EventHandler {
|
||||
eventType: string;
|
||||
condition?: any;
|
||||
actions: EventHandlerAction[];
|
||||
}
|
||||
|
||||
export interface State {
|
||||
name: string;
|
||||
actions: Action[];
|
||||
eventHandlers: EventHandler[];
|
||||
}
|
||||
|
||||
export interface Definition {
|
||||
states: State[];
|
||||
workflowName: string;
|
||||
}
|
||||
|
||||
export function Json2Preview(definition?: Definition): string {
|
||||
if (!definition) return '';
|
||||
try {
|
||||
console.log(transformDSL(definition))
|
||||
return compress(transformDSL(definition), '/svg/');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function DSL2Json(input: string): Definition {
|
||||
return jsYaml.load(input) as Definition;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
window.ToJson = DSL2Json
|
||||
// @ts-ignore
|
||||
window.transform = transformDSL
|
||||
|
@ -1,24 +1,18 @@
|
||||
import { Action, Definition, State } from "./loadYml";
|
||||
|
||||
const EVENT_COLOR = "#LightYellow";
|
||||
const HTTP_COLOR = "#Peru";
|
||||
const SCRIPT_COLOR = "#Olive;text:white";
|
||||
const BOT_COLOR = "#Blue;text:white";
|
||||
const EMAIL_SENDER_COLOR = "#Red;text:white";
|
||||
const DEFAULT_COLOR = '#Grey;text;white';
|
||||
|
||||
const EVENT_NAME = ": Event";
|
||||
const HTTP_NAME = ": Http";
|
||||
const SCRIPT_NAME = ": Script";
|
||||
const BOT_NAME = ": Chat";
|
||||
const EMAIL_SENDER_NAME = ": EmailSender";
|
||||
|
||||
interface Action {
|
||||
actionType: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
name: string;
|
||||
actions: Action[];
|
||||
eventHandlers: any;
|
||||
}
|
||||
const EXIT_NAME = ": Exit";
|
||||
|
||||
function isHttp(actions: Action[]): boolean {
|
||||
return actions
|
||||
@ -35,6 +29,10 @@ function isChat(actions: Action[]): boolean {
|
||||
.some((action) => action.actionType === 'Chat');
|
||||
}
|
||||
|
||||
function isExit(actions: Action[]): boolean {
|
||||
return actions.some((action) => action.actionType === 'Exit');
|
||||
}
|
||||
|
||||
function isEmailSender(actions: Action[]): boolean {
|
||||
return actions
|
||||
.some((action) => action.actionType === "EmailSender");
|
||||
@ -48,7 +46,7 @@ function isNotBlank(str: string): boolean {
|
||||
return str !== undefined && str !== null && str.trim() !== "";
|
||||
}
|
||||
|
||||
function drawState(eventState: Map<any, any>, state: State): string {
|
||||
function drawState(eventState: Map<string, Set<string>>, state: State): string {
|
||||
let sb = "";
|
||||
const name = state.name;
|
||||
const actions = state.actions;
|
||||
@ -57,7 +55,7 @@ function drawState(eventState: Map<any, any>, state: State): string {
|
||||
if (isEmpty(handlers)) {
|
||||
throw Error(`state ${name} handlers must not empty`);
|
||||
}
|
||||
let events = new Set();
|
||||
let events = new Set<string>();
|
||||
for (const handle of handlers) {
|
||||
const stateName = handle.eventType;
|
||||
events.add(stateName);
|
||||
@ -74,20 +72,21 @@ function drawState(eventState: Map<any, any>, state: State): string {
|
||||
} else if (isEmailSender(actions)) {
|
||||
sb += `state ${name} ${EMAIL_SENDER_COLOR} ${EMAIL_SENDER_NAME}\n`;
|
||||
} else {
|
||||
throw Error(`state ${name} actions is incorrect`);
|
||||
console.log('====', name, actions);
|
||||
// throw Error(`state ${name} actions is incorrect`);
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
function append(from:any, to:any, description:any) {
|
||||
function append(from: any, to: any, description: any) {
|
||||
if (isNotBlank(description)) {
|
||||
description = ": " + description;
|
||||
}
|
||||
return `${from} ---> ${to}${description}\n`;
|
||||
return `${from} ---> ${to || ''}${description}\n`;
|
||||
}
|
||||
|
||||
function wrapCond(cond:any) {
|
||||
function wrapCond(cond: any) {
|
||||
if (isNotBlank(cond)) {
|
||||
return "(" + cond + ")";
|
||||
} else {
|
||||
@ -95,7 +94,7 @@ function wrapCond(cond:any) {
|
||||
}
|
||||
}
|
||||
|
||||
function drawTransitionCondition(eventCondition:any, transition:any) {
|
||||
function drawTransitionCondition(eventCondition: any, transition: any) {
|
||||
const list = [];
|
||||
if (eventCondition !== undefined && eventCondition !== null) {
|
||||
list.push(eventCondition);
|
||||
@ -111,7 +110,7 @@ function drawTransitionCondition(eventCondition:any, transition:any) {
|
||||
return list.join(" && ");
|
||||
}
|
||||
|
||||
function drawExitCondition(eventCondition:any, exit:any) {
|
||||
function drawExitCondition(eventCondition: any, exit: any) {
|
||||
const list = [];
|
||||
if (eventCondition !== undefined && eventCondition !== null) {
|
||||
list.push(eventCondition);
|
||||
@ -122,7 +121,7 @@ function drawExitCondition(eventCondition:any, exit:any) {
|
||||
return list.join(" && ");
|
||||
}
|
||||
|
||||
function drawStateLine(eventState:any, state:any) {
|
||||
function drawStateLine(eventState: Map<string, Set<string>>, state: State) {
|
||||
const handlers = state.eventHandlers;
|
||||
if (isEmpty(handlers)) {
|
||||
return;
|
||||
@ -141,13 +140,13 @@ function drawStateLine(eventState:any, state:any) {
|
||||
const actions = handler.actions;
|
||||
for (const action of actions) {
|
||||
const actionType = action.actionType;
|
||||
let targetState = action.targetState;
|
||||
let targetState = action.targetState || '';
|
||||
if (actionType === "Transition") {
|
||||
if (eventState.has(targetState)) {
|
||||
let events = eventState.get(targetState);
|
||||
if (events.size > 1) {
|
||||
if (events && events.size > 1) {
|
||||
continue;
|
||||
} else {
|
||||
} else if (events) {
|
||||
targetState = events.values().next().value;
|
||||
}
|
||||
}
|
||||
@ -162,15 +161,15 @@ function drawStateLine(eventState:any, state:any) {
|
||||
return sb;
|
||||
}
|
||||
|
||||
export function transformDSL(definition: any): string {
|
||||
export function transformDSL(definition: Definition): string {
|
||||
let sb = "";
|
||||
const eventState = new Map();
|
||||
const states = definition["states"];
|
||||
const eventState = new Map<string, Set<string>>();
|
||||
const states = definition["states"] || [];
|
||||
for (const state of states) {
|
||||
sb += drawState(eventState, state);
|
||||
sb += drawState(eventState, state) || '';
|
||||
}
|
||||
for (const state of states) {
|
||||
sb += drawStateLine(eventState, state);
|
||||
sb += drawStateLine(eventState, state) || '';
|
||||
}
|
||||
return `@startuml
|
||||
|
||||
|
34
yarn.lock
34
yarn.lock
@ -2407,7 +2407,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.8.tgz#95f6c6a08f2ad868ba230ead1d2d7f7be3db3837"
|
||||
integrity sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==
|
||||
|
||||
"@types/qs@*":
|
||||
"@types/qs@*", "@types/qs@^6.9.14":
|
||||
version "6.9.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.14.tgz#169e142bfe493895287bee382af6039795e9b75b"
|
||||
integrity sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==
|
||||
@ -3123,6 +3123,15 @@ axe-core@=4.7.0:
|
||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf"
|
||||
integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==
|
||||
|
||||
axios@^1.6.8:
|
||||
version "1.6.8"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66"
|
||||
integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.6"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
axobject-query@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a"
|
||||
@ -5062,7 +5071,7 @@ flatted@^3.2.9:
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
|
||||
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.15.6:
|
||||
version "1.15.6"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
|
||||
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
|
||||
@ -5110,6 +5119,15 @@ form-data@^3.0.0:
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
forwarded@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||
@ -8161,6 +8179,11 @@ proxy-addr@~2.0.7:
|
||||
forwarded "0.2.0"
|
||||
ipaddr.js "1.9.1"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
psl@^1.1.33:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
|
||||
@ -8183,6 +8206,13 @@ qs@6.11.0:
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
qs@^6.12.0:
|
||||
version "6.12.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.0.tgz#edd40c3b823995946a8a0b1f208669c7a200db77"
|
||||
integrity sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==
|
||||
dependencies:
|
||||
side-channel "^1.0.6"
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
|
Loading…
Reference in New Issue
Block a user