stream-chat-react-native-core 5.43.3-beta.1 → 5.44.0-beta.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.
Files changed (219) hide show
  1. package/lib/commonjs/components/AITypingIndicatorView/AITypingIndicatorView.js +53 -0
  2. package/lib/commonjs/components/AITypingIndicatorView/AITypingIndicatorView.js.map +1 -0
  3. package/lib/commonjs/components/AITypingIndicatorView/hooks/useAIState.js +59 -0
  4. package/lib/commonjs/components/AITypingIndicatorView/hooks/useAIState.js.map +1 -0
  5. package/lib/commonjs/components/AITypingIndicatorView/index.js +26 -0
  6. package/lib/commonjs/components/AITypingIndicatorView/index.js.map +1 -0
  7. package/lib/commonjs/components/Attachment/AudioAttachment.js +27 -22
  8. package/lib/commonjs/components/Attachment/AudioAttachment.js.map +1 -1
  9. package/lib/commonjs/components/Channel/Channel.js +9 -1
  10. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  11. package/lib/commonjs/components/Channel/hooks/useCreateInputMessageInputContext.js +2 -0
  12. package/lib/commonjs/components/Channel/hooks/useCreateInputMessageInputContext.js.map +1 -1
  13. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js +2 -0
  14. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  15. package/lib/commonjs/components/Message/Message.js +3 -1
  16. package/lib/commonjs/components/Message/Message.js.map +1 -1
  17. package/lib/commonjs/components/Message/MessageSimple/MessageContent.js +8 -3
  18. package/lib/commonjs/components/Message/MessageSimple/MessageContent.js.map +1 -1
  19. package/lib/commonjs/components/Message/MessageSimple/MessageFooter.js +5 -4
  20. package/lib/commonjs/components/Message/MessageSimple/MessageFooter.js.map +1 -1
  21. package/lib/commonjs/components/Message/MessageSimple/MessageSimple.js +1 -1
  22. package/lib/commonjs/components/Message/MessageSimple/MessageSimple.js.map +1 -1
  23. package/lib/commonjs/components/Message/MessageSimple/StreamingMessageView.js +36 -0
  24. package/lib/commonjs/components/Message/MessageSimple/StreamingMessageView.js.map +1 -0
  25. package/lib/commonjs/components/Message/MessageSimple/utils/generateMarkdownText.js +5 -1
  26. package/lib/commonjs/components/Message/MessageSimple/utils/generateMarkdownText.js.map +1 -1
  27. package/lib/commonjs/components/Message/MessageSimple/utils/renderText.js +209 -23
  28. package/lib/commonjs/components/Message/MessageSimple/utils/renderText.js.map +1 -1
  29. package/lib/commonjs/components/Message/hooks/useStreamingMessage.js +43 -0
  30. package/lib/commonjs/components/Message/hooks/useStreamingMessage.js.map +1 -0
  31. package/lib/commonjs/components/MessageInput/MessageInput.js +20 -6
  32. package/lib/commonjs/components/MessageInput/MessageInput.js.map +1 -1
  33. package/lib/commonjs/components/MessageInput/StopMessageStreamingButton.js +39 -0
  34. package/lib/commonjs/components/MessageInput/StopMessageStreamingButton.js.map +1 -0
  35. package/lib/commonjs/components/MessageOverlay/MessageOverlay.js +6 -1
  36. package/lib/commonjs/components/MessageOverlay/MessageOverlay.js.map +1 -1
  37. package/lib/commonjs/components/index.js +44 -0
  38. package/lib/commonjs/components/index.js.map +1 -1
  39. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +4 -2
  40. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  41. package/lib/commonjs/contexts/messageInputContext/hooks/useCreateMessageInputContext.js +2 -0
  42. package/lib/commonjs/contexts/messageInputContext/hooks/useCreateMessageInputContext.js.map +1 -1
  43. package/lib/commonjs/contexts/messagesContext/MessagesContext.js.map +1 -1
  44. package/lib/commonjs/contexts/themeContext/utils/theme.js +7 -0
  45. package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
  46. package/lib/commonjs/i18n/en.json +2 -0
  47. package/lib/commonjs/i18n/es.json +2 -0
  48. package/lib/commonjs/i18n/fr.json +2 -0
  49. package/lib/commonjs/i18n/he.json +2 -0
  50. package/lib/commonjs/i18n/hi.json +2 -0
  51. package/lib/commonjs/i18n/it.json +2 -0
  52. package/lib/commonjs/i18n/ja.json +2 -0
  53. package/lib/commonjs/i18n/ko.json +2 -0
  54. package/lib/commonjs/i18n/nl.json +2 -0
  55. package/lib/commonjs/i18n/pt-br.json +2 -0
  56. package/lib/commonjs/i18n/ru.json +2 -0
  57. package/lib/commonjs/i18n/tr.json +2 -0
  58. package/lib/commonjs/utils/getTrimmedAttachmentTitle.js +8 -2
  59. package/lib/commonjs/utils/getTrimmedAttachmentTitle.js.map +1 -1
  60. package/lib/commonjs/utils/utils.js +1 -1
  61. package/lib/commonjs/utils/utils.js.map +1 -1
  62. package/lib/commonjs/version.json +1 -1
  63. package/lib/module/components/AITypingIndicatorView/AITypingIndicatorView.js +53 -0
  64. package/lib/module/components/AITypingIndicatorView/AITypingIndicatorView.js.map +1 -0
  65. package/lib/module/components/AITypingIndicatorView/hooks/useAIState.js +59 -0
  66. package/lib/module/components/AITypingIndicatorView/hooks/useAIState.js.map +1 -0
  67. package/lib/module/components/AITypingIndicatorView/index.js +26 -0
  68. package/lib/module/components/AITypingIndicatorView/index.js.map +1 -0
  69. package/lib/module/components/Attachment/AudioAttachment.js +27 -22
  70. package/lib/module/components/Attachment/AudioAttachment.js.map +1 -1
  71. package/lib/module/components/Channel/Channel.js +9 -1
  72. package/lib/module/components/Channel/Channel.js.map +1 -1
  73. package/lib/module/components/Channel/hooks/useCreateInputMessageInputContext.js +2 -0
  74. package/lib/module/components/Channel/hooks/useCreateInputMessageInputContext.js.map +1 -1
  75. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js +2 -0
  76. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  77. package/lib/module/components/Message/Message.js +3 -1
  78. package/lib/module/components/Message/Message.js.map +1 -1
  79. package/lib/module/components/Message/MessageSimple/MessageContent.js +8 -3
  80. package/lib/module/components/Message/MessageSimple/MessageContent.js.map +1 -1
  81. package/lib/module/components/Message/MessageSimple/MessageFooter.js +5 -4
  82. package/lib/module/components/Message/MessageSimple/MessageFooter.js.map +1 -1
  83. package/lib/module/components/Message/MessageSimple/MessageSimple.js +1 -1
  84. package/lib/module/components/Message/MessageSimple/MessageSimple.js.map +1 -1
  85. package/lib/module/components/Message/MessageSimple/StreamingMessageView.js +36 -0
  86. package/lib/module/components/Message/MessageSimple/StreamingMessageView.js.map +1 -0
  87. package/lib/module/components/Message/MessageSimple/utils/generateMarkdownText.js +5 -1
  88. package/lib/module/components/Message/MessageSimple/utils/generateMarkdownText.js.map +1 -1
  89. package/lib/module/components/Message/MessageSimple/utils/renderText.js +209 -23
  90. package/lib/module/components/Message/MessageSimple/utils/renderText.js.map +1 -1
  91. package/lib/module/components/Message/hooks/useStreamingMessage.js +43 -0
  92. package/lib/module/components/Message/hooks/useStreamingMessage.js.map +1 -0
  93. package/lib/module/components/MessageInput/MessageInput.js +20 -6
  94. package/lib/module/components/MessageInput/MessageInput.js.map +1 -1
  95. package/lib/module/components/MessageInput/StopMessageStreamingButton.js +39 -0
  96. package/lib/module/components/MessageInput/StopMessageStreamingButton.js.map +1 -0
  97. package/lib/module/components/MessageOverlay/MessageOverlay.js +6 -1
  98. package/lib/module/components/MessageOverlay/MessageOverlay.js.map +1 -1
  99. package/lib/module/components/index.js +44 -0
  100. package/lib/module/components/index.js.map +1 -1
  101. package/lib/module/contexts/messageInputContext/MessageInputContext.js +4 -2
  102. package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  103. package/lib/module/contexts/messageInputContext/hooks/useCreateMessageInputContext.js +2 -0
  104. package/lib/module/contexts/messageInputContext/hooks/useCreateMessageInputContext.js.map +1 -1
  105. package/lib/module/contexts/messagesContext/MessagesContext.js.map +1 -1
  106. package/lib/module/contexts/themeContext/utils/theme.js +7 -0
  107. package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
  108. package/lib/module/i18n/en.json +2 -0
  109. package/lib/module/i18n/es.json +2 -0
  110. package/lib/module/i18n/fr.json +2 -0
  111. package/lib/module/i18n/he.json +2 -0
  112. package/lib/module/i18n/hi.json +2 -0
  113. package/lib/module/i18n/it.json +2 -0
  114. package/lib/module/i18n/ja.json +2 -0
  115. package/lib/module/i18n/ko.json +2 -0
  116. package/lib/module/i18n/nl.json +2 -0
  117. package/lib/module/i18n/pt-br.json +2 -0
  118. package/lib/module/i18n/ru.json +2 -0
  119. package/lib/module/i18n/tr.json +2 -0
  120. package/lib/module/utils/getTrimmedAttachmentTitle.js +8 -2
  121. package/lib/module/utils/getTrimmedAttachmentTitle.js.map +1 -1
  122. package/lib/module/utils/utils.js +1 -1
  123. package/lib/module/utils/utils.js.map +1 -1
  124. package/lib/module/version.json +1 -1
  125. package/lib/typescript/components/AITypingIndicatorView/AITypingIndicatorView.d.ts +11 -0
  126. package/lib/typescript/components/AITypingIndicatorView/AITypingIndicatorView.d.ts.map +1 -0
  127. package/lib/typescript/components/AITypingIndicatorView/hooks/useAIState.d.ts +18 -0
  128. package/lib/typescript/components/AITypingIndicatorView/hooks/useAIState.d.ts.map +1 -0
  129. package/lib/typescript/components/AITypingIndicatorView/index.d.ts +3 -0
  130. package/lib/typescript/components/AITypingIndicatorView/index.d.ts.map +1 -0
  131. package/lib/typescript/components/Attachment/AudioAttachment.d.ts.map +1 -1
  132. package/lib/typescript/components/Channel/Channel.d.ts +2 -2
  133. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  134. package/lib/typescript/components/Channel/hooks/useCreateInputMessageInputContext.d.ts +1 -1
  135. package/lib/typescript/components/Channel/hooks/useCreateInputMessageInputContext.d.ts.map +1 -1
  136. package/lib/typescript/components/Channel/hooks/useCreateMessagesContext.d.ts +1 -1
  137. package/lib/typescript/components/Channel/hooks/useCreateMessagesContext.d.ts.map +1 -1
  138. package/lib/typescript/components/Message/Message.d.ts.map +1 -1
  139. package/lib/typescript/components/Message/MessageSimple/MessageContent.d.ts +1 -1
  140. package/lib/typescript/components/Message/MessageSimple/MessageContent.d.ts.map +1 -1
  141. package/lib/typescript/components/Message/MessageSimple/MessageFooter.d.ts.map +1 -1
  142. package/lib/typescript/components/Message/MessageSimple/MessageSimple.d.ts.map +1 -1
  143. package/lib/typescript/components/Message/MessageSimple/StreamingMessageView.d.ts +12 -0
  144. package/lib/typescript/components/Message/MessageSimple/StreamingMessageView.d.ts.map +1 -0
  145. package/lib/typescript/components/Message/MessageSimple/utils/generateMarkdownText.d.ts.map +1 -1
  146. package/lib/typescript/components/Message/MessageSimple/utils/renderText.d.ts +16 -1
  147. package/lib/typescript/components/Message/MessageSimple/utils/renderText.d.ts.map +1 -1
  148. package/lib/typescript/components/Message/hooks/useStreamingMessage.d.ts +17 -0
  149. package/lib/typescript/components/Message/hooks/useStreamingMessage.d.ts.map +1 -0
  150. package/lib/typescript/components/MessageInput/MessageInput.d.ts +1 -1
  151. package/lib/typescript/components/MessageInput/MessageInput.d.ts.map +1 -1
  152. package/lib/typescript/components/MessageInput/StopMessageStreamingButton.d.ts +10 -0
  153. package/lib/typescript/components/MessageInput/StopMessageStreamingButton.d.ts.map +1 -0
  154. package/lib/typescript/components/MessageOverlay/MessageOverlay.d.ts.map +1 -1
  155. package/lib/typescript/components/index.d.ts +4 -0
  156. package/lib/typescript/components/index.d.ts.map +1 -1
  157. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts +3 -2
  158. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts.map +1 -1
  159. package/lib/typescript/contexts/messageInputContext/hooks/useCreateMessageInputContext.d.ts +1 -1
  160. package/lib/typescript/contexts/messageInputContext/hooks/useCreateMessageInputContext.d.ts.map +1 -1
  161. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts +6 -2
  162. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts.map +1 -1
  163. package/lib/typescript/contexts/themeContext/utils/theme.d.ts +8 -1
  164. package/lib/typescript/contexts/themeContext/utils/theme.d.ts.map +1 -1
  165. package/lib/typescript/i18n/en.json +2 -0
  166. package/lib/typescript/i18n/es.json +2 -0
  167. package/lib/typescript/i18n/fr.json +2 -0
  168. package/lib/typescript/i18n/he.json +2 -0
  169. package/lib/typescript/i18n/hi.json +2 -0
  170. package/lib/typescript/i18n/it.json +2 -0
  171. package/lib/typescript/i18n/ja.json +2 -0
  172. package/lib/typescript/i18n/ko.json +2 -0
  173. package/lib/typescript/i18n/nl.json +2 -0
  174. package/lib/typescript/i18n/pt-br.json +2 -0
  175. package/lib/typescript/i18n/ru.json +2 -0
  176. package/lib/typescript/i18n/tr.json +2 -0
  177. package/lib/typescript/utils/getTrimmedAttachmentTitle.d.ts.map +1 -1
  178. package/lib/typescript/utils/i18n/Streami18n.d.ts +2 -0
  179. package/lib/typescript/utils/i18n/Streami18n.d.ts.map +1 -1
  180. package/lib/typescript/utils/utils.d.ts.map +1 -1
  181. package/package.json +2 -2
  182. package/src/components/AITypingIndicatorView/AITypingIndicatorView.tsx +50 -0
  183. package/src/components/AITypingIndicatorView/hooks/useAIState.ts +68 -0
  184. package/src/components/AITypingIndicatorView/index.ts +2 -0
  185. package/src/components/Attachment/AudioAttachment.tsx +20 -19
  186. package/src/components/Channel/Channel.tsx +26 -2
  187. package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts +2 -0
  188. package/src/components/Channel/hooks/useCreateMessagesContext.ts +2 -0
  189. package/src/components/Message/Message.tsx +4 -1
  190. package/src/components/Message/MessageSimple/MessageContent.tsx +14 -2
  191. package/src/components/Message/MessageSimple/MessageFooter.tsx +6 -4
  192. package/src/components/Message/MessageSimple/MessageSimple.tsx +3 -1
  193. package/src/components/Message/MessageSimple/StreamingMessageView.tsx +34 -0
  194. package/src/components/Message/MessageSimple/utils/generateMarkdownText.ts +9 -1
  195. package/src/components/Message/MessageSimple/utils/renderText.tsx +207 -3
  196. package/src/components/Message/hooks/useStreamingMessage.ts +54 -0
  197. package/src/components/MessageInput/MessageInput.tsx +19 -3
  198. package/src/components/MessageInput/StopMessageStreamingButton.tsx +34 -0
  199. package/src/components/MessageOverlay/MessageOverlay.tsx +10 -1
  200. package/src/components/index.ts +5 -0
  201. package/src/contexts/messageInputContext/MessageInputContext.tsx +5 -1
  202. package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts +2 -0
  203. package/src/contexts/messagesContext/MessagesContext.tsx +6 -1
  204. package/src/contexts/themeContext/utils/theme.ts +14 -1
  205. package/src/i18n/en.json +2 -0
  206. package/src/i18n/es.json +2 -0
  207. package/src/i18n/fr.json +2 -0
  208. package/src/i18n/he.json +2 -0
  209. package/src/i18n/hi.json +2 -0
  210. package/src/i18n/it.json +2 -0
  211. package/src/i18n/ja.json +2 -0
  212. package/src/i18n/ko.json +2 -0
  213. package/src/i18n/nl.json +2 -0
  214. package/src/i18n/pt-br.json +2 -0
  215. package/src/i18n/ru.json +2 -0
  216. package/src/i18n/tr.json +2 -0
  217. package/src/utils/getTrimmedAttachmentTitle.ts +10 -2
  218. package/src/utils/utils.ts +1 -1
  219. package/src/version.json +1 -1
