@memori.ai/memori-react 8.10.1 → 8.12.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.
Files changed (177) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/components/AgeVerificationModal/AgeVerificationModal.css +41 -14
  3. package/dist/components/AgeVerificationModal/AgeVerificationModal.js +2 -2
  4. package/dist/components/AgeVerificationModal/AgeVerificationModal.js.map +1 -1
  5. package/dist/components/Auth/Auth.js +36 -8
  6. package/dist/components/Auth/Auth.js.map +1 -1
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +2 -2
  8. package/dist/components/Chat/Chat.js +2 -2
  9. package/dist/components/Chat/Chat.js.map +1 -1
  10. package/dist/components/ChatBubble/ChatBubble.css +81 -13
  11. package/dist/components/ChatBubble/ChatBubble.js +86 -19
  12. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  13. package/dist/components/ChatHistoryDrawer/ChatHistory.css +5 -1
  14. package/dist/components/ChatInputs/ChatInputs.d.ts +1 -0
  15. package/dist/components/ChatInputs/ChatInputs.js +8 -3
  16. package/dist/components/ChatInputs/ChatInputs.js.map +1 -1
  17. package/dist/components/DateSelector/DateSelector.css +125 -104
  18. package/dist/components/DateSelector/DateSelector.d.ts +1 -1
  19. package/dist/components/DateSelector/DateSelector.js +110 -52
  20. package/dist/components/DateSelector/DateSelector.js.map +1 -1
  21. package/dist/components/Header/Header.js +1 -1
  22. package/dist/components/Header/Header.js.map +1 -1
  23. package/dist/components/LoginDrawer/LoginDrawer.css +37 -5
  24. package/dist/components/LoginDrawer/LoginDrawer.d.ts +1 -2
  25. package/dist/components/LoginDrawer/LoginDrawer.js +2 -9
  26. package/dist/components/LoginDrawer/LoginDrawer.js.map +1 -1
  27. package/dist/components/MediaWidget/MediaItemWidget.js +40 -5
  28. package/dist/components/MediaWidget/MediaItemWidget.js.map +1 -1
  29. package/dist/components/MediaWidget/MediaWidget.css +4 -0
  30. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +1 -1
  31. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -1
  32. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js +1 -1
  33. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js.map +1 -1
  34. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js +3 -0
  35. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js.map +1 -1
  36. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +2 -2
  37. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  38. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +16 -7
  39. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js +6 -4
  40. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js.map +1 -1
  41. package/dist/components/MemoriWidget/MemoriWidget.js +69 -25
  42. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  43. package/dist/components/UploadButton/UploadButton.js +2 -0
  44. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  45. package/dist/components/UploadButton/UploadImages/UploadImages.js +2 -2
  46. package/dist/components/UploadButton/UploadImages/UploadImages.js.map +1 -1
  47. package/dist/components/ui/Drawer.css +8 -0
  48. package/dist/components/ui/Drawer.d.ts +2 -0
  49. package/dist/components/ui/Drawer.js +2 -2
  50. package/dist/components/ui/Drawer.js.map +1 -1
  51. package/dist/components/ui/Tooltip.css +49 -1
  52. package/dist/components/ui/Tooltip.d.ts +1 -1
  53. package/dist/locales/de.json +6 -1
  54. package/dist/locales/en.json +9 -1
  55. package/dist/locales/es.json +6 -1
  56. package/dist/locales/fr.json +5 -1
  57. package/dist/locales/it.json +8 -1
  58. package/dist/styles.css +3 -2
  59. package/esm/components/AgeVerificationModal/AgeVerificationModal.css +41 -14
  60. package/esm/components/AgeVerificationModal/AgeVerificationModal.js +2 -2
  61. package/esm/components/AgeVerificationModal/AgeVerificationModal.js.map +1 -1
  62. package/esm/components/Auth/Auth.js +36 -8
  63. package/esm/components/Auth/Auth.js.map +1 -1
  64. package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +2 -2
  65. package/esm/components/Chat/Chat.js +2 -2
  66. package/esm/components/Chat/Chat.js.map +1 -1
  67. package/esm/components/ChatBubble/ChatBubble.css +81 -13
  68. package/esm/components/ChatBubble/ChatBubble.js +87 -20
  69. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  70. package/esm/components/ChatHistoryDrawer/ChatHistory.css +5 -1
  71. package/esm/components/ChatInputs/ChatInputs.d.ts +1 -0
  72. package/esm/components/ChatInputs/ChatInputs.js +8 -3
  73. package/esm/components/ChatInputs/ChatInputs.js.map +1 -1
  74. package/esm/components/DateSelector/DateSelector.css +125 -104
  75. package/esm/components/DateSelector/DateSelector.d.ts +1 -1
  76. package/esm/components/DateSelector/DateSelector.js +111 -52
  77. package/esm/components/DateSelector/DateSelector.js.map +1 -1
  78. package/esm/components/Header/Header.js +1 -1
  79. package/esm/components/Header/Header.js.map +1 -1
  80. package/esm/components/LoginDrawer/LoginDrawer.css +37 -5
  81. package/esm/components/LoginDrawer/LoginDrawer.d.ts +1 -2
  82. package/esm/components/LoginDrawer/LoginDrawer.js +2 -9
  83. package/esm/components/LoginDrawer/LoginDrawer.js.map +1 -1
  84. package/esm/components/MediaWidget/MediaItemWidget.js +40 -5
  85. package/esm/components/MediaWidget/MediaItemWidget.js.map +1 -1
  86. package/esm/components/MediaWidget/MediaWidget.css +4 -0
  87. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +1 -1
  88. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -1
  89. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js +1 -1
  90. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js.map +1 -1
  91. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js +3 -0
  92. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js.map +1 -1
  93. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +2 -2
  94. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  95. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +16 -7
  96. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js +6 -4
  97. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js.map +1 -1
  98. package/esm/components/MemoriWidget/MemoriWidget.js +69 -25
  99. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  100. package/esm/components/UploadButton/UploadButton.js +2 -0
  101. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  102. package/esm/components/UploadButton/UploadImages/UploadImages.js +2 -2
  103. package/esm/components/UploadButton/UploadImages/UploadImages.js.map +1 -1
  104. package/esm/components/ui/Drawer.css +8 -0
  105. package/esm/components/ui/Drawer.d.ts +2 -0
  106. package/esm/components/ui/Drawer.js +2 -2
  107. package/esm/components/ui/Drawer.js.map +1 -1
  108. package/esm/components/ui/Tooltip.css +49 -1
  109. package/esm/components/ui/Tooltip.d.ts +1 -1
  110. package/esm/locales/de.json +6 -1
  111. package/esm/locales/en.json +9 -1
  112. package/esm/locales/es.json +6 -1
  113. package/esm/locales/fr.json +5 -1
  114. package/esm/locales/it.json +8 -1
  115. package/esm/styles.css +3 -2
  116. package/package.json +2 -2
  117. package/src/components/AgeVerificationModal/AgeVerificationModal.css +41 -14
  118. package/src/components/AgeVerificationModal/AgeVerificationModal.tsx +3 -1
  119. package/src/components/Auth/Auth.tsx +55 -11
  120. package/src/components/Avatar/Avatar.stories.tsx +3 -0
  121. package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +2 -2
  122. package/src/components/Chat/Chat.stories.tsx +16 -2
  123. package/src/components/Chat/Chat.tsx +2 -1
  124. package/src/components/Chat/__snapshots__/Chat.test.tsx.snap +1036 -36
  125. package/src/components/ChatBubble/ChatBubble.css +81 -13
  126. package/src/components/ChatBubble/ChatBubble.stories.tsx +16 -2
  127. package/src/components/ChatBubble/ChatBubble.test.tsx +17 -0
  128. package/src/components/ChatBubble/ChatBubble.tsx +144 -31
  129. package/src/components/ChatBubble/__snapshots__/ChatBubble.test.tsx.snap +304 -8
  130. package/src/components/ChatHistoryDrawer/ChatHistory.css +5 -1
  131. package/src/components/ChatInputs/ChatInputs.tsx +14 -1
  132. package/src/components/DateSelector/DateSelector.css +125 -104
  133. package/src/components/DateSelector/DateSelector.stories.tsx +1 -1
  134. package/src/components/DateSelector/DateSelector.test.tsx +137 -23
  135. package/src/components/DateSelector/DateSelector.tsx +203 -177
  136. package/src/components/Header/Header.stories.tsx +5 -1
  137. package/src/components/Header/Header.tsx +1 -1
  138. package/src/components/Header/__snapshots__/Header.test.tsx.snap +1 -1
  139. package/src/components/LoginDrawer/LoginDrawer.css +37 -5
  140. package/src/components/LoginDrawer/LoginDrawer.stories.tsx +0 -1
  141. package/src/components/LoginDrawer/LoginDrawer.test.tsx +0 -1
  142. package/src/components/LoginDrawer/LoginDrawer.tsx +0 -19
  143. package/src/components/MediaWidget/MediaItemWidget.stories.tsx +69 -0
  144. package/src/components/MediaWidget/MediaItemWidget.tsx +66 -18
  145. package/src/components/MediaWidget/MediaWidget.css +4 -0
  146. package/src/components/MediaWidget/__snapshots__/MediaItemWidget.test.tsx.snap +10 -10
  147. package/src/components/MediaWidget/__snapshots__/MediaWidget.test.tsx.snap +2 -2
  148. package/src/components/MemoriArtifactSystem/ArtifactDrawer.stories.tsx +996 -204
  149. package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.tsx +2 -2
  150. package/src/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.tsx +1 -1
  151. package/src/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.tsx +3 -0
  152. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.tsx +56 -54
  153. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +16 -7
  154. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.tsx +12 -3
  155. package/src/components/MemoriWidget/MemoriWidget.stories.tsx +6 -3
  156. package/src/components/MemoriWidget/MemoriWidget.tsx +123 -48
  157. package/src/components/UploadButton/UploadButton.tsx +2 -0
  158. package/src/components/UploadButton/UploadImages/UploadImages.tsx +3 -2
  159. package/src/components/layouts/FullBody/FullBody.stories.tsx +110 -0
  160. package/src/components/layouts/Totem/Totem.stories.tsx +131 -0
  161. package/src/components/layouts/ZoomedFullBody/ZoomedFullBody.stories.tsx +131 -0
  162. package/src/components/layouts/layouts.stories.tsx +55 -508
  163. package/src/components/ui/Drawer.css +8 -0
  164. package/src/components/ui/Drawer.tsx +16 -12
  165. package/src/components/ui/Tooltip.css +49 -1
  166. package/src/components/ui/Tooltip.tsx +1 -1
  167. package/src/index.stories.tsx +13 -320
  168. package/src/locales/de.json +6 -1
  169. package/src/locales/en.json +9 -1
  170. package/src/locales/es.json +6 -1
  171. package/src/locales/fr.json +5 -1
  172. package/src/locales/it.json +8 -1
  173. package/src/mocks/data.ts +4 -2
  174. package/src/styles.css +3 -2
  175. package/src/components/SignupForm/SignupForm.test.tsx +0 -40
  176. package/src/components/SignupForm/SignupForm.tsx +0 -457
  177. package/src/components/SignupForm/__snapshots__/SignupForm.test.tsx.snap +0 -247
