react-rasa-assistant
Version:
React hook for easily building custom Rasa assistants
172 lines (131 loc) • 4.67 kB
JavaScript
const
io = require('socket.io-client'),
{useState, useEffect, useCallback, useRef} = require('react')
module.exports = ({
sockUrl,
sockOpts,
initSessionId,
initMsg,
onError = noop,
onUtter = noop,
}) => {
const
sockRef = useRef(null),
sessionIdRef = useRef(null),
inputRef = useRef({focus: noop, blur: noop}),
[userText, setUserText] = useState(''),
[msgHistory, setMsgHistory] = useState([]),
pushMsgToHistory = useCallback(
msg =>
setMsgHistory(lastMsgHistory => [...lastMsgHistory, msg]),
),
removeMsgFromHistory = useCallback(
msgIdx =>
setMsgHistory(lastMsgHistory => [
...lastMsgHistory.slice(0, msgIdx),
...lastMsgHistory.slice(msgIdx + 1),
])),
restartSession = useCallback(
(sock, session_id) =>
sockRef.current.emit('session_request', {session_id}),
),
userUtter = useCallback(
(text, payload) => {
sockRef.current.emit('user_uttered', {
session_id: sessionIdRef.current,
message: payload || text,
})
const msg = {ts: Date.now(), direction: 'out', text}
onUtter(msg)
pushMsgToHistory(msg)
},
[sockRef.current, sessionIdRef.current],
),
sendUserText = useCallback(
() => {
userUtter(userText)
setUserText('')
},
[userText],
),
selectOption = useCallback(
(msgIdx, optIdx) => {
const
msg = msgHistory[msgIdx],
opt = (msg.buttons || msg.quick_replies)[optIdx]
userUtter(opt.title, opt.payload)
inputRef.current.focus()
if (msg.quick_replies)
removeMsgFromHistory(msgIdx)
}
),
handleBotUtter = useCallback(
msg => {
const
{text, quick_replies, buttons} = msg,
msgTpl = {
ts: Date.now(),
direction: 'in',
metadata: msg.metadata,
}
onUtter({...msgTpl, ...msg})
if (text)
pushMsgToHistory({...msgTpl, text})
if (quick_replies)
pushMsgToHistory({...msgTpl, quick_replies})
if (buttons)
pushMsgToHistory({...msgTpl, buttons})
if (!text && !quick_replies && !buttons)
pushMsgToHistory({...msgTpl, ...msg})
if (quick_replies || buttons)
inputRef.current.blur()
})
useEffect(() => {
const
[, sockHostname, sockPath] =
sockUrl.match(/^((?:http|ws)s?:\/\/[^/]+)(\/.*)$/),
sock = io(sockHostname, {path: sockPath, ...sockOpts})
sockRef.current = sock
socketErrorEventNames
.forEach(errorEventName =>
sock.on(errorEventName, e => {
if (
errorEventName === 'disconnect'
&& e === 'io client disconnect'
)
// this is fired on manual disconnects so not an error
return
onError({type: errorEventName, payload: e})
}
))
sock
.on('connect', () => restartSession(sock, initSessionId))
.on('session_confirm', sessInfo => {
sessionIdRef.current = sessInfo.session_id
setMsgHistory([])
inputRef.current.focus()
if (initMsg)
if (typeof initMsg === 'object')
userUtter(initMsg.title, initMsg.payload)
else
userUtter(initMsg)
})
.on('bot_uttered', handleBotUtter)
return () => sock.close()
}, [])
return {
msgHistory,
onInputRef: el => { inputRef.current = el },
userText,
setUserText,
sendUserText,
selectOption,
botUtter: handleBotUtter,
restartSession,
}
}
const noop = () => null
const socketErrorEventNames = [
'error', 'connect_error', 'connect_timeout', 'reconnect_error',
'reconnect_failed', 'disconnect',
]