ssml-builder-js 2.10.0 → 2.12.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/README.md +100 -5
- package/bin/sync-voices.js +62 -0
- package/dist/{chunk-GW6CKHXF.mjs → chunk-4BVNAUVR.mjs} +177 -5
- package/dist/chunk-4BVNAUVR.mjs.map +1 -0
- package/dist/{chunk-BYIZQL2W.mjs → chunk-FHDFRM2F.mjs} +92 -14
- package/dist/chunk-FHDFRM2F.mjs.map +1 -0
- package/dist/core.d.mts +21 -3
- package/dist/core.d.ts +21 -3
- package/dist/core.js +177 -4
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +3 -1
- package/dist/elements.js +91 -13
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +1 -1
- package/dist/index.d.mts +55 -2
- package/dist/index.d.ts +55 -2
- package/dist/index.js +300 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +124 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.js +335 -15
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +245 -3
- package/dist/react.mjs.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-BYIZQL2W.mjs.map +0 -1
- package/dist/chunk-GW6CKHXF.mjs.map +0 -1
package/dist/react.js
CHANGED
|
@@ -57,6 +57,7 @@ var ssmlPresets_exports = {};
|
|
|
57
57
|
__export(ssmlPresets_exports, {
|
|
58
58
|
AUDIO_DURATION_DESCRIPTIONS: () => AUDIO_DURATION_DESCRIPTIONS,
|
|
59
59
|
AUDIO_DURATION_PRESETS: () => AUDIO_DURATION_PRESETS,
|
|
60
|
+
BACKGROUND_AUDIO_DURATION_PRESETS: () => BACKGROUND_AUDIO_DURATION_PRESETS,
|
|
60
61
|
BREAK_STRENGTH_PRESETS: () => BREAK_STRENGTH_PRESETS,
|
|
61
62
|
BREAK_TIME_DESCRIPTIONS: () => BREAK_TIME_DESCRIPTIONS,
|
|
62
63
|
BREAK_TIME_PRESETS: () => BREAK_TIME_PRESETS,
|
|
@@ -138,6 +139,9 @@ var AZURE_VOICE_STYLE_MAP = {
|
|
|
138
139
|
],
|
|
139
140
|
"es-ES-ElviraNeural": [],
|
|
140
141
|
"fil-PH-AngeloNeural": [],
|
|
142
|
+
"fil-PH-Angelo:DragonHDLatestNeural": [],
|
|
143
|
+
"fil-PH-BlessicaNeural": [],
|
|
144
|
+
"fil-PH-Blessica:DragonHDLatestNeural": [],
|
|
141
145
|
"fr-FR-DeniseNeural": ["cheerful", "sad"],
|
|
142
146
|
"fr-FR-HenriNeural": ["cheerful", "sad"],
|
|
143
147
|
"id-ID-GadisNeural": [],
|
|
@@ -415,6 +419,7 @@ var SAY_AS_PRESETS = [
|
|
|
415
419
|
var LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"];
|
|
416
420
|
var SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"];
|
|
417
421
|
var AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"];
|
|
422
|
+
var BACKGROUND_AUDIO_DURATION_PRESETS = ["0", "500", "1000", "3000", "10000"];
|
|
418
423
|
var SILENCE_TYPE_PRESETS = [
|
|
419
424
|
"Leading",
|
|
420
425
|
"Tailing",
|
|
@@ -476,8 +481,8 @@ var SSML_ATTRIBUTE_PRESETS = {
|
|
|
476
481
|
},
|
|
477
482
|
"mstts:backgroundaudio": {
|
|
478
483
|
volume: PROSODY_VOLUME_PRESETS,
|
|
479
|
-
fadein:
|
|
480
|
-
fadeout:
|
|
484
|
+
fadein: BACKGROUND_AUDIO_DURATION_PRESETS,
|
|
485
|
+
fadeout: BACKGROUND_AUDIO_DURATION_PRESETS
|
|
481
486
|
},
|
|
482
487
|
silence: {
|
|
483
488
|
type: SILENCE_TYPE_PRESETS,
|
|
@@ -2668,6 +2673,9 @@ var AZURE_VOICE_DEFINITIONS = [
|
|
|
2668
2673
|
},
|
|
2669
2674
|
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
2670
2675
|
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
2676
|
+
{ name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
|
|
2677
|
+
{ name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
|
|
2678
|
+
{ name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
|
|
2671
2679
|
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
2672
2680
|
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
2673
2681
|
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
@@ -2762,6 +2770,18 @@ var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
|
|
|
2762
2770
|
"Enumerationcomma"
|
|
2763
2771
|
]);
|
|
2764
2772
|
var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
|
|
2773
|
+
var DEFAULT_PREVIEW_TAGS = /* @__PURE__ */ new Set(["mstts:voiceconversion"]);
|
|
2774
|
+
function featureStatusForTag(name, options) {
|
|
2775
|
+
const tagName = canonicalTagName(name);
|
|
2776
|
+
const configured = Object.entries(options.tagStatuses ?? {}).find(
|
|
2777
|
+
([candidate]) => canonicalTagName(candidate) === tagName
|
|
2778
|
+
)?.[1];
|
|
2779
|
+
if (configured) return configured;
|
|
2780
|
+
if ((options.previewTags ?? [...DEFAULT_PREVIEW_TAGS]).some((candidate) => canonicalTagName(candidate) === tagName))
|
|
2781
|
+
return "preview";
|
|
2782
|
+
if ((options.deprecatedTags ?? []).some((candidate) => canonicalTagName(candidate) === tagName)) return "deprecated";
|
|
2783
|
+
return void 0;
|
|
2784
|
+
}
|
|
2765
2785
|
function decodeAttribute(value) {
|
|
2766
2786
|
return value.replace(
|
|
2767
2787
|
/&(?:amp|apos|gt|lt|quot);/gi,
|
|
@@ -2830,6 +2850,7 @@ function tokenizeElements(source) {
|
|
|
2830
2850
|
attributes,
|
|
2831
2851
|
childElementIndex,
|
|
2832
2852
|
end,
|
|
2853
|
+
depth: openElements.length + 1,
|
|
2833
2854
|
name: tokenName,
|
|
2834
2855
|
parentName: parent?.name,
|
|
2835
2856
|
parentVoiceName,
|
|
@@ -2878,9 +2899,9 @@ function isValidAzureAudioDuration(value) {
|
|
|
2878
2899
|
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
2879
2900
|
}
|
|
2880
2901
|
function isValidAzureBackgroundAudioDuration(value) {
|
|
2881
|
-
const match = /^(\d+
|
|
2902
|
+
const match = /^(\d+)$/.exec(value.trim());
|
|
2882
2903
|
if (!match) return false;
|
|
2883
|
-
const milliseconds = Number(match[1])
|
|
2904
|
+
const milliseconds = Number(match[1]);
|
|
2884
2905
|
return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
|
|
2885
2906
|
}
|
|
2886
2907
|
function attr(token, name) {
|
|
@@ -3010,13 +3031,18 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
3010
3031
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
|
|
3011
3032
|
return;
|
|
3012
3033
|
}
|
|
3034
|
+
if (parsed.username || parsed.password)
|
|
3035
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must not contain URL credentials.`);
|
|
3013
3036
|
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
3014
3037
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
3015
3038
|
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
3016
3039
|
try {
|
|
3017
|
-
|
|
3040
|
+
const configured = new URL(allowedOrigin);
|
|
3041
|
+
if (configured.protocol !== "https:" && configured.protocol !== "http:" || configured.username || configured.password || configured.pathname !== "/" || configured.search || configured.hash)
|
|
3042
|
+
return false;
|
|
3043
|
+
return configured.origin === parsed.origin;
|
|
3018
3044
|
} catch {
|
|
3019
|
-
return
|
|
3045
|
+
return false;
|
|
3020
3046
|
}
|
|
3021
3047
|
}) ?? false;
|
|
3022
3048
|
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
@@ -3031,6 +3057,25 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
3031
3057
|
}
|
|
3032
3058
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
3033
3059
|
const name = token.name.toLowerCase();
|
|
3060
|
+
const tagStatus = featureStatusForTag(token.name, options);
|
|
3061
|
+
if (tagStatus === "preview")
|
|
3062
|
+
addDiagnostic(
|
|
3063
|
+
diagnostics,
|
|
3064
|
+
source,
|
|
3065
|
+
token.start,
|
|
3066
|
+
`<${token.name}> is an Azure Speech preview feature and may change or require preview access.`,
|
|
3067
|
+
"warning",
|
|
3068
|
+
"azure-preview-tag"
|
|
3069
|
+
);
|
|
3070
|
+
if (tagStatus === "deprecated")
|
|
3071
|
+
addDiagnostic(
|
|
3072
|
+
diagnostics,
|
|
3073
|
+
source,
|
|
3074
|
+
token.start,
|
|
3075
|
+
`<${token.name}> is deprecated by Azure Speech; migrate to a supported alternative.`,
|
|
3076
|
+
"info",
|
|
3077
|
+
"azure-deprecated-tag"
|
|
3078
|
+
);
|
|
3034
3079
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
3035
3080
|
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
3036
3081
|
if (name === "break") {
|
|
@@ -3205,6 +3250,9 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
3205
3250
|
const maxLength = options.maxLength ?? 1e4;
|
|
3206
3251
|
if (ssml.length > maxLength)
|
|
3207
3252
|
addDiagnostic(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
|
|
3253
|
+
if (options.maxXmlDepth !== void 0 && (!Number.isInteger(options.maxXmlDepth) || options.maxXmlDepth <= 0)) {
|
|
3254
|
+
addDiagnostic(diagnostics, ssml, 0, "maxXmlDepth must be a positive integer.");
|
|
3255
|
+
}
|
|
3208
3256
|
try {
|
|
3209
3257
|
parseSsml(ssml);
|
|
3210
3258
|
} catch (error) {
|
|
@@ -3214,6 +3262,18 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
3214
3262
|
return diagnostics;
|
|
3215
3263
|
}
|
|
3216
3264
|
const tokens = tokenizeElements(ssml);
|
|
3265
|
+
if (options.maxXmlDepth !== void 0) {
|
|
3266
|
+
for (const token of tokens) {
|
|
3267
|
+
if (token.depth > options.maxXmlDepth) {
|
|
3268
|
+
addDiagnostic(
|
|
3269
|
+
diagnostics,
|
|
3270
|
+
ssml,
|
|
3271
|
+
token.start,
|
|
3272
|
+
`XML nesting depth ${token.depth} exceeds the configured maximum of ${options.maxXmlDepth}.`
|
|
3273
|
+
);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3217
3277
|
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
3218
3278
|
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
3219
3279
|
const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
|
|
@@ -3242,6 +3302,24 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
3242
3302
|
const name = attr(token, "name")?.trim();
|
|
3243
3303
|
const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
|
|
3244
3304
|
const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
|
|
3305
|
+
if (name && definition?.status === "preview")
|
|
3306
|
+
addDiagnostic(
|
|
3307
|
+
diagnostics,
|
|
3308
|
+
ssml,
|
|
3309
|
+
token.start,
|
|
3310
|
+
`Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
|
|
3311
|
+
"warning",
|
|
3312
|
+
"azure-preview-voice"
|
|
3313
|
+
);
|
|
3314
|
+
if (name && definition?.status === "deprecated")
|
|
3315
|
+
addDiagnostic(
|
|
3316
|
+
diagnostics,
|
|
3317
|
+
ssml,
|
|
3318
|
+
token.start,
|
|
3319
|
+
`Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
|
|
3320
|
+
"info",
|
|
3321
|
+
"azure-deprecated-voice"
|
|
3322
|
+
);
|
|
3245
3323
|
if (name && !definition && policySeverity)
|
|
3246
3324
|
addDiagnostic(
|
|
3247
3325
|
diagnostics,
|
|
@@ -4929,7 +5007,7 @@ var SSML_COMPLETION_SNIPPETS = [
|
|
|
4929
5007
|
},
|
|
4930
5008
|
{
|
|
4931
5009
|
label: "mstts:backgroundaudio",
|
|
4932
|
-
insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2
|
|
5010
|
+
insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:70}" fadein="\${3:1000}" fadeout="\${4:1000}" />`
|
|
4933
5011
|
},
|
|
4934
5012
|
{
|
|
4935
5013
|
label: "mstts:ttsembedding",
|
|
@@ -5630,18 +5708,18 @@ var SSML_TAG_DEFINITIONS = [
|
|
|
5630
5708
|
},
|
|
5631
5709
|
{
|
|
5632
5710
|
name: "volume",
|
|
5633
|
-
description: "The background audio volume
|
|
5634
|
-
example: "
|
|
5711
|
+
description: "The background audio volume from 0 to 100.",
|
|
5712
|
+
example: "70"
|
|
5635
5713
|
},
|
|
5636
5714
|
{
|
|
5637
5715
|
name: "fadein",
|
|
5638
|
-
description: "The fade-in duration
|
|
5639
|
-
example: "
|
|
5716
|
+
description: "The fade-in duration in milliseconds from 0 to 10000.",
|
|
5717
|
+
example: "1000"
|
|
5640
5718
|
},
|
|
5641
5719
|
{
|
|
5642
5720
|
name: "fadeout",
|
|
5643
|
-
description: "The fade-out duration
|
|
5644
|
-
example: "
|
|
5721
|
+
description: "The fade-out duration in milliseconds from 0 to 10000.",
|
|
5722
|
+
example: "1000"
|
|
5645
5723
|
}
|
|
5646
5724
|
]
|
|
5647
5725
|
},
|
|
@@ -6295,6 +6373,175 @@ function updateNodesAtPath(nodes, path, update) {
|
|
|
6295
6373
|
function updateTextAtPath(document2, path, value) {
|
|
6296
6374
|
return { ...document2, children: updateNodesAtPath(document2.children ?? [], path, () => value) };
|
|
6297
6375
|
}
|
|
6376
|
+
function getNodeAtPath(nodes, path) {
|
|
6377
|
+
let current;
|
|
6378
|
+
let currentNodes = nodes;
|
|
6379
|
+
for (const index of path) {
|
|
6380
|
+
current = currentNodes[index];
|
|
6381
|
+
if (current === void 0) return void 0;
|
|
6382
|
+
if (!isElement(current)) {
|
|
6383
|
+
currentNodes = [];
|
|
6384
|
+
} else {
|
|
6385
|
+
currentNodes = current.children ?? [];
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
return current;
|
|
6389
|
+
}
|
|
6390
|
+
function updateElementAtPath(document2, path, update) {
|
|
6391
|
+
return {
|
|
6392
|
+
...document2,
|
|
6393
|
+
children: updateNodesAtPath(document2.children ?? [], path, (node) => isElement(node) ? update(node) : node)
|
|
6394
|
+
};
|
|
6395
|
+
}
|
|
6396
|
+
function updateElementText(element, value) {
|
|
6397
|
+
return { ...element, children: [value] };
|
|
6398
|
+
}
|
|
6399
|
+
function updateOptionalElementProperty(document2, path, property, value) {
|
|
6400
|
+
return updateElementAtPath(document2, path, (element) => {
|
|
6401
|
+
const next = { ...element };
|
|
6402
|
+
if (value.trim()) next[property] = value;
|
|
6403
|
+
else delete next[property];
|
|
6404
|
+
return next;
|
|
6405
|
+
});
|
|
6406
|
+
}
|
|
6407
|
+
var VISUAL_ELEMENT_FIELDS = {
|
|
6408
|
+
voice: [
|
|
6409
|
+
{ key: "name", label: "Voice name" },
|
|
6410
|
+
{ key: "effect", label: "Effect" }
|
|
6411
|
+
],
|
|
6412
|
+
prosody: [
|
|
6413
|
+
{ key: "rate", label: "Rate" },
|
|
6414
|
+
{ key: "pitch", label: "Pitch" },
|
|
6415
|
+
{ key: "volume", label: "Volume" },
|
|
6416
|
+
{ key: "contour", label: "Contour" },
|
|
6417
|
+
{ key: "range", label: "Range" }
|
|
6418
|
+
],
|
|
6419
|
+
"express-as": [
|
|
6420
|
+
{ key: "style", label: "Style" },
|
|
6421
|
+
{ key: "styleDegree", label: "Style degree" },
|
|
6422
|
+
{ key: "role", label: "Role" }
|
|
6423
|
+
],
|
|
6424
|
+
expressAs: [
|
|
6425
|
+
{ key: "style", label: "Style" },
|
|
6426
|
+
{ key: "styleDegree", label: "Style degree" },
|
|
6427
|
+
{ key: "role", label: "Role" }
|
|
6428
|
+
],
|
|
6429
|
+
"mstts:express-as": [
|
|
6430
|
+
{ key: "style", label: "Style" },
|
|
6431
|
+
{ key: "styleDegree", label: "Style degree" },
|
|
6432
|
+
{ key: "role", label: "Role" }
|
|
6433
|
+
],
|
|
6434
|
+
"say-as": [
|
|
6435
|
+
{ key: "interpretAs", label: "Interpret as" },
|
|
6436
|
+
{ key: "format", label: "Format" },
|
|
6437
|
+
{ key: "detail", label: "Detail" }
|
|
6438
|
+
],
|
|
6439
|
+
sayAs: [
|
|
6440
|
+
{ key: "interpretAs", label: "Interpret as" },
|
|
6441
|
+
{ key: "format", label: "Format" },
|
|
6442
|
+
{ key: "detail", label: "Detail" }
|
|
6443
|
+
],
|
|
6444
|
+
phoneme: [
|
|
6445
|
+
{ key: "alphabet", label: "Alphabet" },
|
|
6446
|
+
{ key: "ph", label: "Pronunciation" }
|
|
6447
|
+
],
|
|
6448
|
+
emphasis: [{ key: "level", label: "Level" }],
|
|
6449
|
+
audio: [
|
|
6450
|
+
{ key: "src", label: "Source URL" },
|
|
6451
|
+
{ key: "desc", label: "Description" },
|
|
6452
|
+
{ key: "clipBegin", label: "Clip begin" },
|
|
6453
|
+
{ key: "clipEnd", label: "Clip end" },
|
|
6454
|
+
{ key: "speed", label: "Speed" },
|
|
6455
|
+
{ key: "repeatCount", label: "Repeat count" },
|
|
6456
|
+
{ key: "repeatDuration", label: "Repeat duration" },
|
|
6457
|
+
{ key: "soundLevel", label: "Sound level" }
|
|
6458
|
+
],
|
|
6459
|
+
mark: [{ key: "name", label: "Mark name" }],
|
|
6460
|
+
bookmark: [{ key: "mark", label: "Bookmark name" }],
|
|
6461
|
+
sub: [{ key: "alias", label: "Alias" }],
|
|
6462
|
+
lang: [{ key: "lang", label: "Language" }],
|
|
6463
|
+
lexicon: [{ key: "uri", label: "Lexicon URI" }],
|
|
6464
|
+
"mstts:silence": [
|
|
6465
|
+
{ key: "typeValue", label: "Silence type" },
|
|
6466
|
+
{ key: "value", label: "Value" }
|
|
6467
|
+
],
|
|
6468
|
+
silence: [
|
|
6469
|
+
{ key: "typeValue", label: "Silence type" },
|
|
6470
|
+
{ key: "value", label: "Value" }
|
|
6471
|
+
],
|
|
6472
|
+
"mstts:audioduration": [{ key: "value", label: "Duration" }],
|
|
6473
|
+
"mstts:ttsembedding": [{ key: "speakerProfileId", label: "Speaker profile ID" }],
|
|
6474
|
+
"mstts:embedding": [
|
|
6475
|
+
{ key: "id", label: "Embedding ID" },
|
|
6476
|
+
{ key: "speakerProfileId", label: "Speaker profile ID" }
|
|
6477
|
+
],
|
|
6478
|
+
"mstts:voiceconversion": [
|
|
6479
|
+
{ key: "url", label: "Source URL" },
|
|
6480
|
+
{ key: "profile", label: "Profile" },
|
|
6481
|
+
{ key: "speakerProfileId", label: "Speaker profile ID" }
|
|
6482
|
+
]
|
|
6483
|
+
};
|
|
6484
|
+
function getElementFields(element) {
|
|
6485
|
+
return VISUAL_ELEMENT_FIELDS[elementLabel(element)] ?? [];
|
|
6486
|
+
}
|
|
6487
|
+
function VisualElementInspector({
|
|
6488
|
+
document: document2,
|
|
6489
|
+
element,
|
|
6490
|
+
path,
|
|
6491
|
+
readOnly,
|
|
6492
|
+
commit
|
|
6493
|
+
}) {
|
|
6494
|
+
const fields = getElementFields(element);
|
|
6495
|
+
return /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, `<${elementLabel(element)}>`), fields.length === 0 ? /* @__PURE__ */ React.createElement("p", null, "This element is preserved in the visual tree. Edit its attributes in Code mode.") : fields.map((field) => {
|
|
6496
|
+
const value = element[field.key];
|
|
6497
|
+
const inputValue = value === void 0 ? "" : String(value);
|
|
6498
|
+
const inputId = `ssml-visual-${field.key}`;
|
|
6499
|
+
return /* @__PURE__ */ React.createElement("label", { key: field.key, htmlFor: inputId }, field.label, field.multiline ? /* @__PURE__ */ React.createElement(
|
|
6500
|
+
"textarea",
|
|
6501
|
+
{
|
|
6502
|
+
id: inputId,
|
|
6503
|
+
value: inputValue,
|
|
6504
|
+
readOnly,
|
|
6505
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, path, field.key, event.target.value))
|
|
6506
|
+
}
|
|
6507
|
+
) : /* @__PURE__ */ React.createElement(
|
|
6508
|
+
"input",
|
|
6509
|
+
{
|
|
6510
|
+
id: inputId,
|
|
6511
|
+
value: inputValue,
|
|
6512
|
+
readOnly,
|
|
6513
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, path, field.key, event.target.value))
|
|
6514
|
+
}
|
|
6515
|
+
));
|
|
6516
|
+
}));
|
|
6517
|
+
}
|
|
6518
|
+
function getElementAncestors(nodes, path, ancestors = []) {
|
|
6519
|
+
const [index, ...rest] = path;
|
|
6520
|
+
const node = nodes[index];
|
|
6521
|
+
if (!node || !isElement(node)) return ancestors;
|
|
6522
|
+
if (rest.length === 0) return ancestors;
|
|
6523
|
+
return getElementAncestors(node.children ?? [], rest, [...ancestors, node]);
|
|
6524
|
+
}
|
|
6525
|
+
function buildVisualPreview(document2, leaf, selection) {
|
|
6526
|
+
const start = Math.max(0, Math.min(selection.start, leaf.value.length));
|
|
6527
|
+
const end = Math.max(start, Math.min(selection.end, leaf.value.length));
|
|
6528
|
+
const value = start === end ? leaf.value : leaf.value.slice(start, end);
|
|
6529
|
+
const ancestors = getElementAncestors(document2.children ?? [], leaf.path);
|
|
6530
|
+
const children = ancestors.reduceRight(
|
|
6531
|
+
(current, ancestor) => [{ ...ancestor, children: current }],
|
|
6532
|
+
[value]
|
|
6533
|
+
);
|
|
6534
|
+
return buildSsml({ ...document2, children });
|
|
6535
|
+
}
|
|
6536
|
+
function addDialogTurn(document2, path) {
|
|
6537
|
+
return updateElementAtPath(document2, path, (element) => ({
|
|
6538
|
+
...element,
|
|
6539
|
+
children: [
|
|
6540
|
+
...element.children ?? [],
|
|
6541
|
+
{ type: "mstts:turn", voice: "en-US-JennyNeural", children: ["New dialog turn"] }
|
|
6542
|
+
]
|
|
6543
|
+
}));
|
|
6544
|
+
}
|
|
6298
6545
|
function wrapTextAtPath(document2, path, start, end, tag, attributes) {
|
|
6299
6546
|
return {
|
|
6300
6547
|
...document2,
|
|
@@ -6346,6 +6593,7 @@ function VisualSsmlEditor({
|
|
|
6346
6593
|
const [selection, setSelection] = (0, import_react3.useState)({ start: 0, end: 0 });
|
|
6347
6594
|
const textLeaves = (0, import_react3.useMemo)(() => collectTextLeaves(document2.children ?? []), [document2]);
|
|
6348
6595
|
const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
|
|
6596
|
+
const selectedElement = selectedPath ? getNodeAtPath(document2.children ?? [], selectedPath) : void 0;
|
|
6349
6597
|
const diagnostics = (0, import_react3.useMemo)(() => validateAzureSsml(buildSsml(document2)), [document2]);
|
|
6350
6598
|
const commit = (nextDocument) => onChange?.(nextDocument);
|
|
6351
6599
|
const applyWrapper = (tag, attributes) => {
|
|
@@ -6363,7 +6611,79 @@ function VisualSsmlEditor({
|
|
|
6363
6611
|
selectedPath,
|
|
6364
6612
|
onSelect: setSelectedPath
|
|
6365
6613
|
}
|
|
6366
|
-
)))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-form" },
|
|
6614
|
+
)))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-form" }, selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:dialog" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, "Dialog"), /* @__PURE__ */ React.createElement("p", null, "Add and edit speaker turns in this dialog."), /* @__PURE__ */ React.createElement(
|
|
6615
|
+
"button",
|
|
6616
|
+
{
|
|
6617
|
+
type: "button",
|
|
6618
|
+
disabled: readOnly,
|
|
6619
|
+
onClick: () => commit(addDialogTurn(document2, selectedPath ?? []))
|
|
6620
|
+
},
|
|
6621
|
+
"Add turn"
|
|
6622
|
+
)) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:turn" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, "Dialog turn"), /* @__PURE__ */ React.createElement("label", null, "Voice", /* @__PURE__ */ React.createElement(
|
|
6623
|
+
"input",
|
|
6624
|
+
{
|
|
6625
|
+
value: selectedElement.voice ?? "",
|
|
6626
|
+
readOnly,
|
|
6627
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "voice", event.target.value))
|
|
6628
|
+
}
|
|
6629
|
+
)), /* @__PURE__ */ React.createElement("label", null, "Speaker", /* @__PURE__ */ React.createElement(
|
|
6630
|
+
"input",
|
|
6631
|
+
{
|
|
6632
|
+
value: selectedElement.speaker ?? "",
|
|
6633
|
+
readOnly,
|
|
6634
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "speaker", event.target.value))
|
|
6635
|
+
}
|
|
6636
|
+
)), /* @__PURE__ */ React.createElement("label", null, "Turn text", /* @__PURE__ */ React.createElement(
|
|
6637
|
+
"textarea",
|
|
6638
|
+
{
|
|
6639
|
+
value: selectedElement.children?.filter((child) => typeof child === "string").join("") ?? "",
|
|
6640
|
+
readOnly,
|
|
6641
|
+
onChange: (event) => commit(
|
|
6642
|
+
updateElementAtPath(
|
|
6643
|
+
document2,
|
|
6644
|
+
selectedPath ?? [],
|
|
6645
|
+
(element) => updateElementText(element, event.target.value)
|
|
6646
|
+
)
|
|
6647
|
+
)
|
|
6648
|
+
}
|
|
6649
|
+
))) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:backgroundaudio" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, "Background audio"), /* @__PURE__ */ React.createElement("label", null, "Source URL", /* @__PURE__ */ React.createElement(
|
|
6650
|
+
"input",
|
|
6651
|
+
{
|
|
6652
|
+
value: selectedElement.src ?? "",
|
|
6653
|
+
readOnly,
|
|
6654
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "src", event.target.value))
|
|
6655
|
+
}
|
|
6656
|
+
)), /* @__PURE__ */ React.createElement("label", null, "Volume", /* @__PURE__ */ React.createElement(
|
|
6657
|
+
"input",
|
|
6658
|
+
{
|
|
6659
|
+
value: selectedElement.volume === void 0 ? "" : String(selectedElement.volume),
|
|
6660
|
+
readOnly,
|
|
6661
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "volume", event.target.value))
|
|
6662
|
+
}
|
|
6663
|
+
)), /* @__PURE__ */ React.createElement("label", null, "Fade in", /* @__PURE__ */ React.createElement(
|
|
6664
|
+
"input",
|
|
6665
|
+
{
|
|
6666
|
+
value: selectedElement.fadeIn === void 0 ? "" : String(selectedElement.fadeIn),
|
|
6667
|
+
readOnly,
|
|
6668
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "fadeIn", event.target.value))
|
|
6669
|
+
}
|
|
6670
|
+
)), /* @__PURE__ */ React.createElement("label", null, "Fade out", /* @__PURE__ */ React.createElement(
|
|
6671
|
+
"input",
|
|
6672
|
+
{
|
|
6673
|
+
value: selectedElement.fadeOut === void 0 ? "" : String(selectedElement.fadeOut),
|
|
6674
|
+
readOnly,
|
|
6675
|
+
onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "fadeOut", event.target.value))
|
|
6676
|
+
}
|
|
6677
|
+
))) : selectedElement && isElement(selectedElement) ? /* @__PURE__ */ React.createElement(
|
|
6678
|
+
VisualElementInspector,
|
|
6679
|
+
{
|
|
6680
|
+
document: document2,
|
|
6681
|
+
element: selectedElement,
|
|
6682
|
+
path: selectedPath ?? [],
|
|
6683
|
+
readOnly,
|
|
6684
|
+
commit
|
|
6685
|
+
}
|
|
6686
|
+
) : selectedLeaf ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, "Text", /* @__PURE__ */ React.createElement(
|
|
6367
6687
|
"textarea",
|
|
6368
6688
|
{
|
|
6369
6689
|
value: selectedLeaf.value,
|
|
@@ -6391,7 +6711,7 @@ function VisualSsmlEditor({
|
|
|
6391
6711
|
"button",
|
|
6392
6712
|
{
|
|
6393
6713
|
type: "button",
|
|
6394
|
-
onClick: () => onPreviewSelection(
|
|
6714
|
+
onClick: () => onPreviewSelection(buildVisualPreview(document2, selectedLeaf, selection))
|
|
6395
6715
|
},
|
|
6396
6716
|
"Preview selection"
|
|
6397
6717
|
))) : /* @__PURE__ */ React.createElement("p", null, "Select an element or text node to edit it."))));
|