Skip to content

Commit

Permalink
fix(hitl): add support for all types of messages (#5736)
Browse files Browse the repository at this point in the history
* fix(hitl): add support for all types of messages

* use csstype v2

* fix card display and agent messages

* removed some temp hack + refactoring
  • Loading branch information
laurentlp committed Dec 10, 2021
1 parent 8c6adff commit dac5e3e
Show file tree
Hide file tree
Showing 6 changed files with 401 additions and 164 deletions.
10 changes: 9 additions & 1 deletion modules/hitl/assets/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@
}

.bph-chat-bubble {
max-width: 60%;
max-width: 50%;

border: 1px solid #f1f1f1;
background-color: rgba(255, 255, 255, 0.9);
Expand All @@ -254,6 +254,14 @@
word-break: break-word;
}

.bph-chat-bubble.card {
min-width: 50%;
}

.bph-chat-bubble p {
margin: 0px;
}

.bph-chat-bubble img {
max-height: 240px;
max-width: 100%;
Expand Down
6 changes: 3 additions & 3 deletions modules/hitl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@
"@types/react-bootstrap": "^0.32.19"
},
"dependencies": {
"@botpress/messaging-components": "^0.0.8",
"bluebird": "^3.5.3",
"classnames": "^2.2.6",
"csstype": "^2.2.0",
"lodash": "^4.17.19",
"mime": "^2.4.4",
"moment": "^2.24.0",
"react-audio-player": "^0.11.0"
"moment": "^2.24.0"
},
"resolutions": {
"fstream": ">=1.0.12",
Expand Down
40 changes: 0 additions & 40 deletions modules/hitl/src/backend/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,42 +159,6 @@ export default class HitlDb {
}
}

formatMessage = event => {
// Convert messenger payloads to HITL-compatible format
if (event.channel === 'messenger' && event.payload.quick_replies) {
return {
type: 'custom',
raw_message: {
type: 'custom',
module: 'channel-messenger',
component: 'QuickReplies',
quick_replies: event.payload.quick_replies,
wrapped: { type: 'text', ..._.omit(event.payload, 'quick_replies') }
}
}
} else if (event.channel === 'messenger' && _.get(event.payload, 'attachment.payload.elements')) {
return {
type: 'carousel',
raw_message: {
text: ' ',
type: 'carousel',
elements: _.get(event.payload, 'attachment.payload.elements').map(card => ({
title: card.title,
picture: card.image_url,
subtitle: card.subtitle,
buttons: card.buttons.map(a => ({
...a,
type: a.type === 'web_url' ? 'open_url' : a.type
}))
})),
fromMessenger: true
}
}
}

return { type: event.type, raw_message: event.payload }
}