@@ -135,6 +135,7 @@ import { MessageSimple as MessageSimpleDefault } from '../Message/MessageSimple/
135
135
  import { MessageStatus as MessageStatusDefault } from '../Message/MessageSimple/MessageStatus';
136
136
  import { MessageTimestamp as MessageTimestampDefault } from '../Message/MessageSimple/MessageTimestamp';
137
137
  import { ReactionList as ReactionListDefault } from '../Message/MessageSimple/ReactionList';
138
+ import { StreamingMessageView as DefaultStreamingMessageView } from '../Message/MessageSimple/StreamingMessageView';
138
139
  import { AttachButton as AttachButtonDefault } from '../MessageInput/AttachButton';
139
140
  import { CommandsButton as CommandsButtonDefault } from '../MessageInput/CommandsButton';
140
141
  import { AudioRecorder as AudioRecorderDefault } from '../MessageInput/components/AudioRecorder/AudioRecorder';
@@ -154,6 +155,7 @@ import { MoreOptionsButton as MoreOptionsButtonDefault } from '../MessageInput/M
154
155
  import { SendButton as SendButtonDefault } from '../MessageInput/SendButton';
155
156
  import { SendMessageDisallowedIndicator as SendMessageDisallowedIndicatorDefault } from '../MessageInput/SendMessageDisallowedIndicator';
