@linktr.ee/messaging-react 1.19.3 → 1.20.0
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/assets/index.css +1 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.js +868 -774
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelView.tsx +5 -1
- package/src/components/CustomMessage/MessageVoteButtons.tsx +62 -0
- package/src/components/CustomMessage/index.tsx +18 -3
- package/src/components/MessagingShell/index.tsx +2 -0
- package/src/hooks/useMessageVote.ts +77 -0
- package/src/index.ts +3 -0
- package/src/styles.css +36 -0
- package/src/types.ts +9 -0
package/package.json
CHANGED
|
@@ -527,6 +527,7 @@ const ChannelViewInner: React.FC<{
|
|
|
527
527
|
onBlockParticipantClick?: () => void
|
|
528
528
|
onReportParticipantClick?: () => void
|
|
529
529
|
showStarButton?: boolean
|
|
530
|
+
chatbotVotingEnabled?: boolean
|
|
530
531
|
}> = ({
|
|
531
532
|
onBack,
|
|
532
533
|
showBackButton,
|
|
@@ -538,6 +539,7 @@ const ChannelViewInner: React.FC<{
|
|
|
538
539
|
onBlockParticipantClick,
|
|
539
540
|
onReportParticipantClick,
|
|
540
541
|
showStarButton = false,
|
|
542
|
+
chatbotVotingEnabled = false,
|
|
541
543
|
}) => {
|
|
542
544
|
const { channel } = useChannelStateContext()
|
|
543
545
|
const infoDialogRef = useRef<HTMLDialogElement>(null)
|
|
@@ -584,7 +586,7 @@ const ChannelViewInner: React.FC<{
|
|
|
584
586
|
<WithComponents
|
|
585
587
|
overrides={{
|
|
586
588
|
Message: (props: MessageUIComponentProps) => (
|
|
587
|
-
<CustomMessage {...props} />
|
|
589
|
+
<CustomMessage {...props} chatbotVotingEnabled={chatbotVotingEnabled} />
|
|
588
590
|
),
|
|
589
591
|
}}
|
|
590
592
|
>
|
|
@@ -655,6 +657,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
655
657
|
messageMetadata,
|
|
656
658
|
onMessageSent,
|
|
657
659
|
showStarButton = false,
|
|
660
|
+
chatbotVotingEnabled = false,
|
|
658
661
|
}) => {
|
|
659
662
|
// Custom send message handler that:
|
|
660
663
|
// 1. Applies messageMetadata if provided
|
|
@@ -727,6 +730,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
727
730
|
onBlockParticipantClick={onBlockParticipantClick}
|
|
728
731
|
onReportParticipantClick={onReportParticipantClick}
|
|
729
732
|
showStarButton={showStarButton}
|
|
733
|
+
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
730
734
|
/>
|
|
731
735
|
</Channel>
|
|
732
736
|
</div>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import type { VoteSelection } from '../../hooks/useMessageVote'
|
|
4
|
+
|
|
5
|
+
interface MessageVoteButtonsProps {
|
|
6
|
+
selected: VoteSelection
|
|
7
|
+
onVoteUp: () => void
|
|
8
|
+
onVoteDown: () => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ThumbUpIcon: React.FC<{ filled: boolean }> = ({ filled }) => (
|
|
12
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
13
|
+
<path
|
|
14
|
+
d="M4.667 7.333l2.666-6A1.333 1.333 0 018.667 2v2.667a.667.667 0 00.666.666h3.764a1.334 1.334 0 011.192 1.93l-2.333 4.666a1.333 1.333 0 01-1.193.738H4.667m0-5.334v5.334m0-5.334H2.667a1.333 1.333 0 00-1.334 1.334v2.666a1.333 1.333 0 001.334 1.334h2"
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
strokeWidth="1.33"
|
|
17
|
+
strokeLinecap="round"
|
|
18
|
+
strokeLinejoin="round"
|
|
19
|
+
fill={filled ? 'currentColor' : 'none'}
|
|
20
|
+
/>
|
|
21
|
+
</svg>
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const ThumbDownIcon: React.FC<{ filled: boolean }> = ({ filled }) => (
|
|
25
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
26
|
+
<path
|
|
27
|
+
d="M11.333 8.667l-2.666 6A1.333 1.333 0 017.333 14v-2.667a.667.667 0 00-.666-.666H2.903a1.334 1.334 0 01-1.192-1.93l2.333-4.666a1.333 1.333 0 011.193-.738h6.096m0 5.334V3.333m0 5.334h2a1.333 1.333 0 001.334-1.334V4.667a1.333 1.333 0 00-1.334-1.334h-2"
|
|
28
|
+
stroke="currentColor"
|
|
29
|
+
strokeWidth="1.33"
|
|
30
|
+
strokeLinecap="round"
|
|
31
|
+
strokeLinejoin="round"
|
|
32
|
+
fill={filled ? 'currentColor' : 'none'}
|
|
33
|
+
/>
|
|
34
|
+
</svg>
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
export const MessageVoteButtons: React.FC<MessageVoteButtonsProps> = ({
|
|
38
|
+
selected,
|
|
39
|
+
onVoteUp,
|
|
40
|
+
onVoteDown,
|
|
41
|
+
}) => (
|
|
42
|
+
<div className="message-vote-buttons">
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
className={`message-vote-button${selected === 'up' ? ' message-vote-button--selected' : ''}`}
|
|
46
|
+
onClick={onVoteUp}
|
|
47
|
+
aria-label="Helpful"
|
|
48
|
+
aria-pressed={selected === 'up'}
|
|
49
|
+
>
|
|
50
|
+
<ThumbUpIcon filled={selected === 'up'} />
|
|
51
|
+
</button>
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
className={`message-vote-button${selected === 'down' ? ' message-vote-button--selected' : ''}`}
|
|
55
|
+
onClick={onVoteDown}
|
|
56
|
+
aria-label="Not helpful"
|
|
57
|
+
aria-pressed={selected === 'down'}
|
|
58
|
+
>
|
|
59
|
+
<ThumbDownIcon filled={selected === 'down'} />
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
@@ -28,15 +28,20 @@ import {
|
|
|
28
28
|
type MessageUIComponentProps,
|
|
29
29
|
} from 'stream-chat-react'
|
|
30
30
|
|
|
31
|
+
import { useMessageVote } from '../../hooks/useMessageVote'
|
|
31
32
|
import { Avatar } from '../Avatar'
|
|
32
33
|
|
|
33
|
-
import { MessageTag, isTipOnlyMessage } from './MessageTag'
|
|
34
|
+
import { MessageTag, isChatbotMessage, isTipOnlyMessage } from './MessageTag'
|
|
35
|
+
import { MessageVoteButtons } from './MessageVoteButtons'
|
|
34
36
|
|
|
35
|
-
type CustomMessageWithContextProps = MessageContextValue
|
|
37
|
+
type CustomMessageWithContextProps = MessageContextValue & {
|
|
38
|
+
chatbotVotingEnabled?: boolean
|
|
39
|
+
}
|
|
36
40
|
|
|
37
41
|
const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
38
42
|
const {
|
|
39
43
|
additionalMessageInputProps,
|
|
44
|
+
chatbotVotingEnabled,
|
|
40
45
|
editing,
|
|
41
46
|
endOfGroup,
|
|
42
47
|
firstOfGroup,
|
|
@@ -55,6 +60,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
55
60
|
const { client } = useChatContext('CustomMessage')
|
|
56
61
|
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false)
|
|
57
62
|
const reminder = useMessageReminder(message.id)
|
|
63
|
+
const { selected: voteState, voteUp, voteDown } = useMessageVote(message)
|
|
58
64
|
|
|
59
65
|
const {
|
|
60
66
|
Attachment = DefaultAttachment,
|
|
@@ -204,6 +210,13 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
204
210
|
</div>
|
|
205
211
|
{/* Tag positioned outside and below the bubble */}
|
|
206
212
|
<MessageTag message={message} />
|
|
213
|
+
{chatbotVotingEnabled && isChatbotMessage(message) && (
|
|
214
|
+
<MessageVoteButtons
|
|
215
|
+
selected={voteState}
|
|
216
|
+
onVoteUp={voteUp}
|
|
217
|
+
onVoteDown={voteDown}
|
|
218
|
+
/>
|
|
219
|
+
)}
|
|
207
220
|
</div>
|
|
208
221
|
)}
|
|
209
222
|
</div>
|
|
@@ -226,7 +239,9 @@ const MemoizedCustomMessage = React.memo(
|
|
|
226
239
|
areMessageUIPropsEqual
|
|
227
240
|
) as typeof CustomMessageWithContext
|
|
228
241
|
|
|
229
|
-
export const CustomMessage = (
|
|
242
|
+
export const CustomMessage = (
|
|
243
|
+
props: MessageUIComponentProps & { chatbotVotingEnabled?: boolean }
|
|
244
|
+
) => {
|
|
230
245
|
const messageContext = useMessageContext('CustomMessage')
|
|
231
246
|
return <MemoizedCustomMessage {...messageContext} {...props} />
|
|
232
247
|
}
|
|
@@ -34,6 +34,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
34
34
|
messageMetadata,
|
|
35
35
|
onMessageSent,
|
|
36
36
|
showStarButton = false,
|
|
37
|
+
chatbotVotingEnabled = false,
|
|
37
38
|
renderMessagePreview,
|
|
38
39
|
}) => {
|
|
39
40
|
const {
|
|
@@ -491,6 +492,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
491
492
|
messageMetadata={messageMetadata}
|
|
492
493
|
onMessageSent={onMessageSent}
|
|
493
494
|
showStarButton={showStarButton}
|
|
495
|
+
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
494
496
|
/>
|
|
495
497
|
</div>
|
|
496
498
|
) : initialParticipantFilter ? (
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react'
|
|
2
|
+
import type { LocalMessage } from 'stream-chat'
|
|
3
|
+
import { useChannelStateContext, useChatContext } from 'stream-chat-react'
|
|
4
|
+
|
|
5
|
+
export type VoteSelection = 'up' | 'down' | null
|
|
6
|
+
|
|
7
|
+
const VOTE_UP = 'vote_up'
|
|
8
|
+
const VOTE_DOWN = 'vote_down'
|
|
9
|
+
|
|
10
|
+
function getVoteFromReactions(
|
|
11
|
+
ownReactions: LocalMessage['own_reactions']
|
|
12
|
+
): VoteSelection {
|
|
13
|
+
if (!ownReactions?.length) return null
|
|
14
|
+
if (ownReactions.some((r) => r.type === VOTE_DOWN)) return 'down'
|
|
15
|
+
if (ownReactions.some((r) => r.type === VOTE_UP)) return 'up'
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface UseMessageVoteResult {
|
|
20
|
+
selected: VoteSelection
|
|
21
|
+
voteUp: () => void
|
|
22
|
+
voteDown: () => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Hook that wraps Stream Chat reactions to provide toggle-style
|
|
27
|
+
* upvote/downvote behavior on a message.
|
|
28
|
+
*
|
|
29
|
+
* Uses enforce_unique so sending a new reaction type automatically
|
|
30
|
+
* removes the previous one. Uses skip_push to avoid notifying the
|
|
31
|
+
* Linker on every vote.
|
|
32
|
+
*/
|
|
33
|
+
export function useMessageVote(message: LocalMessage): UseMessageVoteResult {
|
|
34
|
+
const { channel } = useChannelStateContext()
|
|
35
|
+
const { client } = useChatContext('useMessageVote')
|
|
36
|
+
|
|
37
|
+
const selected = useMemo(
|
|
38
|
+
() => getVoteFromReactions(message.own_reactions),
|
|
39
|
+
[message.own_reactions]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const voteUp = useCallback(async () => {
|
|
43
|
+
if (!client?.userID) return
|
|
44
|
+
try {
|
|
45
|
+
if (selected === 'up') {
|
|
46
|
+
await channel.deleteReaction(message.id, VOTE_UP)
|
|
47
|
+
} else {
|
|
48
|
+
await channel.sendReaction(
|
|
49
|
+
message.id,
|
|
50
|
+
{ type: VOTE_UP },
|
|
51
|
+
{ enforce_unique: true, skip_push: true }
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// Silently fail — voting is non-critical
|
|
56
|
+
}
|
|
57
|
+
}, [channel, client?.userID, message.id, selected])
|
|
58
|
+
|
|
59
|
+
const voteDown = useCallback(async () => {
|
|
60
|
+
if (!client?.userID) return
|
|
61
|
+
try {
|
|
62
|
+
if (selected === 'down') {
|
|
63
|
+
await channel.deleteReaction(message.id, VOTE_DOWN)
|
|
64
|
+
} else {
|
|
65
|
+
await channel.sendReaction(
|
|
66
|
+
message.id,
|
|
67
|
+
{ type: VOTE_DOWN },
|
|
68
|
+
{ enforce_unique: true, skip_push: true }
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// Silently fail — voting is non-critical
|
|
73
|
+
}
|
|
74
|
+
}, [channel, client?.userID, message.id, selected])
|
|
75
|
+
|
|
76
|
+
return { selected, voteUp, voteDown }
|
|
77
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { Avatar } from './components/Avatar'
|
|
|
10
10
|
export { FaqList } from './components/FaqList'
|
|
11
11
|
export { FaqListItem } from './components/FaqList/FaqListItem'
|
|
12
12
|
export { ChannelEmptyState } from './components/MessagingShell/ChannelEmptyState'
|
|
13
|
+
export { MessageVoteButtons } from './components/CustomMessage/MessageVoteButtons'
|
|
13
14
|
|
|
14
15
|
// Providers
|
|
15
16
|
export { MessagingProvider } from './providers/MessagingProvider'
|
|
@@ -17,6 +18,7 @@ export { MessagingProvider } from './providers/MessagingProvider'
|
|
|
17
18
|
// Hooks
|
|
18
19
|
export { useMessaging } from './hooks/useMessaging'
|
|
19
20
|
export { useParticipants } from './hooks/useParticipants'
|
|
21
|
+
export { useMessageVote } from './hooks/useMessageVote'
|
|
20
22
|
|
|
21
23
|
// Utils
|
|
22
24
|
export { formatRelativeTime } from './utils/formatRelativeTime'
|
|
@@ -36,3 +38,4 @@ export type { MessageMetadata } from './stream-custom-data'
|
|
|
36
38
|
export type { AvatarProps } from './components/Avatar'
|
|
37
39
|
export type { Faq, FaqListProps } from './components/FaqList'
|
|
38
40
|
export type { FaqListItemProps } from './components/FaqList/FaqListItem'
|
|
41
|
+
export type { VoteSelection } from './hooks/useMessageVote'
|
package/src/styles.css
CHANGED
|
@@ -139,6 +139,42 @@
|
|
|
139
139
|
color: #7f22fe;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/* Message vote buttons (chatbot feedback) */
|
|
143
|
+
.message-vote-buttons {
|
|
144
|
+
display: inline-flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: 2px;
|
|
147
|
+
margin-top: 4px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.message-vote-button {
|
|
151
|
+
display: flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
justify-content: center;
|
|
154
|
+
width: 28px;
|
|
155
|
+
height: 28px;
|
|
156
|
+
padding: 0;
|
|
157
|
+
border: none;
|
|
158
|
+
border-radius: 50%;
|
|
159
|
+
background-color: transparent;
|
|
160
|
+
color: rgba(0, 0, 0, 0.35);
|
|
161
|
+
cursor: pointer;
|
|
162
|
+
transition: background-color 0.15s, color 0.15s;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.message-vote-button:hover {
|
|
166
|
+
background-color: rgba(0, 0, 0, 0.06);
|
|
167
|
+
color: rgba(0, 0, 0, 0.55);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.message-vote-button--selected {
|
|
171
|
+
color: rgba(0, 0, 0, 0.7);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.message-vote-button--selected:hover {
|
|
175
|
+
color: rgba(0, 0, 0, 0.7);
|
|
176
|
+
}
|
|
177
|
+
|
|
142
178
|
/* Standalone tip message (tip without text) */
|
|
143
179
|
.message-tip-standalone {
|
|
144
180
|
display: inline-flex;
|
package/src/types.ts
CHANGED
|
@@ -139,6 +139,14 @@ export interface ChannelViewProps {
|
|
|
139
139
|
* and filter by starred/pinned status.
|
|
140
140
|
*/
|
|
141
141
|
showStarButton?: boolean
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Enable thumbs up/down voting on chatbot messages.
|
|
145
|
+
* When true, vote buttons render below chatbot (DM Agent) messages.
|
|
146
|
+
* Votes are persisted as Stream Chat reactions (vote_up / vote_down).
|
|
147
|
+
* Defaults to false.
|
|
148
|
+
*/
|
|
149
|
+
chatbotVotingEnabled?: boolean
|
|
142
150
|
}
|
|
143
151
|
|
|
144
152
|
/**
|
|
@@ -156,6 +164,7 @@ export type ChannelViewPassthroughProps = Pick<
|
|
|
156
164
|
| 'messageMetadata'
|
|
157
165
|
| 'onMessageSent'
|
|
158
166
|
| 'showStarButton'
|
|
167
|
+
| 'chatbotVotingEnabled'
|
|
159
168
|
>
|
|
160
169
|
|
|
161
170
|
/**
|