@memori.ai/memori-react 8.8.5 → 8.9.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 +38 -0
- package/README.md +28 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +0 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +0 -10
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +0 -9
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -1
- package/dist/components/Chat/Chat.css +31 -0
- package/dist/components/Chat/Chat.js +18 -4
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChatBubble/ChatBubble.js +1 -2
- package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
- package/dist/components/ChatInputs/ChatInputs.css +23 -0
- package/dist/components/ChatInputs/ChatInputs.d.ts +1 -0
- package/dist/components/ChatInputs/ChatInputs.js +37 -21
- package/dist/components/ChatInputs/ChatInputs.js.map +1 -1
- package/dist/components/ChatTextArea/ChatTextArea.css +31 -0
- package/dist/components/ChatTextArea/ChatTextArea.d.ts +1 -0
- package/dist/components/ChatTextArea/ChatTextArea.js +9 -2
- package/dist/components/ChatTextArea/ChatTextArea.js.map +1 -1
- package/dist/components/FilePreview/FilePreview.css +39 -0
- package/dist/components/Header/Header.js +3 -16
- package/dist/components/Header/Header.js.map +1 -1
- package/dist/components/MediaWidget/LinkItemWidget.js +1 -1
- package/dist/components/MediaWidget/LinkItemWidget.js.map +1 -1
- package/dist/components/MediaWidget/MediaItemWidget.js +5 -9
- package/dist/components/MediaWidget/MediaItemWidget.js.map +1 -1
- package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +65 -51
- package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -1
- package/dist/components/MemoriArtifactSystem/utils/ArtifactAPI.d.ts +5 -0
- package/dist/components/MemoriArtifactSystem/utils/ArtifactAPI.js +287 -0
- package/dist/components/MemoriArtifactSystem/utils/ArtifactAPI.js.map +1 -0
- package/dist/components/MemoriWidget/MemoriWidget.d.ts +12 -0
- package/dist/components/MemoriWidget/MemoriWidget.js +12 -3
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/components/Snippet/Snippet.js +4 -3
- package/dist/components/Snippet/Snippet.js.map +1 -1
- package/dist/components/StartPanel/StartPanel.css +14 -0
- package/dist/components/StartPanel/StartPanel.js +2 -2
- package/dist/components/StartPanel/StartPanel.js.map +1 -1
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +0 -21
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
- package/dist/components/VenueWidget/VenueWidget.js +0 -1
- package/dist/components/VenueWidget/VenueWidget.js.map +1 -1
- package/dist/components/layouts/HiddenChat.js +0 -15
- package/dist/components/layouts/HiddenChat.js.map +1 -1
- package/dist/components/layouts/chat.css +2 -2
- package/dist/context/visemeContext.js +0 -6
- package/dist/context/visemeContext.js.map +1 -1
- package/dist/helpers/constants.d.ts +11 -0
- package/dist/helpers/constants.js +24 -2
- package/dist/helpers/constants.js.map +1 -1
- package/dist/helpers/tts/useTTS.js +0 -3
- package/dist/helpers/tts/useTTS.js.map +1 -1
- package/dist/helpers/utils.d.ts +1 -0
- package/dist/helpers/utils.js +6 -1
- package/dist/helpers/utils.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/locales/de.json +2 -0
- package/dist/locales/en.json +2 -0
- package/dist/locales/es.json +2 -0
- package/dist/locales/fr.json +2 -0
- package/dist/locales/it.json +2 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +0 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +0 -10
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +0 -9
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -1
- package/esm/components/Chat/Chat.css +31 -0
- package/esm/components/Chat/Chat.js +19 -5
- package/esm/components/Chat/Chat.js.map +1 -1
- package/esm/components/ChatBubble/ChatBubble.js +1 -2
- package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
- package/esm/components/ChatInputs/ChatInputs.css +23 -0
- package/esm/components/ChatInputs/ChatInputs.d.ts +1 -0
- package/esm/components/ChatInputs/ChatInputs.js +37 -21
- package/esm/components/ChatInputs/ChatInputs.js.map +1 -1
- package/esm/components/ChatTextArea/ChatTextArea.css +31 -0
- package/esm/components/ChatTextArea/ChatTextArea.d.ts +1 -0
- package/esm/components/ChatTextArea/ChatTextArea.js +9 -2
- package/esm/components/ChatTextArea/ChatTextArea.js.map +1 -1
- package/esm/components/FilePreview/FilePreview.css +39 -0
- package/esm/components/Header/Header.js +3 -16
- package/esm/components/Header/Header.js.map +1 -1
- package/esm/components/MediaWidget/LinkItemWidget.js +1 -1
- package/esm/components/MediaWidget/LinkItemWidget.js.map +1 -1
- package/esm/components/MediaWidget/MediaItemWidget.js +5 -9
- package/esm/components/MediaWidget/MediaItemWidget.js.map +1 -1
- package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +65 -51
- package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -1
- package/esm/components/MemoriArtifactSystem/utils/ArtifactAPI.d.ts +5 -0
- package/esm/components/MemoriArtifactSystem/utils/ArtifactAPI.js +282 -0
- package/esm/components/MemoriArtifactSystem/utils/ArtifactAPI.js.map +1 -0
- package/esm/components/MemoriWidget/MemoriWidget.d.ts +12 -0
- package/esm/components/MemoriWidget/MemoriWidget.js +12 -3
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/components/Snippet/Snippet.js +4 -3
- package/esm/components/Snippet/Snippet.js.map +1 -1
- package/esm/components/StartPanel/StartPanel.css +14 -0
- package/esm/components/StartPanel/StartPanel.js +3 -3
- package/esm/components/StartPanel/StartPanel.js.map +1 -1
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +0 -21
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
- package/esm/components/VenueWidget/VenueWidget.js +0 -1
- package/esm/components/VenueWidget/VenueWidget.js.map +1 -1
- package/esm/components/layouts/HiddenChat.js +0 -15
- package/esm/components/layouts/HiddenChat.js.map +1 -1
- package/esm/components/layouts/chat.css +2 -2
- package/esm/context/visemeContext.js +0 -6
- package/esm/context/visemeContext.js.map +1 -1
- package/esm/helpers/constants.d.ts +11 -0
- package/esm/helpers/constants.js +22 -1
- package/esm/helpers/constants.js.map +1 -1
- package/esm/helpers/tts/useTTS.js +0 -3
- package/esm/helpers/tts/useTTS.js.map +1 -1
- package/esm/helpers/utils.d.ts +1 -0
- package/esm/helpers/utils.js +4 -0
- package/esm/helpers/utils.js.map +1 -1
- package/esm/index.js.map +1 -1
- package/esm/locales/de.json +2 -0
- package/esm/locales/en.json +2 -0
- package/esm/locales/es.json +2 -0
- package/esm/locales/fr.json +2 -0
- package/esm/locales/it.json +2 -0
- package/package.json +1 -1
- package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +0 -1
- package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +0 -17
- package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.ts +0 -20
- package/src/components/Chat/Chat.css +31 -0
- package/src/components/Chat/Chat.stories.tsx +503 -9
- package/src/components/Chat/Chat.tsx +23 -3
- package/src/components/Chat/__snapshots__/Chat.test.tsx.snap +73 -73
- package/src/components/ChatBubble/ChatBubble.tsx +1 -2
- package/src/components/ChatBubble/__snapshots__/ChatBubble.test.tsx.snap +25 -25
- package/src/components/ChatInputs/ChatInputs.css +23 -0
- package/src/components/ChatInputs/ChatInputs.tsx +36 -14
- package/src/components/ChatTextArea/ChatTextArea.css +31 -0
- package/src/components/ChatTextArea/ChatTextArea.tsx +11 -1
- package/src/components/FilePreview/FilePreview.css +39 -0
- package/src/components/Header/Header.tsx +0 -13
- package/src/components/MediaWidget/LinkItemWidget.tsx +1 -1
- package/src/components/MediaWidget/MediaItemWidget.stories.tsx +33 -0
- package/src/components/MediaWidget/MediaItemWidget.tsx +7 -10
- package/src/components/MediaWidget/__snapshots__/LinkItemWidget.test.tsx.snap +4 -4
- package/src/components/MediaWidget/__snapshots__/MediaItemWidget.test.tsx.snap +6 -6
- package/src/components/MediaWidget/__snapshots__/MediaWidget.test.tsx.snap +2 -2
- package/src/components/MemoriArtifactSystem/ArtifactDrawer.stories.tsx +766 -2
- package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.tsx +103 -89
- package/src/components/MemoriArtifactSystem/utils/ArtifactAPI.test.tsx +307 -0
- package/src/components/MemoriArtifactSystem/utils/ArtifactAPI.tsx +373 -0
- package/src/components/MemoriWidget/MemoriWidget.tsx +26 -4
- package/src/components/Snippet/Snippet.tsx +3 -2
- package/src/components/StartPanel/StartPanel.css +14 -0
- package/src/components/StartPanel/StartPanel.tsx +23 -10
- package/src/components/StartPanel/__snapshots__/StartPanel.test.tsx.snap +206 -84
- package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +0 -23
- package/src/components/VenueWidget/VenueWidget.tsx +0 -1
- package/src/components/layouts/HiddenChat.tsx +0 -16
- package/src/components/layouts/__snapshots__/Chat.test.tsx.snap +204 -82
- package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +408 -164
- package/src/components/layouts/__snapshots__/HiddenChat.test.tsx.snap +204 -82
- package/src/components/layouts/__snapshots__/Totem.test.tsx.snap +204 -82
- package/src/components/layouts/__snapshots__/ZoomedFullBody.test.tsx.snap +204 -82
- package/src/components/layouts/chat.css +2 -2
- package/src/context/visemeContext.tsx +0 -7
- package/src/helpers/constants.ts +28 -3
- package/src/helpers/tts/useTTS.ts +0 -2
- package/src/helpers/utils.ts +5 -0
- package/src/index.tsx +0 -1
- package/src/locales/de.json +2 -0
- package/src/locales/en.json +2 -0
- package/src/locales/es.json +2 -0
- package/src/locales/fr.json +2 -0
- package/src/locales/it.json +2 -0
|
@@ -25,9 +25,6 @@ const ArtifactHandler: React.FC<ArtifactHandlerProps> = ({
|
|
|
25
25
|
message,
|
|
26
26
|
}) => {
|
|
27
27
|
const { openArtifact, state, closeArtifact } = useArtifact();
|
|
28
|
-
const [currentArtifact, setCurrentArtifact] = useState<ArtifactData | null>(
|
|
29
|
-
null
|
|
30
|
-
);
|
|
31
28
|
|
|
32
29
|
// Memoize the message text to prevent unnecessary recalculations
|
|
33
30
|
const messageText = useMemo(() => {
|
|
@@ -53,63 +50,56 @@ const ArtifactHandler: React.FC<ArtifactHandlerProps> = ({
|
|
|
53
50
|
// Simple artifact detection - look for <output class="memori-artifact"> tags
|
|
54
51
|
// Remove message dependency to prevent recreation on every message change
|
|
55
52
|
const detectArtifacts = useCallback((text: string, isFromUser: boolean): ArtifactData[] => {
|
|
56
|
-
console.log('Detecting artifacts from text:', text?.substring(0, 100) + '...');
|
|
57
|
-
|
|
58
53
|
if (!text || isFromUser) {
|
|
59
|
-
console.log('No text or message is from user, returning empty array');
|
|
60
54
|
return [];
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
//first strip the output tag isnide the think tag, if there is one
|
|
64
57
|
text = stripReasoningTags(text);
|
|
65
58
|
|
|
66
59
|
const artifacts: ArtifactData[] = [];
|
|
67
60
|
|
|
68
|
-
// Find complete opening and closing tags
|
|
69
61
|
const artifactRegex = /<output\s+class="memori-artifact"[^>]*data-mimetype="([^"]+)"[^>]*>([\s\S]*?)<\/output>/gi;
|
|
70
62
|
const titleRegex = {
|
|
71
63
|
dataTitle: /data-title\s*=\s*["\']([^"']+)["\']/i,
|
|
72
64
|
htmlTitle: /<title>([^<]+)<\/title>/gi
|
|
73
65
|
};
|
|
74
66
|
|
|
75
|
-
const findTitle = (mimeType: string, content: string) => {
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
const findTitle = (mimeType: string, content: string, outputTag: string) => {
|
|
68
|
+
// First try to find data-title in the output tag
|
|
69
|
+
const dataTitleMatch = outputTag.match(/data-title\s*=\s*["\']([^"']+)["\']/i);
|
|
70
|
+
if (dataTitleMatch) {
|
|
71
|
+
return dataTitleMatch[1];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Then try to find title in the content
|
|
75
|
+
const htmlTitleMatch = content.match(/<title>([^<]+)<\/title>/i);
|
|
76
|
+
if (htmlTitleMatch) {
|
|
77
|
+
return htmlTitleMatch[1];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Default title based on mimeType
|
|
81
|
+
return `${mimeType.toUpperCase()} Artifact`;
|
|
78
82
|
};
|
|
79
83
|
|
|
80
84
|
let match;
|
|
85
|
+
let artifactNum = 0;
|
|
81
86
|
while ((match = artifactRegex.exec(text)) !== null) {
|
|
82
|
-
|
|
87
|
+
artifactNum++;
|
|
83
88
|
const mimeType = match[1];
|
|
84
89
|
const content = match[2].trim();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
size: content.length,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
console.log('Created artifact:', {
|
|
100
|
-
id: artifact.id,
|
|
101
|
-
mimeType: artifact.mimeType,
|
|
102
|
-
title: artifact.title,
|
|
103
|
-
size: artifact.size
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
artifacts.push(artifact);
|
|
107
|
-
} else {
|
|
108
|
-
console.log('Skipping artifact - content too short');
|
|
109
|
-
}
|
|
90
|
+
const outputTag = match[0]; // Full output tag for title extraction
|
|
91
|
+
|
|
92
|
+
const artifact = {
|
|
93
|
+
id: `artifact-${Date.now()}-${artifactNum}-${Math.random().toString(36).substr(2, 9)}`,
|
|
94
|
+
content,
|
|
95
|
+
mimeType,
|
|
96
|
+
title: findTitle(mimeType, content, outputTag),
|
|
97
|
+
timestamp: new Date(),
|
|
98
|
+
size: content.length,
|
|
99
|
+
};
|
|
100
|
+
artifacts.push(artifact);
|
|
110
101
|
}
|
|
111
102
|
|
|
112
|
-
console.log('Detected artifacts:', artifacts.length);
|
|
113
103
|
return artifacts;
|
|
114
104
|
}, []); // Remove message dependency
|
|
115
105
|
|
|
@@ -118,7 +108,7 @@ const ArtifactHandler: React.FC<ArtifactHandlerProps> = ({
|
|
|
118
108
|
return detectArtifacts(messageText, message.fromUser || false);
|
|
119
109
|
}, [messageText, message.fromUser, detectArtifacts]);
|
|
120
110
|
|
|
121
|
-
// Auto-open
|
|
111
|
+
// Auto-open first artifact when detected in new messages
|
|
122
112
|
// Only run when messageId changes (actual new message), not on every render
|
|
123
113
|
useEffect(() => {
|
|
124
114
|
if (messageText.length > 0 && artifacts.length > 0) {
|
|
@@ -127,68 +117,92 @@ const ArtifactHandler: React.FC<ArtifactHandlerProps> = ({
|
|
|
127
117
|
dispatchArtifactCreatedEvent(artifact);
|
|
128
118
|
});
|
|
129
119
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
setCurrentArtifact(artifacts[0]);
|
|
133
|
-
} else {
|
|
120
|
+
// Only auto-open the first artifact
|
|
121
|
+
if (!isChatlogPanel) {
|
|
134
122
|
setTimeout(() => {
|
|
135
123
|
openArtifact(artifacts[0]);
|
|
136
|
-
setCurrentArtifact(artifacts[0]);
|
|
137
124
|
}, 100);
|
|
138
125
|
}
|
|
139
126
|
}
|
|
140
127
|
}, [messageId, artifacts, dispatchArtifactCreatedEvent, isChatlogPanel, openArtifact]);
|
|
141
128
|
|
|
129
|
+
const handleArtifactClick = useCallback((artifact: ArtifactData) => {
|
|
130
|
+
if (
|
|
131
|
+
state.isDrawerOpen &&
|
|
132
|
+
state.currentArtifact?.id === artifact.id
|
|
133
|
+
) {
|
|
134
|
+
closeArtifact();
|
|
135
|
+
} else {
|
|
136
|
+
openArtifact(artifact);
|
|
137
|
+
}
|
|
138
|
+
}, [state.isDrawerOpen, state.currentArtifact?.id, closeArtifact, openArtifact]);
|
|
139
|
+
|
|
140
|
+
const getIconForMimeType = useCallback((mimeType: string): string => {
|
|
141
|
+
if (mimeType.includes('html')) return '🌐';
|
|
142
|
+
if (mimeType.includes('markdown')) return '📝';
|
|
143
|
+
if (mimeType.includes('javascript') || mimeType.includes('typescript')) return '📜';
|
|
144
|
+
if (mimeType.includes('python')) return '🐍';
|
|
145
|
+
if (mimeType.includes('json')) return '📊';
|
|
146
|
+
if (mimeType.includes('css')) return '🎨';
|
|
147
|
+
if (mimeType.includes('xml')) return '📋';
|
|
148
|
+
if (mimeType.includes('svg')) return '🖼️';
|
|
149
|
+
return '📄';
|
|
150
|
+
}, []);
|
|
151
|
+
|
|
142
152
|
if (artifacts.length === 0) return null;
|
|
143
153
|
|
|
144
154
|
return (
|
|
145
155
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
156
|
+
{artifacts.map((artifact) => {
|
|
157
|
+
const isSelected = state.isDrawerOpen && state.currentArtifact?.id === artifact.id;
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<React.Fragment key={artifact.id}>
|
|
161
|
+
<div
|
|
162
|
+
className={`memori-artifact-handler${isSelected ? ' memori-artifact-handler--selected' : ''}`}
|
|
163
|
+
onClick={() => handleArtifactClick(artifact)}
|
|
164
|
+
style={isSelected ? {
|
|
165
|
+
border: '2px solid var(--memori-primary, #3b82f6)',
|
|
166
|
+
boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.1)',
|
|
167
|
+
} : undefined}
|
|
168
|
+
>
|
|
169
|
+
<div className="memori-artifact-handler-icon">
|
|
170
|
+
{getIconForMimeType(artifact.mimeType)}
|
|
171
|
+
</div>
|
|
172
|
+
<div className="memori-artifact-handler-info">
|
|
173
|
+
<div className="memori-artifact-handler-title">
|
|
174
|
+
{artifact.title}
|
|
175
|
+
</div>
|
|
176
|
+
<div className="memori-artifact-handler-meta">
|
|
177
|
+
{artifact.mimeType} •{' '}
|
|
178
|
+
{formatBytes(artifact.size || 0)}
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
<div className="memori-artifact-handler-action">
|
|
182
|
+
{isChatlogPanel ? (
|
|
183
|
+
state.isDrawerOpen &&
|
|
184
|
+
state.currentArtifact?.id === artifact.id ? (
|
|
185
|
+
<ChevronUp className="memori-artifact-handler-action-icon" />
|
|
186
|
+
) : (
|
|
187
|
+
<ChevronDown className="memori-artifact-handler-action-icon" />
|
|
188
|
+
)
|
|
189
|
+
) : state.isDrawerOpen &&
|
|
190
|
+
state.currentArtifact?.id === artifact.id ? (
|
|
191
|
+
<ChevronLeft className="memori-artifact-handler-action-icon" />
|
|
192
|
+
) : (
|
|
193
|
+
<ChevronRight className="memori-artifact-handler-action-icon" />
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
164
196
|
</div>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
<ChevronUp className="memori-artifact-handler-action-icon" />
|
|
175
|
-
) : (
|
|
176
|
-
<ChevronDown className="memori-artifact-handler-action-icon" />
|
|
177
|
-
)
|
|
178
|
-
) : state.isDrawerOpen &&
|
|
179
|
-
state.currentArtifact?.id === currentArtifact?.id ? (
|
|
180
|
-
<ChevronLeft className="memori-artifact-handler-action-icon" />
|
|
181
|
-
) : (
|
|
182
|
-
<ChevronRight className="memori-artifact-handler-action-icon" />
|
|
183
|
-
)}
|
|
184
|
-
</div>
|
|
185
|
-
</div>
|
|
186
|
-
|
|
187
|
-
{/* Render ArtifactDrawer inline when in chatlog panel */}
|
|
188
|
-
{state.isDrawerOpen &&
|
|
189
|
-
state.currentArtifact?.id === currentArtifact?.id && (
|
|
190
|
-
<ArtifactDrawer isChatLogPanel={isChatlogPanel} />
|
|
191
|
-
)}
|
|
197
|
+
|
|
198
|
+
{/* Render ArtifactDrawer inline when in chatlog panel */}
|
|
199
|
+
{state.isDrawerOpen &&
|
|
200
|
+
state.currentArtifact?.id === artifact.id && (
|
|
201
|
+
<ArtifactDrawer isChatLogPanel={isChatlogPanel} />
|
|
202
|
+
)}
|
|
203
|
+
</React.Fragment>
|
|
204
|
+
);
|
|
205
|
+
})}
|
|
192
206
|
</div>
|
|
193
207
|
);
|
|
194
208
|
};
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { ArtifactProvider } from '../context/ArtifactContext';
|
|
4
|
+
import { ArtifactAPIBridge } from './ArtifactAPI';
|
|
5
|
+
import { ArtifactData } from '../types/artifact.types';
|
|
6
|
+
import MemoriWidget from '../../MemoriWidget/MemoriWidget';
|
|
7
|
+
import { Memori } from '@memori.ai/memori-api-client/dist/types';
|
|
8
|
+
import { VisemeProvider } from '../../../context/visemeContext';
|
|
9
|
+
|
|
10
|
+
const mockMemori: Memori = {
|
|
11
|
+
memoriID: 'test-memori-id',
|
|
12
|
+
name: 'Test Memori',
|
|
13
|
+
culture: 'en-US',
|
|
14
|
+
coverURL: '',
|
|
15
|
+
enableBoardOfExperts: false,
|
|
16
|
+
} as Memori;
|
|
17
|
+
|
|
18
|
+
// Helper function to wrap component with all required providers
|
|
19
|
+
const renderWithProviders = (component: React.ReactElement) => {
|
|
20
|
+
return render(
|
|
21
|
+
<VisemeProvider>
|
|
22
|
+
<ArtifactProvider>
|
|
23
|
+
{component}
|
|
24
|
+
</ArtifactProvider>
|
|
25
|
+
</VisemeProvider>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe('ArtifactAPIBridge', () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
// Clean up any previous API instance
|
|
32
|
+
if (window.MemoriArtifactAPI) {
|
|
33
|
+
delete window.MemoriArtifactAPI;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Mock window.matchMedia
|
|
37
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
38
|
+
writable: true,
|
|
39
|
+
value: jest.fn().mockImplementation(query => ({
|
|
40
|
+
matches: false,
|
|
41
|
+
media: query,
|
|
42
|
+
onchange: null,
|
|
43
|
+
addListener: jest.fn(), // deprecated
|
|
44
|
+
removeListener: jest.fn(), // deprecated
|
|
45
|
+
addEventListener: jest.fn(),
|
|
46
|
+
removeEventListener: jest.fn(),
|
|
47
|
+
dispatchEvent: jest.fn(),
|
|
48
|
+
})),
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
// Clean up after each test
|
|
54
|
+
if (window.MemoriArtifactAPI) {
|
|
55
|
+
delete window.MemoriArtifactAPI;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should expose MemoriArtifactAPI on window', () => {
|
|
60
|
+
renderWithProviders(
|
|
61
|
+
<MemoriWidget memori={mockMemori} tenantID="test-tenant-id" />
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(window.MemoriArtifactAPI).toBeDefined();
|
|
65
|
+
expect(typeof window.MemoriArtifactAPI?.createAndOpenArtifact).toBe('function');
|
|
66
|
+
expect(typeof window.MemoriArtifactAPI?.openArtifact).toBe('function');
|
|
67
|
+
expect(typeof window.MemoriArtifactAPI?.closeArtifact).toBe('function');
|
|
68
|
+
expect(typeof window.MemoriArtifactAPI?.toggleFullscreen).toBe('function');
|
|
69
|
+
expect(typeof window.MemoriArtifactAPI?.getState).toBe('function');
|
|
70
|
+
expect(typeof window.MemoriArtifactAPI?.createFromOutputElement).toBe('function');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should create and open artifact with simple parameters', async () => {
|
|
74
|
+
renderWithProviders(
|
|
75
|
+
<MemoriWidget memori={mockMemori} tenantID="test-tenant-id" />
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const testContent = '<h1>Test Content</h1><p>This is a test</p>';
|
|
79
|
+
const testTitle = 'Test Artifact';
|
|
80
|
+
|
|
81
|
+
window.MemoriArtifactAPI!.createAndOpenArtifact(
|
|
82
|
+
testContent,
|
|
83
|
+
'html',
|
|
84
|
+
testTitle
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
await waitFor(() => {
|
|
88
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
89
|
+
expect(state.isDrawerOpen).toBe(true);
|
|
90
|
+
expect(state.currentArtifact?.title).toBe(testTitle);
|
|
91
|
+
expect(state.currentArtifact?.content).toBe(testContent);
|
|
92
|
+
expect(state.currentArtifact?.mimeType).toBe('html');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should generate default title from mime type when not provided', async () => {
|
|
97
|
+
renderWithProviders(
|
|
98
|
+
<MemoriWidget memori={mockMemori} tenantID="test-tenant-id" />
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
window.MemoriArtifactAPI!.createAndOpenArtifact(
|
|
102
|
+
'console.log("test")',
|
|
103
|
+
'javascript'
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
await waitFor(() => {
|
|
107
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
108
|
+
expect(state.currentArtifact?.title).toBe('JavaScript Code');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should open artifact with full ArtifactData object', async () => {
|
|
113
|
+
renderWithProviders(
|
|
114
|
+
<MemoriWidget memori={mockMemori} tenantID="test-tenant-id" />
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const artifact: ArtifactData = {
|
|
118
|
+
id: 'test-artifact-123',
|
|
119
|
+
content: '<div>Full artifact</div>',
|
|
120
|
+
mimeType: 'html',
|
|
121
|
+
title: 'Full Artifact Test',
|
|
122
|
+
timestamp: new Date(),
|
|
123
|
+
size: 100,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
window.MemoriArtifactAPI!.openArtifact(artifact);
|
|
127
|
+
|
|
128
|
+
await waitFor(() => {
|
|
129
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
130
|
+
expect(state.isDrawerOpen).toBe(true);
|
|
131
|
+
expect(state.currentArtifact?.id).toBe('test-artifact-123');
|
|
132
|
+
expect(state.currentArtifact?.title).toBe('Full Artifact Test');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should close artifact', async () => {
|
|
137
|
+
renderWithProviders(
|
|
138
|
+
<MemoriWidget memori={mockMemori} tenantID="test-tenant-id" />
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// First open an artifact
|
|
142
|
+
window.MemoriArtifactAPI!.createAndOpenArtifact(
|
|
143
|
+
'<h1>Test</h1>',
|
|
144
|
+
'html',
|
|
145
|
+
'Test'
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
await waitFor(() => {
|
|
149
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
150
|
+
expect(state.isDrawerOpen).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Then close it
|
|
154
|
+
window.MemoriArtifactAPI!.closeArtifact();
|
|
155
|
+
|
|
156
|
+
await waitFor(() => {
|
|
157
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
158
|
+
expect(state.isDrawerOpen).toBe(false);
|
|
159
|
+
expect(state.currentArtifact).toBeNull();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should toggle fullscreen', async () => {
|
|
164
|
+
renderWithProviders(
|
|
165
|
+
<MemoriWidget memori={mockMemori} tenantID="test-tenant-id" />
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Open an artifact first
|
|
169
|
+
window.MemoriArtifactAPI!.createAndOpenArtifact(
|
|
170
|
+
'<h1>Test</h1>',
|
|
171
|
+
'html',
|
|
172
|
+
'Test'
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
await waitFor(() => {
|
|
176
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
177
|
+
expect(state.isFullscreen).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Toggle fullscreen
|
|
181
|
+
window.MemoriArtifactAPI!.toggleFullscreen();
|
|
182
|
+
|
|
183
|
+
await waitFor(() => {
|
|
184
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
185
|
+
expect(state.isFullscreen).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Toggle back
|
|
189
|
+
window.MemoriArtifactAPI!.toggleFullscreen();
|
|
190
|
+
|
|
191
|
+
await waitFor(() => {
|
|
192
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
193
|
+
expect(state.isFullscreen).toBe(false);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should create artifact from output element', async () => {
|
|
198
|
+
renderWithProviders(
|
|
199
|
+
<MemoriWidget memori={mockMemori} tenantID="test-tenant-id" />
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Create a mock output element
|
|
203
|
+
const outputElement = document.createElement('output');
|
|
204
|
+
outputElement.className = 'memori-artifact';
|
|
205
|
+
outputElement.innerHTML = '<div>Content from output</div>';
|
|
206
|
+
outputElement.setAttribute('data-mimetype', 'html');
|
|
207
|
+
outputElement.setAttribute('data-title', 'Output Artifact');
|
|
208
|
+
document.body.appendChild(outputElement);
|
|
209
|
+
|
|
210
|
+
const artifactId = window.MemoriArtifactAPI!.createFromOutputElement(outputElement);
|
|
211
|
+
|
|
212
|
+
expect(artifactId).toBeDefined();
|
|
213
|
+
expect(outputElement.style.display).toBe('none');
|
|
214
|
+
expect(outputElement.getAttribute('data-memori-processed')).toBe('true');
|
|
215
|
+
|
|
216
|
+
await waitFor(() => {
|
|
217
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
218
|
+
expect(state.isDrawerOpen).toBe(true);
|
|
219
|
+
expect(state.currentArtifact?.content).toBe('<div>Content from output</div>');
|
|
220
|
+
expect(state.currentArtifact?.title).toBe('Output Artifact');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Clean up
|
|
224
|
+
document.body.removeChild(outputElement);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should handle multiple artifacts in the same message', async () => {
|
|
228
|
+
renderWithProviders(
|
|
229
|
+
<MemoriWidget memori={mockMemori} tenantID="test-tenant-id" />
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Create a message with multiple output elements
|
|
233
|
+
const messageContent = `
|
|
234
|
+
<output class="memori-artifact" data-mimetype="html" data-title="Artifact 1">
|
|
235
|
+
<div>Content 1</div>
|
|
236
|
+
</output>
|
|
237
|
+
<output class="memori-artifact" data-mimetype="javascript" data-title="Artifact 2">
|
|
238
|
+
console.log("test");
|
|
239
|
+
</output>
|
|
240
|
+
`;
|
|
241
|
+
|
|
242
|
+
window.MemoriArtifactAPI!.createAndOpenArtifact(messageContent, 'html');
|
|
243
|
+
|
|
244
|
+
await waitFor(() => {
|
|
245
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
246
|
+
// The first artifact should be opened
|
|
247
|
+
expect(state.isDrawerOpen).toBe(true);
|
|
248
|
+
expect(state.currentArtifact).toBeDefined();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should return initial state correctly', () => {
|
|
253
|
+
renderWithProviders(
|
|
254
|
+
<MemoriWidget memori={mockMemori} tenantID="test-tenant-id" />
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
258
|
+
|
|
259
|
+
expect(state.currentArtifact).toBeNull();
|
|
260
|
+
expect(state.isDrawerOpen).toBe(false);
|
|
261
|
+
expect(state.isFullscreen).toBe(false);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should clean up API on unmount', () => {
|
|
265
|
+
const { unmount } = renderWithProviders(
|
|
266
|
+
<MemoriWidget memori={mockMemori} tenantID="test-tenant-id" />
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
expect(window.MemoriArtifactAPI).toBeDefined();
|
|
270
|
+
|
|
271
|
+
unmount();
|
|
272
|
+
|
|
273
|
+
expect(window.MemoriArtifactAPI).toBeUndefined();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should handle different mime types with correct titles', async () => {
|
|
277
|
+
renderWithProviders(
|
|
278
|
+
<MemoriWidget memori={mockMemori} tenantID="test-tenant-id" />
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const testCases = [
|
|
282
|
+
{ mimeType: 'html', expectedTitle: 'HTML Document' },
|
|
283
|
+
{ mimeType: 'markdown', expectedTitle: 'Markdown Document' },
|
|
284
|
+
{ mimeType: 'javascript', expectedTitle: 'JavaScript Code' },
|
|
285
|
+
{ mimeType: 'python', expectedTitle: 'Python Code' },
|
|
286
|
+
{ mimeType: 'json', expectedTitle: 'JSON Data' },
|
|
287
|
+
{ mimeType: 'css', expectedTitle: 'CSS Stylesheet' },
|
|
288
|
+
{ mimeType: 'typescript', expectedTitle: 'TypeScript Code' },
|
|
289
|
+
{ mimeType: 'xml', expectedTitle: 'XML Document' },
|
|
290
|
+
{ mimeType: 'svg', expectedTitle: 'SVG Image' },
|
|
291
|
+
{ mimeType: 'unknown', expectedTitle: 'Document' },
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
for (const testCase of testCases) {
|
|
295
|
+
window.MemoriArtifactAPI!.createAndOpenArtifact(
|
|
296
|
+
'test content',
|
|
297
|
+
testCase.mimeType
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
await waitFor(() => {
|
|
301
|
+
const state = window.MemoriArtifactAPI!.getState();
|
|
302
|
+
expect(state.currentArtifact?.title).toBe(testCase.expectedTitle);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|