156
157
  import { ShowThreadMessageInChannelButton as ShowThreadMessageInChannelButtonDefault } from '../MessageInput/ShowThreadMessageInChannelButton';
158
+ import { StopMessageStreamingButton as DefaultStopMessageStreamingButton } from '../MessageInput/StopMessageStreamingButton';
157
159
  import { UploadProgressIndicator as UploadProgressIndicatorDefault } from '../MessageInput/UploadProgressIndicator';
158
160
  import { DateHeader as DateHeaderDefault } from '../MessageList/DateHeader';
159
161
  import type { MessageType } from '../MessageList/hooks/useMessageList';
@@ -333,6 +335,7 @@ export type ChannelPropsWithContext<
333
335
  | 'VideoThumbnail'
334
336
  | 'PollContent'
335
337
  | 'hasCreatePoll'
338
+ | 'StreamingMessageView'
336
339
  >
337
340
  > &
338
341
  Partial<Pick<ThreadContextValue<StreamChatGenerics>, 'allowThreadMessagesInChannel'>> & {
@@ -420,7 +423,12 @@ export type ChannelPropsWithContext<
420
423
  * Tells if channel is rendering a thread list
421
424
  */
422
425
  threadList?: boolean;
423
- } & Partial<Pick<InputMessageInputContextValue, 'openPollCreationDialog' | 'CreatePollContent'>>;
426
+ } & Partial<
427
+ Pick<
428
+ InputMessageInputContextValue,
429
+ 'openPollCreationDialog' | 'CreatePollContent' | 'StopMessageStreamingButton'
430
+ >
431
+ >;
424
432
 