@@ -14,6 +14,35 @@
14
14
  background: #c5c6d1;
15
15
  }
16
16
 
17
+ .memori-chat--bubble-status-message {
18
+ display: flex;
19
+ width: 100%;
20
+ align-items: center;
21
+ justify-content: center;
22
+ padding: 0 1rem;
23
+ margin: 1.5rem auto;
24
+ }
25
+
26
+ .memori-chat--bubble-status-message-content {
27
+ display: inline-flex;
28
+ max-width: 90%;
29
+ align-items: center;
30
+ justify-content: center;
31
+ padding: 0.5rem 1rem;
32
+ border: 1px solid rgba(76, 111, 255, 0.2);
33
+ border-radius: 20px;
34
+ backdrop-filter: blur(10px);
35
+ background: linear-gradient(135deg, rgba(76, 111, 255, 0.1) 0%, rgba(76, 111, 255, 0.05) 100%);
36
+ box-shadow: 0 2px 8px rgba(76, 111, 255, 0.1);
37
+ color: var(--memori-primary, #4c6fff);
38
+ font-size: 0.75rem;
39
+ font-weight: 500;
40
+ letter-spacing: 0.02em;
41
+ line-height: 1.4;
42
+ text-align: center;
43
+ word-wrap: break-word;
44
+ }
45
+
17
46
  .memori-chat--bubble {
18
47
  display: inline-flex;
19
48
  max-width: 90%;
@@ -30,10 +59,33 @@
30
59
  line-height: 1.33;
31
60
  }
32
61
 
62
+
63
+ .memori-chat--bubble-from-user {
64
+ z-index: -1;
65
+ }
66
+
33
67
  .memori-chat--bubble-container.memori-chat--with-addon .memori-chat--bubble {
34
68
  min-width: 9rem;
35
69
  }
36
70
 
71
+
72
+ .memori-chat--bubble-avatar svg {
73
+ width: 40px;
74
+ height: 40px;
75
+ padding: 0.33rem;
76
+ fill: #666;
77
+ font-size: 1.5rem;
78
+ }
79
+
80
+
81
+ .memori-chat--bubble-action-icon--from-user > span > svg {
82
+ color: #fff !important;
83
+ }
84
+
85
+ .memori-chat--bubble-action-icon--from-user:focus {
86
+ border-color: #fff !important;
87
+ }
88
+
37
89
  .memori-chat--bubble #wave {
38
90
  position: relative;
39
91
  width: 50px;
@@ -145,14 +197,6 @@
145
197
  object-fit: cover;
146
198
  }
