@memori.ai/memori-react 9.0.0-alpha.4 → 9.0.0-alpha.6

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 (268) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/components/AgeVerificationModal/AgeVerificationModal.js +5 -2
  3. package/dist/components/AgeVerificationModal/AgeVerificationModal.js.map +1 -1
  4. package/dist/components/Avatar/Avatar.js +5 -10
  5. package/dist/components/Avatar/Avatar.js.map +1 -1
  6. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +8 -1
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  8. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +1 -1
  9. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  10. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +2 -0
  11. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js +14 -5
  12. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  13. package/dist/components/ChatBubble/ChatBubble.js +22 -16
  14. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  15. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.js +6 -3
  16. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.js.map +1 -1
  17. package/dist/components/DateSelector/DateSelector.d.ts +2 -1
  18. package/dist/components/DateSelector/DateSelector.js +17 -7
  19. package/dist/components/DateSelector/DateSelector.js.map +1 -1
  20. package/dist/components/FilePreview/FilePreview.js +22 -5
  21. package/dist/components/FilePreview/FilePreview.js.map +1 -1
  22. package/dist/components/Header/ChatConsumptionDropdown.js +1 -1
  23. package/dist/components/Header/ChatConsumptionDropdown.js.map +1 -1
  24. package/dist/components/Header/Header.css +2 -1
  25. package/dist/components/Header/Header.js +1 -1
  26. package/dist/components/Header/Header.js.map +1 -1
  27. package/dist/components/MediaWidget/MediaItemWidget.js +14 -12
  28. package/dist/components/MediaWidget/MediaItemWidget.js.map +1 -1
  29. package/dist/components/MediaWidget/MediaItemWidget.utils.d.ts +3 -2
  30. package/dist/components/MediaWidget/MediaItemWidget.utils.js +28 -6
  31. package/dist/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
  32. package/dist/components/MediaWidget/MediaPreviewModal.js +6 -7
  33. package/dist/components/MediaWidget/MediaPreviewModal.js.map +1 -1
  34. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/styles.css +5 -4
  35. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +90 -38
  36. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.d.ts +1 -0
  37. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +14 -9
  38. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  39. package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +2 -2
  40. package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -1
  41. package/dist/components/MemoriWidget/MemoriWidget.js +632 -280
  42. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  43. package/dist/components/StartPanel/StartPanel.js +10 -5
  44. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  45. package/dist/components/UploadButton/UploadButton.js +21 -5
  46. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  47. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +123 -59
  48. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  49. package/dist/components/layouts/Chat.js +25 -30
  50. package/dist/components/layouts/Chat.js.map +1 -1
  51. package/dist/components/layouts/FullPage.js +5 -8
  52. package/dist/components/layouts/FullPage.js.map +1 -1
  53. package/dist/components/layouts/HiddenChat.js +2 -1
  54. package/dist/components/layouts/HiddenChat.js.map +1 -1
  55. package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js +5 -1
  56. package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +1 -1
  57. package/dist/components/layouts/WebsiteAssistant/website-assistant.css +3 -4
  58. package/dist/components/layouts/ZoomedFullBody.js +4 -7
  59. package/dist/components/layouts/ZoomedFullBody.js.map +1 -1
  60. package/dist/components/layouts/chat.css +19 -31
  61. package/dist/components/layouts/fullpage.css +52 -19
  62. package/dist/components/layouts/zoomed-full-body.css +40 -1
  63. package/dist/helpers/constants.d.ts +3 -0
  64. package/dist/helpers/constants.js +27 -1
  65. package/dist/helpers/constants.js.map +1 -1
  66. package/dist/helpers/credits.d.ts +5 -2
  67. package/dist/helpers/credits.js +5 -1
  68. package/dist/helpers/credits.js.map +1 -1
  69. package/dist/helpers/message.d.ts +1 -1
  70. package/dist/helpers/message.js +4 -4
  71. package/dist/helpers/message.js.map +1 -1
  72. package/dist/helpers/nats/getNatsConfig.d.ts +9 -0
  73. package/dist/helpers/nats/getNatsConfig.js +65 -0
  74. package/dist/helpers/nats/getNatsConfig.js.map +1 -0
  75. package/dist/helpers/nats/isSessionExpiredError.d.ts +3 -0
  76. package/dist/helpers/nats/isSessionExpiredError.js +36 -0
  77. package/dist/helpers/nats/isSessionExpiredError.js.map +1 -0
  78. package/dist/helpers/nats/useNats.d.ts +12 -0
  79. package/dist/helpers/nats/useNats.js +75 -0
  80. package/dist/helpers/nats/useNats.js.map +1 -0
  81. package/dist/helpers/nats/useNatsSession.d.ts +28 -0
  82. package/dist/helpers/nats/useNatsSession.js +212 -0
  83. package/dist/helpers/nats/useNatsSession.js.map +1 -0
  84. package/dist/helpers/usePressTooltip.d.ts +13 -0
  85. package/dist/helpers/usePressTooltip.js +23 -0
  86. package/dist/helpers/usePressTooltip.js.map +1 -0
  87. package/dist/helpers/utils.d.ts +21 -0
  88. package/dist/helpers/utils.js +75 -4
  89. package/dist/helpers/utils.js.map +1 -1
  90. package/dist/locales/de.json +1 -0
  91. package/dist/locales/en.json +1 -0
  92. package/dist/locales/es.json +1 -0
  93. package/dist/locales/fr.json +1 -0
  94. package/dist/locales/it.json +1 -0
  95. package/dist/styles.css +1 -1
  96. package/dist/types/integration.d.ts +3 -0
  97. package/dist/types/integration.js +16 -0
  98. package/dist/types/integration.js.map +1 -0
  99. package/dist/version.d.ts +1 -1
  100. package/dist/version.js +1 -1
  101. package/esm/components/AgeVerificationModal/AgeVerificationModal.js +5 -2
  102. package/esm/components/AgeVerificationModal/AgeVerificationModal.js.map +1 -1
  103. package/esm/components/Avatar/Avatar.js +5 -10
  104. package/esm/components/Avatar/Avatar.js.map +1 -1
  105. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +9 -2
  106. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  107. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +2 -2
  108. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  109. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +2 -0
  110. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js +13 -4
  111. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  112. package/esm/components/ChatBubble/ChatBubble.js +22 -16
  113. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  114. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.js +6 -3
  115. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.js.map +1 -1
  116. package/esm/components/DateSelector/DateSelector.d.ts +2 -1
  117. package/esm/components/DateSelector/DateSelector.js +17 -7
  118. package/esm/components/DateSelector/DateSelector.js.map +1 -1
  119. package/esm/components/FilePreview/FilePreview.js +24 -7
  120. package/esm/components/FilePreview/FilePreview.js.map +1 -1
  121. package/esm/components/Header/ChatConsumptionDropdown.js +1 -1
  122. package/esm/components/Header/ChatConsumptionDropdown.js.map +1 -1
  123. package/esm/components/Header/Header.css +2 -1
  124. package/esm/components/Header/Header.js +1 -1
  125. package/esm/components/Header/Header.js.map +1 -1
  126. package/esm/components/MediaWidget/MediaItemWidget.js +16 -14
  127. package/esm/components/MediaWidget/MediaItemWidget.js.map +1 -1
  128. package/esm/components/MediaWidget/MediaItemWidget.utils.d.ts +3 -2
  129. package/esm/components/MediaWidget/MediaItemWidget.utils.js +27 -6
  130. package/esm/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
  131. package/esm/components/MediaWidget/MediaPreviewModal.js +6 -7
  132. package/esm/components/MediaWidget/MediaPreviewModal.js.map +1 -1
  133. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/styles.css +5 -4
  134. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +90 -38
  135. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.d.ts +1 -0
  136. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +14 -9
  137. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  138. package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +2 -2
  139. package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -1
  140. package/esm/components/MemoriWidget/MemoriWidget.js +632 -280
  141. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  142. package/esm/components/StartPanel/StartPanel.js +10 -5
  143. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  144. package/esm/components/UploadButton/UploadButton.js +21 -5
  145. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  146. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +123 -59
  147. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  148. package/esm/components/layouts/Chat.js +25 -30
  149. package/esm/components/layouts/Chat.js.map +1 -1
  150. package/esm/components/layouts/FullPage.js +5 -8
  151. package/esm/components/layouts/FullPage.js.map +1 -1
  152. package/esm/components/layouts/HiddenChat.js +2 -1
  153. package/esm/components/layouts/HiddenChat.js.map +1 -1
  154. package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js +5 -1
  155. package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +1 -1
  156. package/esm/components/layouts/WebsiteAssistant/website-assistant.css +3 -4
  157. package/esm/components/layouts/ZoomedFullBody.js +4 -7
  158. package/esm/components/layouts/ZoomedFullBody.js.map +1 -1
  159. package/esm/components/layouts/chat.css +19 -31
  160. package/esm/components/layouts/fullpage.css +52 -19
  161. package/esm/components/layouts/zoomed-full-body.css +40 -1
  162. package/esm/helpers/constants.d.ts +3 -0
  163. package/esm/helpers/constants.js +26 -0
  164. package/esm/helpers/constants.js.map +1 -1
  165. package/esm/helpers/credits.d.ts +5 -2
  166. package/esm/helpers/credits.js +5 -1
  167. package/esm/helpers/credits.js.map +1 -1
  168. package/esm/helpers/message.d.ts +1 -1
  169. package/esm/helpers/message.js +2 -2
  170. package/esm/helpers/message.js.map +1 -1
  171. package/esm/helpers/nats/getNatsConfig.d.ts +9 -0
  172. package/esm/helpers/nats/getNatsConfig.js +61 -0
  173. package/esm/helpers/nats/getNatsConfig.js.map +1 -0
  174. package/esm/helpers/nats/isSessionExpiredError.d.ts +3 -0
  175. package/esm/helpers/nats/isSessionExpiredError.js +32 -0
  176. package/esm/helpers/nats/isSessionExpiredError.js.map +1 -0
  177. package/esm/helpers/nats/useNats.d.ts +12 -0
  178. package/esm/helpers/nats/useNats.js +72 -0
  179. package/esm/helpers/nats/useNats.js.map +1 -0
  180. package/esm/helpers/nats/useNatsSession.d.ts +28 -0
  181. package/esm/helpers/nats/useNatsSession.js +208 -0
  182. package/esm/helpers/nats/useNatsSession.js.map +1 -0
  183. package/esm/helpers/usePressTooltip.d.ts +13 -0
  184. package/esm/helpers/usePressTooltip.js +20 -0
  185. package/esm/helpers/usePressTooltip.js.map +1 -0
  186. package/esm/helpers/utils.d.ts +21 -0
  187. package/esm/helpers/utils.js +68 -3
  188. package/esm/helpers/utils.js.map +1 -1
  189. package/esm/locales/de.json +1 -0
  190. package/esm/locales/en.json +1 -0
  191. package/esm/locales/es.json +1 -0
  192. package/esm/locales/fr.json +1 -0
  193. package/esm/locales/it.json +1 -0
  194. package/esm/styles.css +1 -1
  195. package/esm/types/integration.d.ts +3 -0
  196. package/esm/types/integration.js +11 -0
  197. package/esm/types/integration.js.map +1 -0
  198. package/esm/version.d.ts +1 -1
  199. package/esm/version.js +1 -1
  200. package/package.json +6 -4
  201. package/src/components/AgeVerificationModal/AgeVerificationModal.tsx +6 -1
  202. package/src/components/Avatar/Avatar.stories.tsx +45 -28
  203. package/src/components/Avatar/Avatar.test.tsx +54 -0
  204. package/src/components/Avatar/Avatar.tsx +8 -12
  205. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +12 -5
  206. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.ts +2 -1
  207. package/src/components/Avatar/AvatarView/AvatarComponent/constants.ts +16 -4
  208. package/src/components/Avatar/__snapshots__/Avatar.test.tsx.snap +164 -0
  209. package/src/components/ChatBubble/ChatBubble.tsx +41 -12
  210. package/src/components/CompletionProviderStatus/CompletionProviderStatus.tsx +8 -4
  211. package/src/components/DateSelector/DateSelector.stories.tsx +11 -2
  212. package/src/components/DateSelector/DateSelector.tsx +28 -9
  213. package/src/components/FilePreview/FilePreview.tsx +28 -7
  214. package/src/components/Header/ChatConsumptionDropdown.tsx +5 -1
  215. package/src/components/Header/Header.css +2 -1
  216. package/src/components/Header/Header.tsx +1 -0
  217. package/src/components/MediaWidget/MediaItemWidget.tsx +26 -13
  218. package/src/components/MediaWidget/MediaItemWidget.utils.test.ts +45 -2
  219. package/src/components/MediaWidget/MediaItemWidget.utils.ts +39 -6
  220. package/src/components/MediaWidget/MediaPreviewModal.tsx +9 -7
  221. package/src/components/MemoriArtifactSystem/ArtifactDrawer.stories.tsx +218 -2252
  222. package/src/components/MemoriArtifactSystem/components/ArtifactActions/styles.css +5 -4
  223. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +90 -38
  224. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.tsx +31 -14
  225. package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.tsx +4 -3
  226. package/src/components/MemoriWidget/MemoriWidget.tsx +785 -331
  227. package/src/components/StartPanel/StartPanel.stories.tsx +22 -0
  228. package/src/components/StartPanel/StartPanel.test.tsx +64 -1
  229. package/src/components/StartPanel/StartPanel.tsx +26 -3
  230. package/src/components/StartPanel/__snapshots__/StartPanel.test.tsx.snap +261 -0
  231. package/src/components/UploadButton/UploadButton.tsx +30 -11
  232. package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +148 -61
  233. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +2 -2
  234. package/src/components/layouts/Chat.tsx +16 -19
  235. package/src/components/layouts/FullPage.tsx +32 -25
  236. package/src/components/layouts/HiddenChat.tsx +4 -0
  237. package/src/components/layouts/WebsiteAssistant/WebsiteAssistant.tsx +8 -0
  238. package/src/components/layouts/WebsiteAssistant/website-assistant.css +3 -4
  239. package/src/components/layouts/ZoomedFullBody.tsx +30 -24
  240. package/src/components/layouts/__snapshots__/Chat.test.tsx.snap +6 -3
  241. package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +824 -810
  242. package/src/components/layouts/__snapshots__/ZoomedFullBody.test.tsx.snap +280 -273
  243. package/src/components/layouts/chat.css +19 -31
  244. package/src/components/layouts/fullpage.css +52 -19
  245. package/src/components/layouts/layouts.stories.tsx +131 -12
  246. package/src/components/layouts/zoomed-full-body.css +40 -1
  247. package/src/helpers/constants.ts +32 -1
  248. package/src/helpers/credits.ts +16 -1
  249. package/src/helpers/message.test.ts +28 -1
  250. package/src/helpers/message.ts +4 -4
  251. package/src/helpers/nats/getNatsConfig.ts +145 -0
  252. package/src/helpers/nats/isSessionExpiredError.test.ts +38 -0
  253. package/src/helpers/nats/isSessionExpiredError.ts +51 -0
  254. package/src/helpers/nats/useNats.ts +124 -0
  255. package/src/helpers/nats/useNatsSession.ts +394 -0
  256. package/src/helpers/usePressTooltip.ts +29 -0
  257. package/src/helpers/utils.test.ts +162 -0
  258. package/src/helpers/utils.ts +108 -3
  259. package/src/index.stories.tsx +32 -0
  260. package/src/locales/de.json +1 -0
  261. package/src/locales/en.json +1 -0
  262. package/src/locales/es.json +1 -0
  263. package/src/locales/fr.json +1 -0
  264. package/src/locales/it.json +1 -0
  265. package/src/styles.css +4 -7
  266. package/src/types/integration.test.ts +32 -0
  267. package/src/types/integration.ts +31 -0
  268. package/src/version.ts +1 -1
