Skip to content

Commit

Permalink
feat(hitl2): shortcuts (#4315)
Browse files Browse the repository at this point in the history
* Add react-textarea-autocomplete dependency

* Handle autocomplete module configuration

* Create autocomplete composer

* Use autocomplete with composer

* refact api to hitlclient

* final suggestion composer

* fixed styling issue

* best attempt to shortcut dropdown positioning

* fixed empty buttons

* some validation

* fixed errors

* remove dead code

* one last fix

* hot fix

Co-authored-by: Francois-Xavier P. Darveau <frxpoulin@gmail.com>
Co-authored-by: Francois-Xavier P. Darveau <955524+EFF@users.noreply.github.com>
  • Loading branch information
3 people committed Jan 8, 2021
1 parent 8604354 commit 54068b6
Show file tree
Hide file tree
Showing 19 changed files with 345 additions and 79 deletions.
1 change: 1 addition & 0 deletions modules/hitlnext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@blueprintjs/core": "^3.36.0",
"axios": "^0.21.0",
"@webscopeio/react-textarea-autocomplete": "^4.7.2",
"bluebird": "^3.5",
"classnames": "^2.2.6",
"haikunator": "^2.1.2",
Expand Down
25 changes: 25 additions & 0 deletions modules/hitlnext/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export interface Config {
*/
agentSessionTimeout: string

/**
* @param autoComplete
*/
autoComplete?: IAutoComplete

/**
* @param messageCount Number of messages to display in the conversation history
* @default 10
Expand Down Expand Up @@ -34,3 +39,23 @@ export interface Config {
*/
enableConversationDeletion: boolean
}

export interface IShortcut {
name: string
value: string
}

export interface IAutoComplete {
/**
* @param trigger
* @default :
*/
trigger: string

/**
* @param shortcuts
* @default []
* @example [{ "name": "hello", "value": "Hello friend!" }]
*/
shortcuts: IShortcut[]
}
3 changes: 2 additions & 1 deletion modules/hitlnext/src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"deleted": "Conversation successfully deleted",
"empty": "Select a pending handoff to engage conversation with your clients.",
"tab": "Conversation",
"composerPlaceholder": "Reply to user"
"composerPlaceholder": "Reply to user",
"send": "Send"
},
"handoffs": {
"empty": "Thanks to you and your team, there are no pending handoffs right now."
Expand Down
3 changes: 2 additions & 1 deletion modules/hitlnext/src/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
"deleted": "La conversation a été supprimée avec succès",
"empty": "Sélectionnez une escalation en attente pour engager la conversation avec vos clients.",
"tab": "Conversation",
"composerPlaceholder": "Répondre à l'utilisateur"
"composerPlaceholder": "Répondre à l'utilisateur",
"send": "Envoyer"
},
"handoffs": {
"empty": "Il n'y a présentement aucune escalation grâce à vous et votre équipe."
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { AxiosInstance } from 'axios'
import _ from 'lodash'
import moment from 'moment'

import { Config } from '../../config'
import { IAgent, IComment, IEvent, IHandoff } from '../../types'
import { Config } from '../config'
import { IAgent, IComment, IEvent, IHandoff } from '../types'

// TODO Handle casting when object is undefined
function castDate<T extends object>(object: T, paths: string[]): T {
paths.map(path => {
_.get(object, path, false) && _.set(object, path, moment(_.get(object, path)).toDate())
paths.forEach(path => {
const dateStr = _.get(object, path, false)
dateStr && _.set(object, path, new Date(dateStr))
})
return object
}
Expand Down Expand Up @@ -38,7 +38,7 @@ export function castHandoff(item: IHandoff) {
export function castComment(item: IComment) {
return castDate(item, ['createdAt', 'updatedAt'])
}
export interface ApiType {
export interface HitlClient {
getConfig: () => Promise<Config>
setOnline: (online: boolean) => Promise<{ online: boolean }>
getAgents: (online?: boolean) => Promise<IAgent[]>
Expand All @@ -53,7 +53,7 @@ export interface ApiType {
getMessages: (id: string, column?: string, desc?: boolean, limit?: number) => Promise<IEvent[]>
}

export const Api = (bp: { axios: AxiosInstance }): ApiType => {
export const makeClient = (bp: { axios: AxiosInstance }): HitlClient => {
const config = {
baseURL: bp.axios.defaults.baseURL.concat('/mod/hitlnext')
}
Expand Down
4 changes: 2 additions & 2 deletions modules/hitlnext/src/views/full/AgentStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import { AxiosInstance } from 'axios'
import React, { FC, useContext, useEffect } from 'react'

import { WEBSOCKET_TOPIC } from '../../constants'
import { makeClient } from '../client'

import { ISocketMessage } from './../../types'
import { Context, Store } from './agentStatus/Store'
import AgentIcon from './shared/components/AgentIcon'
import { Api } from './Api'

interface Props {
bp: { axios: AxiosInstance; events: any }
}

const AgentStatus: FC<Props> = ({ bp }) => {
const api = Api(bp)
const api = makeClient(bp)

const { state, dispatch } = useContext(Context)

Expand Down
4 changes: 2 additions & 2 deletions modules/hitlnext/src/views/full/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import _ from 'lodash'
import React, { FC, useContext, useEffect, useState } from 'react'

import { IHandoff, ISocketMessage } from '../../types'
import { castHandoff, makeClient } from '../client'

import { WEBSOCKET_TOPIC } from './../../constants'
import AgentList from './app/components/AgentList'
Expand All @@ -14,14 +15,13 @@ import EmptyConversation from './app/components/EmptyConversation'
import HandoffList from './app/components/HandoffList'
import { Context, Store } from './app/Store'
import style from './style.scss'
import { Api, castHandoff } from './Api'

interface Props {
bp: { axios: AxiosInstance; events: any }
}

const App: FC<Props> = ({ bp }) => {
const api = Api(bp)
const api = makeClient(bp)

const { state, dispatch } = useContext(Context)

Expand Down
4 changes: 2 additions & 2 deletions modules/hitlnext/src/views/full/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ import _ from 'lodash'
import React, { FC, useContext, useEffect, useState } from 'react'

import { WEBSOCKET_TOPIC } from '../../constants'
import { castHandoff, makeClient } from '../client'

import { IHandoff, ISocketMessage } from './../../types'
import AgentList from './studio-sidebar/components/AgentList'
import HandoffList from './studio-sidebar/components/HandoffList'
import { Context, Store } from './studio-sidebar/Store'
import styles from './style.scss'
import { Api, castHandoff } from './Api'

interface Props {
bp: { axios: AxiosInstance; events: any }
close: Function
}

const Sidebar: FC<Props> = ({ bp, close }) => {
const api = Api(bp)
const api = makeClient(bp)

const { state, dispatch } = useContext(Context)

Expand Down
4 changes: 2 additions & 2 deletions modules/hitlnext/src/views/full/app/components/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import _ from 'lodash'
import React, { FC, Fragment, useContext, useState } from 'react'

import { IHandoff } from '../../../../types'
import { ApiType } from '../../Api'
import { HitlClient } from '../../../client'
import { Context } from '../Store'

import Comment from './Comment'
import CommentForm from './CommentForm'

interface Props {
api: ApiType
api: HitlClient
handoff: IHandoff
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import cx from 'classnames'
import _ from 'lodash'
import React, { FC, Fragment, useContext } from 'react'

import { HitlClient } from '../../../client'
import style from '../../style.scss'
import { ApiType } from '../../Api'
import { Context } from '../Store'

import ConversationDetails from './ConversationDetails'
import ConversationHistory from './ConversationHistory'
import LiveChat from './LiveChat'

interface Props {
api: ApiType
api: HitlClient
bp: { axios: AxiosInstance; events: any }
}

Expand Down Expand Up @@ -67,58 +67,61 @@ const ConversationContainer: FC<Props> = ({ api, bp }) => {
selectedHandoff.status === 'assigned' &&
selectedHandoff.agentId === state.currentAgent.agentId

const liveChatButtons = () => [
{
content: (
<Button
className={style.coversationButton}
minimal
rightIcon="tick-circle"
onClick={handleResolve}
text={lang.tr('module.hitlnext.handoff.resolve')}
/>
)
},
state.config.enableConversationDeletion && {
content: (
<Button
className={style.coversationButton}
minimal
rightIcon="delete"
onClick={handleDeleteConversation}
text={lang.tr('module.hitlnext.conversation.delete')}
/>
)
}
]

const historyButtons = () => [
{
content: (
<Button
className={style.coversationButton}
minimal
rightIcon="following"
disabled={
!(selectedHandoff.status === 'pending' && currentAgentHasPermission('write') && state.currentAgent.online)
}
onClick={handleAssign}
text={lang.tr('module.hitlnext.handoff.assign')}
/>
)
},
state.config.enableConversationDeletion && selectedHandoff.status === 'resolved' && {
content: (
<Button
className={style.coversationButton}
minimal
rightIcon="delete"
onClick={handleDeleteConversation}
text={lang.tr('module.hitlnext.conversation.delete')}
/>
)
}
]
const liveChatButtons = () =>
[
{
content: (
<Button
className={style.coversationButton}
minimal
rightIcon="tick-circle"
onClick={handleResolve}
text={lang.tr('module.hitlnext.handoff.resolve')}
/>
)
},
state.config.enableConversationDeletion && {
content: (
<Button
className={style.coversationButton}
minimal
rightIcon="delete"
onClick={handleDeleteConversation}
text={lang.tr('module.hitlnext.conversation.delete')}
/>
)
}
].filter(Boolean)

const historyButtons = () =>
[
{
content: (
<Button
className={style.coversationButton}
minimal
rightIcon="following"
disabled={
!(selectedHandoff.status === 'pending' && currentAgentHasPermission('write') && state.currentAgent.online)
}
onClick={handleAssign}
text={lang.tr('module.hitlnext.handoff.assign')}
/>
)
},
state.config.enableConversationDeletion &&
selectedHandoff.status === 'resolved' && {
content: (
<Button
className={style.coversationButton}
minimal
rightIcon="delete"
onClick={handleDeleteConversation}
text={lang.tr('module.hitlnext.conversation.delete')}
/>
)
}
].filter(Boolean)

const content = shouldRenderLiveChat ? (
<LiveChat handoff={selectedHandoff} currentAgent={state.currentAgent} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import _ from 'lodash'
import React, { FC } from 'react'

import { IHandoff } from '../../../../types'
import { HitlClient } from '../../../client'
import style from '../../style.scss'
import { ApiType } from '../../Api'

import { Comments } from './Comments'
import { Tags } from './Tags'
import UserProfile from './UserProfile'

interface Props {
api: ApiType
api: HitlClient
handoff: IHandoff
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import React, { FC, Fragment, useContext, useEffect, useState } from 'react'

import { WEBSOCKET_TOPIC } from '../../../../constants'
import { ISocketMessage } from '../../../../types'
import { HitlClient } from '../../../client'
import MessageList from '../../../lite/MessageList'
import { ApiType } from '../../Api'
import { Context } from '../Store'

interface Props {
api: ApiType
api: HitlClient
bp: { axios: AxiosInstance; events: any }
conversationId: string
}
Expand Down
11 changes: 10 additions & 1 deletion modules/hitlnext/src/views/full/app/components/LiveChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Spinner } from '@blueprintjs/core'
import { lang } from 'botpress/shared'
import React, { useEffect, useState } from 'react'

import { MODULE_NAME } from '../../../../constants'
import { IAgent, IHandoff } from '../../../../types'
import style from '../../style.scss'

Expand Down Expand Up @@ -55,7 +56,15 @@ const LiveChat: React.FC<Props> = ({ handoff, currentAgent }) => {
enableConversationDeletion: false,
closeOnEscape: false,
composerPlaceholder: lang.tr('module.hitlnext.conversation.composerPlaceholder'),
stylesheet: 'assets/modules/hitlnext/webchat-theme.css'
stylesheet: 'assets/modules/hitlnext/webchat-theme.css',
overrides: {
composer: [
{
module: MODULE_NAME,
component: 'ShortcutComposer'
}
]
}
}
window.botpressWebChat.init(webchatConfig, `#${WRAPPER_ID}`)
window.addEventListener('message', webchatEventListener)
Expand Down
4 changes: 2 additions & 2 deletions modules/hitlnext/src/views/full/app/components/Tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import _ from 'lodash'
import React, { FC, useContext, useState } from 'react'

import { IHandoff } from '../../../../types'
import { ApiType } from '../../Api'
import { HitlClient } from '../../../client'
import { Context } from '../Store'

interface Props {
api: ApiType
api: HitlClient
handoff: IHandoff
}

Expand Down

0 comments on commit 54068b6

Please sign in to comment.