@memori.ai/memori-react 8.2.0 → 8.4.0-rc.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 (146) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/components/Chat/Chat.js +8 -1
  3. package/dist/components/Chat/Chat.js.map +1 -1
  4. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.css +160 -0
  5. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.d.ts +4 -0
  6. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +166 -0
  7. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -0
  8. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +877 -0
  9. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.d.ts +3 -0
  10. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +115 -0
  11. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -0
  12. package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.css +238 -0
  13. package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.d.ts +4 -0
  14. package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +104 -0
  15. package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -0
  16. package/dist/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.css +319 -0
  17. package/dist/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.d.ts +4 -0
  18. package/dist/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.js +50 -0
  19. package/dist/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.js.map +1 -0
  20. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +343 -0
  21. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.d.ts +4 -0
  22. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js +78 -0
  23. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js.map +1 -0
  24. package/dist/components/MemoriArtifactSystem/context/ArtifactSystemContext.d.ts +12 -0
  25. package/dist/components/MemoriArtifactSystem/context/ArtifactSystemContext.js +22 -0
  26. package/dist/components/MemoriArtifactSystem/context/ArtifactSystemContext.js.map +1 -0
  27. package/dist/components/MemoriArtifactSystem/hooks/useArtifactSystem.d.ts +12 -0
  28. package/dist/components/MemoriArtifactSystem/hooks/useArtifactSystem.js +288 -0
  29. package/dist/components/MemoriArtifactSystem/hooks/useArtifactSystem.js.map +1 -0
  30. package/dist/components/MemoriArtifactSystem/index.d.ts +9 -0
  31. package/dist/components/MemoriArtifactSystem/index.js +28 -0
  32. package/dist/components/MemoriArtifactSystem/index.js.map +1 -0
  33. package/dist/components/MemoriArtifactSystem/types/artifact.types.d.ts +108 -0
  34. package/dist/components/MemoriArtifactSystem/types/artifact.types.js +31 -0
  35. package/dist/components/MemoriArtifactSystem/types/artifact.types.js.map +1 -0
  36. package/dist/components/icons/Print.d.ts +6 -0
  37. package/dist/components/icons/Print.js +6 -0
  38. package/dist/components/icons/Print.js.map +1 -0
  39. package/dist/components/layouts/Chat.js +29 -1
  40. package/dist/components/layouts/Chat.js.map +1 -1
  41. package/dist/components/layouts/FullPage.js +33 -1
  42. package/dist/components/layouts/FullPage.js.map +1 -1
  43. package/dist/components/layouts/ZoomedFullBody.js +29 -2
  44. package/dist/components/layouts/ZoomedFullBody.js.map +1 -1
  45. package/dist/components/layouts/chat.css +335 -13
  46. package/dist/components/layouts/zoomed-full-body.css +1 -3
  47. package/dist/helpers/message.js +1 -0
  48. package/dist/helpers/message.js.map +1 -1
  49. package/dist/helpers/stt/useSTT.js +76 -9
  50. package/dist/helpers/stt/useSTT.js.map +1 -1
  51. package/dist/index.js +58 -15
  52. package/dist/index.js.map +1 -1
  53. package/dist/styles.css +5 -0
  54. package/esm/components/Chat/Chat.js +8 -1
  55. package/esm/components/Chat/Chat.js.map +1 -1
  56. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.css +160 -0
  57. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.d.ts +4 -0
  58. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +163 -0
  59. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -0
  60. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +877 -0
  61. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.d.ts +3 -0
  62. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +112 -0
  63. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -0
  64. package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.css +238 -0
  65. package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.d.ts +4 -0
  66. package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +101 -0
  67. package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -0
  68. package/esm/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.css +319 -0
  69. package/esm/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.d.ts +4 -0
  70. package/esm/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.js +47 -0
  71. package/esm/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.js.map +1 -0
  72. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +343 -0
  73. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.d.ts +4 -0
  74. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js +75 -0
  75. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js.map +1 -0
  76. package/esm/components/MemoriArtifactSystem/context/ArtifactSystemContext.d.ts +12 -0
  77. package/esm/components/MemoriArtifactSystem/context/ArtifactSystemContext.js +17 -0
  78. package/esm/components/MemoriArtifactSystem/context/ArtifactSystemContext.js.map +1 -0
  79. package/esm/components/MemoriArtifactSystem/hooks/useArtifactSystem.d.ts +12 -0
  80. package/esm/components/MemoriArtifactSystem/hooks/useArtifactSystem.js +281 -0
  81. package/esm/components/MemoriArtifactSystem/hooks/useArtifactSystem.js.map +1 -0
  82. package/esm/components/MemoriArtifactSystem/index.d.ts +9 -0
  83. package/esm/components/MemoriArtifactSystem/index.js +9 -0
  84. package/esm/components/MemoriArtifactSystem/index.js.map +1 -0
  85. package/esm/components/MemoriArtifactSystem/types/artifact.types.d.ts +108 -0
  86. package/esm/components/MemoriArtifactSystem/types/artifact.types.js +28 -0
  87. package/esm/components/MemoriArtifactSystem/types/artifact.types.js.map +1 -0
  88. package/esm/components/icons/Print.d.ts +6 -0
  89. package/esm/components/icons/Print.js +4 -0
  90. package/esm/components/icons/Print.js.map +1 -0
  91. package/esm/components/layouts/Chat.js +29 -1
  92. package/esm/components/layouts/Chat.js.map +1 -1
  93. package/esm/components/layouts/FullPage.js +33 -1
  94. package/esm/components/layouts/FullPage.js.map +1 -1
  95. package/esm/components/layouts/ZoomedFullBody.js +30 -3
  96. package/esm/components/layouts/ZoomedFullBody.js.map +1 -1
  97. package/esm/components/layouts/chat.css +335 -13
  98. package/esm/components/layouts/zoomed-full-body.css +1 -3
  99. package/esm/helpers/message.js +1 -0
  100. package/esm/helpers/message.js.map +1 -1
  101. package/esm/helpers/stt/useSTT.js +76 -9
  102. package/esm/helpers/stt/useSTT.js.map +1 -1
  103. package/esm/index.js +58 -15
  104. package/esm/index.js.map +1 -1
  105. package/esm/styles.css +5 -0
  106. package/package.json +1 -1
  107. package/src/components/Avatar/Avatar.test.tsx +13 -0
  108. package/src/components/Chat/Chat.stories.tsx +33 -2
  109. package/src/components/Chat/Chat.test.tsx +340 -213
  110. package/src/components/Chat/Chat.tsx +27 -4
  111. package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.css +160 -0
  112. package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.tsx +278 -0
  113. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +877 -0
  114. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.tsx +308 -0
  115. package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.css +238 -0
  116. package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.tsx +282 -0
  117. package/src/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.css +319 -0
  118. package/src/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.tsx +178 -0
  119. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +343 -0
  120. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.tsx +190 -0
  121. package/src/components/MemoriArtifactSystem/context/ArtifactSystemContext.tsx +57 -0
  122. package/src/components/MemoriArtifactSystem/hooks/useArtifactSystem.ts +419 -0
  123. package/src/components/MemoriArtifactSystem/index.ts +45 -0
  124. package/src/components/MemoriArtifactSystem/types/artifact.types.ts +180 -0
  125. package/src/components/icons/Print.tsx +34 -0
  126. package/src/components/layouts/Chat.test.tsx +13 -0
  127. package/src/components/layouts/Chat.tsx +80 -25
  128. package/src/components/layouts/FullPage.test.tsx +40 -11
  129. package/src/components/layouts/FullPage.tsx +92 -24
  130. package/src/components/layouts/HiddenChat.test.tsx +13 -0
  131. package/src/components/layouts/Totem.test.tsx +13 -0
  132. package/src/components/layouts/WebsiteAssistant.test.tsx +13 -0
  133. package/src/components/layouts/ZoomedFullBody.test.tsx +13 -0
  134. package/src/components/layouts/ZoomedFullBody.tsx +78 -14
  135. package/src/components/layouts/__snapshots__/Chat.test.tsx.snap +252 -248
  136. package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +504 -496
  137. package/src/components/layouts/__snapshots__/ZoomedFullBody.test.tsx.snap +252 -248
  138. package/src/components/layouts/chat.css +335 -13
  139. package/src/components/layouts/layouts.stories.tsx +13 -2
  140. package/src/components/layouts/zoomed-full-body.css +1 -3
  141. package/src/helpers/message.ts +1 -0
  142. package/src/helpers/stt/useSTT.ts +101 -16
  143. package/src/index.stories.tsx +26 -22
  144. package/src/index.tsx +46 -0
  145. package/src/mocks/data.ts +258 -0
  146. package/src/styles.css +5 -0
