@memori.ai/memori-react 7.16.2 → 7.17.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 (73) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/components/Avatar/AvatarView/AvatarComponent/lights/Lights.d.ts +27 -0
  3. package/dist/components/Avatar/AvatarView/AvatarComponent/lights/Lights.js +52 -0
  4. package/dist/components/Avatar/AvatarView/AvatarComponent/lights/Lights.js.map +1 -0
  5. package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +19 -7
  6. package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js +7 -7
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js.map +1 -1
  8. package/dist/components/Avatar/AvatarView/index.js +2 -3
  9. package/dist/components/Avatar/AvatarView/index.js.map +1 -1
  10. package/dist/components/ChatTextArea/ChatTextArea.css +55 -60
  11. package/dist/components/MemoriWidget/MemoriWidget.js +215 -138
  12. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  13. package/dist/components/SettingsDrawer/SettingsDrawer.css +5 -0
  14. package/dist/components/SettingsDrawer/SettingsDrawer.d.ts +2 -1
  15. package/dist/components/SettingsDrawer/SettingsDrawer.js +6 -3
  16. package/dist/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  17. package/dist/components/UploadButton/UploadButton.d.ts +5 -0
  18. package/dist/components/UploadButton/UploadButton.js +49 -48
  19. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  20. package/dist/components/ui/Slider.css +59 -44
  21. package/dist/context/visemeContext.d.ts +1 -1
  22. package/dist/context/visemeContext.js +2 -2
  23. package/dist/context/visemeContext.js.map +1 -1
  24. package/dist/locales/de.json +1 -0
  25. package/dist/locales/en.json +1 -0
  26. package/dist/locales/es.json +1 -0
  27. package/dist/locales/fr.json +1 -0
  28. package/dist/locales/it.json +1 -0
  29. package/esm/components/Avatar/AvatarView/AvatarComponent/lights/Lights.d.ts +27 -0
  30. package/esm/components/Avatar/AvatarView/AvatarComponent/lights/Lights.js +48 -0
  31. package/esm/components/Avatar/AvatarView/AvatarComponent/lights/Lights.js.map +1 -0
  32. package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +19 -7
  33. package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js +7 -7
  34. package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.js.map +1 -1
  35. package/esm/components/Avatar/AvatarView/index.js +3 -4
  36. package/esm/components/Avatar/AvatarView/index.js.map +1 -1
  37. package/esm/components/ChatTextArea/ChatTextArea.css +55 -60
  38. package/esm/components/MemoriWidget/MemoriWidget.js +216 -139
  39. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  40. package/esm/components/SettingsDrawer/SettingsDrawer.css +5 -0
  41. package/esm/components/SettingsDrawer/SettingsDrawer.d.ts +2 -1
  42. package/esm/components/SettingsDrawer/SettingsDrawer.js +6 -3
  43. package/esm/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  44. package/esm/components/UploadButton/UploadButton.d.ts +5 -0
  45. package/esm/components/UploadButton/UploadButton.js +50 -49
  46. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  47. package/esm/components/ui/Slider.css +59 -44
  48. package/esm/context/visemeContext.d.ts +1 -1
  49. package/esm/context/visemeContext.js +2 -2
  50. package/esm/context/visemeContext.js.map +1 -1
  51. package/esm/locales/de.json +1 -0
  52. package/esm/locales/en.json +1 -0
  53. package/esm/locales/es.json +1 -0
  54. package/esm/locales/fr.json +1 -0
  55. package/esm/locales/it.json +1 -0
  56. package/package.json +1 -2
  57. package/src/components/Avatar/AvatarView/AvatarComponent/lights/Lights.tsx +145 -0
  58. package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +19 -7
  59. package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.tsx +6 -14
  60. package/src/components/Avatar/AvatarView/index.tsx +5 -14
  61. package/src/components/ChatTextArea/ChatTextArea.css +55 -60
  62. package/src/components/MemoriWidget/MemoriWidget.tsx +337 -187
  63. package/src/components/SettingsDrawer/SettingsDrawer.css +5 -0
  64. package/src/components/SettingsDrawer/SettingsDrawer.tsx +29 -11
  65. package/src/components/UploadButton/UploadButton.tsx +139 -118
  66. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +3 -52
  67. package/src/components/ui/Slider.css +59 -44
  68. package/src/context/visemeContext.tsx +2 -2
  69. package/src/locales/de.json +1 -0
  70. package/src/locales/en.json +1 -0
  71. package/src/locales/es.json +1 -0
  72. package/src/locales/fr.json +1 -0
  73. package/src/locales/it.json +1 -0