147
199
 
148
- .memori-chat--bubble-avatar svg {
149
- width: 40px;
150
- height: 40px;
151
- padding: 0.33rem;
152
- fill: #666;
153
- font-size: 1.5rem;
154
- }
155
-
156
200
  .memori-chat--bubble-addon {
157
201
  display: flex;
158
202
  align-items: center;
@@ -164,14 +208,10 @@
164
208
  .memori-chat--bubble .memori-chat--bubble-action-icon {
165
209
  display: flex;
166
210
  justify-content: flex-end;
167
- padding: 0;
211
+ padding: 2px;
168
212
  text-align: right;
169
213
  }
170
214
 
171
- .memori-chat--bubble .memori-chat--bubble-action-icon.memori-chat--bubble-action-icon--ai {
172
- padding: 3px;
173
- }
174
-
175
215
  .memori-chat--bubble .memori-chat--bubble-action-icon svg {
176
216
  overflow: visible;
177
217
  width: 1rem;
@@ -179,6 +219,21 @@
179
219
  color: #000;
180
220
  }
181
221
 
222
+ .memori-chat--bubble .memori-chat--bubble-action-icon.memori-chat--bubble-action-icon--copied {
223
+ border-color: var(--memori-primary, #4c6fff) !important;
224
+ background-color: rgba(76, 111, 255, 0.08);
225
+ }
226
+
227
+ .memori-chat--bubble .memori-chat--bubble-action-icon.memori-chat--bubble-action-icon--copied svg {
228
+ color: var(--memori-primary, #4c6fff);
229
+ }
230
+
231
+ .memori-chat--bubble .memori-chat--bubble-action-icon.memori-chat--bubble-action-icon--ai {
232
+ padding: 3px;
233
+ }
234
+
235
+
236
+
182
237
  .memori-chat--bubble .memori-chat--bubble-action-icon.memori-chat--bubble-action-icon--debug {
183
238
  color: var(--memori-error-color);
184
239
  }
@@ -212,6 +267,19 @@
212
267
  padding: 2.5px;
213
268
  }
214
269
 
270
+ .memori-chat--bubble-action-feedback {
271
+ margin-left: 0.35rem;
272
+ color: var(--memori-primary, #4c6fff);
273
+ font-size: 0.65rem;
274
+ font-weight: 600;
275
+ letter-spacing: 0.03em;
276
+ text-transform: uppercase;
277
+ }
278
+
279
+ .memori-chat--bubble-action-feedback--from-user {
280
+ color: var(--memori-primary-text, #fff);
281
+ }
282
+
215
283
  .memori-chat--bubble-addon .memori-chat--feedback button .memori-button--icon svg {
216
284
  width: 0.9rem;
217
285
  height: 0.9rem;
@@ -201,10 +201,10 @@ WithAllAddonsContents.args = {
201
201
  tenant,
202
202
  message: {
203
203
  fromUser: false,
204
- text: 'Proin libero ante, dignissim sit amet turpis a, pretium condimentum dolor.',
204
+ text: 'Proin libero ante.',
205
205
  initial: false,
206
206
  translatedText:
207
- 'Proin libero ante, dignissim sit amet turpis a, pretium condimentum dolor.',
207
+ 'Proin libero ter.',
208
208
  generatedByAI: true,
209
209
  },
210
210
  showFeedback: true,
@@ -314,6 +314,20 @@ FromUserWithCustomAvatarElement.args = {
314
314
  },
315
315
  };
316
316
 
317
+ export const FromUserWithCopyButtons = Template.bind({});
318
+ FromUserWithCopyButtons.args = {
319
+ memori,
320
+ tenant,
321
+ message: {
322
+ fromUser: true,
323
+ text: '<p>Questo messaggio mostra i pulsanti di copia anche per gli utenti finali.</p>',
324
+ initial: false,
325
+ translatedText:
326
+ 'Questo messaggio mostra i pulsanti di copia anche per gli utenti finali.',
327
+ },
328
+ showCopyButton: true,
329
+ };
330
+
317
331
  export const FromUserWithAvatarAndCustomAvatar = Template.bind({});
318
332
  FromUserWithAvatarAndCustomAvatar.args = {
319
333
  memori,
@@ -57,6 +57,23 @@ it('renders ChatBubble with user msg unchanged', () => {
57
57
  expect(container).toMatchSnapshot();
58
58
  });
59
59
 
60
+ it('shows copy button for user messages', () => {
61
+ const { container } = render(
62
+ <ChatBubble
63
+ memori={memori}
64
+ tenant={tenant}
65
+ sessionID={sessionID}
66
+ message={{
67
+ fromUser: true,
68
+ text: 'Mostra i pulsanti copia sul messaggio utente.',
69
+ initial: false,
70
+ }}
71
+ />
72
+ );
73
+
74
+ expect(container).toMatchSnapshot();
75
+ });
76
+
60
77
  it('renders ChatBubble with copy disabled unchanged', () => {
61
78
  const { container } = render(
62
79
  <ChatBubble
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useLayoutEffect, useState } from 'react';
1
+ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
2
2
  import cx from 'classnames';
3
3
  import {
4
4
  ExpertReference,
@@ -89,6 +89,17 @@ const ChatBubble: React.FC<Props> = ({
89
89
  const lang = i18n.language || 'en';
90
90
  const [showingWhyThisAnswer, setShowingWhyThisAnswer] = useState(false);
91
91
  const [openFunctionCache, setOpenFunctionCache] = useState(false);
92
+ const [copyFeedback, setCopyFeedback] = useState({
93
+ plain: false,
94
+ raw: false,
95
+ });
96
+ const copyFeedbackTimers = useRef<{
97
+ plain: ReturnType<typeof setTimeout> | null;
98
+ raw: ReturnType<typeof setTimeout> | null;
99
+ }>({
100
+ plain: null,
101
+ raw: null,
102
+ });
92
103
  // Initialize MathJax on component mount
93
104
  useEffect(() => {
94
105
  if (typeof window !== 'undefined' && !window.MathJax) {
@@ -110,6 +121,16 @@ const ChatBubble: React.FC<Props> = ({
110
121
  const plainText = message.fromUser
111
122
  ? truncateMessage(cleanText)
112
123
  : stripHTML(stripOutputTags(renderedText));
124
+ const copyText = message.fromUser ? cleanText : plainText;
125
+ const shouldShowCopyButtons =
126
+ showCopyButton && (!!plainText?.length || !!message.text?.length);
127
+ const shouldShowCopyRawButton =
128
+ shouldShowCopyButtons &&
129
+ !!message.text?.length && plainText !== message.text;
130
+ const rawMessageText = message.fromUser
131
+ ? message.text || ''
132
+ : (message.text || '').replaceAll(/<think.*?>(.*?)<\/think>/gs, '');
133
+ const copiedLabel = t('copied') || 'Copied';
113
134
 
114
135
  // Format function cache content
115
136
  const functionCacheData = message.media?.filter(
@@ -157,9 +178,63 @@ const ChatBubble: React.FC<Props> = ({
157
178
  }
158
179
  }, [cleanText, message.fromUser, renderedText]);
159
180
 
181
+ useEffect(() => {
182
+ return () => {
183
+ (Object.keys(copyFeedbackTimers.current) as Array<'plain' | 'raw'>).forEach(
184
+ key => {
185
+ const timer = copyFeedbackTimers.current[key];
186
+ if (timer) {
187
+ clearTimeout(timer);
188
+ copyFeedbackTimers.current[key] = null;
189
+ }
190
+ }
191
+ );
192
+ };
193
+ }, []);
194
+
195
+ const triggerCopyFeedback = (type: 'plain' | 'raw') => {
196
+ setCopyFeedback(prev => ({ ...prev, [type]: true }));
197
+ if (copyFeedbackTimers.current[type]) {
198
+ clearTimeout(copyFeedbackTimers.current[type]!);
199
+ }
200
+ copyFeedbackTimers.current[type] = setTimeout(() => {
201
+ setCopyFeedback(prev => ({ ...prev, [type]: false }));
202
+ copyFeedbackTimers.current[type] = null;
203
+ }, 1500);
204
+ };
205
+
206
+ const handleCopyClick = (type: 'plain' | 'raw', text: string) => {
207
+ if (!text?.length) return;
208
+ if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
209
+ navigator.clipboard
210
+ .writeText(text)
211
+ .then(() => triggerCopyFeedback(type))
212
+ .catch(err => {
213
+ console.error('Copy failed', err);
214
+ });
215
+ } else {
216
+ triggerCopyFeedback(type);
217
+ }
218
+ };
219
+
220
+ // Check if initial is a string (status message) or boolean (legacy)
221
+ const initialStatus = typeof message.initial === 'string' ? message.initial : null;
222
+ const showInitialDivider = (message.initial === true || isFirst) && !initialStatus;
223
+
224
+
225
+ if(initialStatus) {
226
+ return (
227
+ <div className="memori-chat--bubble-status-message">
228
+ <div className="memori-chat--bubble-status-message-content">
229
+ {initialStatus}
230
+ </div>
231
+ </div>
232
+ );
233
+ }
234
+
160
235
  return (
161
236
  <>
162
- {(message.initial || isFirst) && (
237
+ {showInitialDivider && (
163
238
  <div className="memori-chat--bubble-initial" />
164
239
  )}
165
240
  <Transition
@@ -252,7 +327,7 @@ const ChatBubble: React.FC<Props> = ({
252
327
  className={cx('memori-chat--bubble', {
253
328
  'memori-chat--user-bubble': !!message.fromUser,
254
329
  'memori-chat--with-addon':
255
- (!message.fromUser && showCopyButton) ||
330
+ shouldShowCopyButtons ||
256
331
  (message.generatedByAI && showAIicon) ||
257
332
  (showFeedback && simulateUserPrompt),
258
333
  'memori-chat--ai-generated': message.generatedByAI && showAIicon,
@@ -288,42 +363,80 @@ const ChatBubble: React.FC<Props> = ({
288
363
  />
289
364
  )}
290
365
 
291
- {((!message.fromUser && showCopyButton) ||
366
+ {(shouldShowCopyButtons ||
292
367
  (message.generatedByAI && showAIicon) ||
293
368
  (message.generatedByAI && showFunctionCache) ||
294
369
  (showFeedback && simulateUserPrompt)) && (
295
370
  <div className="memori-chat--bubble-addon">
296
- {!message.fromUser && showCopyButton && (
371
+ {shouldShowCopyButtons && (
297
372
  <Button
298
373
  ghost
299
374
  shape="circle"
300
- title={t('copy') || 'Copy'}
301
- className="memori-chat--bubble-action-icon"
302
- icon={<Copy aria-label={t('copy') || 'Copy'} />}
303
- onClick={() => navigator.clipboard.writeText(plainText)}
375
+ title={copyFeedback.plain ? copiedLabel : t('copy') || 'Copy'}
376
+ className={cx('memori-chat--bubble-action-icon', {
377
+ 'memori-chat--bubble-action-icon--from-user': message.fromUser,
378
+ 'memori-chat--bubble-action-icon--copied': copyFeedback.plain,
379
+ })}
380
+ icon={
381
+ <Copy
382
+ aria-label={
383
+ copyFeedback.plain ? copiedLabel : t('copy') || 'Copy'
384
+ }
385
+ />
386
+ }
387
+ onClick={() => handleCopyClick('plain', copyText)}
304
388
  />
305
389
  )}
390
+ {copyFeedback.plain && (
391
+ <span
392
+ role="status"
393
+ aria-live="polite"
394
+ className={cx('memori-chat--bubble-action-feedback', {
395
+ 'memori-chat--bubble-action-feedback--from-user':
396
+ message.fromUser,
397
+ })}
398
+ >
399
+ {copiedLabel}
400
+ </span>
401
+ )}
306
402
 
307
- {!message.fromUser &&
308
- showCopyButton &&
309
- plainText !== message.text && (
310
- <Button
311
- ghost
312
- shape="circle"
313
- title={t('copyRawCode') || 'Copy raw code'}
314
- className="memori-chat--bubble-action-icon"
315
- icon={
316
- <Code aria-label={t('copyRawCode') || 'Copy raw code'} />
317
- }
318
- onClick={() => {
319
- const text = message.text .replaceAll(
320
- /<think.*?>(.*?)<\/think>/gs,
321
- ''
322
- )
323
- navigator.clipboard.writeText(text);
324
- }}
325
- />
326
- )}
403
+ {shouldShowCopyRawButton && (
404
+ <Button
405
+ ghost
406
+ shape="circle"
407
+ title={
408
+ copyFeedback.raw
409
+ ? copiedLabel
410
+ : t('copyRawCode') || 'Copy raw code'
411
+ }
412
+ className={cx('memori-chat--bubble-action-icon', {
413
+ 'memori-chat--bubble-action-icon--from-user': message.fromUser,
414
+ 'memori-chat--bubble-action-icon--copied': copyFeedback.raw,
415
+ })}
416
+ icon={
417
+ <Code
418
+ aria-label={
419
+ copyFeedback.raw
420
+ ? copiedLabel
421
+ : t('copyRawCode') || 'Copy raw code'
422
+ }
423
+ />
424
+ }
425
+ onClick={() => handleCopyClick('raw', rawMessageText)}
426
+ />
427
+ )}
428
+ {copyFeedback.raw && (
429
+ <span
430
+ role="status"
431
+ aria-live="polite"
432
+ className={cx('memori-chat--bubble-action-feedback', {
433
+ 'memori-chat--bubble-action-feedback--from-user':
434
+ message.fromUser,
435
+ })}
436
+ >
437
+ {copiedLabel}
438
+ </span>
439
+ )}
327
440
 
328
441
  {!message.fromUser &&
329
442
  showFunctionCache &&
@@ -355,7 +468,7 @@ const ChatBubble: React.FC<Props> = ({
355
468
 
356
469
  {message.generatedByAI && showAIicon && (
357
470
  <Tooltip
358
- align="left"
471
+ align="bottomLeft"
359
472
  content={
360
473
  t('generatedByAI') ||
361
474
  (lang === 'it'
@@ -381,7 +494,7 @@ const ChatBubble: React.FC<Props> = ({
381
494
  message.translatedText &&
382
495
  message.translatedText !== message.text && (
383
496
  <Tooltip
384
- align="left"
497
+ align="bottomLeft"
385
498
  content={`${
386
499
  lang === 'it' ? 'Testo originale' : 'Original text'
387
500
  }: ${message.text}`}