@@ -20,6 +20,11 @@ import memoriApiClient from '@memori.ai/memori-api-client';
20
20
  import ChatInputs from '../ChatInputs/ChatInputs';
21
21
  import Typing from '../Typing/Typing';
22
22
  import { boardOfExpertsLoadingSentences } from '../../helpers/constants';
23
+ import {
24
+ ArtifactHandler,
25
+ useArtifactSystemContext,
26
+ } from '../MemoriArtifactSystem';
27
+ import { useArtifactDetector } from '../MemoriArtifactSystem/hooks/useArtifactSystem';
23
28
 
24
29
  export interface Props {
25
30
  memori: Memori;
@@ -122,6 +127,9 @@ const Chat: React.FC<Props> = ({
122
127
  isHistoryView = false,
123
128
  showFunctionCache = false,
124
129
  }) => {
130
+
131
+ const { state, actions, config } = useArtifactSystemContext();
132
+ const { hasArtifacts } = useArtifactDetector();
125
133
  const scrollToBottom = () => {
126
134
  if (isHistoryView) return;
127
135
  setTimeout(() => {
@@ -166,6 +174,8 @@ const Chat: React.FC<Props> = ({
166
174
  }
167
175
  };
168
176
 
177
+ console.log(history);
178
+
169
179
  return (
170
180
  <div
171
181
  className={cx('memori-chat--wrapper', {
@@ -298,7 +308,8 @@ const Chat: React.FC<Props> = ({
298
308
  media={[
299
309
  // Filter out HTML and plain text media items from the message
300
310
  ...(message?.media?.filter(
301
- m => m.mimeType !== 'text/html' && m.mimeType !== 'text/plain'
311
+ m =>
312
+ m.mimeType !== 'text/html' && m.mimeType !== 'text/plain'
302
313
  ) || []),
303
314
 
304
315
  // Extract document attachments that are embedded in the message text
@@ -310,14 +321,16 @@ const Chat: React.FC<Props> = ({
310
321
  // <document_attachment filename="name.ext" type="mime/type">content</document_attachment>
311
322
  const documentAttachmentRegex =
312
323
  /<document_attachment filename="([^"]+)" type="([^"]+)">([\s\S]*?)<\/document_attachment>/g;
313
-
324
+
314
325
  const attachments: (Medium & { type?: string })[] = [];
315
326
  let match;
316
327
 
317
328
  // Find all document attachments in the text
318
- while ((match = documentAttachmentRegex.exec(text)) !== null) {
329
+ while (
330
+ (match = documentAttachmentRegex.exec(text)) !== null
331
+ ) {
319
332
  const [, filename, type, content] = match;
320
-
333
+
321
334
  // Create a Medium object for each attachment with:
322
335
  // - Unique ID using timestamp and random string
323
336
  // - Empty URL since content is embedded
@@ -348,6 +361,16 @@ const Chat: React.FC<Props> = ({
348
361
  customMediaRenderer={customMediaRenderer}
349
362
  fromUser={message.fromUser}
350
363
  />
364
+
365
+ <ArtifactHandler
366
+ onArtifactCreated={artifact => {
367
+ actions.selectArtifact(artifact);
368
+ }}
369
+ artifacts={state.history}
370
+ config={config}
371
+ actions={actions}
372
+ message={message}
373
+ />
351
374
  </React.Fragment>
352
375
  ))}
353
376
 
@@ -0,0 +1,160 @@
1
+ /**
2
+ * ArtifactActions CSS Styles
3
+ * Following the project's design system and CSS patterns
4
+ */
5
+
6
+ .memori-artifact-actions {
7
+ display: flex;
8
+ flex-wrap: wrap;
9
+ align-items: center;
10
+ gap: 0.5rem;
11
+ }
12
+
13
+ .memori-artifact-action-btn {
14
+ display: flex;
15
+ align-items: center;
16
+ padding: 0.75rem 1rem;
17
+ border: none;
18
+ border-radius: var(--memori-border-radius, 4px);
19
+ background: transparent;
20
+ color: var(--memori-text-color, #666);
21
+ cursor: pointer;
22
+ font-size: 0.875rem;
23
+ font-weight: 500;
24
+ gap: 0.5rem;
25
+ transition: all 0.2s ease;
26
+ white-space: nowrap;
27
+ }
28
+
29
+ .memori-artifact-action-icon{
30
+ width: 16px;
31
+ height: 16px;
32
+ }
33
+
34
+ .memori-artifact-action-btn:focus {
35
+ outline: 2px solid var(--memori-primary);
36
+ outline-offset: 2px;
37
+ }
38
+
39
+ .memori-artifact-action-btn:active {
40
+ transform: translateY(0);
41
+ }
42
+
43
+ .memori-artifact-action-btn:disabled {
44
+ cursor: not-allowed;
45
+ opacity: 0.6;
46
+ transform: none !important;
47
+ }
48
+
49
+ .memori-artifact-action-btn:focus:not(:focus-visible) {
50
+ outline: none;
51
+ }
52
+
53
+ .memori-artifact-action-btn:disabled:hover {
54
+ box-shadow: none !important;
55
+ transform: none !important;
56
+ }
57
+
58
+ .memori-artifact-action-btn:hover:not(.memori-artifact-action-btn--active) {
59
+ background: rgba(0, 123, 255, 0.05);
60
+ color: var(--memori-text-color, #333);
61
+ }
62
+
63
+ .memori-artifact-action-btn--active {
64
+ background: var(--memori-chat-bubble-bg, #fff);
65
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
66
+ color: var(--memori-primary);
67
+ }
68
+
69
+ .memori-artifact-action-text {
70
+ font-size: 0.75rem;
71
+ font-weight: 500;
72
+ }
73
+
74
+ /* Fullscreen Button Special Styling */
75
+ .memori-artifact-fullscreen-btn {
76
+ position: relative;
77
+ }
78
+
79
+ .memori-artifact-fullscreen-btn--active {
80
+ border-color: var(--memori-primary) !important;
81
+ background: var(--memori-primary) !important;
82
+ color: var(--memori-primary-text) !important;
83
+ }
84
+
85
+
86
+ /* Responsive Design */
87
+ @media (max-width: 768px) {
88
+ .memori-artifact-actions {
89
+ gap: 0.25rem;
90
+ }
91
+
92
+ .memori-artifact-action-btn {
93
+ min-width: 42px;
94
+ min-height: auto;
95
+ justify-content: center;
96
+ }
97
+
98
+ .memori-artifact-action-text {
99
+ display: none;
100
+ }
101
+
102
+ .memori-artifact-fullscreen-btn{
103
+ display: none;
104
+ }
105
+ }
106
+
107
+ @media (max-width: 480px) {
108
+ .memori-artifact-actions {
109
+ flex-flow: row;
110
+ flex-wrap: wrap;
111
+ justify-content: center;
112
+ }
113
+
114
+ .memori-artifact-action-btn {
115
+ min-width: 0;
116
+ max-width: 3rem;
117
+ flex: 1;
118
+ }
119
+ }
120
+
121
+ /* Loading State */
122
+
123
+ /* Icon Sizing */
124
+ .memori-artifact-action-btn .memori-button--icon {
125
+ font-size: 1rem;
126
+ line-height: 1;
127
+ }
128
+
129
+ /* Dark Mode Support */
130
+ @media (prefers-color-scheme: dark) {
131
+ .memori-artifact-action-btn:hover {
132
+ box-shadow: 0 2px 8px rgba(255, 255, 255, 0.1);
133
+ }
134
+ }
135
+
136
+ /* Animation for Copy Feedback */
137
+ @keyframes copyFeedback {
138
+ 0% {
139
+ opacity: 1;
140
+ transform: scale(1);
141
+ }
142
+ 50% {
143
+ opacity: 0.8;
144
+ transform: scale(1.05);
145
+ }
146
+ 100% {
147
+ opacity: 1;
148
+ transform: scale(1);
149
+ }
150
+ }
151
+
152
+ .memori-artifact-action-btn.copied {
153
+ animation: copyFeedback 0.3s ease;
154
+ }
155
+
156
+ /* Tooltip Support */
157
+ .memori-artifact-action-btn[title] {
158
+ position: relative;
159
+ }
160
+
@@ -0,0 +1,278 @@
1
+ /**
2
+ * ArtifactActions Component
3
+ * Provides action buttons for artifact operations (copy, download, print, etc.)
4
+ * Following the project's component patterns and design system
5
+ */
6
+
7
+ import React, { useState, useCallback } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import cx from 'classnames';
10
+ import Button from '../../../ui/Button';
11
+ import { ArtifactActionsProps } from '../../types/artifact.types';
12
+ import Download from '../../../icons/Download';
13
+ import Link from '../../../icons/Link';
14
+ import Fullscreen from '../../../icons/Fullscreen';
15
+ import FullscreenExit from '../../../icons/FullscreenExit';
16
+ import PrintIcon from '../../../icons/Print';
17
+ import Copy from '../../../icons/Copy';
18
+
19
+ const ArtifactActions: React.FC<ArtifactActionsProps> = ({
20
+ artifact,
21
+ onCopy,
22
+ onDownload,
23
+ onPrint,
24
+ onOpenExternal,
25
+ onToggleFullscreen,
26
+ isFullscreen,
27
+ loading = false,
28
+ }) => {
29
+ const { t } = useTranslation();
30
+ const [copyFeedback, setCopyFeedback] = useState<string | null>(null);
31
+
32
+ /**
33
+ * Handle copy action with feedback
34
+ */
35
+ const handleCopy = useCallback(async () => {
36
+ try {
37
+ await onCopy();
38
+ setCopyFeedback('✅ Copied!');
39
+ setTimeout(() => setCopyFeedback(null), 2000);
40
+ } catch (error) {
41
+ setCopyFeedback('❌ Error');
42
+ setTimeout(() => setCopyFeedback(null), 2000);
43
+ }
44
+ }, [onCopy]);
45
+
46
+ /**
47
+ * Get file extension for download
48
+ */
49
+ const getFileExtension = useCallback((mimeType: string): string => {
50
+ const extensions: Record<string, string> = {
51
+ html: 'html',
52
+ json: 'json',
53
+ markdown: 'md',
54
+ css: 'css',
55
+ javascript: 'js',
56
+ typescript: 'ts',
57
+ svg: 'svg',
58
+ xml: 'xml',
59
+ text: 'txt',
60
+ python: 'py',
61
+ java: 'java',
62
+ cpp: 'cpp',
63
+ csharp: 'cs',
64
+ php: 'php',
65
+ ruby: 'rb',
66
+ go: 'go',
67
+ rust: 'rs',
68
+ yaml: 'yml',
69
+ sql: 'sql',
70
+ };
71
+ return extensions[mimeType] || 'txt';
72
+ }, []);
73
+
74
+ /**
75
+ * Get MIME type string for downloads
76
+ */
77
+ const getMimeTypeString = useCallback((mimeType: string): string => {
78
+ const mimeTypes: Record<string, string> = {
79
+ html: 'text/html',
80
+ json: 'application/json',
81
+ markdown: 'text/markdown',
82
+ css: 'text/css',
83
+ javascript: 'text/javascript',
84
+ typescript: 'text/typescript',
85
+ svg: 'image/svg+xml',
86
+ xml: 'text/xml',
87
+ text: 'text/plain',
88
+ python: 'text/x-python',
89
+ java: 'text/x-java',
90
+ cpp: 'text/x-c++',
91
+ csharp: 'text/x-csharp',
92
+ php: 'text/x-php',
93
+ ruby: 'text/x-ruby',
94
+ go: 'text/x-go',
95
+ rust: 'text/x-rust',
96
+ yaml: 'text/yaml',
97
+ sql: 'text/x-sql',
98
+ };
99
+ return mimeTypes[mimeType] || 'text/plain';
100
+ }, []);
101
+
102
+ /**
103
+ * Handle download action
104
+ */
105
+ const handleDownload = useCallback(() => {
106
+ try {
107
+ const extension = getFileExtension(artifact.mimeType);
108
+ const filename = `artifact-${Date.now()}.${extension}`;
109
+ const mimeType = getMimeTypeString(artifact.mimeType);
110
+
111
+ const blob = new Blob([artifact.content], { type: mimeType });
112
+ const url = URL.createObjectURL(blob);
113
+
114
+ const a = document.createElement('a');
115
+ a.href = url;
116
+ a.download = filename;
117
+ document.body.appendChild(a);
118
+ a.click();
119
+ document.body.removeChild(a);
120
+ URL.revokeObjectURL(url);
121
+
122
+ onDownload();
123
+ } catch (error) {
124
+ console.error('Download failed:', error);
125
+ }
126
+ }, [artifact, getFileExtension, getMimeTypeString, onDownload]);
127
+
128
+ /**
129
+ * Handle print action
130
+ */
131
+ const handlePrint = useCallback(() => {
132
+ try {
133
+ const printWindow = window.open('', '_blank');
134
+ if (!printWindow) {
135
+ alert('Popup blocked! Please enable popups to print the artifact.');
136
+ return;
137
+ }
138
+
139
+ let printContent: string;
140
+ if (artifact.mimeType === 'html') {
141
+ printContent = artifact.content;
142
+ } else {
143
+ printContent = `
144
+ <!DOCTYPE html>
145
+ <html>
146
+ <head>
147
+ <title>Artifact - ${artifact.mimeType.toUpperCase()}</title>
148
+ <style>
149
+ body {
150
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
151
+ white-space: pre-wrap;
152
+ margin: 20px;
153
+ line-height: 1.4;
154
+ }
155
+ @media print {
156
+ body { margin: 0; }
157
+ }
158
+ </style>
159
+ </head>
160
+ <body>${artifact.content.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</body>
161
+ </html>
162
+ `;
163
+ }
164
+
165
+ printWindow.document.write(printContent);
166
+ printWindow.document.close();
167
+
168
+ setTimeout(() => {
169
+ printWindow.print();
170
+ printWindow.close();
171
+ }, 500);
172
+
173
+ onPrint();
174
+ } catch (error) {
175
+ console.error('Print failed:', error);
176
+ }
177
+ }, [artifact, onPrint]);
178
+
179
+ /**
180
+ * Handle external open action
181
+ */
182
+ const handleOpenExternal = useCallback(() => {
183
+ try {
184
+ const mimeType = getMimeTypeString(artifact.mimeType);
185
+ const blob = new Blob([artifact.content], { type: mimeType });
186
+ const url = URL.createObjectURL(blob);
187
+
188
+ const externalWindow = window.open(url, '_blank');
189
+ if (!externalWindow) {
190
+ alert('Popup blocked! Please enable popups to open the artifact in a new window.');
191
+ return;
192
+ }
193
+
194
+ // Cleanup URL after a delay
195
+ setTimeout(() => {
196
+ URL.revokeObjectURL(url);
197
+ }, 60000);
198
+
199
+ onOpenExternal();
200
+ } catch (error) {
201
+ console.error('External open failed:', error);
202
+ }
203
+ }, [artifact, getMimeTypeString, onOpenExternal]);
204
+
205
+ return (
206
+ <div className="memori-artifact-actions">
207
+ <Button
208
+ onClick={handleCopy}
209
+ disabled={loading}
210
+ title={t('artifact.copy', 'Copy code') || 'Copy code'}
211
+ className="memori-artifact-action-btn"
212
+ ghost
213
+ >
214
+ <Copy className="memori-artifact-action-icon" />
215
+ <span className="memori-artifact-action-text">
216
+ {copyFeedback || t('artifact.copy', 'Copy')}
217
+ </span>
218
+ </Button>
219
+
220
+ <Button
221
+ onClick={handleDownload}
222
+ disabled={loading}
223
+ title={t('artifact.download', 'Download artifact') || 'Download artifact'}
224
+ className="memori-artifact-action-btn"
225
+ ghost
226
+ >
227
+ <Download className="memori-artifact-action-icon" />
228
+ <span className="memori-artifact-action-text">
229
+ {t('artifact.download', 'Download')}
230
+ </span>
231
+ </Button>
232
+
233
+ <Button
234
+ onClick={handlePrint}
235
+ disabled={loading}
236
+ title={t('artifact.print', 'Print artifact') || 'Print artifact'}
237
+ className="memori-artifact-action-btn"
238
+ ghost
239
+ >
240
+ <PrintIcon className="memori-artifact-action-icon" />
241
+ <span className="memori-artifact-action-text">
242
+ {t('artifact.print', 'Print')}
243
+ </span>
244
+ </Button>
245
+
246
+ <Button
247
+ onClick={handleOpenExternal}
248
+ disabled={loading}
249
+ title={t('artifact.external', 'Open in new window') || 'Open in new window'}
250
+ className="memori-artifact-action-btn"
251
+ ghost
252
+ >
253
+ <Link className="memori-artifact-action-icon" />
254
+ <span className="memori-artifact-action-text">
255
+ {t('artifact.external', 'External')}
256
+ </span>
257
+ </Button>
258
+
259
+ <Button
260
+ onClick={onToggleFullscreen}
261
+ disabled={loading}
262
+ title={isFullscreen ? (t('artifact.exitFullscreen', 'Exit fullscreen') || 'Exit fullscreen') : (t('artifact.fullscreen', 'Fullscreen') || 'Fullscreen')}
263
+ className={cx('memori-artifact-action-btn', 'memori-artifact-fullscreen-btn', {
264
+ 'memori-artifact-fullscreen-btn--active': isFullscreen,
265
+ })}
266
+ primary={isFullscreen}
267
+ ghost={!isFullscreen}
268
+ >
269
+ {isFullscreen ? <Fullscreen className="memori-artifact-action-icon" /> : <FullscreenExit className="memori-artifact-action-icon" />}
270
+ <span className="memori-artifact-action-text">
271
+ {isFullscreen ? t('artifact.exitFullscreen', 'Exit') : t('artifact.fullscreen', 'Full')}
272
+ </span>
273
+ </Button>
274
+ </div>
275
+ );
276
+ };
277
+
278
+ export default ArtifactActions;