425
433
  const ChannelWithContext = <
426
434
  StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
@@ -544,7 +552,15 @@ const ChannelWithContext = <
544
552
  MessageAvatar = MessageAvatarDefault,
545
553
  MessageBounce = MessageBounceDefault,
546
554
  MessageContent = MessageContentDefault,
547
- messageContentOrder = ['quoted_reply', 'gallery', 'files', 'poll', 'text', 'attachments'],
555
+ messageContentOrder = [
556
+ 'quoted_reply',
557
+ 'gallery',
558
+ 'files',
559
+ 'poll',
560
+ 'ai_text',
561
+ 'text',
562
+ 'attachments',
563
+ ],
548
564
  MessageDeleted = MessageDeletedDefault,
549
565
  MessageEditedTimestamp = MessageEditedTimestampDefault,
550
566
  MessageError = MessageErrorDefault,
@@ -596,6 +612,8 @@ const ChannelWithContext = <
596
612
  StartAudioRecordingButton = AudioRecordingButtonDefault,
597
613
  stateUpdateThrottleInterval = defaultThrottleInterval,
598
614
  StickyHeader = StickyHeaderDefault,
615
+ StopMessageStreamingButton: StopMessageStreamingButtonOverride,
616
+ StreamingMessageView = DefaultStreamingMessageView,
599
617
  supportedReactions = reactionData,
600
618
  t,
601
619
  thread: threadFromProps,
@@ -612,6 +630,10 @@ const ChannelWithContext = <
612
630
  } = props;
613
631
 
614
632
  const { thread: threadProps, threadInstance } = threadFromProps;
633
+ const StopMessageStreamingButton =
634
+ StopMessageStreamingButtonOverride === undefined
635
+ ? DefaultStopMessageStreamingButton
636
+ : StopMessageStreamingButtonOverride;
615
637
 
616
638
  const {
617
639
  theme: {
@@ -2338,6 +2360,7 @@ const ChannelWithContext = <
2338
2360
  setQuotedMessageState,
2339
2361
  ShowThreadMessageInChannelButton,
2340
2362
  StartAudioRecordingButton,
2363
+ StopMessageStreamingButton,
2341
2364
  UploadProgressIndicator,
2342
2365
  });
2343
2366
 
@@ -2439,6 +2462,7 @@ const ChannelWithContext = <
2439
2462
  setEditingState,
2440
2463
  setQuotedMessageState,
2441
2464
  shouldShowUnreadUnderlay,
