@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.
- package/CHANGELOG.md +27 -0
- package/dist/components/Chat/Chat.js +8 -1
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.css +160 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.d.ts +4 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +166 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +877 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.d.ts +3 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +115 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.css +238 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.d.ts +4 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +104 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.css +319 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.d.ts +4 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.js +50 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.js.map +1 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +343 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.d.ts +4 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js +78 -0
- package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js.map +1 -0
- package/dist/components/MemoriArtifactSystem/context/ArtifactSystemContext.d.ts +12 -0
- package/dist/components/MemoriArtifactSystem/context/ArtifactSystemContext.js +22 -0
- package/dist/components/MemoriArtifactSystem/context/ArtifactSystemContext.js.map +1 -0
- package/dist/components/MemoriArtifactSystem/hooks/useArtifactSystem.d.ts +12 -0
- package/dist/components/MemoriArtifactSystem/hooks/useArtifactSystem.js +288 -0
- package/dist/components/MemoriArtifactSystem/hooks/useArtifactSystem.js.map +1 -0
- package/dist/components/MemoriArtifactSystem/index.d.ts +9 -0
- package/dist/components/MemoriArtifactSystem/index.js +28 -0
- package/dist/components/MemoriArtifactSystem/index.js.map +1 -0
- package/dist/components/MemoriArtifactSystem/types/artifact.types.d.ts +108 -0
- package/dist/components/MemoriArtifactSystem/types/artifact.types.js +31 -0
- package/dist/components/MemoriArtifactSystem/types/artifact.types.js.map +1 -0
- package/dist/components/icons/Print.d.ts +6 -0
- package/dist/components/icons/Print.js +6 -0
- package/dist/components/icons/Print.js.map +1 -0
- package/dist/components/layouts/Chat.js +29 -1
- package/dist/components/layouts/Chat.js.map +1 -1
- package/dist/components/layouts/FullPage.js +33 -1
- package/dist/components/layouts/FullPage.js.map +1 -1
- package/dist/components/layouts/ZoomedFullBody.js +29 -2
- package/dist/components/layouts/ZoomedFullBody.js.map +1 -1
- package/dist/components/layouts/chat.css +335 -13
- package/dist/components/layouts/zoomed-full-body.css +1 -3
- package/dist/helpers/message.js +1 -0
- package/dist/helpers/message.js.map +1 -1
- package/dist/helpers/stt/useSTT.js +76 -9
- package/dist/helpers/stt/useSTT.js.map +1 -1
- package/dist/index.js +58 -15
- package/dist/index.js.map +1 -1
- package/dist/styles.css +5 -0
- package/esm/components/Chat/Chat.js +8 -1
- package/esm/components/Chat/Chat.js.map +1 -1
- package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.css +160 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.d.ts +4 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +163 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +877 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.d.ts +3 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +112 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.css +238 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.d.ts +4 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +101 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.css +319 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.d.ts +4 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.js +47 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.js.map +1 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +343 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.d.ts +4 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js +75 -0
- package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js.map +1 -0
- package/esm/components/MemoriArtifactSystem/context/ArtifactSystemContext.d.ts +12 -0
- package/esm/components/MemoriArtifactSystem/context/ArtifactSystemContext.js +17 -0
- package/esm/components/MemoriArtifactSystem/context/ArtifactSystemContext.js.map +1 -0
- package/esm/components/MemoriArtifactSystem/hooks/useArtifactSystem.d.ts +12 -0
- package/esm/components/MemoriArtifactSystem/hooks/useArtifactSystem.js +281 -0
- package/esm/components/MemoriArtifactSystem/hooks/useArtifactSystem.js.map +1 -0
- package/esm/components/MemoriArtifactSystem/index.d.ts +9 -0
- package/esm/components/MemoriArtifactSystem/index.js +9 -0
- package/esm/components/MemoriArtifactSystem/index.js.map +1 -0
- package/esm/components/MemoriArtifactSystem/types/artifact.types.d.ts +108 -0
- package/esm/components/MemoriArtifactSystem/types/artifact.types.js +28 -0
- package/esm/components/MemoriArtifactSystem/types/artifact.types.js.map +1 -0
- package/esm/components/icons/Print.d.ts +6 -0
- package/esm/components/icons/Print.js +4 -0
- package/esm/components/icons/Print.js.map +1 -0
- package/esm/components/layouts/Chat.js +29 -1
- package/esm/components/layouts/Chat.js.map +1 -1
- package/esm/components/layouts/FullPage.js +33 -1
- package/esm/components/layouts/FullPage.js.map +1 -1
- package/esm/components/layouts/ZoomedFullBody.js +30 -3
- package/esm/components/layouts/ZoomedFullBody.js.map +1 -1
- package/esm/components/layouts/chat.css +335 -13
- package/esm/components/layouts/zoomed-full-body.css +1 -3
- package/esm/helpers/message.js +1 -0
- package/esm/helpers/message.js.map +1 -1
- package/esm/helpers/stt/useSTT.js +76 -9
- package/esm/helpers/stt/useSTT.js.map +1 -1
- package/esm/index.js +58 -15
- package/esm/index.js.map +1 -1
- package/esm/styles.css +5 -0
- package/package.json +1 -1
- package/src/components/Avatar/Avatar.test.tsx +13 -0
- package/src/components/Chat/Chat.stories.tsx +33 -2
- package/src/components/Chat/Chat.test.tsx +340 -213
- package/src/components/Chat/Chat.tsx +27 -4
- package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.css +160 -0
- package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.tsx +278 -0
- package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +877 -0
- package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.tsx +308 -0
- package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.css +238 -0
- package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.tsx +282 -0
- package/src/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.css +319 -0
- package/src/components/MemoriArtifactSystem/components/ArtifactHistory/ArtifactHistory.tsx +178 -0
- package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +343 -0
- package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.tsx +190 -0
- package/src/components/MemoriArtifactSystem/context/ArtifactSystemContext.tsx +57 -0
- package/src/components/MemoriArtifactSystem/hooks/useArtifactSystem.ts +419 -0
- package/src/components/MemoriArtifactSystem/index.ts +45 -0
- package/src/components/MemoriArtifactSystem/types/artifact.types.ts +180 -0
- package/src/components/icons/Print.tsx +34 -0
- package/src/components/layouts/Chat.test.tsx +13 -0
- package/src/components/layouts/Chat.tsx +80 -25
- package/src/components/layouts/FullPage.test.tsx +40 -11
- package/src/components/layouts/FullPage.tsx +92 -24
- package/src/components/layouts/HiddenChat.test.tsx +13 -0
- package/src/components/layouts/Totem.test.tsx +13 -0
- package/src/components/layouts/WebsiteAssistant.test.tsx +13 -0
- package/src/components/layouts/ZoomedFullBody.test.tsx +13 -0
- package/src/components/layouts/ZoomedFullBody.tsx +78 -14
- package/src/components/layouts/__snapshots__/Chat.test.tsx.snap +252 -248
- package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +504 -496
- package/src/components/layouts/__snapshots__/ZoomedFullBody.test.tsx.snap +252 -248
- package/src/components/layouts/chat.css +335 -13
- package/src/components/layouts/layouts.stories.tsx +13 -2
- package/src/components/layouts/zoomed-full-body.css +1 -3
- package/src/helpers/message.ts +1 -0
- package/src/helpers/stt/useSTT.ts +101 -16
- package/src/index.stories.tsx +26 -22
- package/src/index.tsx +46 -0
- package/src/mocks/data.ts +258 -0
- 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 =>
|
|
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 (
|
|
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, '<').replace(/>/g, '>')}</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;
|