diff --git a/package.json b/package.json index 2ad30a8..1c67229 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "match-sorter": "^6.3.4", "mini-css-extract-plugin": "^2.4.5", "monaco-editor": "^0.47.0", + "mui-message": "^1.0.3", "postcss": "^8.4.4", "postcss-flexbugs-fixes": "^5.0.2", "postcss-loader": "^6.2.1", diff --git a/src/AIBot.tsx b/src/AIBot.tsx new file mode 100644 index 0000000..e8f2c09 --- /dev/null +++ b/src/AIBot.tsx @@ -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) => { + onChange(e.target.value); + }; + return ( + + {name ? `${name}` : 'AI Robot'} + + You can maintain your knowledge in Artificial + Intelligence Admin Portal + + + + + + ); +}; diff --git a/src/App.tsx b/src/App.tsx index ad47847..ffaefa9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,8 +15,11 @@ const router = createBrowserRouter([ path: '/', element: }, { - path: '/create', + path: '/create/:id', element: + }, { + path: '/detail/:id', + element: }] }, ]); diff --git a/src/BotAddin.tsx b/src/BotAddin.tsx deleted file mode 100644 index dec6cb4..0000000 --- a/src/BotAddin.tsx +++ /dev/null @@ -1,32 +0,0 @@ -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' - -interface Value { - id: string - secret: string -} - -export const BotAddin: FC<{ - value: Value - onChange: (value: Value) => void -}> -= ({ value, onChange }) => { - const handleChange = (e: ChangeEvent) => { - onChange({ - ...value, - [e.target.name]: e.target.value - }) - } - return ( - - RC Bot Add-in - - - - - - ) -} diff --git a/src/ChatItem.tsx b/src/ChatItem.tsx index 7117386..d27e205 100644 --- a/src/ChatItem.tsx +++ b/src/ChatItem.tsx @@ -44,7 +44,7 @@ const ChatMessageWrap = styled(Box)<{ isMe: boolean }>(({ isMe }) => ({ })); const ChatMessage = styled(Box)(() => ({ - maxWidth: '80%', + maxWidth: 'calc(100% - 50px)', whiteSpace: 'pre-line' })); export const ChatItem: FC = ({ isMe, message, loading }) => { diff --git a/src/Create.tsx b/src/Create.tsx index acf29d7..b57e9b6 100644 --- a/src/Create.tsx +++ b/src/Create.tsx @@ -5,26 +5,59 @@ * * */ -import React, { ChangeEvent, KeyboardEvent, useEffect, useMemo, useRef, useState } from "react"; +import React, { ChangeEvent, FC, 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'; import TextField from '@mui/material/TextField'; -import OutlinedInput from '@mui/material/OutlinedInput'; import SendIcon from '@mui/icons-material/Send'; import List from '@mui/material/List'; import Chip from '@mui/material/Chip'; import Divider from '@mui/material/Divider'; import { ChatItem } from "./ChatItem"; import Stack from '@mui/material/Stack'; -import { uniqueId } from 'lodash'; +import { cloneDeep, uniqueId } from 'lodash'; import Typography from "@mui/material/Typography"; -import { useLocation } from "react-router-dom"; +import { useLocation, useNavigate, useParams } from "react-router-dom"; import axios from 'axios'; -import { Definition, DSL2Json, Json2Preview } from "./loadYml"; +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 { isChatBot } from "./transform"; + +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: '', + glip: '' +}; interface Message { id: string; @@ -35,56 +68,82 @@ interface Message { const SupportedBlock = ['BotMessage', 'Chat', 'GlipSender', 'Script']; -export const Create = () => { - 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; +export const Create: FC<{ edit?: boolean }> = ({ edit }) => { + const { id } = useParams(); + const [formJson, setFormJson] = useState({ + id: 'abc', + description: '', + name: 'My workflow', + versionId: '', + versionVisibleId: '' }); const [chatList, setChatList] = useState([{ 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'), + message: getDefaultMessage(edit), 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 flowDefinition = useMemo(() => DSL2Json(workflowContent), [workflowContent]); + const previewImg = useMemo(() => Json2Preview(flowDefinition), [flowDefinition]); const chatContentRef = useRef(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({ + const actions = useMemo>(() => ((flowDefinition || {}).states || []).reduce>((l, s) => { + l.push(...s.actions.map(a => ({ ...a, stateName: s.name }))); + return l; + }, []), [flowDefinition]); + const hasEmailSender = useMemo(() => actions.some(a => a.actionType === "EmailSender"), [actions]); + const botAddinAction = useMemo(() => actions.find(a => a.actionType === "Chat"), [actions]); + const scriptAction = useMemo(() => actions.find(a => a.actionType === "Script"), [actions]); + const glipSenderAction = useMemo(() => actions.find(a => a.actionType === "GlipSender"), [actions]); + const [aibot, setAIBot] = useState(''); + const [script, setScript] = useState(''); + const [emailData, setEmailData] = useState({ to: '', subject: '', - content: `${content}` + // eslint-disable-next-line no-template-curly-in-string + content: '${localData.Desc}', }); + const [glipSenderData, setGlipSenderData] = useState({ + clientId: '', + clientSecret: '', + // eslint-disable-next-line no-template-curly-in-string + message: '' + }); + const [channelId, setChannelId] = useState('78394499078-4034902020'); + const location = useLocation(); + const debug = useMemo(() => { + return (new URLSearchParams(location.search)).get('debug') === 'true'; + }, [location]); useEffect(() => { console.log(workflowContent); console.log(flowDefinition); - }, [flowDefinition]); - // useEffect(() => { - // axios.post('/chat/start', { - // accountId: 10086, - // channelId: "10000", - // dialogId: "1", - // segmentId: "1" - // }); - // }, []); + }, [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) => { setChatInput(e.target.value); }; @@ -123,6 +182,57 @@ export const Create = () => { } }; + useEffect(() => { + if (glipSenderAction) { + let actionValueString = glipSenderAction.message; + let crtValueString = glipSenderData.message; + if (actionValueString !== preValueObj.glip && crtValueString === preValueObj.glip) { + setGlipSenderData({ + clientSecret: '', + clientId: '', + message: actionValueString || '', + }); + preValueObj.glip = actionValueString || ''; + } + } else { + if (preValueObj.glip) { + preValueObj.glip = ''; + setGlipSenderData({ + clientSecret: '', + clientId: '', + // eslint-disable-next-line no-template-curly-in-string + message: '${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 (chatContentRef.current) { chatContentRef.current.scrollTop = chatContentRef.current.scrollHeight - chatContentRef.current.offsetHeight; @@ -133,27 +243,74 @@ export const Create = () => { 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'), + message: getDefaultMessage(edit), isMe: false }]); setChatInput(''); setWorkflowContent(''); }; + useEffect(() => { + if (!hasEmailSender) + setEmailData({ + to: '', + subject: '', + // eslint-disable-next-line no-template-curly-in-string + content: '${localData.Desc}', + }); + }, [hasEmailSender]); + 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; + action.message = glipSenderData.message || '${localData.Desc}'; + } + }); + }); + console.log(Json2Yml(newJson)); + await axios.put(`/bot/workflow/${id}`, { + name: formJson.name, + content: Json2Yml(newJson) + }); + if (!debug) { + await axios.delete(`/bot/workflow/${id}/bind/${channelId}`); + await axios.post(`/bot/workflow/${id}/bind`, { channelId }); + } + message.success(`Saved! ${debug ? 'Back to List and go bind your channel!' : ''}`); + setTimeout(() => { + navigate(`/${debug ? '?debug=true' : ''}`); + }, 3000); + }; + return ( <> - { setFormJson(json => ({ ...json, name: e.target.value })); }}/> - + {/**/} @@ -166,7 +323,7 @@ export const Create = () => { borderColor: 'rgba(255,255,255,0.23)', position: 'relative' }}> - + @@ -180,27 +337,29 @@ export const Create = () => { - - - - Supported Action Types: - - {SupportedBlock.map(label => ( - ))} - - + {/**/} + {/* */} + {/* */} + {/* Supported Action Types:*/} + {/* */} + {/* {SupportedBlock.map(label => (*/} + {/* ))}*/} + {/* */} + {/**/} - ) => { - if (e.key === 'Enter' && chatInput.length) { - e.preventDefault(); - handleSend(); - } - }} - autoFocus - multiline maxRows={3} - value={chatInput}/> + ) => { + if (e.key === 'Enter' && chatInput.length) { + e.preventDefault(); + handleSend(); + } + }} + autoFocus + variant="outlined" + focused placeholder="Enter your goal ..." + multiline maxRows={3} + value={chatInput}/> + ); diff --git a/src/EmailSender.tsx b/src/EmailSender.tsx index 037dca4..a1d336d 100644 --- a/src/EmailSender.tsx +++ b/src/EmailSender.tsx @@ -1,22 +1,41 @@ -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' +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 EmailSender: FC<{ value: string; onChange: (value: string) => void }> = ({ value, onChange }) => { - const handleChange = (e: ChangeEvent) => { - onChange(e.target.value) - } - return ( - - Email Sender - - - - - ) +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) => { + onChange({ + ...value, + [e.target.name]: e.target.value + }); + }; + return ( + + Email Sender + + + + + + + ); +}; diff --git a/src/GlipSender.tsx b/src/GlipSender.tsx new file mode 100644 index 0000000..81283df --- /dev/null +++ b/src/GlipSender.tsx @@ -0,0 +1,44 @@ +import React, { ChangeEvent, FC, useEffect, 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'; + +export const GlipSender: FC<{ + debug?: boolean + value: { clientId: string; clientSecret: string; message: string }; + onChange: (value: { + clientId: string; clientSecret: string; message: string + }) => void +}> = ({ value, debug, onChange }) => { + const handleChange = (e: ChangeEvent) => { + onChange({ + ...value, + [e.target.name]: e.target.value + }); + }; + return ( + + GlipSender + + WFL Hub will send messages via RingCentral Bot, please fill in: + + + {!debug && + <> + + + } + + + + ); +}; diff --git a/src/Home.tsx b/src/Home.tsx index 3dd853d..34e06fd 100644 --- a/src/Home.tsx +++ b/src/Home.tsx @@ -6,7 +6,7 @@ * */ import React from "react"; -import { Outlet } from "react-router-dom"; +import { Link, Outlet } from "react-router-dom"; import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; @@ -37,18 +37,20 @@ const Content = styled(Box)(({ theme }) => ({ export const Home = () => { return ( - + RingCentral AI Workflow Hub - + ({ display: 'flex', @@ -37,11 +38,13 @@ interface Workflow { description: string; id: string; name: string; + lastModifiedTime: string; } export const List = () => { const [list, setList] = useState([]); const [showModal, setShowModal] = useState(false); + const [showBindModal, setShowBindModel] = useState(''); const navigate = useNavigate(); useEffect(() => { @@ -55,20 +58,38 @@ export const List = () => { const handleOpen = () => { setShowModal(true); }; + const handleOpenBind = (id: string) => { + setShowBindModel(id); + }; const handleClose = () => { setShowModal(false); }; - + const handleCloseBindModel = () => { + setShowBindModel(''); + }; + const location = useLocation(); + const debug = useMemo(() => { + return (new URLSearchParams(location.search)).get('debug') === 'true'; + }, [location]); const handleSubmit = (event: React.FormEvent) => { 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}`); + navigate(`/create/${data.visibleId}${debug ? '?debug=true' : ''}`,); }); }; + const handleSubmitBind = async (event: React.FormEvent) => { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const formJson = Object.fromEntries((formData as any).entries()); + const channelId = formJson.channelId.trim(); + await axios.delete(`/bot/workflow/${showBindModal}/bind/${channelId}`); + await axios.post(`/bot/workflow/${showBindModal}/bind`, { channelId }); + message.success('Success!'); + handleCloseBindModel(); + }; return ( @@ -78,21 +99,30 @@ export const List = () => { {list.map(item => ( - + - {item.name || 'My Workflow'} + {getRandomEmoji()} {item.name || 'My Workflow'} - - - - + sx={{ + display: { xs: 'flex' }, + justifyContent: "space-between", + borderTop: 1, + borderColor: 'divider' + }}> + Last + modified: {formatTime(item.lastModifiedTime)} + {debug ? + : + } @@ -121,7 +151,7 @@ export const List = () => { label="Workflow Name" fullWidth focused - variant="standard" + variant="outlined" /> @@ -129,6 +159,27 @@ export const List = () => { + + Bind Channel + + + + + + + + ); }; diff --git a/src/Script.tsx b/src/Script.tsx index 2dde949..da1332b 100644 --- a/src/Script.tsx +++ b/src/Script.tsx @@ -1,16 +1,19 @@ -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' +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; onChange: (value: string) => void }> = (props) => { - return ( - - Script - - - - - ) -} +export const ScriptBlock: FC<{ value: string; name?: string; onChange: (value: string) => void }> = ({ + name, + ...props + }) => { + return ( + + {name ? `${name}` : 'Script'} + + + + + ); +}; diff --git a/src/index.css b/src/index.css index e12e6ce..0846f2c 100644 --- a/src/index.css +++ b/src/index.css @@ -26,3 +26,7 @@ a { height: 600px; border: 1px solid #ccc; } + +.MuiButton-contained:hover { + background-color: rgb(118, 126, 168) !important; +} diff --git a/src/index.tsx b/src/index.tsx index e22181c..0afb13c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -8,32 +8,34 @@ import ReactDOM from 'react-dom/client'; import App from './App'; import { ThemeProvider, createTheme } from '@mui/material/styles'; import CssBaseline from '@mui/material/CssBaseline'; - +import { MessageBox } from 'mui-message'; const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement + document.getElementById('root') as HTMLElement ); const theme = createTheme({ - palette: { - mode: 'dark', - primary: { - main: '#3f51b5', - }, - tonalOffset: 0.6, - secondary: { - main: '#f50057', - }, - background: { - paper: '#404041', - default: '#242425', - }, + palette: { + mode: 'dark', + primary: { + // main: '#3f51b5', + main: '#aab5f1' }, + tonalOffset: 0.6, + secondary: { + main: '#f50057', + }, + background: { + paper: '#404041', + default: '#242425', + }, + }, }); root.render( - - - - - - + + + + + + + ); diff --git a/src/loadYml.ts b/src/loadYml.ts index f434192..6b8bfb7 100644 --- a/src/loadYml.ts +++ b/src/loadYml.ts @@ -27,20 +27,28 @@ export interface EventHandlerAction { actionType: string; targetState?: string; scriptSource?: string; - transitionName?:string + transitionName?: string; } export interface Action { actionType: string; + /* Script */ scriptSource?: string; + /* Chat */ + type?: string; + token?: string; id?: string; question?: string; score?: null; - token?: string; - type?: string; - content?:string; - subject?:string; - to?:string + /*EmailSender*/ + content?: string; + subject?: string; + to?: string; + /*GlipSender*/ + url?: string; + creatorId?: string; + groupId?: string; + message?: string; } export interface EventHandler { @@ -63,7 +71,7 @@ export interface Definition { export function Json2Preview(definition?: Definition): string { if (!definition) return ''; try { - console.log(transformDSL(definition)) + console.log(transformDSL(definition)); return compress(transformDSL(definition), '/svg/'); } catch (e) { console.error(e); @@ -75,7 +83,11 @@ 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 +window.ToJson = DSL2Json; // @ts-ignore -window.transform = transformDSL +window.transform = transformDSL; diff --git a/src/randomEmoji.ts b/src/randomEmoji.ts new file mode 100644 index 0000000..4625e19 --- /dev/null +++ b/src/randomEmoji.ts @@ -0,0 +1,14 @@ +/* + * @Author : Eleanor Mao + * @Date : 2024-04-12 10:50:29 + * @LastEditTime : 2024-04-12 10:50:29 + * + * Copyright © RingCentral. All rights reserved. + */ +const emojis = [ + '😄', '😃', '😀', '😊', '😉', '😍', '😘', '😚', '😗', '😙', '😜', '😝', '😛', '😳', '😁', '😔', '😌', '😒', '😞', '😣', '😢', '😂', '😭', '😪', '😥', '😰', '😅', '😓', '😩', '😫', '😨', '😱', '😠', '😡', '😤', '😖', '😆', '😋', '😷', '😎', '😴', '😵', '😲', '😟', '😦', '😧', '😈', '👿', '😮', '😬', '😐', '😕', '😯', '😶', '😇', '😏', '😑', '👲', '👳', '👮', '👷', '💂', '👶', '👦', '👧', '👨', '👩', '👴', '👵', '👱', '👼', '👸', '😺', '😸', '😻', '😽', '😼', '🙀', '😿', '😹', '😾', '👹', '👺', '🙈', '🙉', '🙊', '💀', '👽', '💩', '🔥', '✨', '🌟', '💫', '💥', '💢', '💦', '💧', '💤', '💨', '👂', '👀', '👃', '👅', '👄', '👍', '👎', '👌', '👊', '✊', '✌', '👋', '✋', '👐', '👆', '👇', '👉', '👈', '🙌', '🙏', '☝', '👏', '💪', '🚶', '🏃', '💃', '👫', '👪', '👬', '👭', '💏', '💑', '👯', '🙆', '🙅', '💁', '🙋', '💆', '💇', '💅', '👰', '🙎', '🙍', '🙇', '🎩', '👑', '👒', '👟', '👞', '👡', '👠', '👢', '👕', '👔', '👚', '👗', '🎽', '👖', '👘', '👙', '💼', '👜', '👝', '👛', '👓', '🎀', '🌂', '💄', '💛', '💙', '💜', '💚', '❤', '💔', '💗', '💓', '💕', '💖', '💞', '💘', '💌', '💋', '💍', '💎', '👤', '👥', '💬', '👣', '💭', '🐶', '🐺', '🐱', '🐭', '🐹', '🐰', '🐸', '🐯', '🐨', '🐻', '🐷', '🐽', '🐮', '🐗', '🐵', '🐒', '🐴', '🐑', '🐘', '🐼', '🐧', '🐦', '🐤', '🐥', '🐣', '🐔', '🐍', '🐢', '🐛', '🐝', '🐜', '🐞', '🐌', '🐙', '🐚', '🐠', '🐟', '🐬', '🐳', '🐋', '🐄', '🐏', '🐀', '🐃', '🐅', '🐇', '🐉', '🐎', '🐐', '🐓', '🐕', '🐖', '🐁', '🐂', '🐲', '🐡', '🐊', '🐫', '🐪', '🐆', '🐈', '🐩', '🐾', '💐', '🌸', '🌷', '🍀', '🌹', '🌻', '🌺', '🍁', '🍃', '🍂', '🌿', '🌾', '🍄', '🌵', '🌴', '🌲', '🌳', '🌰', '🌱', '🌼', '🌐', '🌞', '🌝', '🌚', '🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘', '🌜', '🌛', '🌙', '🌍', '🌎', '🌏', '🌋', '🌌', '🌠', '⭐', '☀', '⛅', '☁', '⚡', '☔', '❄', '⛄', '🌀', '🌁', '🌈', '🌊', '🎍', '💝', '🎎', '🎒', '🎓', '🎏', '🎆', '🎇', '🎐', '🎑', '🎃', '👻', '🎅', '🎄', '🎁', '🎋', '🎉', '🎊', '🎈', '🎌', '🔮', '🎥', '📷', '📹', '📼', '💿', '📀', '💽', '💾', '💻', '📱', '☎', '📞', '📟', '📠', '📡', '📺', '📻', '🔊', '🔉', '🔈', '🔇', '🔔', '🔕', '📢', '📣', '⏳', '⌛', '⏰', '⌚', '🔓', '🔒', '🔏', '🔐', '🔑', '🔎', '💡', '🔦', '🔆', '🔅', '🔌', '🔋', '🔍', '🛁', '🛀', '🚿', '🚽', '🔧', '🔩', '🔨', '🚪', '🚬', '💣', '🔫', '🔪', '💊', '💉', '💰', '💴', '💵', '💷', '💶', '💳', '💸', '📲', '📧', '📥', '📤', '✉', '📩', '📨', '📯', '📫', '📪', '📬', '📭', '📮', '📦', '📝', '📄', '📃', '📑', '📊', '📈', '📉', '📜', '📋', '📅', '📆', '📇', '📁', '📂', '📌', '📎', '📏', '📐', '📕', '📗', '📘', '📙', '📓', '📔', '📒', '📚', '📖', '🔖', '📛', '🔬', '🔭', '📰', '🎨', '🎬', '🎤', '🎧', '🎹', '🎻', '🎺', '🎷', '🎸', '👾', '🎮', '🃏', '🎴', '🀄', '🎲', '🎯', '🏈', '🏀', '⚽', '⚾', '🎾', '🎱', '🏉', '🎳', '⛳', '🚵', '🚴', '🏁', '🏇', '🏆', '🎿', '🏂', '🏊', '🏄', '🎣', '🍵', '🍶', '🍼', '🍺', '🍻', '🍸', '🍹', '🍷', '🍴', '🍕', '🍔', '🍟', '🍗', '🍖', '🍝', '🍛', '🍤', '🍱', '🍣', '🍥', '🍙', '🍘', '🍚', '🍜', '🍲', '🍢', '🍡', '🍳', '🍞', '🍩', '🍮', '🍦', '🍨', '🍧', '🎂', '🍰', '🍪', '🍫', '🍬', '🍭', '🍯', '🍎', '🍏', '🍊', '🍋', '🍒', '🍇', '🍉', '🍓', '🍑', '🍈', '🍌', '🍐', '🍍', '🍠', '🍆', '🍅', '🌽', '🏠', '🏡', '🏫', '🏢', '🏣', '🏥', '🏦', '🏪', '🏩', '🏨', '💒', '⛪', '🏬', '🏤', '🌇', '🌆', '🏯', '🏰', '⛺', '🏭', '🗼', '🗾', '🗻', '🌄', '🌅', '🌃', '🗽', '🌉', '🎠', '🎡', '⛲', '🎢', '🚢', '⛵', '🚤', '🚣', '🚀', '💺', '🚁', '🚂', '🚊', '🚉', '🚞', '🚆', '🚄', '🚅', '🚈', '🚇', '🚝', '🚋', '🚃', '🚎', '🚌', '🚍', '🚙', '🚘', '🚗', '🚕', '🚖', '🚛', '🚚', '🚨', '🚓', '🚔', '🚒', '🚑', '🚐', '🚲', '🚡', '🚟', '🚠', '🚜', '💈', '🚏', '🎫', '🚦', '🚥', '⚠', '🚧', '🔰', '⛽', '🏮', '🎰', '♨', '🗿', '🎪', '🎭', '📍', '🚩', '⬆', '⬇', '⬅', '➡', '🔠', '🔡', '🔤', '🔄', '🔼', '🔽', '⏪', '⏩', '⏫', '⏬', '🆗', '🔀', '🔁', '🔂', '🆕', '🆙', '🆒', '🆓', '🆖', '📶', '🎦', '🈁', '🈯', '🈳', '🈵', '🈴', '🈲', '🉐', '🈹', '🈺', '🈶', '🈚', '🚻', '🚹', '🚺', '🚼', '🚾', '🚰', '🚮', '🅿', '♿', '🚭', '🈷', '🈸', '🈂', 'Ⓜ', '🛂', '🛄', '🛅', '🛃', '🉑', '㊙', '㊗', '🆑', '🆘', '🆔', '🚫', '🔞', '📵', '🚯', '🚱', '🚳', '🚷', '🚸', '⛔', '❎', '✅', '✴', '💟', '🆚', '📳', '📴', '🅰', '🅱', '🆎', '🅾', '💠', '⛎', '🔯', '🏧', '💹', '❌', '⭕', '❗', '❓', '❕', '❔', '🔃', '🕛', '🕧', '🕐', '🕜', '🕑', '🕝', '🕒', '🕞', '🕓', '🕟', '🕔', '🕠', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', '🕡', '🕢', '🕣', '🕤', '🕥', '🕦', '💮', '💯', '🔘', '🔗', '🔱', '🔲', '🔳', '🔺', '⬜', '⚪', '🔴', '🔵', '🔻', '🔶', '🔷', '🔸', '🔹' +]; + +export function getRandomEmoji() { + return emojis[Math.floor(Math.random() * emojis.length)]; +}; diff --git a/src/timeFormat.ts b/src/timeFormat.ts new file mode 100644 index 0000000..713a251 --- /dev/null +++ b/src/timeFormat.ts @@ -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}`; +}; diff --git a/src/transform.ts b/src/transform.ts index 943394b..940bf73 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -5,6 +5,8 @@ const HTTP_COLOR = "#Peru"; const SCRIPT_COLOR = "#Olive;text:white"; const BOT_COLOR = "#Blue;text:white"; const EMAIL_SENDER_COLOR = "#Red;text:white"; +const CHAT_BOT_COLOR = "#Orange;text:white"; +const GLIP_COLOR = "#Pink;text:white"; const DEFAULT_COLOR = '#Grey;text;white'; const EVENT_NAME = ": Event"; @@ -12,6 +14,7 @@ const HTTP_NAME = ": Http"; const SCRIPT_NAME = ": Script"; const BOT_NAME = ": Chat"; const EMAIL_SENDER_NAME = ": EmailSender"; +const GLIP_SENDER_NAME = ': GlipSender'; const EXIT_NAME = ": Exit"; function isHttp(actions: Action[]): boolean { @@ -26,7 +29,12 @@ function isScript(actions: Action[]): boolean { function isChat(actions: Action[]): boolean { return actions - .some((action) => action.actionType === 'Chat'); + .some((action) => action.actionType === 'ChatBot'); +} + +function isGlipSender(actions: Action[]): boolean { + return actions + .some((action) => action.actionType === 'GlipSender'); } function isExit(actions: Action[]): boolean { @@ -38,6 +46,10 @@ function isEmailSender(actions: Action[]): boolean { .some((action) => action.actionType === "EmailSender"); } +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; } @@ -71,6 +83,10 @@ function drawState(eventState: Map>, state: State): string { sb += `state ${name} ${BOT_COLOR} ${BOT_NAME}\n`; } else if (isEmailSender(actions)) { 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 { console.log('====', name, actions); // throw Error(`state ${name} actions is incorrect`); diff --git a/yarn.lock b/yarn.lock index 9ec15d0..6ba2c86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3551,6 +3551,11 @@ cliui@^7.0.2: strip-ansi "^6.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: version "2.1.0" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" @@ -5416,7 +5421,7 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" 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" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -7084,6 +7089,13 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 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: version "7.2.5" resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" @@ -7164,6 +7176,14 @@ normalize-url@^6.0.1: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" 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: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"