async appendMessageToSession(event: sdk.IO.Event, sessionId: string, direction: string) {
const payload = event.payload || {}
const text = event.preview || payload.text || (payload.wrapped && payload.wrapped.text)
Expand All @@ -214,10 +178,6 @@ export default class HitlDb {
ts: new Date()
}

const { type, raw_message } = this.formatMessage(event)
message.type = type
message.raw_message = raw_message

return Bluebird.join(
this.knex(TABLE_NAME_MESSAGES).insert({
...message,
Expand Down
133 changes: 25 additions & 108 deletions modules/hitl/src/views/full/components/messages/Message.tsx
Original file line number Diff line number Diff line change
@@ -1,119 +1,33 @@
import ReactMessageRenderer, { defaultMessageConfig } from '@botpress/messaging-components'
import classnames from 'classnames'
import _ from 'lodash'
import mimeTypes from 'mime/lite'
import moment from 'moment'
import path from 'path'
import React from 'react'
import ReactAudioPlayer from 'react-audio-player'

import { Message as HitlMessage } from '../../../../backend/typings'
import SVGIcon from '../SVGIcon'

const validMessageTypes = [
'text',
'message',
'image',
'video',
'audio',
'quick_reply',
'custom',
'visit',
'file',
'carousel'
]

const getComponent = (moduleName: string, componentName: string) =>
window.botpress[moduleName] && window.botpress[moduleName][componentName]

export default class Message extends React.Component<{ message: HitlMessage }> {
renderFile() {
const { type, raw_message, text } = this.props.message

const url = _.get(raw_message, 'url', text)
const name = _.get(raw_message, 'name', '')

const extension = path.extname(url)
const mime = mimeTypes.getType(extension)

if (type === 'image' || mime.includes('image/')) {
return (
<a href={url} target={'_blank'}>
<img src={url} title={name} />
</a>
)
} else if (type === 'audio' || mime.includes('audio/')) {
return <ReactAudioPlayer src={this.props.message.text} />
} else if (type === 'video' || mime.includes('video/')) {
return (
<video controls>
<source src={this.props.message.text} type="video/mp4" />
</video>
)
}

return <p>Unsupported media</p>
}

renderText(text?: string) {
const Text = getComponent('channel-web', 'Text')
return Text ? <Text markdown={true} text={text || this.props.message.text} /> : <span>{text}</span>
}

renderDropdown() {
const Dropdown = getComponent('channel-web', 'Dropdown')
return Dropdown ? (
<Dropdown isLastGroup={true} isLastOfGroup={true} options={this.props.message.raw_message.options} />
) : null
}

renderCarousel() {
const Carousel = getComponent('channel-web', 'Carousel')
return Carousel ? (
<Carousel style={{ maxWidth: '400px' }} carousel={this.props.message.raw_message} />
) : (
<span>Could not display carousel</span>
)
}

renderQuickReply(quickReplies) {
const Keyboard = getComponent('channel-web', 'Keyboard')
const QuickReplies = getComponent('channel-web', 'QuickReplies')

return Keyboard && QuickReplies ? (
<span>
<div style={{ paddingBottom: 10 }}>{this.renderText()}</div>
<Keyboard.Default />
<QuickReplies quick_replies={quickReplies} isLastGroup={true} isLastOfGroup={true} />
</span>
) : (
<span>Could not display quick replies</span>
)
}

renderContent() {
const { type, raw_message } = this.props.message

if (type === 'message' || type === 'text' || type === 'quick_reply') {
return this.renderText()
} else if (type === 'image' || type === 'file' || type === 'video' || type === 'audio') {
return this.renderFile()
} else if (type === 'custom' && raw_message) {
if (raw_message.component === 'Dropdown' && raw_message.options) {
return this.renderDropdown()
} else if (raw_message.component === 'QuickReplies' && raw_message.quick_replies) {
return this.renderQuickReply(raw_message.quick_replies)
}
let { raw_message: payload } = this.props.message
const { ts, source } = this.props.message

return (
<span>
Bot sent custom component: [Module: {raw_message.module}, Component: {raw_message.component}]
</span>
)
} else if (type === 'carousel') {
return this.renderCarousel()
if (!payload.type) {
payload = { ...payload, type: 'text' }
}

return <span>Cannot display this message</span>
return (
<ReactMessageRenderer
content={payload}
config={{
...defaultMessageConfig,
isBotMessage: source !== 'user',
isLastGroup: false,
isLastOfGroup: false,
sentOn: ts
}}
/>
)
}

renderMessage() {
Expand All @@ -122,12 +36,19 @@ export default class Message extends React.Component<{ message: HitlMessage }> {

let messageFrom = 'bot'
if (direction === 'in') {
// TODO: Visit and session reset are currently unsupported by @botpress/messaging-components
if (type === 'visit') {
return (
<div className={classnames('bph-message', 'bph-from-system')}>
<p>User visit: {date}</p>
</div>
)
} else if (type === 'session_reset') {
return (
<div className={classnames('bph-message', 'bph-from-system')}>
<p>Reset the conversation</p>
</div>
)
}
messageFrom = 'user'
} else if (source === 'agent') {
Expand All @@ -150,18 +71,14 @@ export default class Message extends React.Component<{ message: HitlMessage }> {
>
{messageFrom === 'user' && avatar}
<div className="bph-message-container">
<div className="bph-chat-bubble">{this.renderContent()}</div>
<div className={classnames('bph-chat-bubble', { ['card']: type === 'card' })}>{this.renderContent()}</div>
</div>
{messageFrom !== 'user' && avatar}
</div>
)
}

render() {
if (!validMessageTypes.includes(this.props.message.type)) {
return null
}

return this.renderMessage()
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import _ from 'lodash'
import moment from 'moment'
import React, { FC } from 'react'
import { renderPayload } from '../../../../../../../packages/ui-shared-lite/Payloads'

import { Message as HitlMessage } from '../../../../backend/typings'

Expand All @@ -26,7 +25,7 @@ class MessageWrapper extends React.Component<{ message: any }> {
return <p className="bph-chat-error">* Cannot display message *</p>
}

return <Message message={{ ...this.props.message, raw_message: renderPayload(this.props.message.raw_message) }} />
return <Message message={this.props.message} />
}
}

Expand Down

0 comments on commit dac5e3e

Please sign in to comment.