@nualang/nualang-ui-components 0.1.1173 → 0.1.1174
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.
|
@@ -78,12 +78,14 @@ function VoiceSelector(props) {
|
|
|
78
78
|
value = "",
|
|
79
79
|
onVoicePitchChange,
|
|
80
80
|
voicePitch,
|
|
81
|
+
generateAudio = false,
|
|
82
|
+
csv = false,
|
|
81
83
|
...otherProps
|
|
82
84
|
} = props;
|
|
83
85
|
const voiceAccents = Object.values(voices);
|
|
84
86
|
const voicesArray = voiceAccents.flatMap(voiceOption => voiceOption.voiceOptions);
|
|
85
87
|
const selectedVoice = (voicesArray || []).find(v => v.name === value);
|
|
86
|
-
const
|
|
88
|
+
const audioOrStorybook = generateAudio || window.location.host === "localhost:9009";
|
|
87
89
|
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_material.TextField, _extends({
|
|
88
90
|
select: true,
|
|
89
91
|
value: value
|
|
@@ -106,11 +108,11 @@ function VoiceSelector(props) {
|
|
|
106
108
|
alt: ""
|
|
107
109
|
}))), /*#__PURE__*/_react.default.createElement(_material.Grid, _extends({
|
|
108
110
|
item: true
|
|
109
|
-
},
|
|
111
|
+
}, audioOrStorybook ? {
|
|
110
112
|
sx: {
|
|
111
113
|
flexGrow: 1
|
|
112
114
|
}
|
|
113
|
-
} : {}), voiceOption.isChild && voiceOption.isChild === true ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, " ", `${voiceOption.name} (${t("child")}) `, " ") : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, voiceOption.name)),
|
|
115
|
+
} : {}), voiceOption.isChild && voiceOption.isChild === true ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, " ", `${voiceOption.name} (${t("child")}) `, " ") : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, voiceOption.name)), audioOrStorybook && /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
114
116
|
item: true,
|
|
115
117
|
sx: {
|
|
116
118
|
mr: 2,
|
|
@@ -122,7 +124,7 @@ function VoiceSelector(props) {
|
|
|
122
124
|
}) : /*#__PURE__*/_react.default.createElement("img", {
|
|
123
125
|
src: _aws.default,
|
|
124
126
|
alt: "AWS Icon"
|
|
125
|
-
}))))])])), value && /*#__PURE__*/_react.default.createElement(_material.Box, {
|
|
127
|
+
}))))])])), value && !csv && /*#__PURE__*/_react.default.createElement(_material.Box, {
|
|
126
128
|
sx: {
|
|
127
129
|
width: "100%",
|
|
128
130
|
px: 1,
|
|
@@ -14,6 +14,16 @@ var _AvatarSelector = _interopRequireDefault(require("../../Misc/AvatarSelector/
|
|
|
14
14
|
var _InputHelper = _interopRequireDefault(require("../../Forms/InputHelper/InputHelper"));
|
|
15
15
|
var _utils = require("../../utils");
|
|
16
16
|
var _AudioNuala = _interopRequireDefault(require("../../img/AudioNuala.svg"));
|
|
17
|
+
var _papaparse = _interopRequireDefault(require("papaparse"));
|
|
18
|
+
var _PlayArrow = _interopRequireDefault(require("@mui/icons-material/PlayArrow"));
|
|
19
|
+
var _jszip = _interopRequireDefault(require("jszip"));
|
|
20
|
+
var _fileSaver = require("file-saver");
|
|
21
|
+
var _lab = require("@mui/lab");
|
|
22
|
+
var _Add = _interopRequireDefault(require("@mui/icons-material/Add"));
|
|
23
|
+
var _Download = _interopRequireDefault(require("@mui/icons-material/Download"));
|
|
24
|
+
var _FileUpload = _interopRequireDefault(require("@mui/icons-material/FileUpload"));
|
|
25
|
+
var _ResponsiveDialog = _interopRequireDefault(require("../../Dialogs/ResponsiveDialog/ResponsiveDialog"));
|
|
26
|
+
var _Delete = _interopRequireDefault(require("@mui/icons-material/Delete"));
|
|
17
27
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
28
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
19
29
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
@@ -97,7 +107,8 @@ function ActorForm({
|
|
|
97
107
|
filteredVoices,
|
|
98
108
|
setFieldValue,
|
|
99
109
|
setCheckAudioUrl,
|
|
100
|
-
formik
|
|
110
|
+
formik,
|
|
111
|
+
csv = false
|
|
101
112
|
}) {
|
|
102
113
|
const [avatarSelector, setAvatarSelector] = (0, _react.useState)(false);
|
|
103
114
|
const handleAvatarSelection = value => {
|
|
@@ -149,7 +160,9 @@ function ActorForm({
|
|
|
149
160
|
helperText: touched?.actor?.voice && errors?.actor?.voice,
|
|
150
161
|
error: touched?.actor?.voice && Boolean(errors?.actor?.voice),
|
|
151
162
|
voicePitch: actor.voicePitch,
|
|
152
|
-
onVoicePitchChange: handleVoicePitchChange
|
|
163
|
+
onVoicePitchChange: handleVoicePitchChange,
|
|
164
|
+
csv: csv,
|
|
165
|
+
generateAudio: true
|
|
153
166
|
}), /*#__PURE__*/_react.default.createElement(_AvatarSelector.default, {
|
|
154
167
|
t: t,
|
|
155
168
|
open: avatarSelector,
|
|
@@ -157,7 +170,7 @@ function ActorForm({
|
|
|
157
170
|
picture: actor.picture,
|
|
158
171
|
handleClose: () => setAvatarSelector(false),
|
|
159
172
|
handleSubmit: handleAvatarSelection
|
|
160
|
-
}), formik.values.actor.voice && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_material.Box, {
|
|
173
|
+
}), formik.values.actor.voice && !csv && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_material.Box, {
|
|
161
174
|
sx: {
|
|
162
175
|
width: "100%",
|
|
163
176
|
px: 1,
|
|
@@ -180,6 +193,193 @@ function ActorForm({
|
|
|
180
193
|
learnLang: learnLang
|
|
181
194
|
}))));
|
|
182
195
|
}
|
|
196
|
+
function getVoiceLanguageCode(voice, languages) {
|
|
197
|
+
for (const languageCode in languages) {
|
|
198
|
+
const voicesObject = languages[languageCode].Voices;
|
|
199
|
+
if (voicesObject) {
|
|
200
|
+
for (const langKey in voicesObject) {
|
|
201
|
+
const voiceOptions = voicesObject[langKey].voiceOptions;
|
|
202
|
+
const matchingVoice = voiceOptions?.find(option => option.name === voice);
|
|
203
|
+
if (matchingVoice) {
|
|
204
|
+
return langKey;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
function AddPhraseDialog({
|
|
212
|
+
t,
|
|
213
|
+
open,
|
|
214
|
+
onClose,
|
|
215
|
+
handleAddPhrase,
|
|
216
|
+
languages,
|
|
217
|
+
avatars,
|
|
218
|
+
handleSpeak,
|
|
219
|
+
langChar
|
|
220
|
+
}) {
|
|
221
|
+
const [checkAudioUrl, setCheckAudioUrl] = (0, _react.useState)(false);
|
|
222
|
+
const [filteredVoices, setFilteredVoices] = (0, _react.useState)([]);
|
|
223
|
+
const [learnLang, setLearnLang] = (0, _react.useState)("english");
|
|
224
|
+
const formik = (0, _formik.useFormik)({
|
|
225
|
+
initialValues: {
|
|
226
|
+
learnLang: "english",
|
|
227
|
+
actor: {
|
|
228
|
+
voice: "",
|
|
229
|
+
voiceLanguageCode: "",
|
|
230
|
+
voicePitch: "",
|
|
231
|
+
picture: ""
|
|
232
|
+
},
|
|
233
|
+
phraseToSpeak: "",
|
|
234
|
+
voiceSpeed: "",
|
|
235
|
+
fileName: ""
|
|
236
|
+
},
|
|
237
|
+
validationSchema: Yup.object({
|
|
238
|
+
actor: Yup.object().shape({
|
|
239
|
+
voice: Yup.string().nullable(),
|
|
240
|
+
voiceLanguageCode: Yup.string().nullable(),
|
|
241
|
+
voicePitch: Yup.string().nullable(),
|
|
242
|
+
picture: Yup.string().nullable()
|
|
243
|
+
}),
|
|
244
|
+
phraseToSpeak: Yup.string().required("Required"),
|
|
245
|
+
fileName: Yup.string()
|
|
246
|
+
}),
|
|
247
|
+
onSubmit: values => {
|
|
248
|
+
const phraseToAdd = {
|
|
249
|
+
phrase: values.phraseToSpeak,
|
|
250
|
+
actor: values.actor,
|
|
251
|
+
fileName: values.fileName
|
|
252
|
+
};
|
|
253
|
+
handleAddPhrase(phraseToAdd);
|
|
254
|
+
formik.resetForm();
|
|
255
|
+
onClose();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
(0, _react.useEffect)(() => {
|
|
259
|
+
setCheckAudioUrl(true);
|
|
260
|
+
}, [formik.values.phraseToSpeak]);
|
|
261
|
+
(0, _react.useEffect)(() => {
|
|
262
|
+
if (learnLang) {
|
|
263
|
+
const voicesForLang = languages[learnLang]?.Voices || [];
|
|
264
|
+
setFilteredVoices(voicesForLang);
|
|
265
|
+
}
|
|
266
|
+
}, [learnLang, languages]);
|
|
267
|
+
const handleLanguageChange = e => {
|
|
268
|
+
const selectedLang = e.target.value;
|
|
269
|
+
setLearnLang(selectedLang);
|
|
270
|
+
formik.setFieldValue("actor.voice", "");
|
|
271
|
+
setCheckAudioUrl(true);
|
|
272
|
+
};
|
|
273
|
+
return /*#__PURE__*/_react.default.createElement(_ResponsiveDialog.default, {
|
|
274
|
+
open: open,
|
|
275
|
+
onClose: onClose,
|
|
276
|
+
maxWidth: "80%"
|
|
277
|
+
}, /*#__PURE__*/_react.default.createElement(_material.DialogTitle, null, t("add_phrase")), /*#__PURE__*/_react.default.createElement(_material.DialogContent, null, /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
278
|
+
container: true,
|
|
279
|
+
spacing: 2
|
|
280
|
+
}, /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
281
|
+
item: true,
|
|
282
|
+
xs: 12,
|
|
283
|
+
marginTop: 2
|
|
284
|
+
}, /*#__PURE__*/_react.default.createElement(_LanguageSelector.default, {
|
|
285
|
+
id: "learnLang",
|
|
286
|
+
name: "learnLang",
|
|
287
|
+
label: t("language"),
|
|
288
|
+
fullWidth: true,
|
|
289
|
+
required: true,
|
|
290
|
+
t: t,
|
|
291
|
+
languages: languages,
|
|
292
|
+
value: learnLang,
|
|
293
|
+
onChange: handleLanguageChange,
|
|
294
|
+
onBlur: formik.handleBlur
|
|
295
|
+
})), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
296
|
+
item: true,
|
|
297
|
+
xs: 12
|
|
298
|
+
}, /*#__PURE__*/_react.default.createElement(ActorForm, {
|
|
299
|
+
t: t,
|
|
300
|
+
actor: formik.values.actor,
|
|
301
|
+
handleBlur: formik.handleBlur,
|
|
302
|
+
touched: formik.touched,
|
|
303
|
+
errors: formik.errors,
|
|
304
|
+
learnLang: learnLang,
|
|
305
|
+
filteredVoices: filteredVoices,
|
|
306
|
+
handleSpeak: handleSpeak,
|
|
307
|
+
avatars: avatars,
|
|
308
|
+
langChar: langChar,
|
|
309
|
+
setFieldValue: formik.setFieldValue,
|
|
310
|
+
formik: formik,
|
|
311
|
+
setCheckAudioUrl: setCheckAudioUrl,
|
|
312
|
+
csv: true
|
|
313
|
+
})), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
314
|
+
item: true,
|
|
315
|
+
xs: 12
|
|
316
|
+
}, /*#__PURE__*/_react.default.createElement(_material.TextField, {
|
|
317
|
+
id: "fileName",
|
|
318
|
+
"data-cy": "fileName",
|
|
319
|
+
name: "fileName",
|
|
320
|
+
label: t("file_name"),
|
|
321
|
+
value: formik.values.fileName,
|
|
322
|
+
onChange: formik.handleChange,
|
|
323
|
+
onBlur: formik.handleBlur,
|
|
324
|
+
type: "text",
|
|
325
|
+
margin: "normal",
|
|
326
|
+
variant: "outlined",
|
|
327
|
+
fullWidth: true,
|
|
328
|
+
helperText: formik.touched.fileName && formik.errors.fileName,
|
|
329
|
+
error: formik.touched.fileName && Boolean(formik.errors.fileName),
|
|
330
|
+
InputProps: {
|
|
331
|
+
endAdornment: /*#__PURE__*/_react.default.createElement(_material.InputAdornment, {
|
|
332
|
+
position: "end"
|
|
333
|
+
}, ".mp3")
|
|
334
|
+
}
|
|
335
|
+
})), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
336
|
+
item: true,
|
|
337
|
+
xs: 12,
|
|
338
|
+
marginTop: -1
|
|
339
|
+
}, /*#__PURE__*/_react.default.createElement(_InputHelper.default, {
|
|
340
|
+
t: t,
|
|
341
|
+
id: "phraseToSpeak",
|
|
342
|
+
"data-cy": "phraseToSpeak",
|
|
343
|
+
name: "phraseToSpeak",
|
|
344
|
+
label: t("phrase_to_speak"),
|
|
345
|
+
value: formik.values.phraseToSpeak,
|
|
346
|
+
onChange: formik.handleChange,
|
|
347
|
+
onBlur: formik.handleBlur,
|
|
348
|
+
type: "text",
|
|
349
|
+
margin: "normal",
|
|
350
|
+
variant: "outlined",
|
|
351
|
+
fullWidth: true,
|
|
352
|
+
required: true,
|
|
353
|
+
multiline: true,
|
|
354
|
+
characters: langChar,
|
|
355
|
+
learnLang: learnLang,
|
|
356
|
+
helperText: formik.touched.phraseToSpeak && formik.errors.phraseToSpeak,
|
|
357
|
+
error: formik.touched.phraseToSpeak && Boolean(formik.errors.phraseToSpeak)
|
|
358
|
+
})))), /*#__PURE__*/_react.default.createElement(_material.DialogActions, null, /*#__PURE__*/_react.default.createElement(_material.Button, {
|
|
359
|
+
onClick: onClose
|
|
360
|
+
}, t("close")), /*#__PURE__*/_react.default.createElement(_material.Button, {
|
|
361
|
+
variant: "outlined",
|
|
362
|
+
color: "primary",
|
|
363
|
+
disabled: formik.values.phraseToSpeak === "" || !formik.values.actor.voice,
|
|
364
|
+
onClick: () => handleSpeak({
|
|
365
|
+
text: formik.values.phraseToSpeak,
|
|
366
|
+
learnLanguage: learnLang,
|
|
367
|
+
voiceName: formik.values.actor.voice,
|
|
368
|
+
mute: false,
|
|
369
|
+
callback: () => {},
|
|
370
|
+
textContainer: null,
|
|
371
|
+
languageCode: formik.values.actor.voiceLanguageCode,
|
|
372
|
+
isHoverText: false,
|
|
373
|
+
pitch: formik.values.actor.voicePitch,
|
|
374
|
+
voiceSpeed: formik.values.voiceSpeed
|
|
375
|
+
})
|
|
376
|
+
}, t("play_audio")), /*#__PURE__*/_react.default.createElement(_material.Button, {
|
|
377
|
+
variant: "contained",
|
|
378
|
+
onClick: formik.handleSubmit,
|
|
379
|
+
color: "primary",
|
|
380
|
+
disabled: formik.values.phraseToSpeak === "" || !formik.values.actor.voice
|
|
381
|
+
}, t("add_phrase"))));
|
|
382
|
+
}
|
|
183
383
|
function GenerateAudio({
|
|
184
384
|
t,
|
|
185
385
|
handleSpeak,
|
|
@@ -187,12 +387,18 @@ function GenerateAudio({
|
|
|
187
387
|
languages,
|
|
188
388
|
langChar,
|
|
189
389
|
downloadAudio,
|
|
190
|
-
audioUrl
|
|
390
|
+
audioUrl,
|
|
391
|
+
openSnackbar
|
|
191
392
|
}) {
|
|
192
393
|
const [learnLang, setLearnLang] = (0, _react.useState)("english");
|
|
193
394
|
const [filteredVoices, setFilteredVoices] = (0, _react.useState)([]);
|
|
194
395
|
const [checkAudioUrl, setCheckAudioUrl] = (0, _react.useState)(false);
|
|
195
396
|
const [voiceSpeed, setVoiceSpeed] = (0, _react.useState)(50);
|
|
397
|
+
const [showCSV, setShowCSV] = (0, _react.useState)(false);
|
|
398
|
+
const [csvData, setCsvData] = (0, _react.useState)([]);
|
|
399
|
+
const [csvHeaders, setCsvHeaders] = (0, _react.useState)(["phrase", "fileName", "voice"]);
|
|
400
|
+
const [isGenerating, setIsGenerating] = (0, _react.useState)(false);
|
|
401
|
+
const [dialogOpen, setDialogOpen] = (0, _react.useState)(false);
|
|
196
402
|
const formik = (0, _formik.useFormik)({
|
|
197
403
|
initialValues: {
|
|
198
404
|
actor: {
|
|
@@ -216,7 +422,7 @@ function GenerateAudio({
|
|
|
216
422
|
phraseToSpeak: Yup.string().required("Required").test("noSpecialChars", "No special characters", value => !(0, _utils.containsInvalidSymbols)(value))
|
|
217
423
|
}),
|
|
218
424
|
onSubmit: values => {
|
|
219
|
-
handleSpeak(values.phraseToSpeak,
|
|
425
|
+
handleSpeak(values.phraseToSpeak, values.actor.voice);
|
|
220
426
|
}
|
|
221
427
|
});
|
|
222
428
|
(0, _react.useEffect)(() => {
|
|
@@ -237,6 +443,187 @@ function GenerateAudio({
|
|
|
237
443
|
const handleSpeedChange = (event, newValue) => {
|
|
238
444
|
setVoiceSpeed(newValue);
|
|
239
445
|
};
|
|
446
|
+
const playPhraseAudio = (phrase, rowVoice) => {
|
|
447
|
+
const voiceToUse = rowVoice || formik.values.actor.voice || "Joey";
|
|
448
|
+
handleSpeak({
|
|
449
|
+
text: phrase,
|
|
450
|
+
learnLanguage: formik.values.learnLang || "english",
|
|
451
|
+
voiceName: voiceToUse,
|
|
452
|
+
mute: false,
|
|
453
|
+
callback: () => {},
|
|
454
|
+
textContainer: null,
|
|
455
|
+
languageCode: getVoiceLanguageCode(voiceToUse, languages),
|
|
456
|
+
isHoverText: false,
|
|
457
|
+
pitch: null,
|
|
458
|
+
voiceSpeed: null
|
|
459
|
+
});
|
|
460
|
+
};
|
|
461
|
+
const languagesContainsVoice = voice => {
|
|
462
|
+
for (const languageCode in languages) {
|
|
463
|
+
const voicesObject = languages[languageCode].Voices;
|
|
464
|
+
if (voicesObject) {
|
|
465
|
+
for (const langKey in voicesObject) {
|
|
466
|
+
const voiceOptions = voicesObject[langKey].voiceOptions;
|
|
467
|
+
if (voiceOptions?.find(option => option.name === voice)) {
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
};
|
|
475
|
+
const handleAddPhrase = newPhrase => {
|
|
476
|
+
csvHeaders.length === 0 && setCsvHeaders(["phrase", "fileName", "voice"]);
|
|
477
|
+
const fileName = `audio_${csvData.length + 1}_${new Date().toISOString().replace(/:/g, "-").slice(0, 19)}.mp3`;
|
|
478
|
+
if (newPhrase.fileName && !newPhrase.fileName.endsWith(".mp3")) {
|
|
479
|
+
newPhrase.fileName = `${newPhrase.fileName}.mp3`;
|
|
480
|
+
}
|
|
481
|
+
const phraseWithDefaults = {
|
|
482
|
+
phrase: newPhrase.phrase,
|
|
483
|
+
fileName: newPhrase.fileName || fileName,
|
|
484
|
+
voice: newPhrase.actor.voice || "Joey"
|
|
485
|
+
};
|
|
486
|
+
setCsvData([...csvData, phraseWithDefaults]);
|
|
487
|
+
};
|
|
488
|
+
const downloadCSVTemplate = () => {
|
|
489
|
+
const csvTemplate = "phrase,fileName,voice\nThis is a test phrase,Test.mp3,Joey\n";
|
|
490
|
+
downloadCSV(csvTemplate, "template.csv");
|
|
491
|
+
};
|
|
492
|
+
const handleCSVUpload = event => {
|
|
493
|
+
const file = event.target.files[0];
|
|
494
|
+
if (file) {
|
|
495
|
+
_papaparse.default.parse(file, {
|
|
496
|
+
header: true,
|
|
497
|
+
skipEmptyLines: true,
|
|
498
|
+
complete: function (results) {
|
|
499
|
+
let headers = results.meta.fields || [];
|
|
500
|
+
|
|
501
|
+
// Ensure 'phrase' is present in the headers
|
|
502
|
+
if (headers.includes("phrase")) {
|
|
503
|
+
if (!headers.includes("fileName")) {
|
|
504
|
+
headers.push("fileName");
|
|
505
|
+
}
|
|
506
|
+
if (!headers.includes("voice")) {
|
|
507
|
+
headers.push("voice");
|
|
508
|
+
}
|
|
509
|
+
const data = results.data.map((row, index) => {
|
|
510
|
+
if (!row.phrase) {
|
|
511
|
+
openSnackbar(`Row ${index + 1} is missing a phrase.`, "error");
|
|
512
|
+
return null; // Invalid row, will be filtered out
|
|
513
|
+
}
|
|
514
|
+
if (!row.fileName) {
|
|
515
|
+
row.fileName = `audio_${index + 1}_${new Date().toISOString().replace(/:/g, "-").slice(0, 19)}.mp3`;
|
|
516
|
+
} else {
|
|
517
|
+
row.fileName = row.fileName.endsWith(".mp3") ? row.fileName : `${row.fileName}.mp3`;
|
|
518
|
+
}
|
|
519
|
+
row.voice = languagesContainsVoice(row.voice) ? row.voice : "Joey";
|
|
520
|
+
return row;
|
|
521
|
+
}).filter(Boolean);
|
|
522
|
+
setCsvData(csvData.length > 0 ? [...csvData, ...data] : data);
|
|
523
|
+
setCsvHeaders(csvHeaders.length > 0 ? csvHeaders : headers);
|
|
524
|
+
} else {
|
|
525
|
+
openSnackbar("CSV must contain 'phrase' header.", "error");
|
|
526
|
+
removeCSVFile();
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
error: function (error) {
|
|
530
|
+
console.error("Error parsing CSV: ", error);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
const removeCSVFile = () => {
|
|
536
|
+
setCsvData([]);
|
|
537
|
+
setCsvHeaders([]);
|
|
538
|
+
};
|
|
539
|
+
const handleDeleteRow = rowIndex => {
|
|
540
|
+
const updatedCsvData = csvData.filter((_, index) => index !== rowIndex);
|
|
541
|
+
setCsvData(updatedCsvData);
|
|
542
|
+
};
|
|
543
|
+
const handleChange = (event, newValue) => {
|
|
544
|
+
setShowCSV(newValue);
|
|
545
|
+
};
|
|
546
|
+
const downloadCSV = (text, filename) => {
|
|
547
|
+
const element = document.createElement("a");
|
|
548
|
+
const file = new Blob([text], {
|
|
549
|
+
type: "text/plain"
|
|
550
|
+
});
|
|
551
|
+
element.href = URL.createObjectURL(file);
|
|
552
|
+
element.download = filename;
|
|
553
|
+
document.body.appendChild(element); // Required for this to work in FireFox
|
|
554
|
+
element.click();
|
|
555
|
+
};
|
|
556
|
+
const generateAudioForCSV = async () => {
|
|
557
|
+
const zip = new _jszip.default();
|
|
558
|
+
const timestamp = new Date().toISOString().replace(/:/g, "-").slice(0, 19);
|
|
559
|
+
const folder = zip.folder(`audio_files_${timestamp}`);
|
|
560
|
+
setIsGenerating(true);
|
|
561
|
+
const fileNameCount = {};
|
|
562
|
+
try {
|
|
563
|
+
for (const row of csvData) {
|
|
564
|
+
const phrase = row.phrase;
|
|
565
|
+
let fileName = row.fileName || `audio_${new Date().toISOString().replace(/:/g, "-").slice(0, 19)}`;
|
|
566
|
+
const rowVoice = row.voice || formik.values.actor.voice;
|
|
567
|
+
if (!phrase) {
|
|
568
|
+
openSnackbar("Invalid CSV data: 'phrase' column is required", "error");
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
try {
|
|
572
|
+
const result = await handleSpeak({
|
|
573
|
+
text: phrase,
|
|
574
|
+
learnLanguage: learnLang,
|
|
575
|
+
voiceName: rowVoice,
|
|
576
|
+
mute: true,
|
|
577
|
+
callback: () => {},
|
|
578
|
+
textContainer: null,
|
|
579
|
+
languageCode: getVoiceLanguageCode(rowVoice, languages),
|
|
580
|
+
isHoverText: false,
|
|
581
|
+
pitch: null,
|
|
582
|
+
voiceSpeed: null
|
|
583
|
+
});
|
|
584
|
+
const audioUrl = result ? result.audioUrl : null;
|
|
585
|
+
if (!audioUrl) continue;
|
|
586
|
+
const response = await fetch(audioUrl);
|
|
587
|
+
if (!response.ok) {
|
|
588
|
+
openSnackbar(`Failed to fetch audio for phrase: "${phrase}"`, "error");
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
const blob = await response.blob();
|
|
592
|
+
let fileNameWithExtension = fileName.endsWith(".mp3") ? fileName : `${fileName}.mp3`;
|
|
593
|
+
const contentType = blob.type;
|
|
594
|
+
const allowedAudioTypes = ["audio/mpeg", "audio/wav", "audio/ogg", "application/octet-stream"];
|
|
595
|
+
if (!allowedAudioTypes.includes(contentType)) {
|
|
596
|
+
openSnackbar(`Unsupported audio type for phrase: "${phrase}"`, "error");
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (fileNameCount[fileNameWithExtension]) {
|
|
600
|
+
fileNameCount[fileNameWithExtension] += 1;
|
|
601
|
+
const baseName = fileNameWithExtension.replace(".mp3", "");
|
|
602
|
+
fileNameWithExtension = `${baseName}_${fileNameCount[fileNameWithExtension]}.mp3`;
|
|
603
|
+
} else {
|
|
604
|
+
fileNameCount[fileNameWithExtension] = 1;
|
|
605
|
+
}
|
|
606
|
+
folder.file(fileNameWithExtension, blob);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
console.error(`Error generating audio for ${fileName}: ${error.message}`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Prepare CSV data with updated headers and rows
|
|
613
|
+
const csvContent = [csvHeaders.join(","), ...csvData.map(row => csvHeaders.map(header => row[header]).join(","))].join("\n");
|
|
614
|
+
|
|
615
|
+
// Add CSV file to zip
|
|
616
|
+
zip.file(`updated_data_${timestamp}.csv`, csvContent);
|
|
617
|
+
const zipBlob = await zip.generateAsync({
|
|
618
|
+
type: "blob"
|
|
619
|
+
});
|
|
620
|
+
(0, _fileSaver.saveAs)(zipBlob, `audio_files_${timestamp}.zip`);
|
|
621
|
+
} catch (error) {
|
|
622
|
+
console.error(`Error generating audio files: ${error.message}`);
|
|
623
|
+
} finally {
|
|
624
|
+
setIsGenerating(false);
|
|
625
|
+
}
|
|
626
|
+
};
|
|
240
627
|
return /*#__PURE__*/_react.default.createElement("form", {
|
|
241
628
|
onSubmit: formik.handleSubmit
|
|
242
629
|
}, /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
@@ -248,16 +635,43 @@ function GenerateAudio({
|
|
|
248
635
|
}
|
|
249
636
|
}, /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
250
637
|
item: true,
|
|
251
|
-
xs:
|
|
638
|
+
xs: 6,
|
|
639
|
+
md: 4,
|
|
252
640
|
style: {
|
|
253
|
-
textAlign: "center"
|
|
641
|
+
textAlign: "center",
|
|
642
|
+
marginTop: "15px"
|
|
254
643
|
}
|
|
255
644
|
}, /*#__PURE__*/_react.default.createElement("img", {
|
|
256
645
|
src: _AudioNuala.default,
|
|
257
646
|
alt: "Audio Nuala",
|
|
258
647
|
style: {
|
|
259
|
-
maxWidth: "
|
|
260
|
-
|
|
648
|
+
maxWidth: "300px"
|
|
649
|
+
}
|
|
650
|
+
})), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
651
|
+
item: true,
|
|
652
|
+
xs: 6,
|
|
653
|
+
md: 4,
|
|
654
|
+
style: {
|
|
655
|
+
textAlign: "left"
|
|
656
|
+
}
|
|
657
|
+
}, /*#__PURE__*/_react.default.createElement(_material.Typography, {
|
|
658
|
+
variant: "h5",
|
|
659
|
+
fontWeight: "bold"
|
|
660
|
+
}, t("text_studio")), /*#__PURE__*/_react.default.createElement(_material.Typography, {
|
|
661
|
+
variant: "body1",
|
|
662
|
+
gutterBottom: true
|
|
663
|
+
}, t("text_studio_description"))), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
664
|
+
item: true,
|
|
665
|
+
xs: 12,
|
|
666
|
+
md: 8,
|
|
667
|
+
marginTop: 2,
|
|
668
|
+
marginBottom: 1,
|
|
669
|
+
style: {
|
|
670
|
+
textAlign: "center"
|
|
671
|
+
}
|
|
672
|
+
}, /*#__PURE__*/_react.default.createElement(_material.Divider, {
|
|
673
|
+
sx: {
|
|
674
|
+
borderBottomWidth: 4
|
|
261
675
|
}
|
|
262
676
|
})), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
263
677
|
item: true,
|
|
@@ -268,9 +682,28 @@ function GenerateAudio({
|
|
|
268
682
|
}, /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
269
683
|
item: true,
|
|
270
684
|
xs: 12,
|
|
271
|
-
md:
|
|
272
|
-
|
|
273
|
-
|
|
685
|
+
md: 12,
|
|
686
|
+
marginTop: 2,
|
|
687
|
+
marginBottom: 1.5
|
|
688
|
+
}, /*#__PURE__*/_react.default.createElement(_material.ToggleButtonGroup, {
|
|
689
|
+
color: "primary",
|
|
690
|
+
value: showCSV,
|
|
691
|
+
exclusive: true,
|
|
692
|
+
onChange: handleChange,
|
|
693
|
+
"aria-label": "Platform",
|
|
694
|
+
sx: {
|
|
695
|
+
marginLeft: "auto",
|
|
696
|
+
height: "30px"
|
|
697
|
+
}
|
|
698
|
+
}, /*#__PURE__*/_react.default.createElement(_material.ToggleButton, {
|
|
699
|
+
value: false
|
|
700
|
+
}, t("single_phrase")), /*#__PURE__*/_react.default.createElement(_material.ToggleButton, {
|
|
701
|
+
value: true
|
|
702
|
+
}, t("list_of_phrases")))), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
703
|
+
item: true,
|
|
704
|
+
xs: 12,
|
|
705
|
+
md: 12
|
|
706
|
+
}, !showCSV && /*#__PURE__*/_react.default.createElement(_LanguageSelector.default, {
|
|
274
707
|
id: "learnLang",
|
|
275
708
|
name: "learnLang",
|
|
276
709
|
"data-cy": "learnLang",
|
|
@@ -287,9 +720,8 @@ function GenerateAudio({
|
|
|
287
720
|
})), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
288
721
|
item: true,
|
|
289
722
|
xs: 12,
|
|
290
|
-
md:
|
|
291
|
-
|
|
292
|
-
}, /*#__PURE__*/_react.default.createElement(ActorForm, {
|
|
723
|
+
md: 12
|
|
724
|
+
}, !showCSV && /*#__PURE__*/_react.default.createElement(ActorForm, {
|
|
293
725
|
t: t,
|
|
294
726
|
actor: formik.values.actor,
|
|
295
727
|
handleBlur: formik.handleBlur,
|
|
@@ -311,7 +743,7 @@ function GenerateAudio({
|
|
|
311
743
|
xs: 12,
|
|
312
744
|
md: 12,
|
|
313
745
|
marginBottom: 2
|
|
314
|
-
}, /*#__PURE__*/_react.default.createElement(_InputHelper.default, {
|
|
746
|
+
}, !showCSV ? /*#__PURE__*/_react.default.createElement(_InputHelper.default, {
|
|
315
747
|
t: t,
|
|
316
748
|
id: "phraseToSpeak",
|
|
317
749
|
"data-cy": "phraseToSpeak",
|
|
@@ -330,7 +762,144 @@ function GenerateAudio({
|
|
|
330
762
|
learnLang: learnLang,
|
|
331
763
|
helperText: formik.touched.phraseToSpeak && formik.errors.phraseToSpeak,
|
|
332
764
|
error: formik.touched.phraseToSpeak && Boolean(formik.errors.phraseToSpeak)
|
|
765
|
+
}) : /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
766
|
+
container: true,
|
|
767
|
+
item: true,
|
|
768
|
+
xs: 12,
|
|
769
|
+
md: 12,
|
|
770
|
+
spacing: 1,
|
|
771
|
+
marginTop: 0.5,
|
|
772
|
+
marginBottom: 1,
|
|
773
|
+
sx: {
|
|
774
|
+
display: "flex",
|
|
775
|
+
justifyContent: "left",
|
|
776
|
+
alignItems: "center"
|
|
777
|
+
}
|
|
778
|
+
}, /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
779
|
+
item: true
|
|
780
|
+
}, /*#__PURE__*/_react.default.createElement(_material.Button, {
|
|
781
|
+
startIcon: /*#__PURE__*/_react.default.createElement(_Add.default, null),
|
|
782
|
+
onClick: () => setDialogOpen(true),
|
|
783
|
+
variant: "contained",
|
|
784
|
+
sx: {
|
|
785
|
+
marginBottom: 2
|
|
786
|
+
}
|
|
787
|
+
}, t("add_phrase"))), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
788
|
+
item: true
|
|
789
|
+
}, /*#__PURE__*/_react.default.createElement("label", {
|
|
790
|
+
htmlFor: "upload-csv"
|
|
791
|
+
}, /*#__PURE__*/_react.default.createElement(_material.Button, {
|
|
792
|
+
variant: "contained",
|
|
793
|
+
component: "span",
|
|
794
|
+
startIcon: /*#__PURE__*/_react.default.createElement(_FileUpload.default, null),
|
|
795
|
+
sx: {
|
|
796
|
+
marginBottom: 2
|
|
797
|
+
}
|
|
798
|
+
}, t("upload_csv"))), /*#__PURE__*/_react.default.createElement("input", {
|
|
799
|
+
id: "upload-csv",
|
|
800
|
+
type: "file",
|
|
801
|
+
accept: ".csv",
|
|
802
|
+
onChange: handleCSVUpload,
|
|
803
|
+
style: {
|
|
804
|
+
display: "none"
|
|
805
|
+
}
|
|
333
806
|
})), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
807
|
+
item: true
|
|
808
|
+
}, /*#__PURE__*/_react.default.createElement(_material.Button, {
|
|
809
|
+
variant: "outlined",
|
|
810
|
+
startIcon: /*#__PURE__*/_react.default.createElement(_Download.default, null),
|
|
811
|
+
onClick: downloadCSVTemplate,
|
|
812
|
+
sx: {
|
|
813
|
+
marginBottom: 2
|
|
814
|
+
}
|
|
815
|
+
}, t("download_csv_template"))), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
816
|
+
item: true,
|
|
817
|
+
xs: 12,
|
|
818
|
+
sx: {
|
|
819
|
+
justifyContent: "left"
|
|
820
|
+
}
|
|
821
|
+
}, csvData.length === 0 && /*#__PURE__*/_react.default.createElement(_material.Typography, {
|
|
822
|
+
variant: "body1",
|
|
823
|
+
sx: {
|
|
824
|
+
marginTop: 1
|
|
825
|
+
}
|
|
826
|
+
}, t("no_phrases_yet"))), /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
827
|
+
container: true,
|
|
828
|
+
item: true,
|
|
829
|
+
xs: 12,
|
|
830
|
+
md: 12,
|
|
831
|
+
spacing: 1,
|
|
832
|
+
marginBottom: 1
|
|
833
|
+
}, csvData.length > 0 && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
834
|
+
item: true,
|
|
835
|
+
xs: 12
|
|
836
|
+
}, /*#__PURE__*/_react.default.createElement(_material.TableContainer, {
|
|
837
|
+
component: _material.Paper
|
|
838
|
+
}, /*#__PURE__*/_react.default.createElement(_material.Table, {
|
|
839
|
+
sx: {
|
|
840
|
+
minWidth: 650
|
|
841
|
+
},
|
|
842
|
+
"aria-label": "csv table"
|
|
843
|
+
}, /*#__PURE__*/_react.default.createElement(_material.TableHead, null, /*#__PURE__*/_react.default.createElement(_material.TableRow, null, csvHeaders.map((header, index) => /*#__PURE__*/_react.default.createElement(_material.TableCell, {
|
|
844
|
+
key: index,
|
|
845
|
+
align: "center"
|
|
846
|
+
}, /*#__PURE__*/_react.default.createElement("strong", null, header))), /*#__PURE__*/_react.default.createElement(_material.TableCell, {
|
|
847
|
+
align: "center"
|
|
848
|
+
}, /*#__PURE__*/_react.default.createElement("strong", null, t("play_audio"))), /*#__PURE__*/_react.default.createElement(_material.TableCell, {
|
|
849
|
+
align: "center"
|
|
850
|
+
}, /*#__PURE__*/_react.default.createElement("strong", null, t("delete")), " "))), /*#__PURE__*/_react.default.createElement(_material.TableBody, null, csvData.map((row, rowIndex) => /*#__PURE__*/_react.default.createElement(_material.TableRow, {
|
|
851
|
+
key: rowIndex
|
|
852
|
+
}, csvHeaders.map((header, colIndex) => /*#__PURE__*/_react.default.createElement(_material.TableCell, {
|
|
853
|
+
key: colIndex,
|
|
854
|
+
align: "center"
|
|
855
|
+
}, row[header] || null)), /*#__PURE__*/_react.default.createElement(_material.TableCell, {
|
|
856
|
+
align: "center"
|
|
857
|
+
}, /*#__PURE__*/_react.default.createElement(_material.IconButton, {
|
|
858
|
+
onClick: () => playPhraseAudio(row.phrase, row.voice),
|
|
859
|
+
"aria-label": "play"
|
|
860
|
+
}, /*#__PURE__*/_react.default.createElement(_PlayArrow.default, null))), /*#__PURE__*/_react.default.createElement(_material.TableCell, {
|
|
861
|
+
align: "center"
|
|
862
|
+
}, /*#__PURE__*/_react.default.createElement(_material.IconButton, {
|
|
863
|
+
onClick: () => handleDeleteRow(rowIndex) // Call the row deletion function
|
|
864
|
+
,
|
|
865
|
+
"aria-label": "delete"
|
|
866
|
+
}, /*#__PURE__*/_react.default.createElement(_Delete.default, null))))))))))), csvData.length > 0 && /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
867
|
+
item: true,
|
|
868
|
+
xs: 12,
|
|
869
|
+
sx: {
|
|
870
|
+
display: "flex",
|
|
871
|
+
justifyContent: "left",
|
|
872
|
+
alignItems: "center",
|
|
873
|
+
marginTop: 1
|
|
874
|
+
}
|
|
875
|
+
}, /*#__PURE__*/_react.default.createElement(_lab.LoadingButton, {
|
|
876
|
+
variant: "contained",
|
|
877
|
+
onClick: generateAudioForCSV,
|
|
878
|
+
loading: isGenerating,
|
|
879
|
+
startIcon: /*#__PURE__*/_react.default.createElement(_Download.default, null),
|
|
880
|
+
sx: {
|
|
881
|
+
marginBottom: 2
|
|
882
|
+
}
|
|
883
|
+
}, t("download_audio")))), /*#__PURE__*/_react.default.createElement(AddPhraseDialog, {
|
|
884
|
+
t: t,
|
|
885
|
+
open: dialogOpen,
|
|
886
|
+
onClose: () => setDialogOpen(false),
|
|
887
|
+
handleAddPhrase: handleAddPhrase,
|
|
888
|
+
initialValues: {
|
|
889
|
+
learnLang: "english",
|
|
890
|
+
actor: {
|
|
891
|
+
voice: "",
|
|
892
|
+
voiceLanguageCode: "",
|
|
893
|
+
voicePitch: "",
|
|
894
|
+
picture: ""
|
|
895
|
+
},
|
|
896
|
+
phraseToSpeak: "",
|
|
897
|
+
voiceSpeed: 50
|
|
898
|
+
},
|
|
899
|
+
languages: languages,
|
|
900
|
+
avatars: avatars,
|
|
901
|
+
handleSpeak: handleSpeak
|
|
902
|
+
})), !showCSV && /*#__PURE__*/_react.default.createElement(_material.Grid, {
|
|
334
903
|
container: true,
|
|
335
904
|
item: true,
|
|
336
905
|
xs: 12,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nualang/nualang-ui-components",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1174",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"easymde": "2.18.0",
|
|
42
42
|
"emoji-mart": "5.5.2",
|
|
43
43
|
"export-to-csv": "^1.3.0",
|
|
44
|
+
"file-saver": "^2.0.5",
|
|
44
45
|
"formik": "^2.2.9",
|
|
45
46
|
"fs": "^0.0.1-security",
|
|
46
47
|
"immutability-helper": "^3.0",
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
"json-2-csv": "^5.5.1",
|
|
49
50
|
"jsonlint": "^1.6.3",
|
|
50
51
|
"jsonrepair": "^3.6.0",
|
|
52
|
+
"jszip": "^3.10.1",
|
|
51
53
|
"luxon": "^3.3.0",
|
|
52
54
|
"moment": "^2.29.4",
|
|
53
55
|
"n2words": "^1.21.0",
|