2465
+ StreamingMessageView,
2442
2466
  supportedReactions,
2443
2467
  targetedMessage,
2444
2468
  TypingIndicator,
@@ -64,6 +64,7 @@ export const useCreateInputMessageInputContext = <
64
64
  showPollCreationDialog,
65
65
  ShowThreadMessageInChannelButton,
66
66
  StartAudioRecordingButton,
67
+ StopMessageStreamingButton,
67
68
  UploadProgressIndicator,
68
69
  }: InputMessageInputContextValue<StreamChatGenerics> & {
69
70
  /**
@@ -137,6 +138,7 @@ export const useCreateInputMessageInputContext = <
137
138
  showPollCreationDialog,
138
139
  ShowThreadMessageInChannelButton,
139
140
  StartAudioRecordingButton,
141
+ StopMessageStreamingButton,
140
142
  UploadProgressIndicator,
141
143
  }),
142
144
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -88,6 +88,7 @@ export const useCreateMessagesContext = <
88
88
  setEditingState,
89
89
  setQuotedMessageState,
90
90
  shouldShowUnreadUnderlay,
91
+ StreamingMessageView,
91
92
  supportedReactions,
92
93
  targetedMessage,
93
94
  TypingIndicator,
@@ -189,6 +190,7 @@ export const useCreateMessagesContext = <
189
190
  setEditingState,
190
191
  setQuotedMessageState,
191
192
  shouldShowUnreadUnderlay,
193
+ StreamingMessageView,
192
194
  supportedReactions,
193
195
  targetedMessage,
194
196
  TypingIndicator,
@@ -456,6 +456,8 @@ const MessageWithContext = <
456
456
  return !!attachments.images.length || !!attachments.videos.length;
457
457
  case 'poll':
458
458
  return !!message.poll_id;
459
+ case 'ai_text':
460
+ return !!message.ai_generated;
459
461
  case 'text':
460
462
  default:
461
463
  return !!message.text;
@@ -863,7 +865,8 @@ const areEqual = <StreamChatGenerics extends DefaultStreamChatGenerics = Default
863
865
  prevMessage.text === nextMessage.text &&
864
866
  prevMessage.pinned === nextMessage.pinned &&
865
867
  `${prevMessage?.updated_at}` === `${nextMessage?.updated_at}` &&
866
- prevMessage.i18n === nextMessage.i18n;
868
+ prevMessage.i18n === nextMessage.i18n &&
869
+ prevMessage.ai_generated === nextMessage.ai_generated;
867
870
 
868
871
  if (!messageEqual) return false;
869
872
 
@@ -100,6 +100,7 @@ export type MessageContentPropsWithContext<
100
100
  | 'myMessageTheme'
101
101
  | 'onPressInMessage'
102
102
  | 'Reply'
103
+ | 'StreamingMessageView'
103
104
  > &
104
105
  Pick<TranslationContextValue, 't'> & {
105
106
  setMessageContentWidth: React.Dispatch<React.SetStateAction<number>>;
@@ -142,6 +143,7 @@ const MessageContentWithContext = <
142
143
  Reply,
143
144
  setMessageContentWidth,
144
145
  showMessageStatus,
146
+ StreamingMessageView,
145
147
  threadList,
146
148
  } = props;
147
149
  const { client } = useChatContext();
@@ -393,9 +395,16 @@ const MessageContentWithContext = <
393
395
  />
394
396
  ) : null;
395
397
  }
398
+ case 'ai_text':
399
+ return message.ai_generated ? (
400
+ <StreamingMessageView
401
+ key={`ai_message_text_container_${messageContentOrderIndex}`}
402
+ />
403
+ ) : null;
396
404
  case 'text':
397
405
  default:
398
- return otherAttachments.length && otherAttachments[0].actions ? null : (
406
+ return (otherAttachments.length && otherAttachments[0].actions) ||
407
+ message.ai_generated ? null : (
399
408
  <MessageTextContainer<StreamChatGenerics>
400
409
  key={`message_text_container_${messageContentOrderIndex}`}
401
410
  />
@@ -484,7 +493,8 @@ const areEqual = <StreamChatGenerics extends DefaultStreamChatGenerics = Default
484
493
  prevMessage.type === nextMessage.type &&
485
494
  prevMessage.text === nextMessage.text &&
486
495
  prevMessage.pinned === nextMessage.pinned &&
487
- prevMessage.i18n === nextMessage.i18n;
496
+ prevMessage.i18n === nextMessage.i18n &&
497
+ prevMessage.ai_generated === nextMessage.ai_generated;
488
498
  if (!messageEqual) return false;
489
499
 
490
500
  const isPrevQuotedMessageTypeDeleted = prevMessage.quoted_message?.type === 'deleted';
@@ -597,6 +607,7 @@ export const MessageContent = <
597
607
  MessageStatus,
598
608
  myMessageTheme,
599
609
  Reply,
610
+ StreamingMessageView,
600
611
  } = useMessagesContext<StreamChatGenerics>();
601
612
  const { t } = useTranslationContext();
602
613
 
@@ -635,6 +646,7 @@ export const MessageContent = <
635
646
  preventPress,
636
647
  Reply,
637
648
  showMessageStatus,
649
+ StreamingMessageView,
638
650
  t,
639
651
  threadList,
640
652
  }}
@@ -129,6 +129,8 @@ const MessageFooterWithContext = <
129
129
  return null;
130
130
  }
131
131
 
132
+ const isEdited = isEditedMessage(message);
133
+
132
134
  return (
133
135
  <>
134
136
  <View style={[styles.container, metaContainer]} testID='message-status-time'>
@@ -141,7 +143,7 @@ const MessageFooterWithContext = <
141
143
  {showMessageStatus && <MessageStatus />}
142
144
  <MessageTimestamp formattedDate={formattedDate} timestamp={date} />
143
145
 
144
- {isEditedMessage(message) && !isEditedMessageOpen && (
146
+ {isEdited && !isEditedMessageOpen ? (
145
147
  <>
146
148
  <Text
147
149
  style={[
@@ -159,11 +161,11 @@ const MessageFooterWithContext = <
159
161
  {t<string>('Edited')}
160
162
  </Text>
161
163
  </>
162
- )}
164
+ ) : null}
163
165
  </View>
164
- {isEditedMessageOpen && (
166
+ {isEdited && isEditedMessageOpen ? (
165
167
  <MessageEditedTimestamp message={message} MessageTimestamp={MessageTimestamp} />
166
- )}
168
+ ) : null}
167
169
  </>
168
170
  );
169
171
  };
@@ -154,7 +154,9 @@ const areEqual = <StreamChatGenerics extends DefaultStreamChatGenerics = Default
154
154
  prevMessage.status === nextMessage.status &&
155
155
  prevMessage.type === nextMessage.type &&
156
156
  prevMessage.text === nextMessage.text &&
157
- prevMessage.i18n === nextMessage.i18n;
157
+ prevMessage.i18n === nextMessage.i18n &&
158
+ prevMessage.pinned === nextMessage.pinned &&
159
+ prevMessage.ai_generated === nextMessage.ai_generated;
158
160
  if (!messageEqual) return false;
159
161
 
160
162
  const isPrevQuotedMessageTypeDeleted = prevMessage.quoted_message?.type === 'deleted';
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+
3
+ import { MessageTextContainer, MessageTextContainerProps } from './MessageTextContainer';
4
+
5
+ import { useMessageContext } from '../../../contexts';
6
+ import type { DefaultStreamChatGenerics } from '../../../types/types';
7
+ import { useStreamingMessage } from '../hooks/useStreamingMessage';
8
+
9
+ export type StreamingMessageViewProps<
10
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
11
+ > = Pick<MessageTextContainerProps<StreamChatGenerics>, 'message'> & {
12
+ letterInterval?: number;
13
+ renderingLetterCount?: number;
14
+ };
15
+
16
+ export const StreamingMessageView = <
17
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
18
+ >(
19
+ props: StreamingMessageViewProps<StreamChatGenerics>,
20
+ ) => {
21
+ const { letterInterval, message: messageFromProps, renderingLetterCount } = props;
22
+ const { message: messageFromContext } = useMessageContext<StreamChatGenerics>();
23
+ const message = messageFromProps || messageFromContext;
24
+ const { text = '' } = message;
25
+ const { streamedMessageText } = useStreamingMessage({
26
+ letterInterval,
27
+ renderingLetterCount,
28
+ text,
29
+ });
30
+
31
+ return <MessageTextContainer message={{ ...message, text: streamedMessageText }} />;
32
+ };
33
+
34
+ StreamingMessageView.displayName = 'StreamingMessageView{messageSimple{content}}';
@@ -33,7 +33,11 @@ export const generateMarkdownText = (text?: string) => {
33
33
  resultText = resultText.replace(mentionsRegex, `@${displayLink}`);
34
34
  }
35
35
 
36
- resultText = resultText.replace(/[<"'>]/g, '\\$&');
36
+ // Escape the " and ' characters, except in code blocks where we deem this allowed.
37
+ resultText = resultText.replace(/(```[\s\S]*?```|`.*?`)|[<"'>]/g, (match, code) => {
38
+ if (code) return code;
39
+ return `\\${match}`;
40
+ });
37
41
 
38
42
  // Remove whitespaces that come directly after newlines except in code blocks where we deem this allowed.
39
43
  resultText = resultText.replace(/(```[\s\S]*?```|`.*?`)|\n[ ]{2,}/g, (_, code) => {
@@ -41,5 +45,9 @@ export const generateMarkdownText = (text?: string) => {
41
45
  return '\n';
42
46
  });
43
47
 
48
+ // Always replace \n``` with \n\n``` to force the markdown state machine to treat it as a separate block. Otherwise, code blocks inside of list
49
+ // items for example were broken. We clean up the code block closing state within the rendering itself.
50
+ resultText = resultText.replace(/\n```/g, '\n\n```');
51
+
44
52
  return resultText;
45
53
  };
@@ -1,8 +1,18 @@
1
- import React, { PropsWithChildren } from 'react';
2
- import { GestureResponderEvent, Linking, Text, TextProps, View, ViewProps } from 'react-native';
3
-
1
+ import React, { PropsWithChildren, ReactNode, useCallback, useMemo } from 'react';
2
+ import {
3
+ GestureResponderEvent,
4
+ Linking,
5
+ Platform,
6
+ Text,
7
+ TextProps,
8
+ View,
9
+ ViewProps,
10
+ } from 'react-native';
11
+
12
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
4
13
  // @ts-expect-error
5
14
  import Markdown from 'react-native-markdown-package';
15
+ import Animated, { clamp, scrollTo, useAnimatedRef, useSharedValue } from 'react-native-reanimated';
6
16
 
7
17
  import {
8
18
  DefaultRules,
@@ -26,7 +36,64 @@ import type { DefaultStreamChatGenerics } from '../../../../types/types';
26
36
  import { escapeRegExp } from '../../../../utils/utils';
27
37
  import type { MessageType } from '../../../MessageList/hooks/useMessageList';
28
38
 
39
+ export const MarkdownReactiveScrollView = ({ children }: { children: ReactNode }) => {
40
+ const scrollViewRef = useAnimatedRef<Animated.ScrollView>();
41
+ const contentWidth = useSharedValue(0);
42
+ const visibleContentWidth = useSharedValue(0);
43
+ const offsetBeforeScroll = useSharedValue(0);
44
+
45
+ const panGesture = Gesture.Pan()
46
+ .activeOffsetX([-5, 5])
47
+ .onUpdate((event) => {
48
+ const { translationX } = event;
49
+
50
+ scrollTo(scrollViewRef, offsetBeforeScroll.value - translationX, 0, false);
51
+ })
52
+ .onEnd((event) => {
53
+ const { translationX } = event;
54
+
55
+ const velocityEffect = event.velocityX * 0.3;
56
+
57
+ const finalPosition = clamp(
58
+ offsetBeforeScroll.value - translationX - velocityEffect,
59
+ 0,
60
+ contentWidth.value - visibleContentWidth.value,
61
+ );
62
+
63
+ offsetBeforeScroll.value = finalPosition;
64
+
65
+ scrollTo(scrollViewRef, finalPosition, 0, true);
66
+ });
67
+
68
+ return (
69
+ <View style={{ width: '100%' }}>
70
+ <GestureDetector gesture={panGesture}>
71
+ <Animated.ScrollView
72
+ contentContainerStyle={{ flexGrow: 1 }}
73
+ horizontal
74
+ nestedScrollEnabled={true}
75
+ onContentSizeChange={(width) => {
76
+ contentWidth.value = width;
77
+ }}
78
+ onLayout={(e) => {
79
+ visibleContentWidth.value = e.nativeEvent.layout.width;
80
+ }}
81
+ ref={scrollViewRef}
82
+ scrollEnabled={false}
83
+ >
84
+ {children}
85
+ </Animated.ScrollView>
86
+ </GestureDetector>
87
+ </View>
88
+ );
89
+ };
90
+
29
91
  const defaultMarkdownStyles: MarkdownStyle = {
92
+ codeBlock: {
93
+ fontFamily: Platform.OS === 'ios' ? 'Courier' : 'Monospace',
94
+ fontWeight: '500',
95
+ marginVertical: 8,
96
+ },
30
97
  inlineCode: {
31
98
  fontSize: 13,
32
99
  padding: 3,
@@ -60,6 +127,26 @@ const defaultMarkdownStyles: MarkdownStyle = {
60
127
  marginBottom: 8,
61
128
  marginTop: 8,
62
129
  },
130
+ table: {
131
+ borderRadius: 3,
132
+ borderWidth: 1,
133
+ flex: 1,
134
+ flexDirection: 'row',
135
+ },
136
+ tableHeader: {
137
+ flexDirection: 'row',
138
+ justifyContent: 'space-around',
139
+ },
140
+ tableHeaderCell: {
141
+ fontWeight: '500',
142
+ },
143
+ tableRow: {
144
+ alignItems: 'center',
145
+ justifyContent: 'space-around',
146
+ },
147
+ tableRowCell: {
148
+ flex: 1,
149
+ },
63
150
  };
64
151
 
65
152
  const mentionsParseFunction: ParseFunction = (capture, parse, state) => ({
@@ -113,6 +200,13 @@ export const renderText = <
113
200
  color: colors.accent_blue,
114
201
  ...markdownStyles?.autolink,
115
202
  },
203
+ codeBlock: {
204
+ ...defaultMarkdownStyles.codeBlock,
205
+ backgroundColor: colors.code_block,
206
+ color: colors.black,
207
+ padding: 8,
208
+ ...markdownStyles?.codeBlock,
209
+ },
116
210
  inlineCode: {
117
211
  ...defaultMarkdownStyles.inlineCode,
118
212
  backgroundColor: colors.white_smoke,
@@ -125,6 +219,35 @@ export const renderText = <
125
219
  color: colors.accent_blue,
126
220
  ...markdownStyles?.mentions,
127
221
  },
222
+ table: {
223
+ ...defaultMarkdownStyles.table,
224
+ borderColor: colors.grey_dark,
225
+ marginVertical: 8,
226
+ ...markdownStyles?.table,
227
+ },
228
+ tableHeader: {
229
+ ...defaultMarkdownStyles.tableHeader,
230
+ backgroundColor: colors.grey,
231
+ ...markdownStyles?.tableHeader,
232
+ },
233
+ tableHeaderCell: {
234
+ ...defaultMarkdownStyles.tableHeaderCell,
235
+ padding: 5,
236
+ ...markdownStyles?.tableHeaderCell,
237
+ },
238
+ tableRow: {
239
+ ...defaultMarkdownStyles.tableRow,
240
+ ...markdownStyles?.tableRow,
241
+ },
242
+ tableRowCell: {
243
+ ...defaultMarkdownStyles.tableRowCell,
244
+ borderColor: colors.grey_dark,
245
+ padding: 5,
246
+ ...markdownStyles?.tableRowCell,
247
+ },
248
+ tableRowLast: {
249
+ ...markdownStyles?.tableRowLast,
250
+ },
128
251
  text: {
129
252
  ...defaultMarkdownStyles.text,
130
253
  color: colors.black,
@@ -263,6 +386,18 @@ export const renderText = <
263
386
  />
264
387
  );
265
388
 
389
+ const codeBlockReact: ReactNodeOutput = (node, _, state) => (
390
+ <MarkdownReactiveScrollView key={state.key}>
391
+ <Text style={styles.codeBlock}>{node?.content?.trim()}</Text>
392
+ </MarkdownReactiveScrollView>
393
+ );
394
+
395
+ const tableReact: ReactNodeOutput = (node, output, state) => (
396
+ <MarkdownReactiveScrollView key={state.key}>
397
+ <MarkdownTable node={node} output={output} state={state} styles={styles} />
398
+ </MarkdownReactiveScrollView>
399
+ );
400
+
266
401
  const customRules = {
267
402
  // do not render images, we will scrape them out of the message and show on attachment card component
268
403
  image: { match: () => null },
@@ -283,6 +418,8 @@ export const renderText = <
283
418
  },
284
419
  }
285
420
  : {}),
421
+ codeBlock: { react: codeBlockReact },
422
+ table: { react: tableReact },
286
423
  };
287
424
 
288
425
  return (
@@ -373,3 +510,70 @@ const ListRow = ({ children, style }: PropsWithChildren<ViewProps>) => (
373
510
  const ListItem = ({ children, style }: PropsWithChildren<TextProps>) => (
374
511
  <Text style={style}>{children}</Text>
375
512
  );
513
+
514
+ export type MarkdownTableProps = {
515
+ node: SingleASTNode;
516
+ output: ReactOutput;
517
+ state: State;
518
+ styles: Partial<MarkdownStyle>;
519
+ };
520
+
521
+ const transpose = (matrix: SingleASTNode[][]) =>
522
+ matrix[0].map((_, colIndex) => matrix.map((row) => row[colIndex]));
523
+
524
+ const MarkdownTable = ({ node, output, state, styles }: MarkdownTableProps) => {
525
+ const content = useMemo(() => {
526
+ const nodeContent = [node?.header, ...node?.cells];
527
+ return transpose(nodeContent);
528
+ }, [node?.cells, node?.header]);
529
+ const columns = content?.map((column, idx) => (
530
+ <MarkdownTableColumn
531
+ items={column}
532
+ key={`column-${idx}`}
533
+ output={output}
534
+ state={state}
535
+ styles={styles}
536
+ />
537
+ ));
538
+
539
+ return (
540
+ <View key={state.key} style={styles.table}>
541
+ {columns}
542
+ </View>
543
+ );
544
+ };
545
+
546
+ export type MarkdownTableRowProps = {
547
+ items: SingleASTNode[];
548
+ output: ReactOutput;
549
+ state: State;
550
+ styles: Partial<MarkdownStyle>;
551
+ };
552
+
553
+ const MarkdownTableColumn = ({ items, output, state, styles }: MarkdownTableRowProps) => {
554
+ const [headerCellContent, ...columnCellContents] = items;
555
+
556
+ const ColumnCell = useCallback(
557
+ ({ content }: { content: SingleASTNode }) =>
558
+ content ? (
559
+ <View style={styles.tableRow}>
560
+ <View style={styles.tableRowCell}>{output(content, state)}</View>
561
+ </View>
562
+ ) : null,
563
+ [output, state, styles],
564
+ );
565
+
566
+ return (
567
+ <View style={{ flex: 1, flexDirection: 'column' }}>
568
+ {headerCellContent ? (
569
+ <View key={-1} style={styles.tableHeader}>
570
+ <Text style={styles.tableHeaderCell}>{output(headerCellContent, state)}</Text>
571
+ </View>
572
+ ) : null}
573
+ {columnCellContents &&
574
+ columnCellContents.map((content, idx) => (
575
+ <ColumnCell content={content} key={`cell-${idx}`} />
576
+ ))}
577
+ </View>
578
+ );
579
+ };
@@ -0,0 +1,54 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+
3
+ import type { DefaultStreamChatGenerics } from '../../../types/types';
4
+ import { StreamingMessageViewProps } from '../MessageSimple/StreamingMessageView';
5
+
6
+ export type UseStreamingMessageProps<
7
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
8
+ > = Pick<
9
+ StreamingMessageViewProps<StreamChatGenerics>,
10
+ 'letterInterval' | 'renderingLetterCount'
11
+ > & { text: string };
12
+
13
+ const DEFAULT_LETTER_INTERVAL = 0;
14
+ const DEFAULT_RENDERING_LETTER_COUNT = 2;
15
+
16
+ /**
17
+ * A hook that returns text in a streamed, typewriter fashion. The speed of streaming is
18
+ * configurable.
19
+ * @param {number} [letterInterval=0] - The timeout between each typing animation in milliseconds.
20
+ * @param {number} [renderingLetterCount=2] - The number of letters to be rendered each time we update.
21
+ * @param {string} text - The text that we want to render in a typewriter fashion.
22
+ * @returns {{ streamedMessageText: string }} - A substring of the text property, up until we've finished rendering the typewriter animation.
23
+ */
24
+ export const useStreamingMessage = <
25
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
26
+ >({
27
+ letterInterval = DEFAULT_LETTER_INTERVAL,
28
+ renderingLetterCount = DEFAULT_RENDERING_LETTER_COUNT,
29
+ text,
30
+ }: UseStreamingMessageProps<StreamChatGenerics>): { streamedMessageText: string } => {
31
+ const [streamedMessageText, setStreamedMessageText] = useState<string>(text);
32
+ const textCursor = useRef<number>(text.length);
33
+
34
+ useEffect(() => {
35
+ const textLength = text.length;
36
+ const interval = setInterval(() => {
37
+ if (!text || textCursor.current >= textLength) {
38
+ clearInterval(interval);
39
+ }
40
+ const newCursorValue = textCursor.current + renderingLetterCount;
41
+ const newText = text.substring(0, newCursorValue);
42
+ textCursor.current += newText.length - textCursor.current;
43
+ const codeBlockCounts = (newText.match(/```/g) || []).length;
44
+ const shouldOptimisticallyCloseCodeBlock = codeBlockCounts > 0 && codeBlockCounts % 2 > 0;
45
+ setStreamedMessageText(shouldOptimisticallyCloseCodeBlock ? newText + '```' : newText);
46
+ }, letterInterval);
47
+
48
+ return () => {
49
+ clearInterval(interval);
50
+ };
51
+ }, [letterInterval, renderingLetterCount, text]);
52
+
53
+ return { streamedMessageText };
54
+ };