@@ -33,17 +33,21 @@ const PROVIDER_CONFIGS: Record<string, ProviderConfig> = {
33
33
  statusPage: 'https://status.mistral-data.com/',
34
34
  apiComponentName: ['Mistral'],
35
35
  },
36
+ // Use status.claude.com directly: status.anthropic.com 302-redirects and
37
+ // browsers block the redirected response (missing CORS on the redirect).
36
38
  Anthropic: {
37
- statusUrl: 'https://status.anthropic.com/api/v2/summary.json',
38
- statusPage: 'https://status.anthropic.com/',
39
- apiComponentName: ['api.anthropic.com'],
39
+ statusUrl: 'https://status.claude.com/api/v2/summary.json',
40
+ statusPage: 'https://status.claude.com/',
41
+ apiComponentName: [
42
+ 'Claude API (api.anthropic.com)',
43
+ 'api.anthropic.com',
44
+ ],
40
45
  },
41
46
  };
42
47
 
43
48
  // Modified to handle fetch errors, different component names, and check incidents
44
49
  const fetchProviderStatus = async (config: ProviderConfig): Promise<Status> => {
45
50
  try {
46
- // Use cross-fetch instead of native fetch
47
51
  const response = await fetch(config.statusUrl);
48
52
  if (!response.ok) {
49
53
  console.warn(`Status API returned ${response.status}`);
@@ -25,14 +25,23 @@ const Template: Story<Props> = args => (
25
25
  // https://storybook.js.org/docs/react/workflows/unit-testing
26
26
  export const Default = Template.bind({});
27
27
  Default.args = {
28
- defaultDate: new Date(Date.now()),
28
+ defaultDate: '1990-06-15',
29
+ minAge: 18,
30
+ onChange: console.log,
31
+ disabled: false,
32
+ };
33
+
34
+ export const MinAge14 = Template.bind({});
35
+ MinAge14.args = {
36
+ minAge: 14,
29
37
  onChange: console.log,
30
38
  disabled: false,
31
39
  };
32
40
 
33
41
  export const Disabled = Template.bind({});
34
42
  Disabled.args = {
35
- defaultDate: new Date(Date.now()),
43
+ defaultDate: '1990-06-15',
44
+ minAge: 18,
36
45
  onChange: () => {},
37
46
  disabled: true,
38
47
  };
@@ -5,16 +5,23 @@ import { useTranslation } from 'react-i18next';
5
5
  export interface Props {
6
6
  defaultDate?: string | Date;
7
7
  disabled?: boolean;
8
+ /** Minimum age; latest selectable birth date is today minus this many years. */
9
+ minAge?: number;
8
10
  onChange: (date: DateTime | undefined) => void;
9
11
  }
10
12
 
11
13
  const DateSelector = memo(
12
- ({ defaultDate, onChange, disabled = false }: Props) => {
14
+ ({ defaultDate, onChange, disabled = false, minAge = 18 }: Props) => {
13
15
  const { t } = useTranslation();
14
16
 
15
- // Calculate constraints for birth date (18 years ago to 1900)
16
- const maxDate = useMemo(() => DateTime.now().minus({ years: 18 }), []);
17
- const minDate = useMemo(() => DateTime.fromObject({ year: 1900, month: 1, day: 1 }), []);
17
+ const maxDate = useMemo(
18
+ () => DateTime.now().minus({ years: minAge }).startOf('day'),
19
+ [minAge]
20
+ );
21
+ const minDate = useMemo(
22
+ () => DateTime.fromObject({ year: 1900, month: 1, day: 1 }),
23
+ []
24
+ );
18
25
 
19
26
  // State for individual fields as strings to allow empty values
20
27
  const [day, setDay] = useState<string>('');
@@ -54,10 +61,22 @@ const DateSelector = memo(
54
61
  return () => window.removeEventListener('resize', checkMobile);
55
62
  }, []);
56
63
 
64
+ // Avoid re-running when parent passes an unstable onChange (inline arrow).
65
+ const onChangeRef = useRef(onChange);
66
+ onChangeRef.current = onChange;
67
+ const lastEmittedRef = useRef<string | null | undefined>(undefined);
68
+
57
69
  // Validate and propagate changes
58
70
  useEffect(() => {
71
+ const emit = (next: DateTime | undefined) => {
72
+ const key = next?.isValid ? next.toISODate() : null;
73
+ if (key === lastEmittedRef.current) return;
74
+ lastEmittedRef.current = key;
75
+ onChangeRef.current(next);
76
+ };
77
+
59
78
  if (!day || !month || !year) {
60
- onChange(undefined);
79
+ emit(undefined);
61
80
  return;
62
81
  }
63
82
 
@@ -66,7 +85,7 @@ const DateSelector = memo(
66
85
  const y = parseInt(year);
67
86
 
68
87
  if (isNaN(d) || isNaN(m) || isNaN(y)) {
69
- onChange(undefined);
88
+ emit(undefined);
70
89
  return;
71
90
  }
72
91
 
@@ -75,11 +94,11 @@ const DateSelector = memo(
75
94
  // Basic validation: check if it's a valid date (e.g. not Feb 30)
76
95
  // and within range.
77
96
  if (newDate.isValid && newDate >= minDate && newDate <= maxDate) {
78
- onChange(newDate);
97
+ emit(newDate);
79
98
  } else {
80
- onChange(undefined);
99
+ emit(undefined);
81
100
  }
82
- }, [day, month, year, onChange, minDate, maxDate]);
101
+ }, [day, month, year, minDate, maxDate]);
83
102
 
84
103
  // Handle mobile native input change
85
104
  const handleMobileChange = (e: ChangeEvent<HTMLInputElement>) => {
@@ -3,8 +3,13 @@ import { File, X } from 'lucide-react';
3
3
  import { Button } from '@memori.ai/ui';
4
4
  import ContentPreviewModal from '../ContentPreviewModal';
5
5
  import Snippet from '../Snippet/Snippet';
6
- import { stripHTML, stripDocumentAttachmentTags } from '../../helpers/utils';
7
- import { getFileExtensionFromMime } from '../MediaWidget/MediaItemWidget.utils';
6
+ import {
7
+ stripHTML,
8
+ stripDocumentAttachmentTags,
9
+ isOfficeNativeFilename,
10
+ getDocumentAttachmentAssetUrl,
11
+ } from '../../helpers/utils';
12
+ import { getDocumentBadgeLabel } from '../MediaWidget/MediaItemWidget.utils';
8
13
  import { useTranslation } from 'react-i18next';
9
14
 
10
15
  type FilePreviewProps = {
@@ -60,6 +65,15 @@ const FilePreview = ({
60
65
  return 'CSV';
61
66
  case 'html':
62
67
  return 'HTML';
68
+ case 'doc':
69
+ case 'docx':
70
+ case 'dotx':
71
+ return 'Word';
72
+ case 'xls':
73
+ case 'xltx':
74
+ return 'Excel';
75
+ case 'potx':
76
+ return 'PowerPoint';
63
77
  case 'jpg':
64
78
  case 'jpeg':
65
79
  return 'JPEG';
@@ -111,9 +125,7 @@ const FilePreview = ({
111
125
  if (isHtmlFile(file)) {
112
126
  let htmlContent = content;
113
127
  if (htmlContent.includes('&lt;') || htmlContent.includes('&quot;')) {
114
- const div = document.createElement('div');
115
- div.innerHTML = htmlContent;
116
- htmlContent = div.textContent || div.innerText || htmlContent;
128
+ htmlContent = stripHTML(htmlContent) || htmlContent;
117
129
  }
118
130
  htmlContent = stripDocumentAttachmentTags(htmlContent);
119
131
  return htmlContent;
@@ -169,7 +181,16 @@ const FilePreview = ({
169
181
  <button
170
182
  type="button"
171
183
  className="memori--preview-item-trigger"
172
- onClick={() => setSelectedFile(file)}
184
+ onClick={() => {
185
+ if (isOfficeNativeFilename(file.name || '')) {
186
+ const url = getDocumentAttachmentAssetUrl(file);
187
+ if (url) {
188
+ window.open(url, '_blank', 'noopener,noreferrer');
189
+ }
190
+ } else {
191
+ setSelectedFile(file);
192
+ }
193
+ }}
173
194
  aria-label={file.name}
174
195
  >
175
196
  {isImageContent(file.content, file.type) ? (
@@ -184,7 +205,7 @@ const FilePreview = ({
184
205
  <span className="memori--preview-filename">{file.name}</span>
185
206
  <span className="memori--preview-filetype">
186
207
  {file.mimeType
187
- ? getFileExtensionFromMime(file.mimeType)
208
+ ? getDocumentBadgeLabel(file.mimeType, file.name)
188
209
  : getFileType(file.name, file.type)}
189
210
  </span>
190
211
  </div>
@@ -285,7 +285,11 @@ const ChatConsumptionDropdown: React.FC<ChatConsumptionDropdownProps> = ({
285
285
  {...triggerButtonProps}
286
286
  variant={triggerVariant}
287
287
  shape="default"
288
- className="memori-header--button memori-header--button--sustainability"
288
+ className={cx(
289
+ 'memori-header--button memori-header--button--sustainability',
290
+ open && 'memori-button--active',
291
+ triggerButtonProps.className
292
+ )}
289
293
  aria-label={triggerLabel}
290
294
  title={triggerLabel}
291
295
  icon={
@@ -630,7 +630,8 @@
630
630
  text-align: left;
631
631
  }
632
632
 
633
- .memori-dropdown--auth-row:hover {
633
+ .memori-dropdown--auth-row:hover,
634
+ .memori-dropdown--auth-row--active {
634
635
  background: color-mix(in srgb, var(--memori-text-color, #111827) 7%, transparent);
635
636
  }
636
637
 
@@ -935,6 +935,7 @@ const Header: React.FC<Props> = ({
935
935
  className={cx(
936
936
  'memori-dropdown--auth-row',
937
937
  'memori-dropdown--auth-row--navigable',
938
+ open && 'memori-dropdown--auth-row--active',
938
939
  triggerProps.className
939
940
  )}
940
941
  >
@@ -11,7 +11,10 @@ import React, {
11
11
  import { getResourceUrl } from '../../helpers/media';
12
12
  import {
13
13
  withLinksOpenInNewTab,
14
+ stripHTML,
14
15
  stripDocumentAttachmentTags,
16
+ isAssetOnlyDocumentAttachment,
17
+ getDocumentAttachmentAssetUrl,
15
18
  } from '../../helpers/utils';
16
19
  import { getTranslation } from '../../helpers/translations';
17
20
  import { prismSyntaxLangs } from '../../helpers/constants';
@@ -32,7 +35,7 @@ import type {
32
35
  import {
33
36
  formatBytes,
34
37
  getFileExtensionFromUrl,
35
- getFileExtensionFromMime,
38
+ getDocumentBadgeLabel,
36
39
  countLines,
37
40
  shouldUseDarkFileCard,
38
41
  fetchLinkPreview,
@@ -268,7 +271,7 @@ export const RenderMediaItem = memo(function RenderMediaItem({
268
271
  ) : (
269
272
  <DocumentCard
270
273
  title={medium.title || 'File'}
271
- badge={getFileExtensionFromMime(medium.mimeType)}
274
+ badge={getDocumentBadgeLabel(medium.mimeType, medium.title)}
272
275
  meta={(() => {
273
276
  const size = getContentSize(medium);
274
277
  return size != null && size > 0 ? formatBytes(size) : null;
@@ -284,7 +287,7 @@ export const RenderMediaItem = memo(function RenderMediaItem({
284
287
  return (
285
288
  <DocumentCard
286
289
  title={medium.title || 'File'}
287
- badge={getFileExtensionFromMime(medium.mimeType)}
290
+ badge={getDocumentBadgeLabel(medium.mimeType, medium.title)}
288
291
  meta={(() => {
289
292
  const size = getContentSize(medium);
290
293
  return size != null && size > 0 ? formatBytes(size) : null;
@@ -298,7 +301,7 @@ export const RenderMediaItem = memo(function RenderMediaItem({
298
301
  return (
299
302
  <DocumentCard
300
303
  title={medium.title || 'File'}
301
- badge={getFileExtensionFromMime(medium.mimeType)}
304
+ badge={getDocumentBadgeLabel(medium.mimeType, medium.title)}
302
305
  meta={(() => {
303
306
  const size = getContentSize(medium);
304
307
  return size != null && size > 0 ? formatBytes(size) : null;
@@ -324,8 +327,11 @@ export const RenderMediaItem = memo(function RenderMediaItem({
324
327
 
325
328
  // Extension and file detection helpers
326
329
  const fileExtensionFromUrl = getFileExtensionFromUrl(normURL || item.url);
327
- const fileExtensionFromMime = getFileExtensionFromMime(item.mimeType);
328
- const fileExtension = fileExtensionFromUrl || fileExtensionFromMime;
330
+ const fileExtension = getDocumentBadgeLabel(
331
+ item.mimeType,
332
+ item.title,
333
+ normURL || item.url
334
+ );
329
335
  const isFile = shouldUseDarkFileCard(
330
336
  item,
331
337
  fileExtensionFromUrl,
@@ -355,8 +361,16 @@ export const RenderMediaItem = memo(function RenderMediaItem({
355
361
  const metaParts = [lineText, sizeText].filter(Boolean);
356
362
  const metaLine = metaParts.length > 0 ? metaParts.join(' · ') : null;
357
363
 
364
+ // Asset-only attachments (e.g. Office native files) open via URL, not modal
365
+ const isAssetOnlyAttachment = isAssetOnlyDocumentAttachment(item);
366
+
358
367
  // Document attachments and attached files should open in modal, not as links
359
- if ((isDocumentAttachment || isAttachedFile) && item.mediumID && _onClick) {
368
+ if (
369
+ (isDocumentAttachment || isAttachedFile) &&
370
+ item.mediumID &&
371
+ _onClick &&
372
+ !isAssetOnlyAttachment
373
+ ) {
360
374
  return (
361
375
  <div
362
376
  onClick={() => _onClick(item)}
@@ -395,16 +409,17 @@ export const RenderMediaItem = memo(function RenderMediaItem({
395
409
 
396
410
  // Build href: open in new tab (never modal). Use URL, or blob for content-only items.
397
411
  const getFileCardHref = (): string => {
398
- if (item.url) {
412
+ const assetUrl = getDocumentAttachmentAssetUrl(item);
413
+ if (assetUrl) {
399
414
  return (
400
415
  getResourceUrl({
401
- resourceURI: item.url,
416
+ resourceURI: assetUrl,
402
417
  sessionID,
403
418
  tenantID,
404
419
  baseURL,
405
420
  apiURL,
406
421
  }) ||
407
- item.url ||
422
+ assetUrl ||
408
423
  '#'
409
424
  );
410
425
  }
@@ -416,9 +431,7 @@ export const RenderMediaItem = memo(function RenderMediaItem({
416
431
  htmlContent.includes('<document_attachment')
417
432
  ) {
418
433
  if (htmlContent.includes('&lt;') || htmlContent.includes('&quot;')) {
419
- const div = document.createElement('div');
420
- div.innerHTML = htmlContent;
421
- htmlContent = div.textContent || div.innerText || htmlContent;
434
+ htmlContent = stripHTML(htmlContent) || htmlContent;
422
435
  }
423
436
  htmlContent = stripDocumentAttachmentTags(htmlContent);
424
437
  }
@@ -2,6 +2,7 @@ import {
2
2
  formatBytes,
3
3
  getFileExtensionFromUrl,
4
4
  getFileExtensionFromMime,
5
+ getDocumentBadgeLabel,
5
6
  countLines,
6
7
  shouldUseDarkFileCard,
7
8
  fetchLinkPreview,
@@ -57,8 +58,25 @@ describe('MediaItemWidget.utils', () => {
57
58
  expect(getFileExtensionFromMime('text/html')).toBe('HTML');
58
59
  expect(getFileExtensionFromMime('text/plain')).toBe('TXT');
59
60
  expect(getFileExtensionFromMime('application/json')).toBe('JSON');
60
- expect(getFileExtensionFromMime('application/vnd.ms-excel')).toBe('XLS');
61
- expect(getFileExtensionFromMime('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')).toBe('XLSX');
61
+ expect(getFileExtensionFromMime('application/vnd.ms-excel')).toBe('Excel');
62
+ expect(
63
+ getFileExtensionFromMime(
64
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
65
+ )
66
+ ).toBe('Excel');
67
+ });
68
+
69
+ it('maps office mime types with parameters to short labels', () => {
70
+ expect(
71
+ getFileExtensionFromMime(
72
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=binary'
73
+ )
74
+ ).toBe('Word');
75
+ expect(
76
+ getFileExtensionFromMime(
77
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template'
78
+ )
79
+ ).toBe('Excel');
62
80
  });
63
81
 
64
82
  it('uses subtype when not in MIME_TO_EXT map', () => {
@@ -71,6 +89,31 @@ describe('MediaItemWidget.utils', () => {
71
89
  });
72
90
  });
73
91
 
92
+ describe('getDocumentBadgeLabel', () => {
93
+ it('prefers URL extension, then filename, then mime type', () => {
94
+ expect(
95
+ getDocumentBadgeLabel(
96
+ 'application/pdf',
97
+ 'report.docx',
98
+ 'https://example.com/file.pdf'
99
+ )
100
+ ).toBe('PDF');
101
+ expect(
102
+ getDocumentBadgeLabel(
103
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
104
+ 'report.docx',
105
+ 'https://example.com/asset/123'
106
+ )
107
+ ).toBe('Word');
108
+ expect(
109
+ getDocumentBadgeLabel(
110
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
111
+ 'budget.xlsx'
112
+ )
113
+ ).toBe('Excel');
114
+ });
115
+ });
116
+
74
117
  describe('countLines', () => {
75
118
  it('returns 0 for undefined or empty', () => {
76
119
  expect(countLines(undefined)).toBe(0);
@@ -1,4 +1,8 @@
1
1
  import type { Medium } from '@memori.ai/memori-api-client/dist/types';
2
+ import {
3
+ officeExtensionShortLabels,
4
+ officeMimeShortLabels,
5
+ } from '../../helpers/constants';
2
6
  import type { LinkPreviewInfo } from './MediaItemWidget.types';
3
7
 
4
8
  export const FILE_EXTENSIONS_DARK_CARD = [
@@ -7,6 +11,7 @@ export const FILE_EXTENSIONS_DARK_CARD = [
7
11
  'PDF',
8
12
  'DOC',
9
13
  'DOCX',
14
+ 'DOTX',
10
15
  'XLS',
11
16
  'XLSX',
12
17
  'JSON',
@@ -22,6 +27,7 @@ export const FILE_MIME_TYPES_DARK_CARD = [
22
27
  'application/pdf',
23
28
  'application/msword',
24
29
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
30
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
25
31
  'application/vnd.ms-excel',
26
32
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
27
33
  'text/html',
@@ -54,11 +60,6 @@ export const IMAGE_MIME_TYPES = [
54
60
 
55
61
  const MIME_TO_EXT: Record<string, string> = {
56
62
  'application/pdf': 'PDF',
57
- 'application/msword': 'DOC',
58
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
59
- 'DOCX',
60
- 'application/vnd.ms-excel': 'XLS',
61
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'XLSX',
62
63
  'text/html': 'HTML',
63
64
  'text/plain': 'TXT',
64
65
  'text/css': 'CSS',
@@ -87,14 +88,46 @@ export function getFileExtensionFromUrl(
87
88
  return match ? match[1].toUpperCase() : null;
88
89
  }
89
90
 
91
+ function normalizeMimeType(mimeType: string): string {
92
+ return mimeType.split(';')[0].trim();
93
+ }
94
+
90
95
  export function getFileExtensionFromMime(mimeType: string): string {
96
+ const normalized = normalizeMimeType(mimeType);
97
+ const normalizedLower = normalized.toLowerCase();
98
+
99
+ const officeLabel =
100
+ officeMimeShortLabels[normalized] ||
101
+ officeMimeShortLabels[normalizedLower];
102
+ if (officeLabel) return officeLabel;
103
+
91
104
  return (
105
+ MIME_TO_EXT[normalized] ||
106
+ MIME_TO_EXT[normalizedLower] ||
92
107
  MIME_TO_EXT[mimeType] ||
93
- mimeType.split('/')[1]?.toUpperCase() ||
108
+ normalized.split('/')[1]?.toUpperCase() ||
94
109
  'FILE'
95
110
  );
96
111
  }
97
112
 
113
+ export function getDocumentBadgeLabel(
114
+ mimeType: string,
115
+ filename?: string | null,
116
+ url?: string | null
117
+ ): string {
118
+ const fromUrl = getFileExtensionFromUrl(url || undefined);
119
+ if (fromUrl) {
120
+ return officeExtensionShortLabels[fromUrl] || fromUrl;
121
+ }
122
+
123
+ const fromFilename = getFileExtensionFromUrl(filename || undefined);
124
+ if (fromFilename) {
125
+ return officeExtensionShortLabels[fromFilename] || fromFilename;
126
+ }
127
+
128
+ return getFileExtensionFromMime(mimeType);
129
+ }
130
+
98
131
  export function countLines(content: string | undefined): number {
99
132
  if (!content) return 0;
100
133
  return content.split(/\r\n|\r|\n/).length;
@@ -70,6 +70,8 @@ const MIME_MARKDOWN = 'text/markdown';
70
70
  const MIME_WORD_DOC = 'application/msword';
71
71
  const MIME_WORD_DOCX =
72
72
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
73
+ const MIME_WORD_DOTX =
74
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template';
73
75
 
74
76
  const VIDEO_MIME_TYPES = [
75
77
  'video/mp4',
@@ -99,7 +101,11 @@ function isAudio(mimeType: string): boolean {
99
101
  }
100
102
 
101
103
  function isWordDoc(mimeType: string): boolean {
102
- return mimeType === MIME_WORD_DOC || mimeType === MIME_WORD_DOCX;
104
+ return (
105
+ mimeType === MIME_WORD_DOC ||
106
+ mimeType === MIME_WORD_DOCX ||
107
+ mimeType === MIME_WORD_DOTX
108
+ );
103
109
  }
104
110
 
105
111
  // Document attachments (PDF, XLSX, TXT, CSV, etc.) are extracted as text, not binary.
@@ -268,9 +274,7 @@ export function MediaPreviewModal({
268
274
  // HTML document attachment: display HTML content in a code-like format
269
275
  let htmlContent = medium.content || '';
270
276
  if (htmlContent.includes('&lt;') || htmlContent.includes('&quot;')) {
271
- const div = document.createElement('div');
272
- div.innerHTML = htmlContent;
273
- htmlContent = div.textContent || div.innerText || htmlContent;
277
+ htmlContent = stripHTML(htmlContent) || htmlContent;
274
278
  } else {
275
279
  htmlContent = stripDocumentAttachmentTags(htmlContent);
276
280
  }
@@ -290,9 +294,7 @@ export function MediaPreviewModal({
290
294
  // Other document attachments: render as plain text
291
295
  let displayContent = medium.content;
292
296
  if (displayContent.includes('&lt;') || displayContent.includes('&quot;')) {
293
- const div = document.createElement('div');
294
- div.innerHTML = displayContent;
295
- displayContent = div.textContent || div.innerText || displayContent;
297
+ displayContent = stripHTML(displayContent) || displayContent;
296
298
  } else {
297
299
  displayContent = stripDocumentAttachmentTags(displayContent);
298
300
  }