@@ -17,3 +17,8 @@
17
17
  margin: 0.5rem 0;
18
18
  gap: 0.2rem;
19
19
  }
20
+
21
+ .memori-settings-drawer--microphoneMode-radio-button-disabled {
22
+ cursor: not-allowed;
23
+ opacity: 0.5;
24
+ }
@@ -8,6 +8,7 @@ import Button from '../ui/Button';
8
8
  import { Props as WidgetProps } from '../MemoriWidget/MemoriWidget';
9
9
  import { useState } from 'react';
10
10
  import Slider from '../ui/Slider';
11
+ import Tooltip from '../ui/Tooltip';
11
12
  export interface Props {
12
13
  open: boolean;
13
14
  layout?: WidgetProps['layout'];
@@ -26,6 +27,7 @@ export interface Props {
26
27
  enablePositionControls?: boolean;
27
28
  setEnablePositionControls: (value: boolean) => void;
28
29
  isAvatar3d?: boolean;
30
+ speakerMuted?: boolean;
29
31
  }
30
32
 
31
33
  const silenceSeconds = [2, 3, 5, 10, 15, 20, 30, 60];
@@ -34,6 +36,7 @@ const SettingsDrawer = ({
34
36
  open,
35
37
  layout = 'DEFAULT',
36
38
  onClose,
39
+ speakerMuted,
37
40
  microphoneMode = 'HOLD_TO_TALK',
38
41
  continuousSpeechTimeout,
39
42
  setMicrophoneMode,
@@ -87,17 +90,33 @@ const SettingsDrawer = ({
87
90
  </Button>
88
91
  )}
89
92
  </RadioGroup.Option>
90
- <RadioGroup.Option
91
- value="CONTINUOUS"
92
- className="memori-settings-drawer--microphoneMode-radio-button"
93
+
94
+ <Tooltip
95
+ content={
96
+ speakerMuted ? t('write_and_speak.continuousSpeechDisabled') : ''
97
+ }
98
+ disabled={!speakerMuted}
93
99
  >
94
- {({ checked }) => (
95
- <Button primary={checked} outlined={!checked}>
96
- {t('write_and_speak.continuousSpeechLabel') ||
97
- 'Continuous speech'}
98
- </Button>
99
- )}
100
- </RadioGroup.Option>
100
+ <RadioGroup.Option
101
+ value="CONTINUOUS"
102
+ className={`memori-settings-drawer--microphoneMode-radio-button ${
103
+ speakerMuted
104
+ ? 'memori-settings-drawer--microphoneMode-radio-button-disabled'
105
+ : ''
106
+ }`}
107
+ >
108
+ {({ checked }) => (
109
+ <Button
110
+ primary={checked}
111
+ outlined={!checked}
112
+ disabled={speakerMuted}
113
+ >
114
+ {t('write_and_speak.continuousSpeechLabel') ||
115
+ 'Continuous speech'}
116
+ </Button>
117
+ )}
118
+ </RadioGroup.Option>
119
+ </Tooltip>
101
120
  </RadioGroup>
102
121
  </div>
103
122
 
@@ -159,7 +178,6 @@ const SettingsDrawer = ({
159
178
  {isAvatar3d && (
160
179
  <>
161
180
  <div className="memori-settings-drawer--field controls">
162
-
163
181
  <label
164
182
  htmlFor="avatarType"
165
183
  className="memori-settings-drawer-label"
@@ -1,6 +1,5 @@
1
- import React, { useState, useRef, useEffect } from 'react';
1
+ import React, { useState, useRef } from 'react';
2
2
  import cx from 'classnames';
3
- import ConvertApi from 'convertapi-js';
4
3
  import UploadIcon from '../icons/Upload';
5
4
  import Spin from '../ui/Spin';
6
5
  import Alert from '../ui/Alert';
@@ -14,13 +13,22 @@ type UploadError = {
14
13
 
15
14
  /**
16
15
  * FileUploadButton component allows users to upload and convert files to text
17
- * Supports PDF, DOC, DOCX and TXT files up to 10MB
18
- * Converts files to text using ConvertAPI service
16
+ * Supports PDF and TXT files up to 10MB
17
+ * Extracts text from PDFs using PDF.js
19
18
  */
20
19
 
21
20
  const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
22
21
  const MAX_TEXT_LENGTH = 100000; // 100,000 characters
23
- const ALLOWED_FILE_TYPES = ['.pdf', '.doc', '.docx', '.txt'];
22
+ const PDF_JS_VERSION = '3.11.174'; // Last stable version with .min.js files
23
+ const WORKER_URL = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDF_JS_VERSION}/pdf.worker.min.js`;
24
+ const PDF_JS_URL = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDF_JS_VERSION}/pdf.min.js`;
25
+
26
+ // Add type definition for pdfjsLib
27
+ declare global {
28
+ interface Window {
29
+ pdfjsLib: any;
30
+ }
31
+ }
24
32
 
25
33
  const FileUploadButton = ({
26
34
  setPreviewFiles,
@@ -31,21 +39,19 @@ const FileUploadButton = ({
31
39
  }) => {
32
40
  // State for loading indicator
33
41
  const [isLoading, setIsLoading] = useState(false);
34
- // State for tracking upload errors
42
+ // State for tracking upload errors
35
43
  const [errors, setErrors] = useState<UploadError[]>([]);
36
44
  // Reference to hidden file input
37
45
  const fileInputRef = useRef<HTMLInputElement>(null);
38
- // State for ConvertAPI authentication token
39
- const [convertapiToken, setConvertapiToken] = useState<string>();
40
46
 
41
47
  // Clear all errors
42
48
  const clearErrors = () => setErrors([]);
43
-
49
+
44
50
  // Remove a specific error by message
45
51
  const removeError = (errorMessage: string) => {
46
52
  setErrors(prev => prev.filter(e => e.message !== errorMessage));
47
53
  };
48
-
54
+
49
55
  // Add a new error and auto-remove after 5 seconds
50
56
  const addError = (error: UploadError) => {
51
57
  setErrors(prev => [...prev, error]);
@@ -56,30 +62,57 @@ const FileUploadButton = ({
56
62
  };
57
63
 
58
64
  /**
59
- * Fetches ConvertAPI token from backend service
60
- * Displays error if token fetch fails
65
+ * Extracts text from PDF using PDF.js
66
+ * @param file PDF file to process
67
+ * @returns Promise resolving to extracted text
61
68
  */
62
- const fetchConvertapiToken = async () => {
69
+ const extractTextFromPDF = async (file: File): Promise<string> => {
63
70
  try {
64
- const result = await fetch('https://www.aisuru.com/api/convertapi-token');
65
- const response = await result.json();
66
- if (!response.Tokens?.[0]?.Id) {
67
- throw new Error('Invalid token response');
71
+ // Load PDF.js if not already loaded
72
+ if (!window.pdfjsLib) {
73
+ await new Promise((resolve, reject) => {
74
+ const script = document.createElement('script');
75
+ script.src = PDF_JS_URL;
76
+ script.onload = () => {
77
+ // Set up worker
78
+ window.pdfjsLib.GlobalWorkerOptions.workerSrc = WORKER_URL;
79
+ resolve(true);
80
+ };
81
+ script.onerror = reject;
82
+ document.head.appendChild(script);
83
+ });
84
+ }
85
+
86
+ // Extract text from PDF
87
+ const arrayBuffer = await file.arrayBuffer();
88
+ // Get PDF document
89
+ const pdf = await window.pdfjsLib.getDocument({ data: arrayBuffer })
90
+ .promise;
91
+ let text = '';
92
+
93
+ // Iterate through each page and extract text
94
+ for (let i = 1; i <= pdf.numPages; i++) {
95
+ const page = await pdf.getPage(i);
96
+ const content = await page.getTextContent();
97
+ // Filter out non-string items and join text
98
+ const pageText = content.items
99
+ .filter((item: any) => item.str && typeof item.str === 'string')
100
+ .map((item: any) => item.str)
101
+ .join(' ');
102
+ text += pageText + '\n';
68
103
  }
69
- setConvertapiToken(response.Tokens[0].Id);
104
+
105
+ // Return extracted text
106
+ return text;
70
107
  } catch (error) {
71
- addError({
72
- message: 'Failed to initialize file conversion service. Please try again later.',
73
- severity: 'error'
74
- });
108
+ throw new Error(
109
+ `PDF extraction failed: ${
110
+ error instanceof Error ? error.message : 'Unknown error'
111
+ }`
112
+ );
75
113
  }
76
114
  };
77
115
 
78
- // Fetch token on component mount
79
- useEffect(() => {
80
- fetchConvertapiToken();
81
- }, []);
82
-
83
116
  /**
84
117
  * Validates uploaded file
85
118
  * Checks file type and size restrictions
@@ -88,21 +121,26 @@ const FileUploadButton = ({
88
121
  */
89
122
  const validateFile = (file: File): boolean => {
90
123
  const fileExt = `.${file.name.split('.').pop()?.toLowerCase()}`;
124
+ const ALLOWED_FILE_TYPES = ['.pdf', '.txt'];
91
125
 
92
126
  if (!ALLOWED_FILE_TYPES.includes(fileExt)) {
93
127
  addError({
94
- message: `File type "${fileExt}" is not supported. Please use: ${ALLOWED_FILE_TYPES.join(', ')}`,
128
+ message: `File type "${fileExt}" is not supported. Please use: ${ALLOWED_FILE_TYPES.join(
129
+ ', '
130
+ )}`,
95
131
  severity: 'error',
96
- fileId: file.name
132
+ fileId: file.name,
97
133
  });
98
134
  return false;
99
135
  }
100
136
 
101
137
  if (file.size > MAX_FILE_SIZE) {
102
138
  addError({
103
- message: `File "${file.name}" exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit`,
139
+ message: `File "${file.name}" exceeds ${
140
+ MAX_FILE_SIZE / 1024 / 1024
141
+ }MB limit`,
104
142
  severity: 'error',
105
- fileId: file.name
143
+ fileId: file.name,
106
144
  });
107
145
  return false;
108
146
  }
@@ -111,46 +149,28 @@ const FileUploadButton = ({
111
149
  };
112
150
 
113
151
  /**
114
- * Converts uploaded file to text using ConvertAPI
115
- * @param file File to convert
116
- * @returns Promise resolving to converted text or null if conversion fails
152
+ * Processes file to extract text content
153
+ * @param file File to process
154
+ * @returns Promise resolving to extracted text or null if processing fails
117
155
  */
118
- const convertToTxt = async (file: File): Promise<string | null> => {
119
- if (!convertapiToken) {
120
- addError({
121
- message: 'File conversion service not initialized',
122
- severity: 'error'
123
- });
124
- return null;
125
- }
126
-
127
- const fileExt = file.name.split('.').pop()?.toLowerCase() || 'pdf';
156
+ const processFile = async (file: File): Promise<string | null> => {
157
+ const fileExt = file.name.split('.').pop()?.toLowerCase() || '';
128
158
 
129
159
  try {
130
- // Initialize ConvertAPI with token
131
- const convertApi = ConvertApi.auth(convertapiToken);
132
- const params = convertApi.createParams();
133
- params.add('File', file);
134
- params.add('TextEncoding', 'UTF-8');
135
- params.add('PageRange', '1-2000');
136
-
137
- // Convert file to text
138
- const result = await convertApi.convert(fileExt, 'txt', params);
139
- const fileUrl = result.files[0].Url;
140
-
141
- // Fetch converted text content
142
- const response = await fetch(fileUrl);
143
- if (!response.ok) {
144
- throw new Error(`HTTP error! status: ${response.status}`);
160
+ let text: string | null = null;
161
+
162
+ if (fileExt === 'pdf') {
163
+ text = await extractTextFromPDF(file);
164
+ } else if (fileExt === 'txt') {
165
+ text = await file.text();
145
166
  }
146
- const text = await response.text();
147
167
 
148
168
  // Check text length limit
149
- if (text.length > MAX_TEXT_LENGTH) {
169
+ if (text && text.length > MAX_TEXT_LENGTH) {
150
170
  addError({
151
171
  message: `File "${file.name}" content exceeds ${MAX_TEXT_LENGTH} characters`,
152
172
  severity: 'error',
153
- fileId: file.name
173
+ fileId: file.name,
154
174
  });
155
175
  return null;
156
176
  }
@@ -158,9 +178,11 @@ const FileUploadButton = ({
158
178
  return text;
159
179
  } catch (error) {
160
180
  addError({
161
- message: `Failed to convert "${file.name}": ${error instanceof Error ? error.message : 'Unknown error'}`,
181
+ message: `Failed to process "${file.name}": ${
182
+ error instanceof Error ? error.message : 'Unknown error'
183
+ }`,
162
184
  severity: 'error',
163
- fileId: file.name
185
+ fileId: file.name,
164
186
  });
165
187
  return null;
166
188
  }
@@ -168,8 +190,8 @@ const FileUploadButton = ({
168
190
 
169
191
  /**
170
192
  * Handles file selection event
171
- * Validates files and converts them to text
172
- * Updates preview files state with converted content
193
+ * Validates files and processes them to extract text
194
+ * Updates preview files state with processed content
173
195
  */
174
196
  const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
175
197
  const files = Array.from(e.target.files || []);
@@ -177,7 +199,7 @@ const FileUploadButton = ({
177
199
 
178
200
  setIsLoading(true);
179
201
  clearErrors();
180
-
202
+
181
203
  const newPreviewFiles: { name: string; id: string; content: string }[] = [];
182
204
 
183
205
  // Process each selected file
@@ -185,24 +207,24 @@ const FileUploadButton = ({
185
207
  if (!validateFile(file)) continue;
186
208
 
187
209
  const fileId = Math.random().toString(36).substr(2, 9);
188
- const text = await convertToTxt(file);
210
+ const text = await processFile(file);
189
211
 
190
212
  if (text) {
191
213
  newPreviewFiles.push({
192
214
  name: file.name,
193
215
  id: fileId,
194
- content: text
216
+ content: text,
195
217
  });
196
218
  }
197
219
  }
198
220
 
199
- // Update preview files if any conversions succeeded
221
+ // Update preview files if any processing succeeded
200
222
  if (newPreviewFiles.length > 0) {
201
223
  setPreviewFiles(newPreviewFiles);
202
224
  if (newPreviewFiles.length < files.length) {
203
225
  addError({
204
226
  message: 'Some files were not processed successfully',
205
- severity: 'warning'
227
+ severity: 'warning',
206
228
  });
207
229
  }
208
230
  }
@@ -213,56 +235,55 @@ const FileUploadButton = ({
213
235
  }
214
236
  };
215
237
 
216
- console.log(errors);
217
-
218
238
  return (
219
239
  <div className="relative file-upload-wrapper">
220
- {/* Hidden file input triggered by button click */}
221
- <input
222
- ref={fileInputRef}
223
- type="file"
224
- accept=".pdf,.doc,.docx,.txt"
225
- className="memori--upload-file-input"
226
- onChange={handleFileSelect}
227
- multiple
228
- />
229
-
230
- {/* Upload button with loading state */}
231
- <button
232
- className={cx(
233
- 'memori-button',
234
- 'memori-button--circle',
235
- 'memori-button--icon-only',
236
- 'memori-share-button--button',
237
- 'memori--conversation-button',
238
- { 'memori--error': errors.length > 0 }
239
- )}
240
- onClick={() => fileInputRef.current?.click()}
241
- disabled={isLoading}
242
- title="Upload file"
243
- >
244
- {isLoading ? (
245
- <Spin spinning className="memori--upload-icon" />
246
- ) : (
247
- <UploadIcon className="memori--upload-icon" />
248
- )}
249
- </button>
250
-
251
- {/* Error messages container */}
252
- <div className="memori--error-message-container">
253
- {errors.map((error, index) => (
254
- <Alert
255
- key={`${error.message}-${index}`}
256
- open={true}
257
- type={error.severity}
258
- title={error.message}
259
- onClose={() => removeError(error.message)}
260
- width="300px"
261
- />
262
- ))}
240
+ {/* Hidden file input triggered by button click */}
241
+ <input
242
+ ref={fileInputRef}
243
+ type="file"
244
+ accept=".pdf,.txt"
245
+ className="memori--upload-file-input"
246
+ onChange={handleFileSelect}
247
+ multiple
248
+ />
249
+
250
+ {/* Upload button with loading state */}
251
+ <button
252
+ className={cx(
253
+ 'memori-button',
254
+ 'memori-button--circle',
255
+ 'memori-button--icon-only',
256
+ 'memori-share-button--button',
257
+ 'memori--conversation-button',
258
+ { 'memori--error': errors.length > 0 }
259
+ )}
260
+ onClick={() => fileInputRef.current?.click()}
261
+ disabled={isLoading}
262
+ title="Upload file"
263
+ >
264
+ {isLoading ? (
265
+ <Spin spinning className="memori--upload-icon" />
266
+ ) : (
267
+ <UploadIcon className="memori--upload-icon" />
268
+ )}
269
+ </button>
270
+
271
+ {/* Error messages container */}
272
+ <div className="memori--error-message-container">
273
+ {errors.map((error, index) => (
274
+ <Alert
275
+ key={`${error.message}-${index}`}
276
+ open={true}
277
+ type={error.severity}
278
+ title={'File upload failed'}
279
+ description={error.message}
280
+ onClose={() => removeError(error.message)}
281
+ width="350px"
282
+ />
283
+ ))}
284
+ </div>
263
285
  </div>
264
- </div>
265
286
  );
266
287
  };
267
288
 
268
- export default FileUploadButton;
289
+ export default FileUploadButton;
@@ -6,13 +6,13 @@ exports[`renders UploadButton unchanged 1`] = `
6
6
  class="relative file-upload-wrapper"
7
7
  >
8
8
  <input
9
- accept=".pdf,.doc,.docx,.txt"
9
+ accept=".pdf,.txt"
10
10
  class="memori--upload-file-input"
11
11
  multiple=""
12
12
  type="file"
13
13
  />
14
14
  <button
15
- class="memori-button memori-button--circle memori-button--icon-only memori-share-button--button memori--conversation-button memori--error"
15
+ class="memori-button memori-button--circle memori-button--icon-only memori-share-button--button memori--conversation-button"
16
16
  title="Upload file"
17
17
  >
18
18
  <svg
@@ -30,56 +30,7 @@ exports[`renders UploadButton unchanged 1`] = `
30
30
  </button>
31
31
  <div
32
32
  class="memori--error-message-container"
33
- >
34
- <div
35
- class="memori-alert memori-alert--error"
36
- >
37
- <style>
38
-
39
- .memori-alert {
40
- --memori-alert--width: 300px;
41
- }
42
-
43
- </style>
44
- <div
45
- class="memori-alert--container ease-out duration-300 opacity-0 translate-y-4"
46
- >
47
- <div
48
- class="memori-alert--content"
49
- >
50
- <div
51
- class="memori-alert--title"
52
- >
53
- Failed to initialize file conversion service. Please try again later.
54
- </div>
55
- </div>
56
- <div
57
- class="memori-alert--actions"
58
- >
59
- <button
60
- class="memori-button memori-button--ghost memori-button--circle memori-button--padded memori-button--icon-only memori-alert--close"
61
- title="close"
62
- >
63
- <span
64
- class="memori-button--icon"
65
- >
66
- <svg
67
- aria-hidden="true"
68
- focusable="false"
69
- role="img"
70
- viewBox="0 0 1024 1024"
71
- xmlns="http://www.w3.org/2000/svg"
72
- >
73
- <path
74
- d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
75
- />
76
- </svg>
77
- </span>
78
- </button>
79
- </div>
80
- </div>
81
- </div>
82
- </div>
33
+ />
83
34
  </div>
84
35
  </div>
85
36
  `;