Compare commits
7 Commits
8bd972c992
...
main
Author | SHA1 | Date | |
---|---|---|---|
38d011692b | |||
6511dc8941 | |||
20481ca406 | |||
89eea2d482 | |||
6a0362e18f | |||
cd269b82b3 | |||
683f45d187 |
@ -18,6 +18,7 @@
|
|||||||
"@types/node": "^16.18.95",
|
"@types/node": "^16.18.95",
|
||||||
"@types/react": "^18.2.74",
|
"@types/react": "^18.2.74",
|
||||||
"@types/react-dom": "^18.2.24",
|
"@types/react-dom": "^18.2.24",
|
||||||
|
"axios": "^1.6.8",
|
||||||
"babel-jest": "^27.4.2",
|
"babel-jest": "^27.4.2",
|
||||||
"babel-loader": "^8.2.3",
|
"babel-loader": "^8.2.3",
|
||||||
"babel-plugin-named-asset-import": "^0.3.8",
|
"babel-plugin-named-asset-import": "^0.3.8",
|
||||||
@ -40,17 +41,20 @@
|
|||||||
"jest": "^27.4.3",
|
"jest": "^27.4.3",
|
||||||
"jest-resolve": "^27.4.2",
|
"jest-resolve": "^27.4.2",
|
||||||
"jest-watch-typeahead": "^1.0.0",
|
"jest-watch-typeahead": "^1.0.0",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"match-sorter": "^6.3.4",
|
"match-sorter": "^6.3.4",
|
||||||
"mini-css-extract-plugin": "^2.4.5",
|
"mini-css-extract-plugin": "^2.4.5",
|
||||||
"monaco-editor": "^0.47.0",
|
"monaco-editor": "^0.47.0",
|
||||||
|
"mui-message": "^1.0.3",
|
||||||
"postcss": "^8.4.4",
|
"postcss": "^8.4.4",
|
||||||
"postcss-flexbugs-fixes": "^5.0.2",
|
"postcss-flexbugs-fixes": "^5.0.2",
|
||||||
"postcss-loader": "^6.2.1",
|
"postcss-loader": "^6.2.1",
|
||||||
"postcss-normalize": "^10.0.1",
|
"postcss-normalize": "^10.0.1",
|
||||||
"postcss-preset-env": "^7.0.1",
|
"postcss-preset-env": "^7.0.1",
|
||||||
"prompts": "^2.4.2",
|
"prompts": "^2.4.2",
|
||||||
|
"qs": "^6.12.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-app-polyfill": "^3.0.0",
|
"react-app-polyfill": "^3.0.0",
|
||||||
"react-dev-utils": "^12.0.1",
|
"react-dev-utils": "^12.0.1",
|
||||||
@ -99,7 +103,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/lodash": "^4.17.0",
|
"@types/lodash": "^4.17.0",
|
||||||
|
"@types/qs": "^6.9.14",
|
||||||
"monaco-editor-webpack-plugin": "^7.1.0"
|
"monaco-editor-webpack-plugin": "^7.1.0"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
@ -157,5 +163,6 @@
|
|||||||
"presets": [
|
"presets": [
|
||||||
"react-app"
|
"react-app"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"proxy": "http://rcw-mock-lab02.lab.nordigy.ru:8080"
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 1.6 KiB |
@ -7,7 +7,6 @@
|
|||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<!--
|
<!--
|
||||||
@ -24,7 +23,8 @@
|
|||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>React App</title>
|
<script src="https://cdn-0.plantuml.com/synchro2.min.js"></script>
|
||||||
|
<title>RingCentral@Workflow Hub</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
31
src/AIBot.tsx
Normal file
31
src/AIBot.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React, { ChangeEvent, FC } from 'react';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
|
||||||
|
|
||||||
|
export const AIBot: FC<{
|
||||||
|
name?: string
|
||||||
|
value: string
|
||||||
|
onChange: (value: string) => void
|
||||||
|
}>
|
||||||
|
= ({ value, name, onChange }) => {
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
onChange(e.target.value);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
|
<Typography variant="subtitle1">{name ? `${name}` : 'AI Robot'}</Typography>
|
||||||
|
<Typography variant="caption" component="div" sx={{ mb: 1 }}>
|
||||||
|
You can maintain your knowledge in <a style={{ color: '#f50057' }} target="_blank" rel="noreferrer"
|
||||||
|
href="https://arf-xmn.int.rclabenv.com/admin/source">Artificial
|
||||||
|
Intelligence Admin Portal</a>
|
||||||
|
</Typography>
|
||||||
|
<Box>
|
||||||
|
<TextField label="AI Robot ID" fullWidth onChange={handleChange} value={value}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
20
src/App.tsx
20
src/App.tsx
@ -13,11 +13,23 @@ const router = createBrowserRouter([
|
|||||||
element: <Home/>,
|
element: <Home/>,
|
||||||
children: [{
|
children: [{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: <List/>
|
element: <List develop/>,
|
||||||
}, {
|
}, {
|
||||||
path: '/create',
|
path: '/create/:id',
|
||||||
element: <Create />
|
element: <Create develop/>
|
||||||
}]
|
}, {
|
||||||
|
path: '/detail/:id',
|
||||||
|
element: <Create develop edit/>
|
||||||
|
}, {
|
||||||
|
path: '/demo',
|
||||||
|
element: <List/>,
|
||||||
|
}, {
|
||||||
|
path: '/demo/create/:id',
|
||||||
|
element: <Create/>
|
||||||
|
}, {
|
||||||
|
path: '/demo/detail/:id',
|
||||||
|
element: <Create edit/>
|
||||||
|
},]
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
import React from 'react'
|
|
@ -11,10 +11,12 @@ import ListItem from '@mui/material/ListItem';
|
|||||||
import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
|
import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import FaceIcon from '@mui/icons-material/Face';
|
import FaceIcon from '@mui/icons-material/Face';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isMe: boolean;
|
isMe: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -38,13 +40,15 @@ const ChatMessageWrap = styled(Box)<{ isMe: boolean }>(({ isMe }) => ({
|
|||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: isMe ? 'flex-end' : 'flex-start'
|
justifyContent: isMe ? 'flex-end' : 'flex-start',
|
||||||
|
color: isMe ? 'rgba(0, 0, 0, 0.87)' : 'white'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const ChatMessage = styled(Box)(() => ({
|
const ChatMessage = styled(Box)(() => ({
|
||||||
maxWidth: '80%',
|
maxWidth: 'calc(100% - 50px)',
|
||||||
|
whiteSpace: 'pre-line'
|
||||||
}));
|
}));
|
||||||
export const ChatItem: FC<Props> = ({ isMe, message }) => {
|
export const ChatItem: FC<Props> = ({ isMe, message, loading }) => {
|
||||||
return (
|
return (
|
||||||
<ChatWrap isMe={isMe} alignItems="flex-start">
|
<ChatWrap isMe={isMe} alignItems="flex-start">
|
||||||
<ChatAvatar>
|
<ChatAvatar>
|
||||||
@ -53,7 +57,8 @@ export const ChatItem: FC<Props> = ({ isMe, message }) => {
|
|||||||
</ChatAvatar>
|
</ChatAvatar>
|
||||||
<ChatMessageWrap isMe={isMe}>
|
<ChatMessageWrap isMe={isMe}>
|
||||||
<ChatMessage sx={{ borderRadius: 2, p: 2, bgcolor: isMe ? 'primary.light' : 'secondary.light' }}>
|
<ChatMessage sx={{ borderRadius: 2, p: 2, bgcolor: isMe ? 'primary.light' : 'secondary.light' }}>
|
||||||
{message}
|
{loading && <CircularProgress color="secondary" size={20}/>}
|
||||||
|
{!loading && message}
|
||||||
</ChatMessage>
|
</ChatMessage>
|
||||||
</ChatMessageWrap>
|
</ChatMessageWrap>
|
||||||
</ChatWrap>
|
</ChatWrap>
|
||||||
|
424
src/Create.tsx
424
src/Create.tsx
@ -5,97 +5,404 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import React, { ChangeEvent, KeyboardEvent, useEffect, useRef, useState } from "react";
|
import React, { ChangeEvent, FC, KeyboardEvent, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
|
||||||
import SendIcon from '@mui/icons-material/Send';
|
import SendIcon from '@mui/icons-material/Send';
|
||||||
import List from '@mui/material/List';
|
import List from '@mui/material/List';
|
||||||
import Chip from '@mui/material/Chip';
|
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
import { ChatItem } from "./ChatItem";
|
import { ChatItem } from "./ChatItem";
|
||||||
import Stack from '@mui/material/Stack';
|
import { cloneDeep, uniqueId } from 'lodash';
|
||||||
import { uniqueId } from 'lodash';
|
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import { Editor } from "./Editor";
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
|
import axios from 'axios';
|
||||||
|
import { Action, Definition, DSL2Json, Json2Preview, Json2Yml } from "./loadYml";
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
|
import { EmailSender, EmailValue } from "./EmailSender";
|
||||||
|
import { AIBot } from "./AIBot";
|
||||||
|
import { GlipSender } from "./GlipSender";
|
||||||
|
import { ScriptBlock } from "./Script";
|
||||||
|
import { message } from 'mui-message';
|
||||||
|
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
|
||||||
|
import InputAdornment from "@mui/material/InputAdornment";
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Switch from '@mui/material/Switch';
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
|
const DefaultMessage = [
|
||||||
|
'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');
|
||||||
|
|
||||||
|
const DefaultMessageZh = [
|
||||||
|
'你好(*´▽`)ノノ\n 快告诉我你想生成什么样的工作流吧 ❗',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const DefaultEditMessageZh = [
|
||||||
|
'又见面啦(*╹▽╹*)\n 你已经生成过工作流啦,如果有需要修改的请告诉我吧 ❗',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
function getDefaultMessage(edit?: boolean): string {
|
||||||
|
if (edit) {
|
||||||
|
return DefaultEditMessageZh;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultMessageZh;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreValue = string | undefined | null
|
||||||
|
const preValueObj: { bot: PreValue; script: PreValue; email: PreValue; glip: PreValue } = {
|
||||||
|
bot: '',
|
||||||
|
script: '',
|
||||||
|
email: '{"to":"","subject":"","content":"${localData.Desc}"}',
|
||||||
|
glip: ''
|
||||||
|
};
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
id: string;
|
id: string;
|
||||||
message: string;
|
message: string;
|
||||||
isMe: boolean;
|
isMe: boolean;
|
||||||
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SupportedBlock = ['BotMessage', 'Chat', 'EmailSender', 'Script'];
|
interface ActionWithStateName extends Action {
|
||||||
|
stateName: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const Create = () => {
|
const AntSwitch = styled(Switch)(({ theme }) => ({
|
||||||
const [chatList, setChatList] = useState<Message[]>([]);
|
width: 28,
|
||||||
const [workflowName, setWorkFlowName] = useState('My workflow');
|
height: 16,
|
||||||
|
padding: 0,
|
||||||
|
display: 'flex',
|
||||||
|
'&:active': {
|
||||||
|
'& .MuiSwitch-thumb': {
|
||||||
|
width: 15,
|
||||||
|
},
|
||||||
|
'& .MuiSwitch-switchBase.Mui-checked': {
|
||||||
|
transform: 'translateX(9px)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .MuiSwitch-switchBase': {
|
||||||
|
padding: 2,
|
||||||
|
'&.Mui-checked': {
|
||||||
|
transform: 'translateX(12px)',
|
||||||
|
color: '#fff',
|
||||||
|
'& + .MuiSwitch-track': {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .MuiSwitch-thumb': {
|
||||||
|
boxShadow: '0 2px 4px 0 rgb(0 35 11 / 20%)',
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
borderRadius: 6,
|
||||||
|
transition: theme.transitions.create(['width'], {
|
||||||
|
duration: 200,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
'& .MuiSwitch-track': {
|
||||||
|
borderRadius: 16 / 2,
|
||||||
|
opacity: 1,
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const Create: FC<{ edit?: boolean; develop?: boolean }> = ({ edit, develop }) => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const [formJson, setFormJson] = useState({
|
||||||
|
id: 'abc',
|
||||||
|
description: '',
|
||||||
|
name: 'My workflow',
|
||||||
|
versionId: '',
|
||||||
|
versionVisibleId: ''
|
||||||
|
});
|
||||||
|
const [chatList, setChatList] = useState<Message[]>([{
|
||||||
|
id: uniqueId('r'),
|
||||||
|
message: getDefaultMessage(edit),
|
||||||
|
isMe: false
|
||||||
|
}]);
|
||||||
const [chatInput, setChatInput] = useState('');
|
const [chatInput, setChatInput] = useState('');
|
||||||
|
const [workflowContent, setWorkflowContent] = useState('');
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
|
const flowDefinition = useMemo<Definition | undefined>(() => DSL2Json(workflowContent), [workflowContent]);
|
||||||
|
const previewImg = useMemo<string>(() => Json2Preview(flowDefinition), [flowDefinition]);
|
||||||
const chatContentRef = useRef<HTMLUListElement>(null);
|
const chatContentRef = useRef<HTMLUListElement>(null);
|
||||||
|
const actions = useMemo<ActionWithStateName[]>(() => ((flowDefinition || {}).states || []).reduce<Array<Action & {
|
||||||
|
stateName: string
|
||||||
|
}>>((l, s) => {
|
||||||
|
l.push(...s.actions.map(a => ({ ...a, stateName: s.name })));
|
||||||
|
return l;
|
||||||
|
}, []), [flowDefinition]);
|
||||||
|
const emailSenderAction = useMemo<ActionWithStateName | undefined>(() => actions.find(a => a.actionType === "EmailSender"), [actions]);
|
||||||
|
const botAddinAction = useMemo<ActionWithStateName | undefined>(() => actions.find(a => a.actionType === "Chat"), [actions]);
|
||||||
|
const scriptAction = useMemo<ActionWithStateName | undefined>(() => actions.find(a => a.actionType === "Script"), [actions]);
|
||||||
|
const glipSenderAction = useMemo<ActionWithStateName | undefined>(() => actions.find(a => a.actionType === "GlipSender"), [actions]);
|
||||||
|
const [aibot, setAIBot] = useState('');
|
||||||
|
const [script, setScript] = useState('');
|
||||||
|
const [emailData, setEmailData] = useState<EmailValue>({
|
||||||
|
to: '',
|
||||||
|
subject: '',
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
|
content: '${localData.Desc}',
|
||||||
|
});
|
||||||
|
const [glipSenderData, setGlipSenderData] = useState('');
|
||||||
|
const [isImageMode, setImageMode] = useState(true);
|
||||||
|
const creatorId = '4034902020';
|
||||||
|
const location = useLocation();
|
||||||
|
const [teamLink, setTeamLink] = useState('');
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(workflowContent);
|
||||||
|
console.log(flowDefinition);
|
||||||
|
}, [flowDefinition, workflowContent]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (id) {
|
||||||
|
axios.get<{
|
||||||
|
id: string
|
||||||
|
visibleId: string
|
||||||
|
versionId: string
|
||||||
|
versionVisibleId: string
|
||||||
|
content: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
}>(`/bot/workflow/${id}`).then(({ data }) => {
|
||||||
|
setFormJson(data);
|
||||||
|
setWorkflowContent(data.content || '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setChatInput(e.target.value);
|
setChatInput(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickChip = (text: string) => () => {
|
const handleSend = async () => {
|
||||||
setChatInput(s => s + text);
|
const rId = uniqueId('r');
|
||||||
};
|
|
||||||
const handleSend = () => {
|
|
||||||
setChatList([...chatList, {
|
setChatList([...chatList, {
|
||||||
id: uniqueId(),
|
id: uniqueId('m'),
|
||||||
message: chatInput,
|
message: chatInput,
|
||||||
isMe: true
|
isMe: true
|
||||||
|
}, {
|
||||||
|
id: rId,
|
||||||
|
message: '',
|
||||||
|
isMe: false,
|
||||||
|
loading: true
|
||||||
}]);
|
}]);
|
||||||
setChatInput('');
|
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(() => {
|
||||||
|
if (glipSenderAction) {
|
||||||
|
let actionValueString = glipSenderAction.message;
|
||||||
|
if (actionValueString !== preValueObj.glip && glipSenderData === preValueObj.glip) {
|
||||||
|
setGlipSenderData(actionValueString || '');
|
||||||
|
preValueObj.glip = actionValueString || '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (preValueObj.glip) {
|
||||||
|
preValueObj.glip = '';
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
|
setGlipSenderData('${localData.Desc}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [glipSenderAction]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (scriptAction) {
|
||||||
|
if (scriptAction.scriptSource !== preValueObj.script && script === preValueObj.script) {
|
||||||
|
setScript(scriptAction.scriptSource || '');
|
||||||
|
preValueObj.script = scriptAction.scriptSource || '';
|
||||||
|
}
|
||||||
|
} else if (preValueObj.script) {
|
||||||
|
preValueObj.script = '';
|
||||||
|
setScript('');
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [scriptAction]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (botAddinAction) {
|
||||||
|
if (botAddinAction.id !== preValueObj.bot && aibot === preValueObj.bot) {
|
||||||
|
setAIBot(botAddinAction.id || '');
|
||||||
|
preValueObj.bot = botAddinAction.id || '';
|
||||||
|
}
|
||||||
|
} else if (preValueObj.bot) {
|
||||||
|
preValueObj.bot = '';
|
||||||
|
setAIBot('');
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [botAddinAction]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (emailSenderAction) {
|
||||||
|
const newValueStr = JSON.stringify({
|
||||||
|
to: emailSenderAction.to || '',
|
||||||
|
subject: emailSenderAction.subject || '',
|
||||||
|
content: emailSenderAction.content || ''
|
||||||
|
});
|
||||||
|
const crtValueStr = JSON.stringify({
|
||||||
|
to: emailData.to,
|
||||||
|
subject: emailData.subject,
|
||||||
|
content: emailData.content
|
||||||
|
});
|
||||||
|
if (newValueStr !== preValueObj.email && crtValueStr === preValueObj.email) {
|
||||||
|
setEmailData({
|
||||||
|
to: emailSenderAction.to || '',
|
||||||
|
subject: emailSenderAction.subject || '',
|
||||||
|
content: emailSenderAction.content || ''
|
||||||
|
});
|
||||||
|
preValueObj.email = newValueStr || '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setEmailData({
|
||||||
|
to: '',
|
||||||
|
subject: '',
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
|
content: '${localData.Desc}',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [emailSenderAction]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chatContentRef.current) {
|
if (chatContentRef.current) {
|
||||||
chatContentRef.current.scrollTop = chatContentRef.current.scrollHeight - chatContentRef.current.offsetHeight;
|
chatContentRef.current.scrollTop = chatContentRef.current.scrollHeight - chatContentRef.current.offsetHeight;
|
||||||
}
|
}
|
||||||
}, [chatList]);
|
}, [chatList]);
|
||||||
|
|
||||||
|
const handleRefresh = async () => {
|
||||||
|
await axios.delete(`/bot/workflow/${id}/messages`);
|
||||||
|
setChatList([{
|
||||||
|
id: uniqueId('r'),
|
||||||
|
message: getDefaultMessage(edit),
|
||||||
|
isMe: false
|
||||||
|
}]);
|
||||||
|
setChatInput('');
|
||||||
|
setWorkflowContent('');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!flowDefinition) return;
|
||||||
|
const newJson = cloneDeep(flowDefinition);
|
||||||
|
newJson.states.forEach(state => {
|
||||||
|
state.actions.forEach(action => {
|
||||||
|
if (action.actionType === 'Chat') {
|
||||||
|
action.id = aibot;
|
||||||
|
}
|
||||||
|
if (action.actionType === 'Script') {
|
||||||
|
action.scriptSource = script;
|
||||||
|
}
|
||||||
|
if (action.actionType === 'EmailSender') {
|
||||||
|
action.to = emailData.to;
|
||||||
|
action.subject = emailData.subject;
|
||||||
|
action.content = emailData.content;
|
||||||
|
}
|
||||||
|
if (action.actionType === 'GlipSender') {
|
||||||
|
// action.url = glipSenderData.url;
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
|
action.message = glipSenderData || '${localData.Desc}';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log(Json2Yml(newJson));
|
||||||
|
const channelId = (teamLink.replace(/[^\d]+/, '')) + '-' + creatorId;
|
||||||
|
console.log(channelId);
|
||||||
|
await axios.put(`/bot/workflow/${id}`, {
|
||||||
|
name: formJson.name,
|
||||||
|
content: Json2Yml(newJson)
|
||||||
|
});
|
||||||
|
if (!develop) {
|
||||||
|
await axios.delete(`/bot/workflow/${id}/bind/${channelId}`);
|
||||||
|
await axios.post(`/bot/workflow/${id}/bind`, { channelId });
|
||||||
|
}
|
||||||
|
message.success(`Publish Success! ${develop ? 'Back to List and go bind your channel!' : ''}`);
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate(`/${develop ? '' : 'demo'}`);
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<TextField value={workflowName} focused fullWidth variant="outlined" label="Workflow Name"
|
<TextField value={formJson.name} fullWidth variant="outlined" label="Workflow Name" InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
placeholder="Please name your workflow" onChange={e => {
|
placeholder="Please name your workflow" onChange={e => {
|
||||||
setWorkFlowName(e.target.value);
|
setFormJson(json => ({
|
||||||
|
...json,
|
||||||
|
name: e.target.value
|
||||||
|
}));
|
||||||
}}/>
|
}}/>
|
||||||
|
{/*<Divider sx={{ mt: 4, mb: -2 }}/>*/}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs>
|
<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="Clear AI 🧠">
|
||||||
|
<IconButton onClick={handleRefresh} style={{ position: 'absolute', right: 10, top: 10, zIndex: 5 }}>
|
||||||
|
<RefreshIcon/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||||
<List ref={chatContentRef}
|
<List ref={chatContentRef}
|
||||||
style={{ flex: '1 1 auto', overflowY: 'auto', overflowX: 'hidden' }}
|
style={{ flex: '1 1 auto', overflowY: 'auto', overflowX: 'hidden' }}
|
||||||
sx={{ p: 2 }}>
|
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>
|
</List>
|
||||||
<Box sx={{ display: 'flex', flex: '0 0 auto', p: 2, borderTop: 1, borderColor: 'divider' }}>
|
<Box sx={{ display: 'flex', flex: '0 0 auto', p: 2, borderTop: 1, borderColor: 'divider' }}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12} style={{ gap: 8 }}>
|
{/*<Grid item xs={12} style={{ gap: 8 }}>*/}
|
||||||
<Stack direction="row" spacing={1}>
|
{/* <Stack direction="row" spacing={1}>*/}
|
||||||
<Typography variant="body2">
|
{/* <Typography variant="body2">*/}
|
||||||
Supported Action Types:
|
{/* Supported Action Types:*/}
|
||||||
</Typography>
|
{/* </Typography>*/}
|
||||||
{SupportedBlock.map(label => (
|
{/* {SupportedBlock.map(label => (*/}
|
||||||
<Chip label={label} key={label} size="small" clickable
|
{/* <Chip label={label} key={label} size="small" clickable color="primary"*/}
|
||||||
onClick={handleClickChip(label)}/>))}
|
{/* onClick={handleClickChip(label)}/>))}*/}
|
||||||
</Stack>
|
{/* </Stack>*/}
|
||||||
</Grid>
|
{/*</Grid>*/}
|
||||||
<Grid item xs>
|
<Grid item xs>
|
||||||
<OutlinedInput fullWidth size="small" onChange={handleChange}
|
<TextField fullWidth size="small" onChange={handleChange}
|
||||||
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
|
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (e.key === 'Enter' && chatInput.length) {
|
if (e.key === 'Enter' && chatInput.length) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSend();
|
handleSend();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
autoFocus
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: <InputAdornment position="start"><AutoAwesomeIcon color="primary"/></InputAdornment>
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
focused placeholder="Enter your goal ..."
|
||||||
multiline maxRows={3}
|
multiline maxRows={3}
|
||||||
value={chatInput}/>
|
value={chatInput}/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -110,16 +417,59 @@ export const Create = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={4}>
|
<Grid item xs={4}>
|
||||||
<Box sx={{ border: 1, height: 600, borderRadius: 1, p: 2, mt: 4, borderColor: 'rgba(0,0,0,0.23)' }}>
|
<Box sx={{
|
||||||
Preview
|
border: 1,
|
||||||
|
height: 600,
|
||||||
|
borderRadius: 1,
|
||||||
|
mt: 4,
|
||||||
|
borderColor: 'rgba(255,255,255,0.23)',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}>
|
||||||
|
{!previewImg ?
|
||||||
|
<div style={{ padding: 16 }}>👋 The flow chart will be previewed here! <br/>😻 Tell the AI robot what
|
||||||
|
you want to
|
||||||
|
do! </div> :
|
||||||
|
(
|
||||||
|
<>
|
||||||
|
<Stack sx={{ p: 2 }} direction="row" spacing={1} alignItems="center" justifyContent="flex-end">
|
||||||
|
<Typography variant="body2">Raw</Typography>
|
||||||
|
<AntSwitch checked={isImageMode} onChange={(_, checked) => setImageMode(checked)}/>
|
||||||
|
<Typography variant="body2">Image</Typography>
|
||||||
|
</Stack>
|
||||||
|
{isImageMode ?
|
||||||
|
<img src={previewImg} style={{ maxWidth: '100%', maxHeight: '100%' }} alt="preview"/> :
|
||||||
|
<pre style={{ height: 546, overflow: 'auto', marginTop: 0 }}><code style={{
|
||||||
|
fontSize: 12,
|
||||||
|
textAlign: 'left',
|
||||||
|
display: 'block',
|
||||||
|
padding: 16,
|
||||||
|
}}>{workflowContent}</code></pre>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider sx={{ mt: 4, mb: 4 }}/>
|
<Divider sx={{ mt: 4, mb: 4 }}/>
|
||||||
<Editor/>
|
{!!scriptAction || !!emailSenderAction || !!botAddinAction || !!glipSenderAction ?
|
||||||
|
<Typography variant="subtitle2" sx={{ mb: 2 }}>
|
||||||
|
Please fill in the following information related to your workflow nodes. 👇🏻👇🏻 </Typography> : null}
|
||||||
|
{!!emailSenderAction && <EmailSender value={emailData} onChange={setEmailData}/>}
|
||||||
|
{!!botAddinAction && <AIBot name={botAddinAction.stateName} value={aibot} onChange={setAIBot}/>}
|
||||||
|
{!!scriptAction && <ScriptBlock name={scriptAction.stateName} value={script} onChange={setScript}/>}
|
||||||
|
{!!glipSenderAction &&
|
||||||
|
<GlipSender develop={!!develop} teamLink={teamLink} id={id} edit={edit} value={glipSenderData}
|
||||||
|
onChange={(value, isTeamLink) => {
|
||||||
|
if (isTeamLink) {
|
||||||
|
setTeamLink(value);
|
||||||
|
} else {
|
||||||
|
setGlipSenderData(value);
|
||||||
|
}
|
||||||
|
}}/>}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<div style={{ height: 80 }}></div>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
@ -129,10 +479,12 @@ export const Create = () => {
|
|||||||
p: 2,
|
p: 2,
|
||||||
boxShadow: 2,
|
boxShadow: 2,
|
||||||
borderTop: 1,
|
borderTop: 1,
|
||||||
|
zIndex: 10,
|
||||||
|
bgcolor: 'background.default',
|
||||||
borderColor: 'divider',
|
borderColor: 'divider',
|
||||||
display: { xs: 'flex' }, justifyContent: "flex-end"
|
display: { xs: 'flex' }, justifyContent: "flex-end"
|
||||||
}}>
|
}}>
|
||||||
<Button variant="contained" style={{ width: 100 }}>Save</Button>
|
<Button variant="contained" style={{ width: 100 }} onClick={handleSubmit}>Publish</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -6,25 +6,23 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import MonacoEditor from 'react-monaco-editor';
|
import MonacoEditor from 'react-monaco-editor';
|
||||||
|
|
||||||
export const Editor: React.FC = () => {
|
export const Editor: React.FC<{ value: string; onChange: (value: string) => void }> = ({ value, onChange }) => {
|
||||||
const [code, setCode] = useState(['function x() {', '\tconsole.log("Hello world!");', '}'].join('\n'))
|
// const [code, setCode] = useState(['function x() {', '\tconsole.log("Hello world!");', '}'].join('\n'))
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MonacoEditor
|
<MonacoEditor
|
||||||
width="100%"
|
width="100%"
|
||||||
height="300"
|
height="300"
|
||||||
language="javascript"
|
language="javascript"
|
||||||
theme="vs-dark"
|
theme="vs-dark"
|
||||||
value={code}
|
value={value}
|
||||||
options={{
|
options={{
|
||||||
selectOnLineNumbers: true,
|
selectOnLineNumbers: true,
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
}}
|
}}
|
||||||
onChange={setCode}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -1 +1,41 @@
|
|||||||
import React from 'react'
|
import React, { ChangeEvent, FC } from 'react';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
|
||||||
|
export interface EmailValue {
|
||||||
|
to: string;
|
||||||
|
content: string;
|
||||||
|
subject: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmailSender: FC<{
|
||||||
|
value: EmailValue; onChange: (value: EmailValue) => void
|
||||||
|
}> = ({ value, onChange }) => {
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
[e.target.name]: e.target.value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Paper sx={{p: 2, mb:2}}>
|
||||||
|
<Typography variant="subtitle1" sx={{mb: 1}}>Email Sender</Typography>
|
||||||
|
<Box>
|
||||||
|
<TextField label="Send To" placeholder="Please enter your email address" type="email"
|
||||||
|
value={value.to} name="to" fullWidth sx={{mb: 4}}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<TextField label="Subject" placeholder="Please enter your email subject"
|
||||||
|
value={value.subject} name="subject" fullWidth sx={{mb: 4}}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<TextField label="Content" placeholder="Please enter your email content"
|
||||||
|
value={value.content} name="content" fullWidth sx={{mb: 4}}
|
||||||
|
onChange={handleChange} maxRows={8} minRows={3} multiline
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
97
src/GlipSender.tsx
Normal file
97
src/GlipSender.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import React, { ChangeEvent, FC, useState } from 'react';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
import InputAdornment from "@mui/material/InputAdornment";
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import Visibility from '@mui/icons-material/Visibility';
|
||||||
|
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||||
|
import { MenuItem, Select, SelectChangeEvent } from "@mui/material";
|
||||||
|
|
||||||
|
export const GlipSender: FC<{
|
||||||
|
value: string;
|
||||||
|
develop?: boolean
|
||||||
|
edit?: boolean
|
||||||
|
id?: string
|
||||||
|
teamLink: string
|
||||||
|
onChange: (value: string, isTeamLink?: boolean) => void
|
||||||
|
}> = ({ value, id,teamLink, develop, edit, onChange }) => {
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
onChange(e.target.value);
|
||||||
|
};
|
||||||
|
const [showPassword, setShowPassword] = React.useState(edit ? false : true);
|
||||||
|
|
||||||
|
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||||
|
|
||||||
|
const handleMouseDownPassword = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const [senderType, setSenderType] = useState('default');
|
||||||
|
const handleChangeType = (e: SelectChangeEvent) => {
|
||||||
|
setSenderType(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeTeam = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
onChange(event.target.value, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
|
<Typography variant="subtitle1" sx={{ mb: 1 }}>GlipSender</Typography>
|
||||||
|
<Box>
|
||||||
|
{!develop &&
|
||||||
|
<>
|
||||||
|
<Select value={senderType} sx={{ mb: 1 }} onChange={handleChangeType}>
|
||||||
|
<MenuItem value="default">Use built-in bot (only supports team message)</MenuItem>
|
||||||
|
<MenuItem value="custom">Use custom bot</MenuItem>
|
||||||
|
</Select>
|
||||||
|
{senderType === 'default' ? <>
|
||||||
|
<Typography sx={{ mb: 2 }} variant="caption" component="div">
|
||||||
|
WFL Hub will send message via default bot <a style={{ color: '#f50057' }}>@David-Bot</a>
|
||||||
|
</Typography>
|
||||||
|
<TextField label="Team Link" fullWidth name="teamLink" id="teamLink" key="1" sx={{ mb: 4 }} value={teamLink}
|
||||||
|
onChange={handleChangeTeam}
|
||||||
|
defaultValue={edit ? 'https://app.ringcentral.com/l/messages/134751420422' : ''}/>
|
||||||
|
</> :
|
||||||
|
<>
|
||||||
|
<Typography variant="caption" component="div" sx={{ mb: 2 }}>
|
||||||
|
WFL Hub will send messages via your <a style={{ color: '#f50057' }}
|
||||||
|
href="https://developers.ringcentral.com/guide/team-messaging/bots/walkthrough"
|
||||||
|
target="_blank" rel="noreferrer">RingCentral Bot</a>, please
|
||||||
|
fill in:
|
||||||
|
</Typography>
|
||||||
|
<TextField label="RingCentral Bot Client ID" fullWidth name="clientId" key="2" id="clientId" sx={{ mb: 4 }}
|
||||||
|
defaultValue={edit ? '0w3wgTW5dkdfGjdseGDJ2y' : ''}/>
|
||||||
|
<TextField label="RingCentral Bot Client Secret" sx={{ mb: 4 }} key="3" name="clientSecret" id="clientSecret"
|
||||||
|
fullWidth
|
||||||
|
defaultValue={edit ? '6oGJ8Nko0socCF6rzFqKsk7FbNyT33ouDebXsOhACDhp' : ''}
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
aria-label="toggle password visibility"
|
||||||
|
onClick={handleClickShowPassword}
|
||||||
|
onMouseDown={handleMouseDownPassword}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff/> : <Visibility/>}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>}
|
||||||
|
|
||||||
|
</>}
|
||||||
|
<TextField label="Message format" placeholder="Please enter your message format"
|
||||||
|
value={value} name="message" fullWidth
|
||||||
|
onChange={handleChange} maxRows={5} minRows={2} multiline
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
14
src/Home.tsx
14
src/Home.tsx
@ -6,7 +6,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Link, Outlet } from "react-router-dom";
|
||||||
import AppBar from '@mui/material/AppBar';
|
import AppBar from '@mui/material/AppBar';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
@ -15,6 +15,7 @@ import Paper from '@mui/material/Paper';
|
|||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import AccountCircle from '@mui/icons-material/AccountCircle';
|
import AccountCircle from '@mui/icons-material/AccountCircle';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
|
import { RCIcon } from './RCIcon';
|
||||||
|
|
||||||
const Layout = styled(Box)(({ theme }) => (
|
const Layout = styled(Box)(({ theme }) => (
|
||||||
{
|
{
|
||||||
@ -36,17 +37,20 @@ const Content = styled(Box)(({ theme }) => ({
|
|||||||
export const Home = () => {
|
export const Home = () => {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<AppBar position="static">
|
<AppBar position="static" enableColorOnDark sx={{ bgcolor: '#3f51b5' }}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
noWrap
|
noWrap
|
||||||
component="div"
|
component={Link}
|
||||||
sx={{ flexGrow: 1, display: { xs: 'block' } }}
|
to="/"
|
||||||
|
style={{ cursor: 'point' }}
|
||||||
|
sx={{ flexGrow: 1, display: { xs: 'block' }, color: 'white', }}
|
||||||
>
|
>
|
||||||
|
<RCIcon style={{ marginRight: 4, verticalAlign: 'middle' }}/>
|
||||||
RingCentral AI Workflow Hub
|
RingCentral AI Workflow Hub
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: { xs: 'block' } }}>
|
<Box sx={{ display: { xs: 'block' }, color: 'white' }}>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="large"
|
size="large"
|
||||||
edge="end"
|
edge="end"
|
||||||
|
183
src/List.tsx
183
src/List.tsx
@ -5,66 +5,187 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import React, { useState } from "react";
|
import React, { FC, useEffect, useMemo, useState } from "react";
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import CardActionArea from '@mui/material/CardActionArea';
|
import CardActionArea from '@mui/material/CardActionArea';
|
||||||
import Switch from '@mui/material/Switch';
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
import CardActions from '@mui/material/CardActions';
|
import CardActions from '@mui/material/CardActions';
|
||||||
import CardContent from '@mui/material/CardContent';
|
import CardContent from '@mui/material/CardContent';
|
||||||
|
import axios from "axios";
|
||||||
|
import Container from '@mui/material/Container';
|
||||||
interface ListItem {
|
import Dialog from '@mui/material/Dialog';
|
||||||
id: string;
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
active: boolean;
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
title: string;
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
}
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
import { getRandomEmoji } from "./randomEmoji";
|
||||||
|
import { formatTime } from "./timeFormat";
|
||||||
|
import { message } from "mui-message";
|
||||||
|
|
||||||
const Flex = styled(Box)(() => ({
|
const Flex = styled(Box)(() => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const List = () => {
|
interface Workflow {
|
||||||
const [list] = useState<ListItem[]>([{
|
description: string;
|
||||||
id: 'a',
|
id: string;
|
||||||
title: 'Workflow 1',
|
name: string;
|
||||||
active: true
|
lastModifiedTime: string;
|
||||||
}]);
|
emoji?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const List: FC<{ develop?: boolean }> = ({ develop }) => {
|
||||||
|
const [list, setList] = useState<Workflow[]>([]);
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const [showBindModal, setShowBindModel] = useState('');
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
useEffect(() => {
|
||||||
|
axios.get<{
|
||||||
|
total: number
|
||||||
|
workflows: Workflow[]
|
||||||
|
}>('/api/public/wf/config/v1/account/10086/workflows').then(({ data }) => {
|
||||||
|
setList(data.workflows.map(it => ({
|
||||||
|
...it,
|
||||||
|
emoji: getRandomEmoji()
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
const handleOpen = () => {
|
||||||
|
setShowModal(true);
|
||||||
|
};
|
||||||
|
const handleOpenBind = (id: string) => {
|
||||||
|
setShowBindModel(id);
|
||||||
|
};
|
||||||
|
const handleClose = () => {
|
||||||
|
setShowModal(false);
|
||||||
|
};
|
||||||
|
const handleCloseBindModel = () => {
|
||||||
|
setShowBindModel('');
|
||||||
|
};
|
||||||
|
const location = useLocation();
|
||||||
|
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 }) => {
|
||||||
|
handleClose();
|
||||||
|
navigate(`${develop ? '' : '/demo'}/create/${data.visibleId}`,);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleSubmitBind = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const formData = new FormData(event.currentTarget);
|
||||||
|
const formJson = Object.fromEntries((formData as any).entries());
|
||||||
|
const channelId = `${formJson.groupId.trim()}-${formJson.creatorId.trim()}`;
|
||||||
|
await axios.delete(`/bot/workflow/${showBindModal}/bind/${channelId}`);
|
||||||
|
await axios.post(`/bot/workflow/${showBindModal}/bind`, { channelId });
|
||||||
|
message.success('Success!');
|
||||||
|
handleCloseBindModel();
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<Container maxWidth="lg">
|
||||||
<Flex style={{ justifyContent: 'flex-end' }}>
|
<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>
|
||||||
<Flex sx={{ gap: 2 }}>
|
<Flex sx={{ gap: 4, mt: 4 }}>
|
||||||
{list.map(item => (
|
{list.map(item => (
|
||||||
<Link
|
<Link
|
||||||
key={item.id}
|
key={item.id}
|
||||||
to={`/detail/${item.id}`}
|
to={`${develop ? '' : '/demo'}/detail/${item.id}`}
|
||||||
>
|
>
|
||||||
<Card sx={{ width: 280, borderRadius: 2, boxShadow: 2 }}>
|
<Card sx={{ width: 263, borderRadius: 2, boxShadow: 2 }}>
|
||||||
<CardActionArea>
|
<CardActionArea>
|
||||||
<CardContent>
|
<CardContent style={{ height: 100 }}>
|
||||||
<Typography gutterBottom variant="h5" component="div">
|
<Typography gutterBottom variant="h5" component="div">
|
||||||
{item.title}
|
{item.emoji} {item.name || 'My Workflow'}
|
||||||
</Typography>
|
</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"/>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions sx={{ display: { xs: 'flex' }, justifyContent: "flex-end" }}>
|
<CardActions
|
||||||
<Switch defaultChecked/>
|
sx={{
|
||||||
|
display: { xs: 'flex' },
|
||||||
|
justifyContent: "space-between",
|
||||||
|
borderTop: 1,
|
||||||
|
borderColor: 'divider'
|
||||||
|
}}>
|
||||||
|
<Typography component="span" variant="caption" style={{ opacity: .6 }}>Last
|
||||||
|
modified: {formatTime(item.lastModifiedTime)}</Typography>
|
||||||
|
{develop ?
|
||||||
|
<Button size="small" onClick={(e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleOpenBind(item.id);
|
||||||
|
}}>Bind</Button> : <Button size="small">Copy</Button>}
|
||||||
|
{/*<Switch defaultChecked/>*/}
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>))}
|
</Link>))}
|
||||||
</Flex>
|
</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"
|
||||||
|
label="Workflow Name"
|
||||||
|
fullWidth
|
||||||
|
focused
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleClose}>Cancel</Button>
|
||||||
|
<Button type="submit">Create</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog open={!!showBindModal} onClose={handleCloseBindModel}
|
||||||
|
PaperProps={{
|
||||||
|
component: 'form',
|
||||||
|
onSubmit: handleSubmitBind,
|
||||||
|
}}>
|
||||||
|
<DialogTitle>Bind Channel</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
margin="dense"
|
||||||
|
id="groupId" focused
|
||||||
|
variant="outlined"
|
||||||
|
label="Group Id" fullWidth name="groupId"
|
||||||
|
helperText="You can get team groupId from team link"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
margin="dense"
|
||||||
|
id="creatorId" focused
|
||||||
|
variant="outlined"
|
||||||
|
label="Creator Id" fullWidth name="creatorId"
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleCloseBindModel}>Cancel</Button>
|
||||||
|
<Button type="submit">Bind</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"/>
|
||||||
|
);
|
||||||
|
};
|
@ -1 +1,19 @@
|
|||||||
import React from 'react'
|
import React, { FC } from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { Editor } from './Editor';
|
||||||
|
|
||||||
|
export const ScriptBlock: FC<{ value: string; name?: string; onChange: (value: string) => void }> = ({
|
||||||
|
name,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
|
<Typography variant="subtitle1" sx={{ mb: 1 }}>{name ? `${name}` : 'Script'}</Typography>
|
||||||
|
<Box>
|
||||||
|
<Editor {...props} />
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -26,3 +26,11 @@ a {
|
|||||||
height: 600px;
|
height: 600px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.MuiButton-contained:hover {
|
||||||
|
background-color: rgb(118, 126, 168) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Mui-focused .MuiOutlinedInput-notchedOutline {
|
||||||
|
/*color: #aab5f1*/
|
||||||
|
}
|
||||||
|
@ -7,29 +7,34 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||||
import {} from '@mui/material/colors';
|
|
||||||
|
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
|
import { MessageBox } from 'mui-message';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById('root') as HTMLElement
|
document.getElementById('root') as HTMLElement
|
||||||
);
|
);
|
||||||
const theme = createTheme({
|
const theme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
mode: 'light',
|
mode: 'dark',
|
||||||
primary: {
|
primary: {
|
||||||
main: '#3f51b5',
|
// main: '#3f51b5',
|
||||||
|
main: '#aab5f1'
|
||||||
},
|
},
|
||||||
tonalOffset: 0.6,
|
tonalOffset: 0.6,
|
||||||
secondary: {
|
secondary: {
|
||||||
main: '#f50057',
|
main: '#f50057',
|
||||||
},
|
},
|
||||||
|
background: {
|
||||||
|
paper: '#404041',
|
||||||
|
default: '#242425',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline/>
|
<CssBaseline/>
|
||||||
|
<MessageBox/>
|
||||||
<App/>
|
<App/>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
|
92
src/loadYml.ts
Normal file
92
src/loadYml.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* @Author : Eleanor Mao
|
||||||
|
* @Date : 2024-04-10 09:14:31
|
||||||
|
* @LastEditTime : 2024-04-10 09:14:31
|
||||||
|
*
|
||||||
|
* Copyright © RingCentral. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import jsYaml from 'js-yaml';
|
||||||
|
import { transformDSL } from './transform';
|
||||||
|
|
||||||
|
function compress(s: string, pre: string): string {
|
||||||
|
s = unescape(encodeURIComponent(s));
|
||||||
|
const arr = [];
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
arr.push(s.charCodeAt(i));
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const compressor = new Zopfli.RawDeflate(arr);
|
||||||
|
const compressed = compressor.compress();
|
||||||
|
// @ts-ignore
|
||||||
|
return "http://www.plantuml.com/plantuml" + pre + encode64_(compressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventHandlerAction {
|
||||||
|
actionType: string;
|
||||||
|
targetState?: string;
|
||||||
|
scriptSource?: string;
|
||||||
|
transitionName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Action {
|
||||||
|
actionType: string;
|
||||||
|
/* Script */
|
||||||
|
scriptSource?: string;
|
||||||
|
/* Chat */
|
||||||
|
type?: string;
|
||||||
|
token?: string;
|
||||||
|
id?: string;
|
||||||
|
question?: string;
|
||||||
|
score?: null;
|
||||||
|
/*EmailSender*/
|
||||||
|
content?: string;
|
||||||
|
subject?: string;
|
||||||
|
to?: string;
|
||||||
|
/*GlipSender*/
|
||||||
|
url?: string;
|
||||||
|
creatorId?: string;
|
||||||
|
groupId?: string;
|
||||||
|
message?: 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 {
|
||||||
|
return compress(transformDSL(definition), '/svg/');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DSL2Json(input: string): Definition {
|
||||||
|
return jsYaml.load(input) as Definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Json2Yml(json: any): string {
|
||||||
|
return jsYaml.dump(json, { quotingType: '"' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
window.ToJson = DSL2Json;
|
||||||
|
// @ts-ignore
|
||||||
|
window.transform = transformDSL;
|
14
src/randomEmoji.ts
Normal file
14
src/randomEmoji.ts
Normal file
File diff suppressed because one or more lines are too long
29
src/timeFormat.ts
Normal file
29
src/timeFormat.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* @Author : Eleanor Mao
|
||||||
|
* @Date : 2024-04-12 10:53:40
|
||||||
|
* @LastEditTime : 2024-04-12 10:53:40
|
||||||
|
*
|
||||||
|
* Copyright © RingCentral. All rights reserved.
|
||||||
|
*/
|
||||||
|
export const formatTime = (time?: string | number) => {
|
||||||
|
if (!time) return 'Never';
|
||||||
|
const date = new Date(typeof time === 'string' ? time : time * 1000);
|
||||||
|
const theTime = date.getTime();
|
||||||
|
const now = Date.now();
|
||||||
|
const diff = now - theTime;
|
||||||
|
const month = date.getMonth() + 1;
|
||||||
|
const daate = date.getDate();
|
||||||
|
const hours = date.getHours();
|
||||||
|
const mins = `${date.getMinutes() + 100}`.slice(1);
|
||||||
|
|
||||||
|
if (diff < 15 * 60 * 1000) {
|
||||||
|
return 'Just now';
|
||||||
|
} else if (diff < 60 * 60 * 1000) {
|
||||||
|
return `${Math.floor(diff / 1000 / 60 )}m ago`;
|
||||||
|
} else if (diff < 24 * 60 * 60 * 1000) {
|
||||||
|
return `${Math.floor(diff / 1000 / 60 / 60)}h ago`;
|
||||||
|
} else if (diff < 7 * 24 * 60 * 60 * 1000) {
|
||||||
|
return `${Math.floor(diff / 1000 / 60 / 60 / 24 )}d ago`;
|
||||||
|
}
|
||||||
|
return `${month}/${daate} ${hours}:${mins}`;
|
||||||
|
};
|
@ -1,84 +0,0 @@
|
|||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Input Box and Button</title>
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
overflow-x: auto;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
}
|
|
||||||
.inputBox {
|
|
||||||
margin-left: 1%;
|
|
||||||
width: 700px;
|
|
||||||
height: 800px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.submit {
|
|
||||||
margin-left: 1%;
|
|
||||||
width: 100px;
|
|
||||||
height: 50px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.outputBox {
|
|
||||||
margin-left: 1%;
|
|
||||||
width: 700px;
|
|
||||||
height: 800px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.image {
|
|
||||||
margin-left: 1%;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<textarea id="inputBox" class="inputBox" placeholder="DSL..."></textarea>
|
|
||||||
<button class="submit" onclick="handleButtonClick()">Submit</button>
|
|
||||||
<textarea id="outputBox" class="outputBox" placeholder="PlantUML..."></textarea>
|
|
||||||
<img id="outputImage" class="image" style="display: none" src="" alt="PlantUML Image">
|
|
||||||
</div>
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/js-yaml/dist/js-yaml.min.js"></script>
|
|
||||||
<script src="transform.js"></script>
|
|
||||||
<script async="" src="https://cdn-0.plantuml.com/synchro2.min.js"></script>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
function compress(s, pre) {
|
|
||||||
//UTF8
|
|
||||||
s = unescape(encodeURIComponent(s));
|
|
||||||
var arr = [];
|
|
||||||
for (var i = 0; i < s.length; i++) {
|
|
||||||
arr.push(s.charCodeAt(i));
|
|
||||||
}
|
|
||||||
var compressor = new Zopfli.RawDeflate(arr);
|
|
||||||
var compressed = compressor.compress();
|
|
||||||
return "http://www.plantuml.com/plantuml" + pre + encode64_(compressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleButtonClick() {
|
|
||||||
const inputValue = document.getElementById('inputBox').value;
|
|
||||||
const definition = jsyaml.load(inputValue)
|
|
||||||
let outputValue;
|
|
||||||
try {
|
|
||||||
outputValue = transformDSL(definition)
|
|
||||||
document.getElementById('outputBox').value = outputValue
|
|
||||||
const imgUrl = compress(outputValue, '/svg/');
|
|
||||||
document.getElementById('outputImage').src = imgUrl;
|
|
||||||
document.getElementById('outputImage').style.display = 'block';
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
document.getElementById('outputBox').value = error.message
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,48 +1,58 @@
|
|||||||
const EVENT_COLOR = "#LightYellow";
|
import { Action, Definition, State } from "./loadYml";
|
||||||
const HTTP_COLOR = "#Peru";
|
|
||||||
const SCRIPT_COLOR = "#Olive;text:white";
|
const EVENT_COLOR = "#IndianRed";
|
||||||
const BOT_COLOR = "#Blue;text:white";
|
const HTTP_COLOR = "#Sienna";
|
||||||
const EMAIL_SENDER_COLOR = "#Red;text:white";
|
const SCRIPT_COLOR = "#Olive";
|
||||||
|
const BOT_COLOR = "#RoyalBlue";
|
||||||
|
const EMAIL_SENDER_COLOR = "#Crimson";
|
||||||
|
const CHAT_BOT_COLOR = "#OrangeRed";
|
||||||
|
const GLIP_COLOR = "#DeepPink";
|
||||||
|
|
||||||
const EVENT_NAME = ": Event";
|
const EVENT_NAME = ": Event";
|
||||||
const HTTP_NAME = ": Http";
|
const HTTP_NAME = ": Http";
|
||||||
const SCRIPT_NAME = ": Script";
|
const SCRIPT_NAME = ": Script";
|
||||||
const BOT_NAME = ": Chat";
|
const BOT_NAME = ": Chat";
|
||||||
const EMAIL_SENDER_NAME = ": EmailSender";
|
const EMAIL_SENDER_NAME = ": EmailSender";
|
||||||
|
const GLIP_SENDER_NAME = ': GlipSender';
|
||||||
|
|
||||||
function isHttp(actions) {
|
export function isHttp(actions: Action[]): boolean {
|
||||||
return actions
|
return actions
|
||||||
.map((action) => action.actionType)
|
.some((action) => action.actionType === "HttpRequest");
|
||||||
.some((type) => type == "HttpRequest");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isScript(actions) {
|
export function isScript(actions: Action[]): boolean {
|
||||||
return actions
|
return actions
|
||||||
.map((action) => action.actionType)
|
.some((action) => action.actionType === "Script");
|
||||||
.some((type) => type == "Script");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isChat(actions) {
|
export function isChat(actions: Action[]): boolean {
|
||||||
return actions
|
return actions
|
||||||
.map((action) => action.actionType)
|
.some((action) => action.actionType === 'ChatBot');
|
||||||
.some((type) => type == "Chat");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEmailSender(actions) {
|
export function isGlipSender(actions: Action[]): boolean {
|
||||||
return actions
|
return actions
|
||||||
.map((action) => action.actionType)
|
.some((action) => action.actionType === 'GlipSender');
|
||||||
.some((type) => type == "EmailSender");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEmpty(arr) {
|
export function isEmailSender(actions: Action[]): boolean {
|
||||||
return arr === undefined || arr == null || arr.length == 0;
|
return actions
|
||||||
|
.some((action) => action.actionType === "EmailSender");
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNotBlank(str) {
|
export function isChatBot(actions: Action[]): boolean {
|
||||||
|
return actions.some(action => action.actionType === 'ChatResponse');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmpty(arr: any[]): boolean {
|
||||||
|
return arr === undefined || arr == null || arr.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotBlank(str: string): boolean {
|
||||||
return str !== undefined && str !== null && str.trim() !== "";
|
return str !== undefined && str !== null && str.trim() !== "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawState(eventState, state) {
|
function drawState(eventState: Map<string, Set<string>>, state: State): string {
|
||||||
let sb = "";
|
let sb = "";
|
||||||
const name = state.name;
|
const name = state.name;
|
||||||
const actions = state.actions;
|
const actions = state.actions;
|
||||||
@ -51,10 +61,10 @@ function drawState(eventState, state) {
|
|||||||
if (isEmpty(handlers)) {
|
if (isEmpty(handlers)) {
|
||||||
throw Error(`state ${name} handlers must not empty`);
|
throw Error(`state ${name} handlers must not empty`);
|
||||||
}
|
}
|
||||||
let events = new Set()
|
let events = new Set<string>();
|
||||||
for (const handle of handlers) {
|
for (const handle of handlers) {
|
||||||
const stateName = handle.eventType;
|
const stateName = handle.eventType;
|
||||||
events.add(stateName)
|
events.add(stateName);
|
||||||
sb += `state ${stateName} ${EVENT_COLOR} ${EVENT_NAME}\n`;
|
sb += `state ${stateName} ${EVENT_COLOR} ${EVENT_NAME}\n`;
|
||||||
}
|
}
|
||||||
eventState.set(name, events);
|
eventState.set(name, events);
|
||||||
@ -67,21 +77,26 @@ function drawState(eventState, state) {
|
|||||||
sb += `state ${name} ${BOT_COLOR} ${BOT_NAME}\n`;
|
sb += `state ${name} ${BOT_COLOR} ${BOT_NAME}\n`;
|
||||||
} else if (isEmailSender(actions)) {
|
} else if (isEmailSender(actions)) {
|
||||||
sb += `state ${name} ${EMAIL_SENDER_COLOR} ${EMAIL_SENDER_NAME}\n`;
|
sb += `state ${name} ${EMAIL_SENDER_COLOR} ${EMAIL_SENDER_NAME}\n`;
|
||||||
|
} else if (isChatBot(actions)) {
|
||||||
|
sb += `state ${name} ${CHAT_BOT_COLOR} ${BOT_NAME}\n`;
|
||||||
|
} else if (isGlipSender(actions)) {
|
||||||
|
sb += `state ${name} ${GLIP_COLOR} ${GLIP_SENDER_NAME}\n`;
|
||||||
} else {
|
} else {
|
||||||
throw Error(`state ${name} actions is incorrect`);
|
console.log('====', name, actions);
|
||||||
|
// throw Error(`state ${name} actions is incorrect`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sb;
|
return sb;
|
||||||
}
|
}
|
||||||
|
|
||||||
function append(from, to, description) {
|
function append(from: any, to: any, description: any) {
|
||||||
if (isNotBlank(description)) {
|
if (isNotBlank(description)) {
|
||||||
description = ": " + description;
|
description = ": " + description;
|
||||||
}
|
}
|
||||||
return `${from} ---> ${to}${description}\n`;
|
return `${from} ---> ${to || ''}${description}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapCond(cond) {
|
function wrapCond(cond: any) {
|
||||||
if (isNotBlank(cond)) {
|
if (isNotBlank(cond)) {
|
||||||
return "(" + cond + ")";
|
return "(" + cond + ")";
|
||||||
} else {
|
} else {
|
||||||
@ -89,7 +104,7 @@ function wrapCond(cond) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawTransitionCondition(eventCondition, transition) {
|
function drawTransitionCondition(eventCondition: any, transition: any) {
|
||||||
const list = [];
|
const list = [];
|
||||||
if (eventCondition !== undefined && eventCondition !== null) {
|
if (eventCondition !== undefined && eventCondition !== null) {
|
||||||
list.push(eventCondition);
|
list.push(eventCondition);
|
||||||
@ -105,7 +120,7 @@ function drawTransitionCondition(eventCondition, transition) {
|
|||||||
return list.join(" && ");
|
return list.join(" && ");
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawExitCondition(eventCondition, exit) {
|
function drawExitCondition(eventCondition: any, exit: any) {
|
||||||
const list = [];
|
const list = [];
|
||||||
if (eventCondition !== undefined && eventCondition !== null) {
|
if (eventCondition !== undefined && eventCondition !== null) {
|
||||||
list.push(eventCondition);
|
list.push(eventCondition);
|
||||||
@ -116,7 +131,7 @@ function drawExitCondition(eventCondition, exit) {
|
|||||||
return list.join(" && ");
|
return list.join(" && ");
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawStateLine(eventState, state) {
|
function drawStateLine(eventState: Map<string, Set<string>>, state: State) {
|
||||||
const handlers = state.eventHandlers;
|
const handlers = state.eventHandlers;
|
||||||
if (isEmpty(handlers)) {
|
if (isEmpty(handlers)) {
|
||||||
return;
|
return;
|
||||||
@ -134,15 +149,15 @@ function drawStateLine(eventState, state) {
|
|||||||
const eventCondition = wrapCond(handler.condition);
|
const eventCondition = wrapCond(handler.condition);
|
||||||
const actions = handler.actions;
|
const actions = handler.actions;
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
const actionType = action.actionType
|
const actionType = action.actionType;
|
||||||
let targetState = action.targetState
|
let targetState = action.targetState || '';
|
||||||
if (actionType === "Transition") {
|
if (actionType === "Transition") {
|
||||||
if (eventState.has(targetState)) {
|
if (eventState.has(targetState)) {
|
||||||
let events = eventState.get(targetState)
|
let events = eventState.get(targetState);
|
||||||
if(events.size > 1) {
|
if (events && events.size > 1) {
|
||||||
continue
|
continue;
|
||||||
} else {
|
} else if (events) {
|
||||||
targetState = events.values().next().value
|
targetState = events.values().next().value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const desc = drawTransitionCondition(eventCondition, action);
|
const desc = drawTransitionCondition(eventCondition, action);
|
||||||
@ -156,21 +171,34 @@ function drawStateLine(eventState, state) {
|
|||||||
return sb;
|
return sb;
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformDSL(definition) {
|
export function transformDSL(definition: Definition): string {
|
||||||
let sb = "";
|
let sb = "";
|
||||||
const eventState = new Map();
|
const eventState = new Map<string, Set<string>>();
|
||||||
const states = definition["states"];
|
const states = definition["states"] || [];
|
||||||
for (const state of states) {
|
for (const state of states) {
|
||||||
sb += drawState(eventState, state);
|
sb += drawState(eventState, state) || '';
|
||||||
}
|
}
|
||||||
for (const state of states) {
|
for (const state of states) {
|
||||||
sb += drawStateLine(eventState, state);
|
sb += drawStateLine(eventState, state) || '';
|
||||||
}
|
}
|
||||||
return `@startuml
|
return `@startuml
|
||||||
|
skinparam backgroundColor #242425
|
||||||
|
skinparam state {
|
||||||
|
FontColor white
|
||||||
|
BackgroundColor DimGray
|
||||||
|
BorderColor #AAB5F1
|
||||||
|
ArrowColor #AAB5F1
|
||||||
|
ArrowFontColor #AAB5F1
|
||||||
|
}
|
||||||
|
|
||||||
|
skinparam activity {
|
||||||
|
StartColor Silver
|
||||||
|
EndColor Silver
|
||||||
|
}
|
||||||
|
|
||||||
${sb}
|
${sb}
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { transformDSL };
|
|
61
yarn.lock
61
yarn.lock
@ -2343,6 +2343,11 @@
|
|||||||
jest-matcher-utils "^27.0.0"
|
jest-matcher-utils "^27.0.0"
|
||||||
pretty-format "^27.0.0"
|
pretty-format "^27.0.0"
|
||||||
|
|
||||||
|
"@types/js-yaml@^4.0.9":
|
||||||
|
version "4.0.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2"
|
||||||
|
integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==
|
||||||
|
|
||||||
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
||||||
version "7.0.15"
|
version "7.0.15"
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
||||||
@ -2402,7 +2407,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.8.tgz#95f6c6a08f2ad868ba230ead1d2d7f7be3db3837"
|
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.8.tgz#95f6c6a08f2ad868ba230ead1d2d7f7be3db3837"
|
||||||
integrity sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==
|
integrity sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==
|
||||||
|
|
||||||
"@types/qs@*":
|
"@types/qs@*", "@types/qs@^6.9.14":
|
||||||
version "6.9.14"
|
version "6.9.14"
|
||||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.14.tgz#169e142bfe493895287bee382af6039795e9b75b"
|
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.14.tgz#169e142bfe493895287bee382af6039795e9b75b"
|
||||||
integrity sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==
|
integrity sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==
|
||||||
@ -3118,6 +3123,15 @@ axe-core@=4.7.0:
|
|||||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf"
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf"
|
||||||
integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==
|
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:
|
axobject-query@^3.2.1:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a"
|
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a"
|
||||||
@ -3537,6 +3551,11 @@ cliui@^7.0.2:
|
|||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
wrap-ansi "^7.0.0"
|
wrap-ansi "^7.0.0"
|
||||||
|
|
||||||
|
clsx@^1.1.0:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
|
||||||
|
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
|
||||||
|
|
||||||
clsx@^2.1.0:
|
clsx@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
|
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
|
||||||
@ -5057,7 +5076,7 @@ flatted@^3.2.9:
|
|||||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
|
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
|
||||||
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
|
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
|
||||||
|
|
||||||
follow-redirects@^1.0.0:
|
follow-redirects@^1.0.0, follow-redirects@^1.15.6:
|
||||||
version "1.15.6"
|
version "1.15.6"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
|
||||||
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
|
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
|
||||||
@ -5105,6 +5124,15 @@ form-data@^3.0.0:
|
|||||||
combined-stream "^1.0.8"
|
combined-stream "^1.0.8"
|
||||||
mime-types "^2.1.12"
|
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:
|
forwarded@0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||||
@ -5393,7 +5421,7 @@ he@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||||
|
|
||||||
hoist-non-react-statics@^3.3.1:
|
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||||
@ -7061,6 +7089,13 @@ ms@2.1.3, ms@^2.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||||
|
|
||||||
|
mui-message@^1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/mui-message/-/mui-message-1.0.3.tgz#0af2af1dfb2192a102a461e3193c2f27ef003d34"
|
||||||
|
integrity sha512-MW+CqmrwGqbNe/cG196zzm+fCdYb4yNCLyfCgTyYCI4NEm9gSLUmc+rbkFd2HoYmrKrMBa9KBmtEBMYMdjKYAQ==
|
||||||
|
dependencies:
|
||||||
|
notistack "^2.0.8"
|
||||||
|
|
||||||
multicast-dns@^7.2.5:
|
multicast-dns@^7.2.5:
|
||||||
version "7.2.5"
|
version "7.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced"
|
resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced"
|
||||||
@ -7141,6 +7176,14 @@ normalize-url@^6.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
||||||
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
||||||
|
|
||||||
|
notistack@^2.0.8:
|
||||||
|
version "2.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/notistack/-/notistack-2.0.8.tgz#78cdf34c64e311bf1d1d71c2123396bcdea5e95b"
|
||||||
|
integrity sha512-/IY14wkFp5qjPgKNvAdfL5Jp6q90+MjgKTPh4c81r/lW70KeuX6b9pE/4f8L4FG31cNudbN9siiFS5ql1aSLRw==
|
||||||
|
dependencies:
|
||||||
|
clsx "^1.1.0"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
|
||||||
npm-run-path@^4.0.1:
|
npm-run-path@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
|
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
|
||||||
@ -8156,6 +8199,11 @@ proxy-addr@~2.0.7:
|
|||||||
forwarded "0.2.0"
|
forwarded "0.2.0"
|
||||||
ipaddr.js "1.9.1"
|
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:
|
psl@^1.1.33:
|
||||||
version "1.9.0"
|
version "1.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
|
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
|
||||||
@ -8178,6 +8226,13 @@ qs@6.11.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
side-channel "^1.0.4"
|
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:
|
querystringify@^2.1.1:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||||
|
Reference in New Issue
Block a user