@linktr.ee/messaging-react 1.40.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Card-A0lkei-S.js → Card-BKP9ml9O.js} +2 -2
- package/dist/{Card-A0lkei-S.js.map → Card-BKP9ml9O.js.map} +1 -1
- package/dist/{Card-DXoAKkv0.js → Card-Bk_4lVzP.js} +2 -2
- package/dist/{Card-DXoAKkv0.js.map → Card-Bk_4lVzP.js.map} +1 -1
- package/dist/assets/index.css +1 -1
- package/dist/index-Bex7eg3v.js +3092 -0
- package/dist/index-Bex7eg3v.js.map +1 -0
- package/dist/index.d.ts +22 -2
- package/dist/index.js +12 -10
- package/package.json +2 -2
- package/src/components/ChannelInfoDialog/ChannelInfoDialog.test.tsx +2 -14
- package/src/components/ChannelInfoDialog/index.tsx +4 -8
- package/src/components/ChannelList/ChannelListContext.tsx +2 -0
- package/src/components/ChannelList/CustomChannelPreview.tsx +14 -3
- package/src/components/ChannelList/index.tsx +9 -1
- package/src/components/ChannelView.test.tsx +11 -0
- package/src/components/ChannelView.tsx +44 -33
- package/src/components/CustomMessage/index.tsx +22 -4
- package/src/components/CustomTypingIndicator/CustomTypingIndicator.stories.tsx +57 -17
- package/src/components/CustomTypingIndicator/CustomTypingIndicator.test.tsx +187 -0
- package/src/components/CustomTypingIndicator/DmAgentContext.ts +3 -0
- package/src/components/CustomTypingIndicator/index.tsx +101 -37
- package/src/components/MessagingShell/index.tsx +4 -4
- package/src/index.ts +13 -2
- package/src/stories/mocks.tsx +2 -9
- package/src/styles.css +7 -0
- package/src/types.ts +11 -1
- package/src/utils/getMessageDisplayText.test.ts +44 -0
- package/src/utils/getMessageDisplayText.ts +27 -0
- package/dist/index-B_PLgcDi.js +0 -2994
- package/dist/index-B_PLgcDi.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -101,6 +101,11 @@ export declare interface ChannelListProps {
|
|
|
101
101
|
sort?: ChannelSort;
|
|
102
102
|
customEmptyStateIndicator?: React.ComponentType<EmptyStateIndicatorProps>;
|
|
103
103
|
renderMessagePreview?: (message: LocalMessage | undefined, defaultPreview?: string) => React.ReactNode;
|
|
104
|
+
/**
|
|
105
|
+
* Language code used to pick translated message text from Stream Chat i18n.
|
|
106
|
+
* Falls back to message.text when no matching translation exists.
|
|
107
|
+
*/
|
|
108
|
+
viewerLanguage?: string;
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
/**
|
|
@@ -112,7 +117,7 @@ export declare const ChannelView: default_2.NamedExoticComponent<ChannelViewProp
|
|
|
112
117
|
* Props that MessagingShell passes through to ChannelView.
|
|
113
118
|
* ChannelViewProps is the source of truth for these props.
|
|
114
119
|
*/
|
|
115
|
-
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onDeleteConversationClick' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'messageMetadata' | 'onMessageSent' | 'showStarButton' | 'chatbotVotingEnabled' | 'renderChannelBanner' | 'customProfileContent' | 'customChannelActions' | 'renderMessage' | 'sendButton'>;
|
|
120
|
+
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onDeleteConversationClick' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'messageMetadata' | 'onMessageSent' | 'showStarButton' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderChannelBanner' | 'customProfileContent' | 'customChannelActions' | 'renderMessage' | 'sendButton'>;
|
|
116
121
|
|
|
117
122
|
/**
|
|
118
123
|
* ChannelView component props
|
|
@@ -185,6 +190,11 @@ export declare interface ChannelViewProps {
|
|
|
185
190
|
* Defaults to false.
|
|
186
191
|
*/
|
|
187
192
|
chatbotVotingEnabled?: boolean;
|
|
193
|
+
/**
|
|
194
|
+
* Language code used to pick translated message text from Stream Chat i18n.
|
|
195
|
+
* Falls back to message.text when no matching translation exists.
|
|
196
|
+
*/
|
|
197
|
+
viewerLanguage?: string;
|
|
188
198
|
/**
|
|
189
199
|
* Custom render function for a banner/card component that renders
|
|
190
200
|
* between the channel header and message list.
|
|
@@ -283,6 +293,11 @@ export declare interface FaqListProps {
|
|
|
283
293
|
*/
|
|
284
294
|
export declare const formatRelativeTime: (date: Date) => string;
|
|
285
295
|
|
|
296
|
+
export declare function getMessageDisplayText({ message, viewerLanguage, }: {
|
|
297
|
+
message?: MessageWithI18n | null;
|
|
298
|
+
viewerLanguage?: string;
|
|
299
|
+
}): string | undefined;
|
|
300
|
+
|
|
286
301
|
export declare function isLinkAttachment(a: Attachment): boolean;
|
|
287
302
|
|
|
288
303
|
export declare const LockedAttachment: {
|
|
@@ -358,6 +373,10 @@ declare interface MessageVoteButtonsProps {
|
|
|
358
373
|
onVoteDown: () => void;
|
|
359
374
|
}
|
|
360
375
|
|
|
376
|
+
declare type MessageWithI18n = Pick<LocalMessage, 'text'> & {
|
|
377
|
+
i18n?: Record<string, string> | null;
|
|
378
|
+
};
|
|
379
|
+
|
|
361
380
|
/**
|
|
362
381
|
* Messaging capabilities configuration
|
|
363
382
|
*/
|
|
@@ -462,13 +481,14 @@ export declare interface MessagingShellProps extends ChannelViewPassthroughProps
|
|
|
462
481
|
renderMessagePreview?: (message: LocalMessage | undefined, defaultPreview?: string) => React.ReactNode;
|
|
463
482
|
}
|
|
464
483
|
|
|
484
|
+
export declare function normalizeLanguageCode(language?: string): string | undefined;
|
|
485
|
+
|
|
465
486
|
/**
|
|
466
487
|
* Generic participant interface for different host environments
|
|
467
488
|
*/
|
|
468
489
|
export declare interface Participant {
|
|
469
490
|
id: string;
|
|
470
491
|
name: string;
|
|
471
|
-
email?: string;
|
|
472
492
|
image?: string;
|
|
473
493
|
username?: string;
|
|
474
494
|
phone?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as e, c as t, C as i, d as n, e as o, f as g, F as m, g as r, L as M, M as l, h, i as
|
|
1
|
+
import { b as e, c as t, C as i, d as n, e as o, f as g, F as m, g as r, L as M, M as l, h as u, i as h, j as L, k as d, l as C, m as c, n as v, r as A, o as k, u as p, p as F, q as f } from "./index-Bex7eg3v.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ActionButton,
|
|
4
4
|
t as Avatar,
|
|
@@ -10,15 +10,17 @@ export {
|
|
|
10
10
|
r as FaqListItem,
|
|
11
11
|
M as LockedAttachment,
|
|
12
12
|
l as MediaMessage,
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
u as MessageVoteButtons,
|
|
14
|
+
h as MessagingProvider,
|
|
15
15
|
L as MessagingShell,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
A as
|
|
21
|
-
k as
|
|
22
|
-
|
|
16
|
+
d as formatRelativeTime,
|
|
17
|
+
C as getMessageDisplayText,
|
|
18
|
+
c as isLinkAttachment,
|
|
19
|
+
v as normalizeLanguageCode,
|
|
20
|
+
A as resolveLinkAttachment,
|
|
21
|
+
k as resolveMediaFromMessage,
|
|
22
|
+
p as useCustomMessage,
|
|
23
|
+
F as useMessageVote,
|
|
24
|
+
f as useMessaging
|
|
23
25
|
};
|
|
24
26
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "React messaging components built on messaging-core for web applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@linktr.ee/component-library": "11.8.6",
|
|
37
|
-
"@linktr.ee/messaging-core": "^
|
|
37
|
+
"@linktr.ee/messaging-core": "^2.0.0",
|
|
38
38
|
"@phosphor-icons/react": "^2.1.10"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
@@ -65,14 +65,13 @@ const createChannel = () =>
|
|
|
65
65
|
}) as unknown as Channel
|
|
66
66
|
|
|
67
67
|
const createParticipant = (
|
|
68
|
-
overrides: Partial<{ name: string;
|
|
68
|
+
overrides: Partial<{ name: string; username: string }> = {}
|
|
69
69
|
) =>
|
|
70
70
|
({
|
|
71
71
|
user: {
|
|
72
72
|
id: 'linker-1',
|
|
73
73
|
name: overrides.name ?? 'Linker',
|
|
74
74
|
image: undefined,
|
|
75
|
-
email: overrides.email,
|
|
76
75
|
username: overrides.username,
|
|
77
76
|
},
|
|
78
77
|
role: 'member',
|
|
@@ -99,18 +98,7 @@ describe('ChannelInfoDialog', () => {
|
|
|
99
98
|
expect(nameEl).toBeInTheDocument()
|
|
100
99
|
})
|
|
101
100
|
|
|
102
|
-
it('renders participant
|
|
103
|
-
renderWithProviders(
|
|
104
|
-
<ChannelInfoDialog
|
|
105
|
-
{...defaultProps()}
|
|
106
|
-
participant={createParticipant({ email: 'linker@example.com' })}
|
|
107
|
-
/>
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
expect(screen.getByText('linker@example.com')).toBeInTheDocument()
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('renders participant username as secondary info when no email', () => {
|
|
101
|
+
it('renders participant username as secondary info', () => {
|
|
114
102
|
renderWithProviders(
|
|
115
103
|
<ChannelInfoDialog
|
|
116
104
|
{...defaultProps()}
|
|
@@ -12,9 +12,8 @@ import ActionButton from '../ActionButton'
|
|
|
12
12
|
import { Avatar } from '../Avatar'
|
|
13
13
|
import { CloseButton } from '../CloseButton'
|
|
14
14
|
|
|
15
|
-
// Custom user type with
|
|
15
|
+
// Custom user type with username
|
|
16
16
|
type CustomUser = {
|
|
17
|
-
email?: string
|
|
18
17
|
username?: string
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -180,13 +179,10 @@ export const ChannelInfoDialog: React.FC<ChannelInfoDialogProps> = ({
|
|
|
180
179
|
const participantName =
|
|
181
180
|
participant.user?.name || participant.user?.id || 'Unknown member'
|
|
182
181
|
const participantImage = participant.user?.image
|
|
183
|
-
const participantEmail = (participant.user as CustomUser)?.email
|
|
184
182
|
const participantUsername = (participant.user as CustomUser)?.username
|
|
185
|
-
const participantSecondary =
|
|
186
|
-
?
|
|
187
|
-
:
|
|
188
|
-
? `linktr.ee/${participantUsername}`
|
|
189
|
-
: undefined
|
|
183
|
+
const participantSecondary = participantUsername
|
|
184
|
+
? `linktr.ee/${participantUsername}`
|
|
185
|
+
: undefined
|
|
190
186
|
const participantId = participant.user?.id || 'unknown'
|
|
191
187
|
|
|
192
188
|
return (
|
|
@@ -9,6 +9,7 @@ type ChannelListContextValue = {
|
|
|
9
9
|
message: LocalMessage | undefined,
|
|
10
10
|
defaultPreview?: string
|
|
11
11
|
) => React.ReactNode
|
|
12
|
+
viewerLanguage?: string
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const ChannelListContext = React.createContext<ChannelListContextValue>({
|
|
@@ -16,6 +17,7 @@ const ChannelListContext = React.createContext<ChannelListContextValue>({
|
|
|
16
17
|
onChannelSelect: () => {},
|
|
17
18
|
debug: false,
|
|
18
19
|
renderMessagePreview: undefined,
|
|
20
|
+
viewerLanguage: undefined,
|
|
19
21
|
})
|
|
20
22
|
|
|
21
23
|
export const ChannelListProvider = ChannelListContext.Provider
|
|
@@ -4,6 +4,7 @@ import { ChannelPreviewUIComponentProps } from 'stream-chat-react'
|
|
|
4
4
|
|
|
5
5
|
import { useChannelStar } from '../../hooks/useChannelStar'
|
|
6
6
|
import { formatRelativeTime } from '../../utils/formatRelativeTime'
|
|
7
|
+
import { getMessageDisplayText } from '../../utils/getMessageDisplayText'
|
|
7
8
|
import { Avatar } from '../Avatar'
|
|
8
9
|
import { isChatbotMessage } from '../CustomMessage/MessageTag'
|
|
9
10
|
|
|
@@ -14,8 +15,13 @@ import { useChannelListContext } from './ChannelListContext'
|
|
|
14
15
|
*/
|
|
15
16
|
const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
|
|
16
17
|
({ channel, unread }) => {
|
|
17
|
-
const {
|
|
18
|
-
|
|
18
|
+
const {
|
|
19
|
+
selectedChannel,
|
|
20
|
+
onChannelSelect,
|
|
21
|
+
debug,
|
|
22
|
+
renderMessagePreview,
|
|
23
|
+
viewerLanguage,
|
|
24
|
+
} = useChannelListContext()
|
|
19
25
|
|
|
20
26
|
const isSelected = selectedChannel?.id === channel?.id
|
|
21
27
|
|
|
@@ -53,7 +59,12 @@ const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
|
|
|
53
59
|
})()
|
|
54
60
|
|
|
55
61
|
const getLastMessageText = () => {
|
|
56
|
-
|
|
62
|
+
const displayText = getMessageDisplayText({
|
|
63
|
+
message: lastMessage,
|
|
64
|
+
viewerLanguage,
|
|
65
|
+
})
|
|
66
|
+
if (displayText) return displayText
|
|
67
|
+
|
|
57
68
|
const isTip = lastMessage?.metadata?.custom_type === 'MESSAGE_TIP'
|
|
58
69
|
if (isTip) return '💵 Sent a tip'
|
|
59
70
|
|
|
@@ -28,6 +28,7 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
28
28
|
className,
|
|
29
29
|
customEmptyStateIndicator,
|
|
30
30
|
renderMessagePreview,
|
|
31
|
+
viewerLanguage,
|
|
31
32
|
}) => {
|
|
32
33
|
// Track renders
|
|
33
34
|
const renderCountRef = React.useRef(0)
|
|
@@ -64,8 +65,15 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
64
65
|
onChannelSelect,
|
|
65
66
|
debug,
|
|
66
67
|
renderMessagePreview,
|
|
68
|
+
viewerLanguage,
|
|
67
69
|
}),
|
|
68
|
-
[
|
|
70
|
+
[
|
|
71
|
+
selectedChannel,
|
|
72
|
+
onChannelSelect,
|
|
73
|
+
debug,
|
|
74
|
+
renderMessagePreview,
|
|
75
|
+
viewerLanguage,
|
|
76
|
+
]
|
|
69
77
|
)
|
|
70
78
|
|
|
71
79
|
return (
|
|
@@ -28,6 +28,17 @@ vi.mock('stream-chat-react', () => ({
|
|
|
28
28
|
),
|
|
29
29
|
useMessageContext: () => ({ message: { id: 'message-1', text: 'hello' } }),
|
|
30
30
|
useChannelStateContext: () => ({ channel: activeChannel }),
|
|
31
|
+
useChatContext: () => ({ client: { user: { id: 'visitor-1' } } }),
|
|
32
|
+
useTypingContext: () => ({ typing: {} }),
|
|
33
|
+
useAIState: () => ({ aiState: 'AI_STATE_IDLE' }),
|
|
34
|
+
AIStates: {
|
|
35
|
+
Error: 'AI_STATE_ERROR',
|
|
36
|
+
ExternalSources: 'AI_STATE_EXTERNAL_SOURCES',
|
|
37
|
+
Generating: 'AI_STATE_GENERATING',
|
|
38
|
+
Idle: 'AI_STATE_IDLE',
|
|
39
|
+
Stop: 'AI_STATE_STOP',
|
|
40
|
+
Thinking: 'AI_STATE_THINKING',
|
|
41
|
+
},
|
|
31
42
|
}))
|
|
32
43
|
|
|
33
44
|
vi.mock('../providers/MessagingProvider', () => ({
|
|
@@ -28,6 +28,7 @@ import { CustomMessage } from './CustomMessage'
|
|
|
28
28
|
import { CustomMessageInput } from './CustomMessageInput'
|
|
29
29
|
import { CustomSystemMessage } from './CustomSystemMessage'
|
|
30
30
|
import CustomTypingIndicator from './CustomTypingIndicator'
|
|
31
|
+
import { DmAgentEnabledContext } from './CustomTypingIndicator/DmAgentContext'
|
|
31
32
|
import { ChannelEmptyState } from './MessagingShell/ChannelEmptyState'
|
|
32
33
|
import { LoadingState } from './MessagingShell/LoadingState'
|
|
33
34
|
|
|
@@ -257,6 +258,7 @@ const ChannelViewInner: React.FC<{
|
|
|
257
258
|
message: NonNullable<MessageUIComponentProps['message']>
|
|
258
259
|
) => React.ReactNode
|
|
259
260
|
dmAgentEnabled?: boolean
|
|
261
|
+
viewerLanguage?: string
|
|
260
262
|
}> = ({
|
|
261
263
|
onBack,
|
|
262
264
|
showBackButton,
|
|
@@ -275,6 +277,7 @@ const ChannelViewInner: React.FC<{
|
|
|
275
277
|
customChannelActions,
|
|
276
278
|
renderMessage,
|
|
277
279
|
dmAgentEnabled = false,
|
|
280
|
+
viewerLanguage,
|
|
278
281
|
}) => {
|
|
279
282
|
const { channel } = useChannelStateContext()
|
|
280
283
|
const infoDialogRef = useRef<HTMLDialogElement>(null)
|
|
@@ -339,7 +342,11 @@ const ChannelViewInner: React.FC<{
|
|
|
339
342
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
340
343
|
const { message } = useMessageContext('ChannelView')
|
|
341
344
|
const messageNode = (
|
|
342
|
-
<CustomMessage
|
|
345
|
+
<CustomMessage
|
|
346
|
+
{...props}
|
|
347
|
+
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
348
|
+
viewerLanguage={viewerLanguage}
|
|
349
|
+
/>
|
|
343
350
|
)
|
|
344
351
|
|
|
345
352
|
if (!renderMessage || !message) {
|
|
@@ -348,7 +355,7 @@ const ChannelViewInner: React.FC<{
|
|
|
348
355
|
|
|
349
356
|
return renderMessage(messageNode, message)
|
|
350
357
|
},
|
|
351
|
-
[chatbotVotingEnabled, renderMessage]
|
|
358
|
+
[chatbotVotingEnabled, renderMessage, viewerLanguage]
|
|
352
359
|
)
|
|
353
360
|
|
|
354
361
|
return (
|
|
@@ -436,6 +443,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
436
443
|
customChannelActions,
|
|
437
444
|
renderMessage,
|
|
438
445
|
sendButton,
|
|
446
|
+
viewerLanguage,
|
|
439
447
|
}) => {
|
|
440
448
|
// Custom send message handler that:
|
|
441
449
|
// 1. Applies messageMetadata if provided
|
|
@@ -488,37 +496,40 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
488
496
|
className
|
|
489
497
|
)}
|
|
490
498
|
>
|
|
491
|
-
<
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
499
|
+
<DmAgentEnabledContext.Provider value={dmAgentEnabled ?? false}>
|
|
500
|
+
<Channel
|
|
501
|
+
channel={channel}
|
|
502
|
+
MessageSystem={CustomSystemMessage}
|
|
503
|
+
EmptyStateIndicator={CustomChannelEmptyState}
|
|
504
|
+
LoadingIndicator={LoadingState}
|
|
505
|
+
DateSeparator={CustomDateSeparator}
|
|
506
|
+
TypingIndicator={CustomTypingIndicator}
|
|
507
|
+
doSendMessageRequest={doSendMessageRequest}
|
|
508
|
+
{...(sendButton ? { SendButton: sendButton } : {})}
|
|
509
|
+
>
|
|
510
|
+
<ChannelViewInner
|
|
511
|
+
onBack={onBack}
|
|
512
|
+
showBackButton={showBackButton}
|
|
513
|
+
renderMessageInputActions={renderMessageInputActions}
|
|
514
|
+
renderConversationFooter={renderConversationFooter}
|
|
515
|
+
onLeaveConversation={onLeaveConversation}
|
|
516
|
+
onBlockParticipant={onBlockParticipant}
|
|
517
|
+
CustomChannelEmptyState={CustomChannelEmptyState}
|
|
518
|
+
showDeleteConversation={showDeleteConversation}
|
|
519
|
+
onDeleteConversationClick={onDeleteConversationClick}
|
|
520
|
+
onBlockParticipantClick={onBlockParticipantClick}
|
|
521
|
+
onReportParticipantClick={onReportParticipantClick}
|
|
522
|
+
showStarButton={showStarButton}
|
|
523
|
+
dmAgentEnabled={dmAgentEnabled}
|
|
524
|
+
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
525
|
+
renderChannelBanner={renderChannelBanner}
|
|
526
|
+
customProfileContent={customProfileContent}
|
|
527
|
+
customChannelActions={customChannelActions}
|
|
528
|
+
renderMessage={renderMessage}
|
|
529
|
+
viewerLanguage={viewerLanguage}
|
|
530
|
+
/>
|
|
531
|
+
</Channel>
|
|
532
|
+
</DmAgentEnabledContext.Provider>
|
|
522
533
|
</div>
|
|
523
534
|
)
|
|
524
535
|
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from 'stream-chat-react'
|
|
31
31
|
|
|
32
32
|
import { useMessageVote } from '../../hooks/useMessageVote'
|
|
33
|
+
import { getMessageDisplayText } from '../../utils/getMessageDisplayText'
|
|
33
34
|
import { Avatar } from '../Avatar'
|
|
34
35
|
import LockedAttachment from '../LockedAttachment'
|
|
35
36
|
import { isLinkAttachment } from '../MediaMessage'
|
|
@@ -45,10 +46,12 @@ import { MessageVoteButtons } from './MessageVoteButtons'
|
|
|
45
46
|
|
|
46
47
|
type CustomMessageUIComponentProps = MessageUIComponentProps & {
|
|
47
48
|
chatbotVotingEnabled?: boolean
|
|
49
|
+
viewerLanguage?: string
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
type CustomMessageWithContextProps = MessageContextValue & {
|
|
51
53
|
chatbotVotingEnabled?: boolean
|
|
54
|
+
viewerLanguage?: string
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
@@ -68,11 +71,13 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
68
71
|
message,
|
|
69
72
|
renderText,
|
|
70
73
|
threadList,
|
|
74
|
+
viewerLanguage,
|
|
71
75
|
} = props
|
|
72
76
|
|
|
73
77
|
const { client } = useChatContext('CustomMessage')
|
|
74
78
|
const { channel } = useChannelStateContext('CustomMessage')
|
|
75
|
-
const { isUnlocking, onUnlockClick, onFetchSource, onDownloadClick } =
|
|
79
|
+
const { isUnlocking, onUnlockClick, onFetchSource, onDownloadClick } =
|
|
80
|
+
useCustomMessage('LockedAttachment')
|
|
76
81
|
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false)
|
|
77
82
|
const reminder = useMessageReminder(message.id)
|
|
78
83
|
const { selected: voteState, voteUp, voteDown } = useMessageVote(message)
|
|
@@ -107,6 +112,12 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
107
112
|
const filtered = raw.filter((a) => !('type' in a) || !isLinkAttachment(a))
|
|
108
113
|
return filtered.length === raw.length ? raw : filtered
|
|
109
114
|
}, [message])
|
|
115
|
+
const displayMessage = useMemo(() => {
|
|
116
|
+
const displayText = getMessageDisplayText({ message, viewerLanguage })
|
|
117
|
+
return displayText === message.text
|
|
118
|
+
? message
|
|
119
|
+
: { ...message, text: displayText }
|
|
120
|
+
}, [message, viewerLanguage])
|
|
110
121
|
|
|
111
122
|
if (isDateSeparatorMessage(message)) {
|
|
112
123
|
return null
|
|
@@ -247,7 +258,10 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
247
258
|
)}
|
|
248
259
|
{message.text && (
|
|
249
260
|
<div className="str-chat__message-bubble">
|
|
250
|
-
<MessageText
|
|
261
|
+
<MessageText
|
|
262
|
+
message={displayMessage}
|
|
263
|
+
renderText={renderText}
|
|
264
|
+
/>
|
|
251
265
|
</div>
|
|
252
266
|
)}
|
|
253
267
|
</div>
|
|
@@ -273,11 +287,14 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
273
287
|
) : null}
|
|
274
288
|
{isAIGenerated ? (
|
|
275
289
|
<StreamedMessageText
|
|
276
|
-
message={
|
|
290
|
+
message={displayMessage}
|
|
277
291
|
renderText={renderText}
|
|
278
292
|
/>
|
|
279
293
|
) : (
|
|
280
|
-
<MessageText
|
|
294
|
+
<MessageText
|
|
295
|
+
message={displayMessage}
|
|
296
|
+
renderText={renderText}
|
|
297
|
+
/>
|
|
281
298
|
)}
|
|
282
299
|
<MessageErrorIcon />
|
|
283
300
|
</div>
|
|
@@ -317,6 +334,7 @@ const MemoizedCustomMessage = React.memo(
|
|
|
317
334
|
CustomMessageWithContext,
|
|
318
335
|
(prev, next) => {
|
|
319
336
|
if (prev.chatbotVotingEnabled !== next.chatbotVotingEnabled) return false
|
|
337
|
+
if (prev.viewerLanguage !== next.viewerLanguage) return false
|
|
320
338
|
return areMessageUIPropsEqual(prev, next)
|
|
321
339
|
}
|
|
322
340
|
) as typeof CustomMessageWithContext
|
|
@@ -2,16 +2,21 @@ import type { Meta, StoryFn } from '@storybook/react'
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import type { Event } from 'stream-chat'
|
|
4
4
|
import {
|
|
5
|
+
AIStates,
|
|
5
6
|
ChannelStateProvider,
|
|
6
7
|
ChatProvider,
|
|
7
8
|
TypingProvider,
|
|
8
9
|
} from 'stream-chat-react'
|
|
9
10
|
|
|
11
|
+
import { DmAgentEnabledContext } from './DmAgentContext'
|
|
12
|
+
|
|
10
13
|
import CustomTypingIndicator from '.'
|
|
11
14
|
|
|
12
15
|
type StoryProps = {
|
|
13
16
|
typingEventsEnabled?: boolean
|
|
14
17
|
typing?: Record<string, Event>
|
|
18
|
+
aiState?: string
|
|
19
|
+
dmAgentEnabled?: boolean
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
const currentUser = {
|
|
@@ -33,10 +38,38 @@ const defaultTyping: Record<string, Event> = {
|
|
|
33
38
|
} as Event,
|
|
34
39
|
}
|
|
35
40
|
|
|
41
|
+
type ListenerMap = Record<string, (event: { ai_state: string; cid: string }) => void>
|
|
42
|
+
|
|
43
|
+
const createMockChannel = ({ aiState }: { aiState?: string }) => {
|
|
44
|
+
const listeners: ListenerMap = {}
|
|
45
|
+
const channel = {
|
|
46
|
+
cid: 'messaging:test',
|
|
47
|
+
state: {
|
|
48
|
+
members: {
|
|
49
|
+
[currentUser.id]: { user: currentUser },
|
|
50
|
+
[typingUser.id]: { user: typingUser },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
on(eventType: string, handler: ListenerMap[string]) {
|
|
54
|
+
listeners[eventType] = handler
|
|
55
|
+
// Fire the requested AI state synchronously so the hook picks it up.
|
|
56
|
+
if (eventType === 'ai_indicator.update' && aiState) {
|
|
57
|
+
handler({ ai_state: aiState, cid: 'messaging:test' })
|
|
58
|
+
}
|
|
59
|
+
return { unsubscribe: () => {} }
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
return channel
|
|
63
|
+
}
|
|
64
|
+
|
|
36
65
|
const StoryWrapper: React.FC<StoryProps> = ({
|
|
37
66
|
typingEventsEnabled = true,
|
|
38
|
-
typing =
|
|
67
|
+
typing = {},
|
|
68
|
+
aiState,
|
|
69
|
+
dmAgentEnabled = true,
|
|
39
70
|
}) => {
|
|
71
|
+
const channel = createMockChannel({ aiState })
|
|
72
|
+
|
|
40
73
|
const chatContextValue = {
|
|
41
74
|
client: {
|
|
42
75
|
user: currentUser,
|
|
@@ -57,18 +90,7 @@ const StoryWrapper: React.FC<StoryProps> = ({
|
|
|
57
90
|
}
|
|
58
91
|
|
|
59
92
|
const channelStateValue = {
|
|
60
|
-
channel
|
|
61
|
-
state: {
|
|
62
|
-
members: {
|
|
63
|
-
[currentUser.id]: {
|
|
64
|
-
user: currentUser,
|
|
65
|
-
},
|
|
66
|
-
[typingUser.id]: {
|
|
67
|
-
user: typingUser,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
},
|
|
93
|
+
channel,
|
|
72
94
|
channelCapabilities: {},
|
|
73
95
|
channelConfig: {
|
|
74
96
|
typing_events: typingEventsEnabled,
|
|
@@ -85,9 +107,11 @@ const StoryWrapper: React.FC<StoryProps> = ({
|
|
|
85
107
|
<ChatProvider value={chatContextValue as never}>
|
|
86
108
|
<ChannelStateProvider value={channelStateValue as never}>
|
|
87
109
|
<TypingProvider value={{ typing }}>
|
|
88
|
-
<
|
|
89
|
-
<
|
|
90
|
-
|
|
110
|
+
<DmAgentEnabledContext.Provider value={dmAgentEnabled}>
|
|
111
|
+
<div className="relative h-20 w-[200px] bg-[#f4f4f4] p-3">
|
|
112
|
+
<CustomTypingIndicator />
|
|
113
|
+
</div>
|
|
114
|
+
</DmAgentEnabledContext.Provider>
|
|
91
115
|
</TypingProvider>
|
|
92
116
|
</ChannelStateProvider>
|
|
93
117
|
</ChatProvider>
|
|
@@ -104,7 +128,9 @@ const meta: Meta<StoryProps> = {
|
|
|
104
128
|
export default meta
|
|
105
129
|
|
|
106
130
|
export const Default: StoryFn<StoryProps> = (args) => <StoryWrapper {...args} />
|
|
107
|
-
Default.args = {
|
|
131
|
+
Default.args = {
|
|
132
|
+
typing: defaultTyping,
|
|
133
|
+
}
|
|
108
134
|
|
|
109
135
|
export const HiddenWhenNoTyping: StoryFn<StoryProps> = (args) => (
|
|
110
136
|
<StoryWrapper {...args} />
|
|
@@ -112,3 +138,17 @@ export const HiddenWhenNoTyping: StoryFn<StoryProps> = (args) => (
|
|
|
112
138
|
HiddenWhenNoTyping.args = {
|
|
113
139
|
typing: {},
|
|
114
140
|
}
|
|
141
|
+
|
|
142
|
+
export const AiAgentThinking: StoryFn<StoryProps> = (args) => (
|
|
143
|
+
<StoryWrapper {...args} />
|
|
144
|
+
)
|
|
145
|
+
AiAgentThinking.args = {
|
|
146
|
+
aiState: AIStates.Thinking,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const AiAgentGenerating: StoryFn<StoryProps> = (args) => (
|
|
150
|
+
<StoryWrapper {...args} />
|
|
151
|
+
)
|
|
152
|
+
AiAgentGenerating.args = {
|
|
153
|
+
aiState: AIStates.Generating,
|
|
154
|
+
}
|