Skip to content

Commit 3b2c7f7

Browse files
authored
Merge pull request #250 from lyjeileen/message-space
fix: update message handling
2 parents c4db51f + 7632ba7 commit 3b2c7f7

19 files changed

+408
-163
lines changed

src/components/elementRenderer/elementRenderer.cy.tsx

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import React from 'react'
2+
import { v4 as getUUID } from 'uuid'
23

34
import {
45
supportedViewports,
56
testUser,
67
} from '../../../cypress/support/variables'
7-
import { YoutubeVideo } from '..'
8+
import { StreamingText, YoutubeVideo } from '..'
89
import Text from '../text/text'
910
import ElementRenderer from './elementRenderer'
1011

1112
const supportedElements = {
1213
text: Text,
1314
video: YoutubeVideo,
15+
streamingText: StreamingText,
1416
}
1517

1618
const sampleMessage = {
@@ -38,23 +40,27 @@ describe('ElementRenderer', () => {
3840
cy.viewport(viewport)
3941
cy.mount(
4042
<ElementRenderer
41-
message={{
42-
...sampleMessage,
43-
data: { text: 'Test Text' },
44-
format: 'text',
45-
}}
43+
messages={[
44+
{
45+
...sampleMessage,
46+
data: { text: 'Test Text' },
47+
format: 'text',
48+
},
49+
]}
4650
{...commonProps}
4751
ws={mockWsClient}
4852
/>
4953
)
5054
cy.get('p').should('contain.text', 'Test Text')
5155
cy.mount(
5256
<ElementRenderer
53-
message={{
54-
...sampleMessage,
55-
data: { youtubeVideoId: 'MtN1YnoL46Q' },
56-
format: 'video',
57-
}}
57+
messages={[
58+
{
59+
...sampleMessage,
60+
data: { youtubeVideoId: 'MtN1YnoL46Q' },
61+
format: 'video',
62+
},
63+
]}
5864
{...commonProps}
5965
ws={mockWsClient}
6066
/>
@@ -71,6 +77,60 @@ describe('ElementRenderer', () => {
7177
})
7278
})
7379

80+
it(`renders the original message and its update messages correctly on ${viewport} screen`, () => {
81+
const mockWsClient = {
82+
send: cy.stub(),
83+
close: cy.stub(),
84+
reconnect: cy.stub(),
85+
}
86+
87+
cy.viewport(viewport)
88+
cy.mount(
89+
<ElementRenderer
90+
messages={[
91+
{
92+
...sampleMessage,
93+
data: { text: 'This' },
94+
format: 'streamingText',
95+
},
96+
{
97+
id: getUUID(),
98+
timestamp: '2020-01-02T00:00:00.000Z',
99+
sender: testUser,
100+
conversationId: 'lkd9vc',
101+
topic: 'default',
102+
threadId: '1',
103+
data: { text: ' is' },
104+
format: 'streamingText',
105+
},
106+
{
107+
id: getUUID(),
108+
timestamp: '2020-01-02T00:00:00.000Z',
109+
sender: testUser,
110+
conversationId: 'lkd9vc',
111+
topic: 'default',
112+
threadId: '1',
113+
data: { text: ' streaming' },
114+
format: 'streamingText',
115+
},
116+
{
117+
id: getUUID(),
118+
timestamp: '2020-01-02T00:00:00.000Z',
119+
sender: testUser,
120+
conversationId: 'lkd9vc',
121+
topic: 'default',
122+
threadId: '1',
123+
data: { text: ' text.' },
124+
format: 'streamingText',
125+
},
126+
]}
127+
{...commonProps}
128+
ws={mockWsClient}
129+
/>
130+
)
131+
cy.get('p').should('contain.text', 'This is streaming text.')
132+
})
133+
74134
it(`renders a message for an unsupported format on ${viewport} screen`, () => {
75135
const mockWsClient = {
76136
send: cy.stub(),
@@ -81,11 +141,13 @@ describe('ElementRenderer', () => {
81141
cy.viewport(viewport)
82142
cy.mount(
83143
<ElementRenderer
84-
message={{
85-
...sampleMessage,
86-
data: { text: 'Test Text' },
87-
format: 'unsupported',
88-
}}
144+
messages={[
145+
{
146+
...sampleMessage,
147+
data: { text: 'Test Text' },
148+
format: 'unsupported',
149+
},
150+
]}
89151
{...commonProps}
90152
ws={mockWsClient}
91153
/>

src/components/elementRenderer/elementRenderer.tsx

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,36 @@
11
import Typography from '@mui/material/Typography'
22
import React from 'react'
33

4-
import type {
5-
ComponentMap,
6-
Sender,
7-
ThreadableMessage,
8-
WebSocketClient,
9-
} from '../types'
4+
import type { ComponentMap, Message, Sender, WebSocketClient } from '../types'
105

116
interface ElementRendererProps {
127
sender: Sender
138
ws: WebSocketClient
14-
message: ThreadableMessage
9+
messages: Message[]
1510
supportedElements: ComponentMap
1611
}
1712

1813
const ElementRenderer = (props: ElementRendererProps) => {
19-
const MaybeElement = props.supportedElements[props.message.format]
14+
const rootMessage = props.messages[0]
15+
const updateMessages = props.messages.slice(1)
2016

17+
const MaybeElement = props.supportedElements[rootMessage.format]
2118
return (
2219
<>
2320
{MaybeElement ? (
2421
React.createElement(MaybeElement, {
2522
sender: props.sender,
2623
ws: props.ws,
27-
messageId: props.message.id,
28-
conversationId: props.message.conversationId,
29-
...props.message.data,
30-
...(props.message.threadMessagesData && {
31-
updatedData: props.message.threadMessagesData,
24+
messageId: rootMessage.id,
25+
conversationId: rootMessage.conversationId,
26+
...rootMessage.data,
27+
...(updateMessages.length > 0 && {
28+
updatedData: updateMessages.map((message) => message.data),
3229
}),
3330
})
3431
) : (
3532
<Typography variant="body2">
36-
Unsupported element format: {props.message.format}
33+
Unsupported element format: {rootMessage.format}
3734
</Typography>
3835
)}
3936
</>

src/components/input/textInput/textInput.stories.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ meta.argTypes = {
5858
'A websocket client with supports the following methods:\n' +
5959
'send: (msg: Message) => void\n' +
6060
'close: () => void\n' +
61-
'reconnect: () => void',
61+
'reconnect: () => void\n' +
62+
'onReceive?: (handler: (message: Message) => void) => void',
6263
},
6364
},
6465
},

src/components/messageCanvas/actions/action.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import Tooltip from '@mui/material/Tooltip'
55
import type { ReactNode } from 'react'
66
import React from 'react'
77

8-
import type { ThreadableMessage } from '../../types'
8+
import type { Message } from '../../types'
99

1010
interface ActionProps {
1111
label: string
12-
message: ThreadableMessage
12+
message: Message
1313
icon: ReactNode
14-
onClick: (message: ThreadableMessage) => void
14+
onClick: (message: Message) => void
1515
}
1616

1717
export default function Action(props: ActionProps) {

src/components/messageCanvas/actions/copy/copyText.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import React, { useState } from 'react'
22

33
import Icon from '../../../icon/icon'
4-
import type { ThreadableMessage } from '../../../types'
4+
import type { Message } from '../../../types'
55
import Action from '../index'
66

77
export interface CopyTextProps {
8-
message: ThreadableMessage
8+
message: Message
99
}
1010

1111
export default function CopyText(props: CopyTextProps) {
1212
const [tooltipContent, setTooltipContent] = useState('Copy text')
1313
const twoSeconds = 2000
1414

15-
function handleOnClick(message: ThreadableMessage) {
15+
function handleOnClick(message: Message) {
1616
if (message.data.text) {
1717
navigator.clipboard
1818
.writeText(message.data.text)

src/components/messageCanvas/actions/textToSpeech/textToSpeech.cy.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { ThreadableMessage } from '../../../types' // Adjust if needed
1+
import type { Message } from '../../../types' // Adjust if needed
22
import TextToSpeech from './textToSpeech'
33

44
describe('TextToSpeech Component', () => {
5-
const mockMessage: ThreadableMessage = {
5+
const mockMessage: Message = {
66
id: '1',
77
timestamp: '2020-01-02T00:00:00.000Z',
88
conversationId: 'lkd9vc',

src/components/messageCanvas/actions/textToSpeech/textToSpeech.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React, { useState } from 'react'
22

33
import Icon from '../../../icon/icon'
4-
import type { ThreadableMessage } from '../../../types'
4+
import type { Message } from '../../../types'
55
import Action from '../index'
66

77
export interface TextToSpeechProps {
8-
message: ThreadableMessage
8+
message: Message
99
}
1010

1111
export default function TextToSpeech(props: TextToSpeechProps) {

src/components/messageCanvas/messageCanvas.cy.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
testUser,
77
} from '../../../cypress/support/variables'
88
import Icon from '../icon/icon'
9-
import type { ThreadableMessage } from '../types'
9+
import type { Message } from '../types'
1010
import CopyText from './actions/copy/copyText'
1111
import MessageCanvas from './messageCanvas'
1212

@@ -43,7 +43,7 @@ describe('MessageCanvas', () => {
4343
cy.mount(
4444
<MessageCanvas
4545
message={testMessage}
46-
getActionsComponent={(message: ThreadableMessage) => {
46+
getActionsComponent={(message: Message) => {
4747
const copyButton = message.format === 'text' && (
4848
<CopyText message={message} />
4949
)

src/components/messageCanvas/messageCanvas.stories.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import Typography from '@mui/material/Typography'
22
import React from 'react'
33

4-
import { ElementRenderer, MarkedMarkdown, type ThreadableMessage } from '..'
4+
import ElementRenderer from '../elementRenderer'
55
import Icon from '../icon/icon'
6+
import MarkedMarkdown from '../markdown'
67
import Text from '../text/text'
8+
import type { Message } from '../types'
79
import CopyText from './actions/copy/copyText'
810
import TextToSpeech from './actions/textToSpeech/textToSpeech'
911
import MessageCanvas from './messageCanvas'
@@ -111,7 +113,7 @@ const elementRendererString = `<ElementRenderer
111113
supportedElements={{ text: Text }}
112114
/>`
113115

114-
const profileString = `(message: ThreadableMessage) => {
116+
const profileString = `(message: Message) => {
115117
<>
116118
{getProfileIcon(message)}
117119
<Typography variant="body1" color="text.secondary">
@@ -120,23 +122,23 @@ const profileString = `(message: ThreadableMessage) => {
120122
</>
121123
}`
122124

123-
function getProfileIcon(message: ThreadableMessage) {
125+
function getProfileIcon(message: Message) {
124126
if (message.sender.name?.includes('agent')) {
125127
return <Icon name="smart_toy" />
126128
} else {
127129
return <Icon name="account_circle" />
128130
}
129131
}
130132

131-
function getProfileName(message: ThreadableMessage) {
133+
function getProfileName(message: Message) {
132134
return (
133135
<Typography variant="body1" color="text.secondary">
134136
{message.sender.name}
135137
</Typography>
136138
)
137139
}
138140

139-
function getProfileIconAndName(message: ThreadableMessage) {
141+
function getProfileIconAndName(message: Message) {
140142
return (
141143
<>
142144
{getProfileIcon(message)}
@@ -149,7 +151,7 @@ export const WithProfileIcon = {
149151
args: {
150152
children: (
151153
<ElementRenderer
152-
message={messageFromHuman}
154+
messages={[messageFromHuman]}
153155
supportedElements={{ text: Text }}
154156
{...commonElementRendererProps}
155157
/>
@@ -175,7 +177,7 @@ export const NoIcon = {
175177
args: {
176178
children: (
177179
<ElementRenderer
178-
message={messageFromAgent}
180+
messages={[messageFromAgent]}
179181
supportedElements={{ text: Text }}
180182
{...commonElementRendererProps}
181183
/>
@@ -201,14 +203,14 @@ export const WithCopyIcon = {
201203
args: {
202204
children: (
203205
<ElementRenderer
204-
message={messageFromHuman}
206+
messages={[messageFromHuman]}
205207
supportedElements={{ text: Text }}
206208
{...commonElementRendererProps}
207209
/>
208210
),
209211
message: messageFromHuman,
210212
getProfileComponent: getProfileIconAndName,
211-
getActionsComponent: (message: ThreadableMessage) => {
213+
getActionsComponent: (message: Message) => {
212214
const copyButton = message.format === 'text' && (
213215
<CopyText message={message} />
214216
)
@@ -222,7 +224,7 @@ export const WithCopyIcon = {
222224
source: {
223225
code: `<MessageCanvas
224226
getProfileComponent={${profileString}}
225-
getActionsComponent={(message: ThreadableMessage) => {
227+
getActionsComponent={(message: Message) => {
226228
const copyButton = message.format === 'text' && <CopyText message={message} />
227229
if (copyButton) {
228230
return <>{copyButton}</>
@@ -241,14 +243,14 @@ export const WithTextToSpeech = {
241243
args: {
242244
children: (
243245
<ElementRenderer
244-
message={markdownMessage}
246+
messages={[markdownMessage]}
245247
supportedElements={{ markdown: MarkedMarkdown }}
246248
{...commonElementRendererProps}
247249
/>
248250
),
249251
message: markdownMessage,
250252
getProfileComponent: getProfileIconAndName,
251-
getActionsComponent: (message: ThreadableMessage) => {
253+
getActionsComponent: (message: Message) => {
252254
return (
253255
<>
254256
<TextToSpeech message={message} />
@@ -265,7 +267,7 @@ export const WithTextToSpeech = {
265267
source: {
266268
code: `<MessageCanvas
267269
getProfileComponent={${profileString}}
268-
getActionsComponent={(message: ThreadableMessage) => {
270+
getActionsComponent={(message: Message) => {
269271
const copyButton = message.format === 'text' && <CopyText message={message} />
270272
if (copyButton) {
271273
return <>{copyButton}</>

0 commit comments

Comments
 (0)