ssml-builder-js 2.8.0 → 2.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/README.md +95 -17
- package/dist/{chunk-TKD3PU5Y.mjs → chunk-7LCSH4SZ.mjs} +610 -124
- package/dist/chunk-7LCSH4SZ.mjs.map +1 -0
- package/dist/{chunk-4RQETJUI.mjs → chunk-JNJVTEL6.mjs} +939 -10
- package/dist/chunk-JNJVTEL6.mjs.map +1 -0
- package/dist/core.d.mts +97 -3
- package/dist/core.d.ts +97 -3
- package/dist/core.js +616 -124
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +15 -3
- package/dist/elements.d.mts +10 -1
- package/dist/elements.d.ts +10 -1
- package/dist/elements.js +1245 -15
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +316 -9
- package/dist/elements.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +616 -124
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +15 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +47 -6
- package/dist/react.d.ts +47 -6
- package/dist/react.js +1219 -25
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +280 -10
- package/dist/react.mjs.map +1 -1
- package/package.json +4 -3
- package/dist/chunk-4RQETJUI.mjs.map +0 -1
- package/dist/chunk-TKD3PU5Y.mjs.map +0 -1
package/dist/react.js
CHANGED
|
@@ -36,6 +36,7 @@ __export(react_exports, {
|
|
|
36
36
|
SSML_HOVER_COPY: () => SSML_HOVER_COPY,
|
|
37
37
|
SSML_INSERTIONS: () => SSML_INSERTIONS,
|
|
38
38
|
SsmlEditor: () => SsmlEditor,
|
|
39
|
+
VisualSsmlEditor: () => VisualSsmlEditor,
|
|
39
40
|
createSsmlEditorInsertionDefinition: () => createSsmlEditorInsertionDefinition,
|
|
40
41
|
registerSsmlCodeLens: () => registerSsmlCodeLens,
|
|
41
42
|
updateTagAttribute: () => updateTagAttribute
|
|
@@ -43,8 +44,8 @@ __export(react_exports, {
|
|
|
43
44
|
module.exports = __toCommonJS(react_exports);
|
|
44
45
|
|
|
45
46
|
// packages/ssml-editor-react/src/SsmlEditor.tsx
|
|
46
|
-
var
|
|
47
|
-
var
|
|
47
|
+
var import_react4 = require("react");
|
|
48
|
+
var import_react5 = __toESM(require("@monaco-editor/react"));
|
|
48
49
|
|
|
49
50
|
// packages/ssml-editor-react/src/buttonVisibility.ts
|
|
50
51
|
function isSsmlEditorButtonVisible(buttonVisibility, button) {
|
|
@@ -54,6 +55,8 @@ function isSsmlEditorButtonVisible(buttonVisibility, button) {
|
|
|
54
55
|
// packages/ssml-editor-react/src/constants/ssmlPresets.ts
|
|
55
56
|
var ssmlPresets_exports = {};
|
|
56
57
|
__export(ssmlPresets_exports, {
|
|
58
|
+
AUDIO_DURATION_DESCRIPTIONS: () => AUDIO_DURATION_DESCRIPTIONS,
|
|
59
|
+
AUDIO_DURATION_PRESETS: () => AUDIO_DURATION_PRESETS,
|
|
57
60
|
BREAK_STRENGTH_PRESETS: () => BREAK_STRENGTH_PRESETS,
|
|
58
61
|
BREAK_TIME_DESCRIPTIONS: () => BREAK_TIME_DESCRIPTIONS,
|
|
59
62
|
BREAK_TIME_PRESETS: () => BREAK_TIME_PRESETS,
|
|
@@ -86,6 +89,103 @@ __export(ssmlPresets_exports, {
|
|
|
86
89
|
getExpressAsStyleCategory: () => getExpressAsStyleCategory,
|
|
87
90
|
resolveExpressAsStyles: () => resolveExpressAsStyles
|
|
88
91
|
});
|
|
92
|
+
|
|
93
|
+
// packages/ssml-editor-react/src/constants/azureVoiceStyleMap.generated.ts
|
|
94
|
+
var AZURE_VOICE_STYLE_MAP = {
|
|
95
|
+
"de-DE-ConradNeural": ["cheerful", "sad"],
|
|
96
|
+
"de-DE-KatjaNeural": ["cheerful", "sad"],
|
|
97
|
+
"en-US-AndrewNeural": ["empathetic", "relieved"],
|
|
98
|
+
"en-US-GuyNeural": [
|
|
99
|
+
"angry",
|
|
100
|
+
"cheerful",
|
|
101
|
+
"excited",
|
|
102
|
+
"friendly",
|
|
103
|
+
"hopeful",
|
|
104
|
+
"newscast",
|
|
105
|
+
"sad",
|
|
106
|
+
"shouting",
|
|
107
|
+
"terrified",
|
|
108
|
+
"unfriendly",
|
|
109
|
+
"whispering"
|
|
110
|
+
],
|
|
111
|
+
"en-US-JennyMultilingualNeural": [
|
|
112
|
+
"cheerful",
|
|
113
|
+
"empathetic",
|
|
114
|
+
"excited",
|
|
115
|
+
"friendly",
|
|
116
|
+
"hopeful",
|
|
117
|
+
"sad",
|
|
118
|
+
"shouting",
|
|
119
|
+
"terrified",
|
|
120
|
+
"unfriendly",
|
|
121
|
+
"whispering"
|
|
122
|
+
],
|
|
123
|
+
"en-US-JennyNeural": [
|
|
124
|
+
"assistant",
|
|
125
|
+
"chat",
|
|
126
|
+
"customerservice",
|
|
127
|
+
"newscast",
|
|
128
|
+
"cheerful",
|
|
129
|
+
"empathetic",
|
|
130
|
+
"excited",
|
|
131
|
+
"friendly",
|
|
132
|
+
"hopeful",
|
|
133
|
+
"sad",
|
|
134
|
+
"shouting",
|
|
135
|
+
"terrified",
|
|
136
|
+
"unfriendly",
|
|
137
|
+
"whispering"
|
|
138
|
+
],
|
|
139
|
+
"es-ES-ElviraNeural": [],
|
|
140
|
+
"fil-PH-AngeloNeural": [],
|
|
141
|
+
"fr-FR-DeniseNeural": ["cheerful", "sad"],
|
|
142
|
+
"fr-FR-HenriNeural": ["cheerful", "sad"],
|
|
143
|
+
"id-ID-GadisNeural": [],
|
|
144
|
+
"it-IT-ElsaNeural": ["cheerful", "sad"],
|
|
145
|
+
"ja-JP-KeitaNeural": ["chat"],
|
|
146
|
+
"ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
|
|
147
|
+
"ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
148
|
+
"ko-KR-SunHiNeural": ["cheerful", "sad"],
|
|
149
|
+
"ms-MY-YasminNeural": [],
|
|
150
|
+
"pt-BR-FranciscaNeural": ["calm"],
|
|
151
|
+
"ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"],
|
|
152
|
+
"th-TH-PremwadeeNeural": [],
|
|
153
|
+
"vi-VN-HoaiMyNeural": [],
|
|
154
|
+
"zh-CN-XiaoxiaoNeural": [
|
|
155
|
+
"assistant",
|
|
156
|
+
"chat",
|
|
157
|
+
"customerservice",
|
|
158
|
+
"newscast",
|
|
159
|
+
"cheerful",
|
|
160
|
+
"empathetic",
|
|
161
|
+
"excited",
|
|
162
|
+
"friendly",
|
|
163
|
+
"hopeful",
|
|
164
|
+
"sad",
|
|
165
|
+
"terrified",
|
|
166
|
+
"whispering",
|
|
167
|
+
"poetry-reading",
|
|
168
|
+
"sports_commentary",
|
|
169
|
+
"sports_commentary_excited",
|
|
170
|
+
"story"
|
|
171
|
+
],
|
|
172
|
+
"zh-CN-YunxiNeural": [
|
|
173
|
+
"narration-relaxed",
|
|
174
|
+
"embarrassed",
|
|
175
|
+
"fearful",
|
|
176
|
+
"sad",
|
|
177
|
+
"disgruntled",
|
|
178
|
+
"serious",
|
|
179
|
+
"angry",
|
|
180
|
+
"depressed",
|
|
181
|
+
"chat",
|
|
182
|
+
"cheerful",
|
|
183
|
+
"assistant"
|
|
184
|
+
],
|
|
185
|
+
"zh-TW-HsiaoChenNeural": []
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// packages/ssml-editor-react/src/constants/ssmlPresets.ts
|
|
89
189
|
var SSML_PRESETS = [
|
|
90
190
|
{
|
|
91
191
|
id: "basic",
|
|
@@ -185,7 +285,7 @@ function getExpressAsStyleCategory(style) {
|
|
|
185
285
|
}
|
|
186
286
|
return "other";
|
|
187
287
|
}
|
|
188
|
-
var
|
|
288
|
+
var LEGACY_VOICE_STYLE_MAP = {
|
|
189
289
|
"ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
|
|
190
290
|
"ja-JP-KeitaNeural": ["chat"],
|
|
191
291
|
"ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
@@ -271,6 +371,7 @@ var VOICE_STYLE_MAP = {
|
|
|
271
371
|
"de-DE-ConradNeural": ["cheerful", "sad"],
|
|
272
372
|
"ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
273
373
|
};
|
|
374
|
+
var VOICE_STYLE_MAP = Object.keys(AZURE_VOICE_STYLE_MAP).length > 0 ? AZURE_VOICE_STYLE_MAP : LEGACY_VOICE_STYLE_MAP;
|
|
274
375
|
var VOICE_STYLE_MAP_BY_NORMALIZED_NAME = new Map(
|
|
275
376
|
Object.entries(VOICE_STYLE_MAP).map(([voiceName, styles]) => [voiceName.toLowerCase(), styles])
|
|
276
377
|
);
|
|
@@ -313,6 +414,7 @@ var SAY_AS_PRESETS = [
|
|
|
313
414
|
];
|
|
314
415
|
var LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"];
|
|
315
416
|
var SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"];
|
|
417
|
+
var AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"];
|
|
316
418
|
var SILENCE_TYPE_PRESETS = [
|
|
317
419
|
"Leading",
|
|
318
420
|
"Tailing",
|
|
@@ -321,6 +423,11 @@ var SILENCE_TYPE_PRESETS = [
|
|
|
321
423
|
"Semicolon",
|
|
322
424
|
"Enumerationcomma"
|
|
323
425
|
];
|
|
426
|
+
var AUDIO_DURATION_DESCRIPTIONS = {
|
|
427
|
+
"5s": { ja: "5\u79D2", en: "5 seconds" },
|
|
428
|
+
"10s": { ja: "10\u79D2", en: "10 seconds" },
|
|
429
|
+
"30s": { ja: "30\u79D2", en: "30 seconds" }
|
|
430
|
+
};
|
|
324
431
|
var PHONEME_ALPHABET_PRESETS = ["ipa", "sapi", "ups", "x-sampa"];
|
|
325
432
|
var VISEME_TYPE_PRESETS = ["redlips_front", "FacialExpression"];
|
|
326
433
|
var SSML_ATTRIBUTE_PRESETS = {
|
|
@@ -364,6 +471,14 @@ var SSML_ATTRIBUTE_PRESETS = {
|
|
|
364
471
|
type: SILENCE_TYPE_PRESETS,
|
|
365
472
|
value: SILENCE_VALUE_PRESETS
|
|
366
473
|
},
|
|
474
|
+
"mstts:audioduration": {
|
|
475
|
+
value: AUDIO_DURATION_PRESETS
|
|
476
|
+
},
|
|
477
|
+
"mstts:backgroundaudio": {
|
|
478
|
+
volume: PROSODY_VOLUME_PRESETS,
|
|
479
|
+
fadein: AUDIO_DURATION_PRESETS,
|
|
480
|
+
fadeout: AUDIO_DURATION_PRESETS
|
|
481
|
+
},
|
|
367
482
|
silence: {
|
|
368
483
|
type: SILENCE_TYPE_PRESETS,
|
|
369
484
|
value: SILENCE_VALUE_PRESETS
|
|
@@ -910,6 +1025,13 @@ var SSML_HOVER_COPY = {
|
|
|
910
1025
|
parameters: {
|
|
911
1026
|
type: { title: "type", description: "\u30D3\u30BC\u30FC\u30E0\u30A4\u30D9\u30F3\u30C8\u306E\u5F62\u5F0F\u3002" }
|
|
912
1027
|
}
|
|
1028
|
+
},
|
|
1029
|
+
"mstts:audioduration": {
|
|
1030
|
+
title: "\u97F3\u58F0\u9577",
|
|
1031
|
+
description: "\u5408\u6210\u97F3\u58F0\u306E\u76EE\u6A19\u6642\u9593\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
|
|
1032
|
+
parameters: {
|
|
1033
|
+
value: { title: "value", description: "\u76EE\u6A19\u6642\u9593\u3002\u4F8B: `10s`\u3001`5000ms`\u3001`00:00:10`\u3002" }
|
|
1034
|
+
}
|
|
913
1035
|
}
|
|
914
1036
|
}
|
|
915
1037
|
},
|
|
@@ -1067,6 +1189,13 @@ var SSML_HOVER_COPY = {
|
|
|
1067
1189
|
parameters: {
|
|
1068
1190
|
type: { title: "type", description: "The viseme event format." }
|
|
1069
1191
|
}
|
|
1192
|
+
},
|
|
1193
|
+
"mstts:audioduration": {
|
|
1194
|
+
title: "Audio duration",
|
|
1195
|
+
description: "Sets the target duration of synthesized audio.",
|
|
1196
|
+
parameters: {
|
|
1197
|
+
value: { title: "value", description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`." }
|
|
1198
|
+
}
|
|
1070
1199
|
}
|
|
1071
1200
|
}
|
|
1072
1201
|
}
|
|
@@ -1166,6 +1295,14 @@ var editorStyles = {
|
|
|
1166
1295
|
display: "grid",
|
|
1167
1296
|
gap: "0.5rem"
|
|
1168
1297
|
},
|
|
1298
|
+
visual: {
|
|
1299
|
+
display: "grid",
|
|
1300
|
+
gap: "0.75rem",
|
|
1301
|
+
minHeight: "8rem",
|
|
1302
|
+
padding: "0.75rem",
|
|
1303
|
+
border: "1px solid var(--ssml-editor-control-border)",
|
|
1304
|
+
borderRadius: "0.25rem"
|
|
1305
|
+
},
|
|
1169
1306
|
toolbarIconOnly: {
|
|
1170
1307
|
justifyContent: "center",
|
|
1171
1308
|
minWidth: "2.25rem",
|
|
@@ -1570,7 +1707,14 @@ var SSML_TAGS = {
|
|
|
1570
1707
|
MSTTS_SILENCE: "mstts:silence",
|
|
1571
1708
|
SILENCE: "silence",
|
|
1572
1709
|
MSTTS_VISEME: "mstts:viseme",
|
|
1573
|
-
VISEME: "viseme"
|
|
1710
|
+
VISEME: "viseme",
|
|
1711
|
+
MSTTS_AUDIO_DURATION: "mstts:audioduration",
|
|
1712
|
+
MSTTS_DIALOG: "mstts:dialog",
|
|
1713
|
+
MSTTS_TURN: "mstts:turn",
|
|
1714
|
+
MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
|
|
1715
|
+
MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
|
|
1716
|
+
MSTTS_EMBEDDING: "mstts:embedding",
|
|
1717
|
+
MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
|
|
1574
1718
|
};
|
|
1575
1719
|
var SSML_ATTRS = {
|
|
1576
1720
|
VERSION: "version",
|
|
@@ -1579,6 +1723,7 @@ var SSML_ATTRS = {
|
|
|
1579
1723
|
LANG: "lang",
|
|
1580
1724
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
1581
1725
|
NAME: "name",
|
|
1726
|
+
VOICE: "voice",
|
|
1582
1727
|
EFFECT: "effect",
|
|
1583
1728
|
RATE: "rate",
|
|
1584
1729
|
PITCH: "pitch",
|
|
@@ -1610,7 +1755,9 @@ var SSML_ATTRS = {
|
|
|
1610
1755
|
MARK: "mark",
|
|
1611
1756
|
URI: "uri",
|
|
1612
1757
|
TYPE: "type",
|
|
1613
|
-
VALUE: "value"
|
|
1758
|
+
VALUE: "value",
|
|
1759
|
+
FADE_IN: "fadein",
|
|
1760
|
+
FADE_OUT: "fadeout"
|
|
1614
1761
|
};
|
|
1615
1762
|
function escapeText(value) {
|
|
1616
1763
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -1697,6 +1844,23 @@ function getAttributes(element) {
|
|
|
1697
1844
|
case SSML_TAGS.VISEME:
|
|
1698
1845
|
addAttribute(attributes, SSML_ATTRS.TYPE, element.typeValue ?? element.visemeType);
|
|
1699
1846
|
break;
|
|
1847
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
1848
|
+
addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
|
|
1849
|
+
break;
|
|
1850
|
+
case SSML_TAGS.MSTTS_TURN:
|
|
1851
|
+
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
1852
|
+
break;
|
|
1853
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
1854
|
+
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
1855
|
+
addAttribute(attributes, SSML_ATTRS.VOLUME, element.volume);
|
|
1856
|
+
addAttribute(attributes, SSML_ATTRS.FADE_IN, element.fadeIn ?? element.fadein);
|
|
1857
|
+
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
1858
|
+
break;
|
|
1859
|
+
case SSML_TAGS.MSTTS_DIALOG:
|
|
1860
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
1861
|
+
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
1862
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
1863
|
+
break;
|
|
1700
1864
|
case SSML_TAGS.PARAGRAPH:
|
|
1701
1865
|
case SSML_TAGS.SENTENCE:
|
|
1702
1866
|
case SSML_TAGS.WORD:
|
|
@@ -1721,6 +1885,8 @@ function getTagName(element) {
|
|
|
1721
1885
|
case SSML_TAGS.VISEME:
|
|
1722
1886
|
case SSML_TAGS.MSTTS_VISEME:
|
|
1723
1887
|
return SSML_TAGS.MSTTS_VISEME;
|
|
1888
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
1889
|
+
return SSML_TAGS.MSTTS_AUDIO_DURATION;
|
|
1724
1890
|
case "element":
|
|
1725
1891
|
case "custom":
|
|
1726
1892
|
return element.name;
|
|
@@ -2253,6 +2419,46 @@ function convertElement(node) {
|
|
|
2253
2419
|
if (typeValue !== void 0) element.typeValue = typeValue;
|
|
2254
2420
|
return finishElement(element, node, attributes);
|
|
2255
2421
|
}
|
|
2422
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION: {
|
|
2423
|
+
const element = { type: SSML_TAGS.MSTTS_AUDIO_DURATION };
|
|
2424
|
+
const value = readAttribute(attributes, SSML_ATTRS.VALUE);
|
|
2425
|
+
if (value !== void 0) element.value = value;
|
|
2426
|
+
return finishElement(element, node, attributes);
|
|
2427
|
+
}
|
|
2428
|
+
case SSML_TAGS.MSTTS_DIALOG: {
|
|
2429
|
+
const element = { type: SSML_TAGS.MSTTS_DIALOG };
|
|
2430
|
+
return finishElement(element, node, attributes);
|
|
2431
|
+
}
|
|
2432
|
+
case SSML_TAGS.MSTTS_TURN: {
|
|
2433
|
+
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
2434
|
+
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
2435
|
+
if (voice !== void 0) element.voice = voice;
|
|
2436
|
+
return finishElement(element, node, attributes);
|
|
2437
|
+
}
|
|
2438
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
2439
|
+
const element = { type: SSML_TAGS.MSTTS_BACKGROUND_AUDIO };
|
|
2440
|
+
const src = readAttribute(attributes, SSML_ATTRS.SRC);
|
|
2441
|
+
const volume = readAttribute(attributes, SSML_ATTRS.VOLUME);
|
|
2442
|
+
const fadeIn = readAttribute(attributes, SSML_ATTRS.FADE_IN);
|
|
2443
|
+
const fadeOut = readAttribute(attributes, SSML_ATTRS.FADE_OUT);
|
|
2444
|
+
if (src !== void 0) element.src = src;
|
|
2445
|
+
if (volume !== void 0) element.volume = volume;
|
|
2446
|
+
if (fadeIn !== void 0) element.fadeIn = fadeIn;
|
|
2447
|
+
if (fadeOut !== void 0) element.fadeOut = fadeOut;
|
|
2448
|
+
return finishElement(element, node, attributes);
|
|
2449
|
+
}
|
|
2450
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
2451
|
+
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
2452
|
+
return finishElement(element, node, attributes);
|
|
2453
|
+
}
|
|
2454
|
+
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
2455
|
+
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
2456
|
+
return finishElement(element, node, attributes);
|
|
2457
|
+
}
|
|
2458
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
2459
|
+
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
2460
|
+
return finishElement(element, node, attributes);
|
|
2461
|
+
}
|
|
2256
2462
|
default: {
|
|
2257
2463
|
const element = {
|
|
2258
2464
|
name: node.name,
|
|
@@ -2373,6 +2579,644 @@ function validateSsml(xmlString) {
|
|
|
2373
2579
|
};
|
|
2374
2580
|
}
|
|
2375
2581
|
}
|
|
2582
|
+
var AZURE_VOICE_DEFINITIONS = [
|
|
2583
|
+
{ name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
2584
|
+
{ name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
2585
|
+
{ name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
|
|
2586
|
+
{
|
|
2587
|
+
name: "en-US-GuyNeural",
|
|
2588
|
+
locale: "en-US",
|
|
2589
|
+
styles: [
|
|
2590
|
+
"angry",
|
|
2591
|
+
"cheerful",
|
|
2592
|
+
"excited",
|
|
2593
|
+
"friendly",
|
|
2594
|
+
"hopeful",
|
|
2595
|
+
"newscast",
|
|
2596
|
+
"sad",
|
|
2597
|
+
"shouting",
|
|
2598
|
+
"terrified",
|
|
2599
|
+
"unfriendly",
|
|
2600
|
+
"whispering"
|
|
2601
|
+
]
|
|
2602
|
+
},
|
|
2603
|
+
{
|
|
2604
|
+
name: "en-US-JennyMultilingualNeural",
|
|
2605
|
+
locale: "en-US",
|
|
2606
|
+
styles: [
|
|
2607
|
+
"cheerful",
|
|
2608
|
+
"empathetic",
|
|
2609
|
+
"excited",
|
|
2610
|
+
"friendly",
|
|
2611
|
+
"hopeful",
|
|
2612
|
+
"sad",
|
|
2613
|
+
"shouting",
|
|
2614
|
+
"terrified",
|
|
2615
|
+
"unfriendly",
|
|
2616
|
+
"whispering"
|
|
2617
|
+
]
|
|
2618
|
+
},
|
|
2619
|
+
{
|
|
2620
|
+
name: "en-US-JennyNeural",
|
|
2621
|
+
locale: "en-US",
|
|
2622
|
+
styles: [
|
|
2623
|
+
"assistant",
|
|
2624
|
+
"chat",
|
|
2625
|
+
"customerservice",
|
|
2626
|
+
"newscast",
|
|
2627
|
+
"cheerful",
|
|
2628
|
+
"empathetic",
|
|
2629
|
+
"excited",
|
|
2630
|
+
"friendly",
|
|
2631
|
+
"hopeful",
|
|
2632
|
+
"sad",
|
|
2633
|
+
"shouting",
|
|
2634
|
+
"terrified",
|
|
2635
|
+
"unfriendly",
|
|
2636
|
+
"whispering"
|
|
2637
|
+
]
|
|
2638
|
+
},
|
|
2639
|
+
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
2640
|
+
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
2641
|
+
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
2642
|
+
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
2643
|
+
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
2644
|
+
{ name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
|
|
2645
|
+
{ name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
|
|
2646
|
+
{ name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
|
|
2647
|
+
{ name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
|
|
2648
|
+
{ name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
|
|
2649
|
+
{ name: "ms-MY-YasminNeural", locale: "ms-MY" },
|
|
2650
|
+
{ name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
|
|
2651
|
+
{
|
|
2652
|
+
name: "ru-RU-SvetlanaNeural",
|
|
2653
|
+
locale: "ru-RU",
|
|
2654
|
+
styles: ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
2655
|
+
},
|
|
2656
|
+
{ name: "th-TH-PremwadeeNeural", locale: "th-TH" },
|
|
2657
|
+
{ name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
|
|
2658
|
+
{
|
|
2659
|
+
name: "zh-CN-XiaoxiaoNeural",
|
|
2660
|
+
locale: "zh-CN",
|
|
2661
|
+
styles: [
|
|
2662
|
+
"assistant",
|
|
2663
|
+
"chat",
|
|
2664
|
+
"customerservice",
|
|
2665
|
+
"newscast",
|
|
2666
|
+
"cheerful",
|
|
2667
|
+
"empathetic",
|
|
2668
|
+
"excited",
|
|
2669
|
+
"friendly",
|
|
2670
|
+
"hopeful",
|
|
2671
|
+
"sad",
|
|
2672
|
+
"terrified",
|
|
2673
|
+
"whispering",
|
|
2674
|
+
"poetry-reading",
|
|
2675
|
+
"sports_commentary",
|
|
2676
|
+
"sports_commentary_excited",
|
|
2677
|
+
"story"
|
|
2678
|
+
]
|
|
2679
|
+
},
|
|
2680
|
+
{
|
|
2681
|
+
name: "zh-CN-YunxiNeural",
|
|
2682
|
+
locale: "zh-CN",
|
|
2683
|
+
styles: [
|
|
2684
|
+
"narration-relaxed",
|
|
2685
|
+
"embarrassed",
|
|
2686
|
+
"fearful",
|
|
2687
|
+
"sad",
|
|
2688
|
+
"disgruntled",
|
|
2689
|
+
"serious",
|
|
2690
|
+
"angry",
|
|
2691
|
+
"depressed",
|
|
2692
|
+
"chat",
|
|
2693
|
+
"cheerful",
|
|
2694
|
+
"assistant"
|
|
2695
|
+
]
|
|
2696
|
+
},
|
|
2697
|
+
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
|
|
2698
|
+
];
|
|
2699
|
+
var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
|
|
2700
|
+
var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
|
|
2701
|
+
"characters",
|
|
2702
|
+
"spell-out",
|
|
2703
|
+
"cardinal",
|
|
2704
|
+
"ordinal",
|
|
2705
|
+
"number",
|
|
2706
|
+
"date",
|
|
2707
|
+
"time",
|
|
2708
|
+
"telephone",
|
|
2709
|
+
"fraction",
|
|
2710
|
+
"address",
|
|
2711
|
+
"name",
|
|
2712
|
+
"currency",
|
|
2713
|
+
"number_digit"
|
|
2714
|
+
]);
|
|
2715
|
+
var ALLOWED_ROLES = /* @__PURE__ */ new Set([
|
|
2716
|
+
"Girl",
|
|
2717
|
+
"Boy",
|
|
2718
|
+
"YoungAdultFemale",
|
|
2719
|
+
"YoungAdultMale",
|
|
2720
|
+
"OlderAdultFemale",
|
|
2721
|
+
"OlderAdultMale",
|
|
2722
|
+
"SeniorFemale",
|
|
2723
|
+
"SeniorMale"
|
|
2724
|
+
]);
|
|
2725
|
+
var ALLOWED_EMPHASIS_LEVELS = /* @__PURE__ */ new Set(["strong", "moderate", "reduced", "none"]);
|
|
2726
|
+
var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
|
|
2727
|
+
"Leading",
|
|
2728
|
+
"Tailing",
|
|
2729
|
+
"Sentenceboundary",
|
|
2730
|
+
"Comma",
|
|
2731
|
+
"Semicolon",
|
|
2732
|
+
"Enumerationcomma"
|
|
2733
|
+
]);
|
|
2734
|
+
var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
|
|
2735
|
+
function decodeAttribute(value) {
|
|
2736
|
+
return value.replace(
|
|
2737
|
+
/&(?:amp|apos|gt|lt|quot);/gi,
|
|
2738
|
+
(entity) => ({ "&": "&", "'": "'", ">": ">", "<": "<", """: '"' })[entity.toLowerCase()] ?? entity
|
|
2739
|
+
);
|
|
2740
|
+
}
|
|
2741
|
+
function findTagEnd2(source, start) {
|
|
2742
|
+
let quote = "";
|
|
2743
|
+
for (let index = start; index < source.length; index += 1) {
|
|
2744
|
+
const character = source[index];
|
|
2745
|
+
if (quote) {
|
|
2746
|
+
if (character === quote) quote = "";
|
|
2747
|
+
} else if (character === '"' || character === "'") quote = character;
|
|
2748
|
+
else if (character === ">") return index;
|
|
2749
|
+
}
|
|
2750
|
+
return source.length - 1;
|
|
2751
|
+
}
|
|
2752
|
+
function tokenizeElements(source) {
|
|
2753
|
+
const tokens = [];
|
|
2754
|
+
const openElements = [];
|
|
2755
|
+
let index = 0;
|
|
2756
|
+
while (index < source.length) {
|
|
2757
|
+
const start = source.indexOf("<", index);
|
|
2758
|
+
if (start === -1) break;
|
|
2759
|
+
if (source.startsWith("<!--", start)) {
|
|
2760
|
+
const end2 = source.indexOf("-->", start + 4);
|
|
2761
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
2762
|
+
continue;
|
|
2763
|
+
}
|
|
2764
|
+
if (source.startsWith("<![CDATA[", start)) {
|
|
2765
|
+
const end2 = source.indexOf("]]>", start + 9);
|
|
2766
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
2767
|
+
continue;
|
|
2768
|
+
}
|
|
2769
|
+
if (source.startsWith("<?", start)) {
|
|
2770
|
+
const end2 = source.indexOf("?>", start + 2);
|
|
2771
|
+
index = end2 === -1 ? source.length : end2 + 2;
|
|
2772
|
+
continue;
|
|
2773
|
+
}
|
|
2774
|
+
const end = findTagEnd2(source, start + 1);
|
|
2775
|
+
const raw = source.slice(start, end + 1);
|
|
2776
|
+
if (raw.startsWith("</")) {
|
|
2777
|
+
openElements.pop();
|
|
2778
|
+
index = end + 1;
|
|
2779
|
+
continue;
|
|
2780
|
+
}
|
|
2781
|
+
const nameMatch = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(raw);
|
|
2782
|
+
if (!nameMatch?.[1]) {
|
|
2783
|
+
index = end + 1;
|
|
2784
|
+
continue;
|
|
2785
|
+
}
|
|
2786
|
+
const attributes = /* @__PURE__ */ new Map();
|
|
2787
|
+
const attributeSource = raw.slice(nameMatch[0].length, raw.length - 1).replace(/\/\s*$/, "");
|
|
2788
|
+
const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
|
|
2789
|
+
for (const match of attributeSource.matchAll(attributePattern)) {
|
|
2790
|
+
attributes.set(match[1].toLowerCase(), decodeAttribute(match[3]));
|
|
2791
|
+
}
|
|
2792
|
+
const selfClosing = /\/\s*>$/.test(raw);
|
|
2793
|
+
const parent = openElements[openElements.length - 1];
|
|
2794
|
+
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
2795
|
+
const tokenName = nameMatch[1];
|
|
2796
|
+
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
2797
|
+
tokens.push({
|
|
2798
|
+
attributes,
|
|
2799
|
+
end,
|
|
2800
|
+
name: tokenName,
|
|
2801
|
+
parentName: parent?.name,
|
|
2802
|
+
parentVoiceName,
|
|
2803
|
+
selfClosing,
|
|
2804
|
+
start
|
|
2805
|
+
});
|
|
2806
|
+
if (!selfClosing) {
|
|
2807
|
+
openElements.push({
|
|
2808
|
+
name: tokenName,
|
|
2809
|
+
voiceName: tokenVoiceName
|
|
2810
|
+
});
|
|
2811
|
+
}
|
|
2812
|
+
index = end + 1;
|
|
2813
|
+
}
|
|
2814
|
+
return tokens;
|
|
2815
|
+
}
|
|
2816
|
+
function location(source, offset) {
|
|
2817
|
+
const before = source.slice(0, Math.max(0, offset));
|
|
2818
|
+
const line = before.split("\n").length;
|
|
2819
|
+
return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
|
|
2820
|
+
}
|
|
2821
|
+
function addDiagnostic(diagnostics, source, offset, message, severity = "error", code2) {
|
|
2822
|
+
diagnostics.push({
|
|
2823
|
+
...location(source, offset),
|
|
2824
|
+
message,
|
|
2825
|
+
severity,
|
|
2826
|
+
source: "ssml-static-validator",
|
|
2827
|
+
...code2 ? { code: code2 } : {}
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
function isSupportedProsodyRate(value) {
|
|
2831
|
+
const trimmed = value.trim();
|
|
2832
|
+
if (/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(trimmed)) return true;
|
|
2833
|
+
const multiplier = /^(\d+(?:\.\d+)?)(x)?$/i.exec(trimmed);
|
|
2834
|
+
if (!multiplier) return false;
|
|
2835
|
+
const numericValue = Number(multiplier[1]);
|
|
2836
|
+
return numericValue >= 0.5 && numericValue <= 2;
|
|
2837
|
+
}
|
|
2838
|
+
function isValidAzureAudioDuration(value) {
|
|
2839
|
+
const trimmed = value.trim();
|
|
2840
|
+
const numeric = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(trimmed);
|
|
2841
|
+
if (numeric) return Number(numeric[1]) > 0;
|
|
2842
|
+
const clock = /^(\d{2,}):([0-5]\d):([0-5]\d)(?:\.(\d{1,3}))?$/.exec(trimmed);
|
|
2843
|
+
if (!clock) return false;
|
|
2844
|
+
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
2845
|
+
}
|
|
2846
|
+
function attr(token, name) {
|
|
2847
|
+
return token.attributes.get(name.toLowerCase());
|
|
2848
|
+
}
|
|
2849
|
+
var DEFAULT_LANGUAGE_ALIASES = {
|
|
2850
|
+
"zh-CN": ["zh-Hans"],
|
|
2851
|
+
"zh-TW": ["zh-Hant"]
|
|
2852
|
+
};
|
|
2853
|
+
function canonicalLanguageTag(language) {
|
|
2854
|
+
const trimmed = language.trim();
|
|
2855
|
+
if (!trimmed) return "";
|
|
2856
|
+
try {
|
|
2857
|
+
return new Intl.Locale(trimmed).toString().toLowerCase();
|
|
2858
|
+
} catch {
|
|
2859
|
+
return trimmed.toLowerCase();
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
function createLanguageNormalizer(options) {
|
|
2863
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
2864
|
+
const addAliasGroup = (canonical, values) => {
|
|
2865
|
+
const normalizedCanonical = canonicalLanguageTag(canonical);
|
|
2866
|
+
if (!normalizedCanonical) return;
|
|
2867
|
+
aliases.set(normalizedCanonical, normalizedCanonical);
|
|
2868
|
+
for (const value of values) {
|
|
2869
|
+
const normalizedValue = canonicalLanguageTag(value);
|
|
2870
|
+
if (normalizedValue) aliases.set(normalizedValue, normalizedCanonical);
|
|
2871
|
+
}
|
|
2872
|
+
};
|
|
2873
|
+
for (const [canonical, values] of Object.entries(DEFAULT_LANGUAGE_ALIASES)) addAliasGroup(canonical, values);
|
|
2874
|
+
for (const [canonical, valueOrValues] of Object.entries(options.languageAliases ?? {}))
|
|
2875
|
+
addAliasGroup(canonical, typeof valueOrValues === "string" ? [valueOrValues] : valueOrValues);
|
|
2876
|
+
return (language) => {
|
|
2877
|
+
const customValue = options.normalizeLanguage ? options.normalizeLanguage(language) : language;
|
|
2878
|
+
const normalized = canonicalLanguageTag(customValue);
|
|
2879
|
+
return aliases.get(normalized) ?? normalized;
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
function voiceLocalePrefix(voiceName) {
|
|
2883
|
+
const match = /^(?<language>[A-Za-z]{2,3})-(?<region>[A-Za-z]{2}|\d{3})(?:-|$)/.exec(voiceName.trim());
|
|
2884
|
+
if (!match?.groups) return void 0;
|
|
2885
|
+
const tag = `${match.groups.language}-${match.groups.region}`;
|
|
2886
|
+
return {
|
|
2887
|
+
language: match.groups.language.toLowerCase(),
|
|
2888
|
+
region: match.groups.region.toLowerCase(),
|
|
2889
|
+
tag
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
function definitionFromStyleMap(voiceName, styles) {
|
|
2893
|
+
return {
|
|
2894
|
+
name: voiceName,
|
|
2895
|
+
locale: voiceLocalePrefix(voiceName)?.tag ?? "",
|
|
2896
|
+
styles
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
function normalizeVoiceCatalog(options) {
|
|
2900
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
2901
|
+
for (const definition of AZURE_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
|
|
2902
|
+
for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
2903
|
+
for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
2904
|
+
for (const definition of options.customVoiceDefinitions ?? [])
|
|
2905
|
+
definitions.set(definition.name.toLowerCase(), definition);
|
|
2906
|
+
for (const [voiceName, styles] of Object.entries(options.customVoiceStyleMap ?? {})) {
|
|
2907
|
+
const key = voiceName.toLowerCase();
|
|
2908
|
+
const current = definitions.get(key);
|
|
2909
|
+
definitions.set(key, {
|
|
2910
|
+
...current ?? definitionFromStyleMap(voiceName, styles),
|
|
2911
|
+
name: current?.name ?? voiceName,
|
|
2912
|
+
styles: styles.map((style) => style.toLowerCase())
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
return definitions;
|
|
2916
|
+
}
|
|
2917
|
+
function diagnosticSeverity(policy) {
|
|
2918
|
+
if (policy === "ignore") return void 0;
|
|
2919
|
+
return policy === "error" ? "error" : "warning";
|
|
2920
|
+
}
|
|
2921
|
+
function languagePart(language) {
|
|
2922
|
+
try {
|
|
2923
|
+
return new Intl.Locale(language).language.toLowerCase();
|
|
2924
|
+
} catch {
|
|
2925
|
+
return language.split("-")[0]?.toLowerCase() ?? "";
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
function definitionMatchesLanguage(definition, voiceName, language, normalizeLanguage) {
|
|
2929
|
+
const candidateLanguages = definition ? [definition.locale, ...definition.secondaryLocales ?? []].filter(Boolean) : [voiceLocalePrefix(voiceName)?.tag ?? ""];
|
|
2930
|
+
if (candidateLanguages.length === 0 || !language.trim()) return void 0;
|
|
2931
|
+
const normalizedLanguage = normalizeLanguage(language);
|
|
2932
|
+
const normalizedCandidates = candidateLanguages.map(normalizeLanguage);
|
|
2933
|
+
if (normalizedCandidates.includes(normalizedLanguage)) return true;
|
|
2934
|
+
if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
|
|
2935
|
+
return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
|
|
2936
|
+
}
|
|
2937
|
+
function canonicalTagName(name) {
|
|
2938
|
+
const normalized = name.toLowerCase();
|
|
2939
|
+
if (normalized === "express-as" || normalized === "expressas") return "mstts:express-as";
|
|
2940
|
+
if (normalized === "sayas") return "say-as";
|
|
2941
|
+
return normalized;
|
|
2942
|
+
}
|
|
2943
|
+
function validateVoiceFeatureMatrix(token, source, diagnostics, voiceName, definition) {
|
|
2944
|
+
if (!voiceName || !definition || token.name.toLowerCase() === "voice" || token.name.toLowerCase() === "mstts:turn")
|
|
2945
|
+
return;
|
|
2946
|
+
const tagName = canonicalTagName(token.name);
|
|
2947
|
+
const unsupportedTags = new Set((definition.unsupportedTags ?? []).map(canonicalTagName));
|
|
2948
|
+
const supportedTags = definition.supportedTags?.map(canonicalTagName);
|
|
2949
|
+
if (unsupportedTags.has(tagName) || supportedTags !== void 0 && !supportedTags.includes(tagName)) {
|
|
2950
|
+
addDiagnostic(
|
|
2951
|
+
diagnostics,
|
|
2952
|
+
source,
|
|
2953
|
+
token.start,
|
|
2954
|
+
`Tag <${token.name}> is not supported by voice "${voiceName}" according to the configured feature matrix.`,
|
|
2955
|
+
"error",
|
|
2956
|
+
"azure-unsupported-tag-for-voice"
|
|
2957
|
+
);
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
function validateAudioSource(token, source, diagnostics, options, elementName2) {
|
|
2961
|
+
const src = attr(token, "src");
|
|
2962
|
+
if (!src) {
|
|
2963
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2}> requires a "src" attribute.`);
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
let parsed;
|
|
2967
|
+
try {
|
|
2968
|
+
parsed = new URL(src);
|
|
2969
|
+
} catch {
|
|
2970
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
2974
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
2975
|
+
if (options.allowedAudioOrigins && !options.allowedAudioOrigins.includes(parsed.origin))
|
|
2976
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
2977
|
+
else if (!options.allowExternalAudio)
|
|
2978
|
+
addDiagnostic(
|
|
2979
|
+
diagnostics,
|
|
2980
|
+
source,
|
|
2981
|
+
token.start,
|
|
2982
|
+
`<${elementName2} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
|
|
2983
|
+
);
|
|
2984
|
+
}
|
|
2985
|
+
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
2986
|
+
const name = token.name.toLowerCase();
|
|
2987
|
+
if (name === "voice" && !attr(token, "name")?.trim())
|
|
2988
|
+
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
2989
|
+
if (name === "break") {
|
|
2990
|
+
const time = attr(token, "time");
|
|
2991
|
+
const strength = attr(token, "strength");
|
|
2992
|
+
if (!time && !strength)
|
|
2993
|
+
addDiagnostic(diagnostics, source, token.start, '<break> requires either "time" or "strength".');
|
|
2994
|
+
if (time && strength)
|
|
2995
|
+
addDiagnostic(diagnostics, source, token.start, '<break> must not specify both "time" and "strength".');
|
|
2996
|
+
if (time && !/^\d+(?:\.\d+)?(?:ms|s)$/.test(time.trim()))
|
|
2997
|
+
addDiagnostic(diagnostics, source, token.start, '<break time> must use a numeric value followed by "ms" or "s".');
|
|
2998
|
+
if (strength && !ALLOWED_BREAK_STRENGTHS.has(strength))
|
|
2999
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <break strength> value "${strength}".`);
|
|
3000
|
+
}
|
|
3001
|
+
if (name === "prosody") {
|
|
3002
|
+
const rate = attr(token, "rate");
|
|
3003
|
+
const pitch = attr(token, "pitch");
|
|
3004
|
+
const volume = attr(token, "volume");
|
|
3005
|
+
if (rate && !isSupportedProsodyRate(rate))
|
|
3006
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody rate> value "${rate}".`);
|
|
3007
|
+
if (pitch && !/^(x-low|low|medium|high|x-high|[+-]?\d+(?:\.\d+)?(?:st|Hz|%)?)$/.test(pitch.trim()))
|
|
3008
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody pitch> value "${pitch}".`);
|
|
3009
|
+
if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%)?)$/.test(volume.trim()))
|
|
3010
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody volume> value "${volume}".`);
|
|
3011
|
+
}
|
|
3012
|
+
if (name === "mstts:express-as" || name === "express-as" || name === "expressas") {
|
|
3013
|
+
const style = attr(token, "style");
|
|
3014
|
+
if (!style?.trim())
|
|
3015
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:express-as> requires a non-empty "style" attribute.');
|
|
3016
|
+
const degree = attr(token, "styledegree") ?? attr(token, "style-degree");
|
|
3017
|
+
if (degree && (!/^\d+(?:\.\d+)?$/.test(degree) || Number(degree) < 0.01 || Number(degree) > 2))
|
|
3018
|
+
addDiagnostic(
|
|
3019
|
+
diagnostics,
|
|
3020
|
+
source,
|
|
3021
|
+
token.start,
|
|
3022
|
+
"<mstts:express-as styledegree> must be a number between 0.01 and 2."
|
|
3023
|
+
);
|
|
3024
|
+
const role = attr(token, "role");
|
|
3025
|
+
if (role && !ALLOWED_ROLES.has(role))
|
|
3026
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:express-as role> value "${role}".`);
|
|
3027
|
+
const definition = voiceName ? voiceCatalog.get(voiceName.toLowerCase()) : void 0;
|
|
3028
|
+
const supportedStyles = definition?.styles;
|
|
3029
|
+
const severity = diagnosticSeverity(options.unsupportedStylePolicy ?? options.unknownVoicePolicy ?? "warn");
|
|
3030
|
+
if (style && definition && !supportedStyles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase()) && severity)
|
|
3031
|
+
addDiagnostic(
|
|
3032
|
+
diagnostics,
|
|
3033
|
+
source,
|
|
3034
|
+
token.start,
|
|
3035
|
+
`Unknown style "${style}" is not supported by voice "${voiceName}" according to the configured voice style map.`,
|
|
3036
|
+
severity,
|
|
3037
|
+
"azure-unsupported-style"
|
|
3038
|
+
);
|
|
3039
|
+
if (style && voiceName && !definition && severity)
|
|
3040
|
+
addDiagnostic(
|
|
3041
|
+
diagnostics,
|
|
3042
|
+
source,
|
|
3043
|
+
token.start,
|
|
3044
|
+
`Unknown style "${style}" cannot be verified because voice "${voiceName}" is not registered in the voice style map.`,
|
|
3045
|
+
severity
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
3048
|
+
if (name === "say-as" || name === "sayas") {
|
|
3049
|
+
const interpretAs = attr(token, "interpret-as");
|
|
3050
|
+
if (!interpretAs || !ALLOWED_SAY_AS.has(interpretAs))
|
|
3051
|
+
addDiagnostic(diagnostics, source, token.start, `<say-as> requires a supported "interpret-as" value.`);
|
|
3052
|
+
}
|
|
3053
|
+
if (name === "phoneme" && (!attr(token, "alphabet") || !attr(token, "ph")))
|
|
3054
|
+
addDiagnostic(diagnostics, source, token.start, '<phoneme> requires both "alphabet" and "ph" attributes.');
|
|
3055
|
+
if (name === "emphasis" && attr(token, "level") && !ALLOWED_EMPHASIS_LEVELS.has(attr(token, "level") ?? ""))
|
|
3056
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <emphasis level> value "${attr(token, "level")}".`);
|
|
3057
|
+
if (name === "sub" && !attr(token, "alias")?.trim())
|
|
3058
|
+
addDiagnostic(diagnostics, source, token.start, '<sub> requires a non-empty "alias" attribute.');
|
|
3059
|
+
if (name === "lang" && !attr(token, "xml:lang")?.trim() && !attr(token, "lang")?.trim())
|
|
3060
|
+
addDiagnostic(diagnostics, source, token.start, '<lang> requires an "xml:lang" attribute.');
|
|
3061
|
+
if (name === "mark" && !attr(token, "name")?.trim())
|
|
3062
|
+
addDiagnostic(diagnostics, source, token.start, '<mark> requires a non-empty "name" attribute.');
|
|
3063
|
+
if (name === "bookmark" && !attr(token, "mark")?.trim())
|
|
3064
|
+
addDiagnostic(diagnostics, source, token.start, '<bookmark> requires a non-empty "mark" attribute.');
|
|
3065
|
+
if (name === "lexicon") {
|
|
3066
|
+
const uri = attr(token, "uri");
|
|
3067
|
+
if (!uri) addDiagnostic(diagnostics, source, token.start, '<lexicon> requires a "uri" attribute.');
|
|
3068
|
+
else {
|
|
3069
|
+
try {
|
|
3070
|
+
const parsed = new URL(uri);
|
|
3071
|
+
if (parsed.protocol !== "https:")
|
|
3072
|
+
addDiagnostic(diagnostics, source, token.start, "<lexicon uri> must use HTTPS.");
|
|
3073
|
+
} catch {
|
|
3074
|
+
addDiagnostic(diagnostics, source, token.start, "<lexicon uri> must be an absolute HTTPS URL.");
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
if (name === "mstts:silence") {
|
|
3079
|
+
const type = attr(token, "type");
|
|
3080
|
+
const value = attr(token, "value");
|
|
3081
|
+
if (!type || !ALLOWED_SILENCE_TYPES.has(type))
|
|
3082
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a supported "type" attribute.');
|
|
3083
|
+
if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
|
|
3084
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
|
|
3085
|
+
}
|
|
3086
|
+
if (name === "mstts:audioduration") {
|
|
3087
|
+
const value = attr(token, "value");
|
|
3088
|
+
if (!value || !isValidAzureAudioDuration(value))
|
|
3089
|
+
addDiagnostic(
|
|
3090
|
+
diagnostics,
|
|
3091
|
+
source,
|
|
3092
|
+
token.start,
|
|
3093
|
+
'<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
|
|
3094
|
+
);
|
|
3095
|
+
if (!token.selfClosing)
|
|
3096
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
|
|
3097
|
+
}
|
|
3098
|
+
if (name === "mstts:viseme") {
|
|
3099
|
+
const type = attr(token, "type");
|
|
3100
|
+
if (!type || !ALLOWED_VISEME_TYPES.has(type))
|
|
3101
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
|
|
3102
|
+
}
|
|
3103
|
+
if (name === "audio") {
|
|
3104
|
+
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
3105
|
+
}
|
|
3106
|
+
if (name === "mstts:turn") {
|
|
3107
|
+
if (!attr(token, "voice")?.trim())
|
|
3108
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:turn> requires a non-empty "voice" attribute.');
|
|
3109
|
+
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
3110
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
3111
|
+
}
|
|
3112
|
+
if (name === "mstts:backgroundaudio") {
|
|
3113
|
+
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
3114
|
+
const volume = attr(token, "volume");
|
|
3115
|
+
if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%))$/i.test(volume.trim()))
|
|
3116
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
3117
|
+
for (const [attribute, value] of [
|
|
3118
|
+
["fadein", attr(token, "fadein")],
|
|
3119
|
+
["fadeout", attr(token, "fadeout")]
|
|
3120
|
+
]) {
|
|
3121
|
+
if (value && !isValidAzureAudioDuration(value))
|
|
3122
|
+
addDiagnostic(
|
|
3123
|
+
diagnostics,
|
|
3124
|
+
source,
|
|
3125
|
+
token.start,
|
|
3126
|
+
`<mstts:backgroundaudio ${attribute}> must be a positive duration such as "500ms" or "10s".`
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
if (!token.selfClosing)
|
|
3130
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
function validateAzureSsml(ssml, options = {}) {
|
|
3134
|
+
const diagnostics = [];
|
|
3135
|
+
if (typeof ssml !== "string") {
|
|
3136
|
+
return [
|
|
3137
|
+
{
|
|
3138
|
+
line: 1,
|
|
3139
|
+
column: 1,
|
|
3140
|
+
message: "SSML input must be a string",
|
|
3141
|
+
severity: "error",
|
|
3142
|
+
source: "ssml-static-validator"
|
|
3143
|
+
}
|
|
3144
|
+
];
|
|
3145
|
+
}
|
|
3146
|
+
const maxLength = options.maxLength ?? 1e4;
|
|
3147
|
+
if (ssml.length > maxLength)
|
|
3148
|
+
addDiagnostic(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
|
|
3149
|
+
try {
|
|
3150
|
+
parseSsml(ssml);
|
|
3151
|
+
} catch (error) {
|
|
3152
|
+
const message = error instanceof Error ? error.message.replace(/ at position \d+$/, "") : String(error);
|
|
3153
|
+
const match = / at position (\d+)$/.exec(error instanceof Error ? error.message : "");
|
|
3154
|
+
addDiagnostic(diagnostics, ssml, match ? Number(match[1]) : 0, message);
|
|
3155
|
+
return diagnostics;
|
|
3156
|
+
}
|
|
3157
|
+
const tokens = tokenizeElements(ssml);
|
|
3158
|
+
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
3159
|
+
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
3160
|
+
if (!speak || voices.length === 0)
|
|
3161
|
+
addDiagnostic(
|
|
3162
|
+
diagnostics,
|
|
3163
|
+
ssml,
|
|
3164
|
+
speak?.start ?? 0,
|
|
3165
|
+
"Azure SSML requires at least one <voice> element under <speak>."
|
|
3166
|
+
);
|
|
3167
|
+
const voiceName = voices[0] ? attr(voices[0], "name") : void 0;
|
|
3168
|
+
const voiceCatalog = normalizeVoiceCatalog(options);
|
|
3169
|
+
const normalizeLanguage = createLanguageNormalizer(options);
|
|
3170
|
+
const policySeverity = diagnosticSeverity(options.unknownVoicePolicy ?? "warn");
|
|
3171
|
+
const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
|
|
3172
|
+
for (const token of voicesToValidate) {
|
|
3173
|
+
const name = attr(token, "name")?.trim();
|
|
3174
|
+
const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
|
|
3175
|
+
const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
|
|
3176
|
+
if (name && !definition && policySeverity)
|
|
3177
|
+
addDiagnostic(
|
|
3178
|
+
diagnostics,
|
|
3179
|
+
ssml,
|
|
3180
|
+
token.start,
|
|
3181
|
+
`Unknown voice "${name}" is not registered in the voice catalog.`,
|
|
3182
|
+
policySeverity,
|
|
3183
|
+
"azure-unknown-voice"
|
|
3184
|
+
);
|
|
3185
|
+
if (name && language && definitionMatchesLanguage(definition, name, language, normalizeLanguage) === false)
|
|
3186
|
+
addDiagnostic(
|
|
3187
|
+
diagnostics,
|
|
3188
|
+
ssml,
|
|
3189
|
+
token.start,
|
|
3190
|
+
`Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
|
|
3191
|
+
"warning",
|
|
3192
|
+
"azure-locale-mismatch"
|
|
3193
|
+
);
|
|
3194
|
+
}
|
|
3195
|
+
for (const token of tokens) {
|
|
3196
|
+
const tokenName = token.name.toLowerCase();
|
|
3197
|
+
const tokenVoiceName = tokenName === "voice" ? attr(token, "name")?.trim() : tokenName === "mstts:turn" ? attr(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
3198
|
+
validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
3199
|
+
const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
|
|
3200
|
+
validateVoiceFeatureMatrix(token, ssml, diagnostics, tokenVoiceName, definition);
|
|
3201
|
+
if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
|
|
3202
|
+
addDiagnostic(
|
|
3203
|
+
diagnostics,
|
|
3204
|
+
ssml,
|
|
3205
|
+
token.start,
|
|
3206
|
+
`Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
|
|
3207
|
+
"error",
|
|
3208
|
+
"azure-unsupported-model-for-voice"
|
|
3209
|
+
);
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
return diagnostics;
|
|
3213
|
+
}
|
|
3214
|
+
var AZURE_VOICE_CATALOG_METADATA = {
|
|
3215
|
+
apiVersion: "2025-10-01",
|
|
3216
|
+
generatedAt: "2026-08-28T00:00:00.000Z",
|
|
3217
|
+
regions: [],
|
|
3218
|
+
voiceCount: AZURE_VOICE_DEFINITIONS.length
|
|
3219
|
+
};
|
|
2376
3220
|
|
|
2377
3221
|
// packages/ssml-editor-react/src/clearSsmlDocument.ts
|
|
2378
3222
|
function getDocumentChildren(document2) {
|
|
@@ -2433,6 +3277,7 @@ var INTRINSICALLY_EMPTY_ELEMENTS = /* @__PURE__ */ new Set([
|
|
|
2433
3277
|
"lexicon",
|
|
2434
3278
|
"mark",
|
|
2435
3279
|
"mstts:silence",
|
|
3280
|
+
"mstts:audioduration",
|
|
2436
3281
|
"mstts:viseme",
|
|
2437
3282
|
"silence",
|
|
2438
3283
|
"viseme"
|
|
@@ -3130,7 +3975,7 @@ function createSsmlInsertionEdit(source, startOffset, endOffset, template, eol =
|
|
|
3130
3975
|
}
|
|
3131
3976
|
|
|
3132
3977
|
// packages/ssml-editor-react/src/ssmlContext.ts
|
|
3133
|
-
function
|
|
3978
|
+
function findTagEnd3(source, start, limit) {
|
|
3134
3979
|
let quote;
|
|
3135
3980
|
for (let index = start + 1; index < limit; index += 1) {
|
|
3136
3981
|
const character = source[index];
|
|
@@ -3200,7 +4045,7 @@ function findActiveSsmlTags(source, offset) {
|
|
|
3200
4045
|
index = end;
|
|
3201
4046
|
continue;
|
|
3202
4047
|
}
|
|
3203
|
-
const tagEnd =
|
|
4048
|
+
const tagEnd = findTagEnd3(source, tagStart, source.length);
|
|
3204
4049
|
const tag = source.slice(tagStart, tagEnd === -1 ? source.length : tagEnd + 1);
|
|
3205
4050
|
const closingMatch = tag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
3206
4051
|
const openingMatch = tag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
@@ -3243,7 +4088,7 @@ function findSsmlVoiceContext(source, offset) {
|
|
|
3243
4088
|
index = end === -1 || end + 2 > limit ? limit : end + 2;
|
|
3244
4089
|
continue;
|
|
3245
4090
|
}
|
|
3246
|
-
const tagEnd =
|
|
4091
|
+
const tagEnd = findTagEnd3(source, tagStart, limit);
|
|
3247
4092
|
if (tagEnd === -1) {
|
|
3248
4093
|
break;
|
|
3249
4094
|
}
|
|
@@ -3278,7 +4123,7 @@ function findSsmlVoiceContext(source, offset) {
|
|
|
3278
4123
|
}
|
|
3279
4124
|
|
|
3280
4125
|
// packages/ssml-editor-react/src/hooks/useSsmlEditorState.ts
|
|
3281
|
-
var TIMING_INSERTION_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence"]);
|
|
4126
|
+
var TIMING_INSERTION_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence", "mstts:audioduration"]);
|
|
3282
4127
|
var PROSODY_INSERTION_TAGS = /* @__PURE__ */ new Set(["prosody", "mstts:express-as", "voice", "emphasis"]);
|
|
3283
4128
|
var TEXT_INSERTION_TAGS = /* @__PURE__ */ new Set(["sub", "say-as", "phoneme", "w", "lang"]);
|
|
3284
4129
|
var EMPTY_SELECTION_OVERLAY = {
|
|
@@ -3999,6 +4844,28 @@ var SSML_COMPLETION_SNIPPETS = [
|
|
|
3999
4844
|
label: "mstts:express-as",
|
|
4000
4845
|
insertText: `<mstts:express-as style="cheerful">\${1:text}</mstts:express-as>`
|
|
4001
4846
|
},
|
|
4847
|
+
{
|
|
4848
|
+
label: "mstts:audioduration",
|
|
4849
|
+
insertText: '<mstts:audioduration value="10s" />'
|
|
4850
|
+
},
|
|
4851
|
+
{
|
|
4852
|
+
label: "mstts:dialog",
|
|
4853
|
+
insertText: `<mstts:dialog>
|
|
4854
|
+
<mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>
|
|
4855
|
+
</mstts:dialog>`
|
|
4856
|
+
},
|
|
4857
|
+
{
|
|
4858
|
+
label: "mstts:turn",
|
|
4859
|
+
insertText: `<mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>`
|
|
4860
|
+
},
|
|
4861
|
+
{
|
|
4862
|
+
label: "mstts:backgroundaudio",
|
|
4863
|
+
insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:-3dB}" />`
|
|
4864
|
+
},
|
|
4865
|
+
{
|
|
4866
|
+
label: "mstts:ttsembedding",
|
|
4867
|
+
insertText: `<mstts:ttsembedding>\${1:text}</mstts:ttsembedding>`
|
|
4868
|
+
},
|
|
4002
4869
|
{
|
|
4003
4870
|
label: "sub",
|
|
4004
4871
|
insertText: `<sub alias="\${1:\u8AAD\u307F}">\${2:\u6F22\u5B57}</sub>`
|
|
@@ -4177,7 +5044,7 @@ function registerSsmlCodeActions(monaco) {
|
|
|
4177
5044
|
|
|
4178
5045
|
// packages/ssml-editor-react/src/ssmlCodeLens.ts
|
|
4179
5046
|
var CODE_LENS_COMMAND = "ssml-editor.codeLens";
|
|
4180
|
-
function
|
|
5047
|
+
function findTagEnd4(source, start) {
|
|
4181
5048
|
let quote;
|
|
4182
5049
|
for (let index = start + 1; index < source.length; index += 1) {
|
|
4183
5050
|
const character = source[index];
|
|
@@ -4217,7 +5084,7 @@ function getElementEnd(source, tagName, tagEnd) {
|
|
|
4217
5084
|
index = index === -1 ? source.length : index + 3;
|
|
4218
5085
|
continue;
|
|
4219
5086
|
}
|
|
4220
|
-
const nextEnd =
|
|
5087
|
+
const nextEnd = findTagEnd4(source, nextStart);
|
|
4221
5088
|
if (nextEnd === -1) {
|
|
4222
5089
|
break;
|
|
4223
5090
|
}
|
|
@@ -4269,12 +5136,12 @@ function registerSsmlCodeLens(monaco, editor, onOpenPopover) {
|
|
|
4269
5136
|
index = index === -1 ? source.length : index + 3;
|
|
4270
5137
|
continue;
|
|
4271
5138
|
}
|
|
4272
|
-
const tagEnd =
|
|
5139
|
+
const tagEnd = findTagEnd4(source, tagStart);
|
|
4273
5140
|
if (tagEnd === -1) {
|
|
4274
5141
|
break;
|
|
4275
5142
|
}
|
|
4276
5143
|
const tag = source.slice(tagStart, tagEnd + 1);
|
|
4277
|
-
const tagName = tag.match(/^<\s*(prosody|break)\b/i)?.[1]?.toLowerCase();
|
|
5144
|
+
const tagName = tag.match(/^<\s*(prosody|break|mstts:audioduration)\b/i)?.[1]?.toLowerCase();
|
|
4278
5145
|
index = tagEnd + 1;
|
|
4279
5146
|
if (!tagName) {
|
|
4280
5147
|
continue;
|
|
@@ -4304,7 +5171,22 @@ function registerSsmlCodeLens(monaco, editor, onOpenPopover) {
|
|
|
4304
5171
|
elementRange
|
|
4305
5172
|
})
|
|
4306
5173
|
);
|
|
4307
|
-
} else if (/\/\s*>$/.test(tag)) {
|
|
5174
|
+
} else if (tagName === "mstts:audioduration" && /\/\s*>$/.test(tag)) {
|
|
5175
|
+
lenses.push(
|
|
5176
|
+
createLens(
|
|
5177
|
+
model,
|
|
5178
|
+
tagStart,
|
|
5179
|
+
tagEnd + 1,
|
|
5180
|
+
`\u26A1 Duration: ${getAttributeValue(tag, "value") ?? "default"} (Click to edit)`,
|
|
5181
|
+
{ type: "attribute", insertionId: "mstts:audioduration", attributeName: "value", tagRange }
|
|
5182
|
+
),
|
|
5183
|
+
createLens(model, tagStart, tagEnd + 1, "Delete", {
|
|
5184
|
+
type: "delete",
|
|
5185
|
+
tagRange,
|
|
5186
|
+
elementRange
|
|
5187
|
+
})
|
|
5188
|
+
);
|
|
5189
|
+
} else if (tagName === "break" && /\/\s*>$/.test(tag)) {
|
|
4308
5190
|
lenses.push(
|
|
4309
5191
|
createLens(
|
|
4310
5192
|
model,
|
|
@@ -4640,6 +5522,74 @@ var SSML_TAG_DEFINITIONS = [
|
|
|
4640
5522
|
values: VISEME_TYPE_PRESETS2
|
|
4641
5523
|
}
|
|
4642
5524
|
]
|
|
5525
|
+
},
|
|
5526
|
+
{
|
|
5527
|
+
name: "mstts:audioduration",
|
|
5528
|
+
description: "Sets the target duration of synthesized audio.",
|
|
5529
|
+
parameters: [
|
|
5530
|
+
{
|
|
5531
|
+
name: "value",
|
|
5532
|
+
description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`.",
|
|
5533
|
+
example: "10s"
|
|
5534
|
+
}
|
|
5535
|
+
]
|
|
5536
|
+
},
|
|
5537
|
+
{
|
|
5538
|
+
name: "mstts:dialog",
|
|
5539
|
+
description: "Groups multiple Azure dialog turns that can use different voices.",
|
|
5540
|
+
parameters: []
|
|
5541
|
+
},
|
|
5542
|
+
{
|
|
5543
|
+
name: "mstts:turn",
|
|
5544
|
+
description: "Adds one dialog turn using the required Azure voice name.",
|
|
5545
|
+
parameters: [
|
|
5546
|
+
{
|
|
5547
|
+
name: "voice",
|
|
5548
|
+
description: "The Azure voice used for this turn, such as `en-US-JennyNeural`.",
|
|
5549
|
+
example: "en-US-JennyNeural"
|
|
5550
|
+
}
|
|
5551
|
+
]
|
|
5552
|
+
},
|
|
5553
|
+
{
|
|
5554
|
+
name: "mstts:backgroundaudio",
|
|
5555
|
+
description: "Plays background audio while speech is synthesized.",
|
|
5556
|
+
parameters: [
|
|
5557
|
+
{
|
|
5558
|
+
name: "src",
|
|
5559
|
+
description: "An absolute HTTP(S) URL for the background audio file.",
|
|
5560
|
+
example: "https://example.com/music.mp3"
|
|
5561
|
+
},
|
|
5562
|
+
{
|
|
5563
|
+
name: "volume",
|
|
5564
|
+
description: "The background audio volume, for example `-3dB` or `medium`.",
|
|
5565
|
+
example: "-3dB"
|
|
5566
|
+
},
|
|
5567
|
+
{
|
|
5568
|
+
name: "fadein",
|
|
5569
|
+
description: "The fade-in duration, for example `1s`.",
|
|
5570
|
+
example: "1s"
|
|
5571
|
+
},
|
|
5572
|
+
{
|
|
5573
|
+
name: "fadeout",
|
|
5574
|
+
description: "The fade-out duration, for example `500ms`.",
|
|
5575
|
+
example: "500ms"
|
|
5576
|
+
}
|
|
5577
|
+
]
|
|
5578
|
+
},
|
|
5579
|
+
{
|
|
5580
|
+
name: "mstts:ttsembedding",
|
|
5581
|
+
description: "Embeds custom voice or speaker profile metadata for Azure Speech.",
|
|
5582
|
+
parameters: []
|
|
5583
|
+
},
|
|
5584
|
+
{
|
|
5585
|
+
name: "mstts:embedding",
|
|
5586
|
+
description: "Specifies embedding metadata for custom voice scenarios.",
|
|
5587
|
+
parameters: []
|
|
5588
|
+
},
|
|
5589
|
+
{
|
|
5590
|
+
name: "mstts:voiceconversion",
|
|
5591
|
+
description: "Specifies voice conversion metadata for custom voice scenarios.",
|
|
5592
|
+
parameters: []
|
|
4643
5593
|
}
|
|
4644
5594
|
];
|
|
4645
5595
|
var definitionsByName = /* @__PURE__ */ new Map();
|
|
@@ -4698,7 +5648,7 @@ function toRange(source, token) {
|
|
|
4698
5648
|
function containsOffset(token, offset) {
|
|
4699
5649
|
return offset >= token.start && offset < token.end;
|
|
4700
5650
|
}
|
|
4701
|
-
function
|
|
5651
|
+
function findTagEnd5(source, start) {
|
|
4702
5652
|
let quote;
|
|
4703
5653
|
for (let index = start; index < source.length; index += 1) {
|
|
4704
5654
|
const character = source[index];
|
|
@@ -4829,7 +5779,7 @@ function findTagAtOffset(source, offset) {
|
|
|
4829
5779
|
searchStart = tokenEnd2;
|
|
4830
5780
|
continue;
|
|
4831
5781
|
}
|
|
4832
|
-
const tagEnd =
|
|
5782
|
+
const tagEnd = findTagEnd5(source, start + 1);
|
|
4833
5783
|
const contentEnd = tagEnd ?? source.length;
|
|
4834
5784
|
const tokenEnd = tagEnd === void 0 ? source.length : tagEnd + 1;
|
|
4835
5785
|
if (offset < tokenEnd) {
|
|
@@ -5244,9 +6194,143 @@ function useSsmlMonaco({
|
|
|
5244
6194
|
return { editorRef, onMount };
|
|
5245
6195
|
}
|
|
5246
6196
|
|
|
6197
|
+
// packages/ssml-editor-react/src/components/VisualSsmlEditor.tsx
|
|
6198
|
+
var import_react3 = require("react");
|
|
6199
|
+
function isElement(node) {
|
|
6200
|
+
return typeof node !== "string" && node.type !== "text";
|
|
6201
|
+
}
|
|
6202
|
+
function elementLabel(element) {
|
|
6203
|
+
return element.type === "custom" || element.type === "element" ? element.name : element.type;
|
|
6204
|
+
}
|
|
6205
|
+
function collectTextLeaves(nodes, path = [], ancestors = []) {
|
|
6206
|
+
return nodes.flatMap((node, index) => {
|
|
6207
|
+
const currentPath = [...path, index];
|
|
6208
|
+
if (typeof node === "string") return [{ ancestors, path: currentPath, value: node }];
|
|
6209
|
+
if (node.type === "text") return [{ ancestors, path: currentPath, value: node.value }];
|
|
6210
|
+
return collectTextLeaves(node.children ?? [], currentPath, [...ancestors, elementLabel(node)]);
|
|
6211
|
+
});
|
|
6212
|
+
}
|
|
6213
|
+
function updateNodesAtPath(nodes, path, update) {
|
|
6214
|
+
if (path.length === 0) return nodes;
|
|
6215
|
+
const [index, ...rest] = path;
|
|
6216
|
+
return nodes.flatMap((node, nodeIndex) => {
|
|
6217
|
+
if (nodeIndex !== index) return [node];
|
|
6218
|
+
if (rest.length === 0) {
|
|
6219
|
+
const next = update(node);
|
|
6220
|
+
return Array.isArray(next) ? next : [next];
|
|
6221
|
+
}
|
|
6222
|
+
if (!isElement(node)) return [node];
|
|
6223
|
+
return [{ ...node, children: updateNodesAtPath(node.children ?? [], rest, update) }];
|
|
6224
|
+
});
|
|
6225
|
+
}
|
|
6226
|
+
function updateTextAtPath(document2, path, value) {
|
|
6227
|
+
return { ...document2, children: updateNodesAtPath(document2.children ?? [], path, () => value) };
|
|
6228
|
+
}
|
|
6229
|
+
function wrapTextAtPath(document2, path, start, end, tag, attributes) {
|
|
6230
|
+
return {
|
|
6231
|
+
...document2,
|
|
6232
|
+
children: updateNodesAtPath(document2.children ?? [], path, (node) => {
|
|
6233
|
+
const value = typeof node === "string" ? node : node.type === "text" ? node.value : "";
|
|
6234
|
+
if (!value || start === end) return node;
|
|
6235
|
+
if (tag === "break") {
|
|
6236
|
+
return [
|
|
6237
|
+
value.slice(0, start),
|
|
6238
|
+
{ type: tag, attributes, children: [] },
|
|
6239
|
+
value.slice(start)
|
|
6240
|
+
].filter((part) => typeof part === "string" ? part.length > 0 : true);
|
|
6241
|
+
}
|
|
6242
|
+
const selected = value.slice(start, end);
|
|
6243
|
+
const wrapped = { type: tag, attributes, children: [selected] };
|
|
6244
|
+
return [value.slice(0, start), wrapped, value.slice(end)].filter(
|
|
6245
|
+
(part) => typeof part === "string" ? part.length > 0 : true
|
|
6246
|
+
);
|
|
6247
|
+
})
|
|
6248
|
+
};
|
|
6249
|
+
}
|
|
6250
|
+
function TreeNode({
|
|
6251
|
+
node,
|
|
6252
|
+
path,
|
|
6253
|
+
selectedPath,
|
|
6254
|
+
onSelect
|
|
6255
|
+
}) {
|
|
6256
|
+
if (!isElement(node)) return null;
|
|
6257
|
+
const name = elementLabel(node);
|
|
6258
|
+
const isSelected = selectedPath?.join(".") === path.join(".");
|
|
6259
|
+
return /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement("button", { type: "button", "aria-current": isSelected ? "true" : void 0, onClick: () => onSelect(path) }, `<${name}>`), (node.children ?? []).length > 0 && /* @__PURE__ */ React.createElement("ul", null, node.children?.map((child, index) => /* @__PURE__ */ React.createElement(
|
|
6260
|
+
TreeNode,
|
|
6261
|
+
{
|
|
6262
|
+
key: `${path.join(".")}/${isElement(child) ? elementLabel(child) : JSON.stringify(child)}`,
|
|
6263
|
+
node: child,
|
|
6264
|
+
path: [...path, index],
|
|
6265
|
+
selectedPath,
|
|
6266
|
+
onSelect
|
|
6267
|
+
}
|
|
6268
|
+
))));
|
|
6269
|
+
}
|
|
6270
|
+
function VisualSsmlEditor({
|
|
6271
|
+
document: document2,
|
|
6272
|
+
readOnly = false,
|
|
6273
|
+
onChange,
|
|
6274
|
+
onPreviewSelection
|
|
6275
|
+
}) {
|
|
6276
|
+
const [selectedPath, setSelectedPath] = (0, import_react3.useState)(null);
|
|
6277
|
+
const [selection, setSelection] = (0, import_react3.useState)({ start: 0, end: 0 });
|
|
6278
|
+
const textLeaves = (0, import_react3.useMemo)(() => collectTextLeaves(document2.children ?? []), [document2]);
|
|
6279
|
+
const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
|
|
6280
|
+
const diagnostics = (0, import_react3.useMemo)(() => validateAzureSsml(buildSsml(document2)), [document2]);
|
|
6281
|
+
const commit = (nextDocument) => onChange?.(nextDocument);
|
|
6282
|
+
const applyWrapper = (tag, attributes) => {
|
|
6283
|
+
if (readOnly || !selectedLeaf) return;
|
|
6284
|
+
const start = selection.start === selection.end ? 0 : selection.start;
|
|
6285
|
+
const end = selection.start === selection.end ? selectedLeaf.value.length : selection.end;
|
|
6286
|
+
commit(wrapTextAtPath(document2, selectedLeaf.path, start, end, tag, attributes));
|
|
6287
|
+
};
|
|
6288
|
+
return /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual", "data-ssml-editor-visual": "" }, /* @__PURE__ */ React.createElement("fieldset", { className: "ssml-editor-visual-breadcrumb" }, /* @__PURE__ */ React.createElement("legend", null, "SSML structure breadcrumb"), /* @__PURE__ */ React.createElement("span", null, "<speak>"), selectedLeaf?.ancestors.map((ancestor, index) => /* @__PURE__ */ React.createElement("span", { key: selectedLeaf?.ancestors.slice(0, index + 1).join("/") }, " / <", ancestor, ">")), selectedPath && /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setSelectedPath(null) }, "Clear parent")), diagnostics.length > 0 && /* @__PURE__ */ React.createElement("div", { role: "alert", "aria-invalid": "true", className: "ssml-editor-visual-errors" }, diagnostics.map((diagnostic) => /* @__PURE__ */ React.createElement("div", { key: `${diagnostic.code}-${diagnostic.message}` }, diagnostic.message))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-layout" }, /* @__PURE__ */ React.createElement("nav", { "aria-label": "SSML structure tree", className: "ssml-editor-visual-tree" }, /* @__PURE__ */ React.createElement("strong", null, "Structure"), /* @__PURE__ */ React.createElement("ul", null, document2.children?.map((node, index) => /* @__PURE__ */ React.createElement(
|
|
6289
|
+
TreeNode,
|
|
6290
|
+
{
|
|
6291
|
+
key: isElement(node) ? elementLabel(node) : JSON.stringify(node),
|
|
6292
|
+
node,
|
|
6293
|
+
path: [index],
|
|
6294
|
+
selectedPath,
|
|
6295
|
+
onSelect: setSelectedPath
|
|
6296
|
+
}
|
|
6297
|
+
)))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-form" }, selectedLeaf ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, "Text", /* @__PURE__ */ React.createElement(
|
|
6298
|
+
"textarea",
|
|
6299
|
+
{
|
|
6300
|
+
value: selectedLeaf.value,
|
|
6301
|
+
readOnly,
|
|
6302
|
+
onSelect: (event) => setSelection({ start: event.currentTarget.selectionStart, end: event.currentTarget.selectionEnd }),
|
|
6303
|
+
onChange: (event) => commit(updateTextAtPath(document2, selectedLeaf.path, event.target.value))
|
|
6304
|
+
}
|
|
6305
|
+
)), /* @__PURE__ */ React.createElement("fieldset", { className: "ssml-editor-visual-actions" }, /* @__PURE__ */ React.createElement("legend", null, "Apply SSML formatting"), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("prosody", { rate: "slow" }) }, "Rate"), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("prosody", { pitch: "high" }) }, "Pitch"), /* @__PURE__ */ React.createElement(
|
|
6306
|
+
"button",
|
|
6307
|
+
{
|
|
6308
|
+
type: "button",
|
|
6309
|
+
disabled: readOnly,
|
|
6310
|
+
onClick: () => applyWrapper("mstts:express-as", { style: "cheerful" })
|
|
6311
|
+
},
|
|
6312
|
+
"Emotion"
|
|
6313
|
+
), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("break", { time: "500ms" }) }, "Pause"), /* @__PURE__ */ React.createElement(
|
|
6314
|
+
"button",
|
|
6315
|
+
{
|
|
6316
|
+
type: "button",
|
|
6317
|
+
disabled: readOnly,
|
|
6318
|
+
onClick: () => applyWrapper("phoneme", { alphabet: "ipa", ph: selectedLeaf.value })
|
|
6319
|
+
},
|
|
6320
|
+
"Pronunciation"
|
|
6321
|
+
), onPreviewSelection && /* @__PURE__ */ React.createElement(
|
|
6322
|
+
"button",
|
|
6323
|
+
{
|
|
6324
|
+
type: "button",
|
|
6325
|
+
onClick: () => onPreviewSelection(buildSsml({ ...document2, children: [selectedLeaf.value] }))
|
|
6326
|
+
},
|
|
6327
|
+
"Preview selection"
|
|
6328
|
+
))) : /* @__PURE__ */ React.createElement("p", null, "Select an element or text node to edit it."))));
|
|
6329
|
+
}
|
|
6330
|
+
|
|
5247
6331
|
// packages/ssml-editor-react/src/SsmlEditor.tsx
|
|
5248
6332
|
var UNGROUPED_TOOLBAR_GROUP = "__ssml-editor-ungrouped__";
|
|
5249
|
-
var TIMING_POPOVER_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence"]);
|
|
6333
|
+
var TIMING_POPOVER_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence", "mstts:audioduration"]);
|
|
5250
6334
|
var PROSODY_POPOVER_TAGS = /* @__PURE__ */ new Set(["prosody", "mstts:express-as", "voice", "emphasis"]);
|
|
5251
6335
|
var TEXT_POPOVER_TAGS = /* @__PURE__ */ new Set(["sub", "say-as", "phoneme", "w", "lang"]);
|
|
5252
6336
|
function localizedText(value) {
|
|
@@ -5474,13 +6558,34 @@ var SSML_INSERTIONS = [
|
|
|
5474
6558
|
suffix: "",
|
|
5475
6559
|
mode: "insert"
|
|
5476
6560
|
})
|
|
6561
|
+
},
|
|
6562
|
+
{
|
|
6563
|
+
id: "mstts:audioduration",
|
|
6564
|
+
icon: "\u25F7",
|
|
6565
|
+
tagName: "mstts:audioduration",
|
|
6566
|
+
selfClosing: true,
|
|
6567
|
+
labels: { ja: "\u97F3\u58F0\u9577", en: "Audio duration" },
|
|
6568
|
+
descriptions: {
|
|
6569
|
+
ja: "\u5408\u6210\u97F3\u58F0\u306E\u76EE\u6A19\u6642\u9593\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
|
|
6570
|
+
en: "Sets the target duration of synthesized audio."
|
|
6571
|
+
},
|
|
6572
|
+
parameterDescription: {
|
|
6573
|
+
ja: "\u76EE\u6A19\u6642\u9593\u3092\u9078\u629E\u3057\u307E\u3059\u3002",
|
|
6574
|
+
en: "Selects the target duration."
|
|
6575
|
+
},
|
|
6576
|
+
options: createInsertionOptions(AUDIO_DURATION_PRESETS, AUDIO_DURATION_DESCRIPTIONS),
|
|
6577
|
+
createTemplate: (value) => ({
|
|
6578
|
+
prefix: `<mstts:audioduration value="${value}"/>`,
|
|
6579
|
+
suffix: "",
|
|
6580
|
+
mode: "insert"
|
|
6581
|
+
})
|
|
5477
6582
|
}
|
|
5478
6583
|
];
|
|
5479
6584
|
var DEFAULT_INSERTION_GROUPS = [
|
|
5480
6585
|
{
|
|
5481
6586
|
id: "pauses",
|
|
5482
6587
|
labels: { ja: "\u9593\u30FB\u7121\u97F3", en: "Pauses" },
|
|
5483
|
-
insertionIds: ["break", "mstts:silence"]
|
|
6588
|
+
insertionIds: ["break", "mstts:silence", "mstts:audioduration"]
|
|
5484
6589
|
},
|
|
5485
6590
|
{
|
|
5486
6591
|
id: "prosody",
|
|
@@ -5689,6 +6794,60 @@ var STYLE_CSS = `
|
|
|
5689
6794
|
> .ssml-editor-help-settings-summary::before {
|
|
5690
6795
|
content: "\u25BE";
|
|
5691
6796
|
}
|
|
6797
|
+
[data-ssml-editor] .ssml-editor-visual-layout {
|
|
6798
|
+
display: grid;
|
|
6799
|
+
grid-template-columns: minmax(12rem, 0.35fr) minmax(16rem, 1fr);
|
|
6800
|
+
gap: 1rem;
|
|
6801
|
+
}
|
|
6802
|
+
[data-ssml-editor] .ssml-editor-visual-tree,
|
|
6803
|
+
[data-ssml-editor] .ssml-editor-visual-form {
|
|
6804
|
+
display: grid;
|
|
6805
|
+
gap: 0.5rem;
|
|
6806
|
+
align-content: start;
|
|
6807
|
+
}
|
|
6808
|
+
[data-ssml-editor] .ssml-editor-visual-tree ul {
|
|
6809
|
+
margin: 0;
|
|
6810
|
+
padding-left: 1.25rem;
|
|
6811
|
+
}
|
|
6812
|
+
[data-ssml-editor] .ssml-editor-visual-tree button,
|
|
6813
|
+
[data-ssml-editor] .ssml-editor-visual-breadcrumb button,
|
|
6814
|
+
[data-ssml-editor] .ssml-editor-visual-actions button {
|
|
6815
|
+
padding: 0.35rem 0.5rem;
|
|
6816
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
6817
|
+
border-radius: 0.25rem;
|
|
6818
|
+
color: var(--ssml-editor-color);
|
|
6819
|
+
background: var(--ssml-editor-control-bg);
|
|
6820
|
+
cursor: pointer;
|
|
6821
|
+
}
|
|
6822
|
+
[data-ssml-editor] .ssml-editor-visual-tree button[aria-current] {
|
|
6823
|
+
border-color: var(--ssml-editor-active-border);
|
|
6824
|
+
background: var(--ssml-editor-active-bg);
|
|
6825
|
+
}
|
|
6826
|
+
[data-ssml-editor] .ssml-editor-visual-breadcrumb,
|
|
6827
|
+
[data-ssml-editor] .ssml-editor-visual-actions {
|
|
6828
|
+
display: flex;
|
|
6829
|
+
flex-wrap: wrap;
|
|
6830
|
+
gap: 0.4rem;
|
|
6831
|
+
align-items: center;
|
|
6832
|
+
}
|
|
6833
|
+
[data-ssml-editor] .ssml-editor-visual-form textarea {
|
|
6834
|
+
box-sizing: border-box;
|
|
6835
|
+
width: 100%;
|
|
6836
|
+
min-height: 6rem;
|
|
6837
|
+
padding: 0.5rem;
|
|
6838
|
+
color: var(--ssml-editor-color);
|
|
6839
|
+
background: var(--ssml-editor-control-bg);
|
|
6840
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
6841
|
+
border-radius: 0.25rem;
|
|
6842
|
+
font: inherit;
|
|
6843
|
+
}
|
|
6844
|
+
[data-ssml-editor] .ssml-editor-visual-errors {
|
|
6845
|
+
padding: 0.5rem;
|
|
6846
|
+
color: var(--ssml-editor-error);
|
|
6847
|
+
background: var(--ssml-editor-error-bg);
|
|
6848
|
+
border: 1px solid var(--ssml-editor-error);
|
|
6849
|
+
border-radius: 0.25rem;
|
|
6850
|
+
}
|
|
5692
6851
|
`.trim();
|
|
5693
6852
|
function injectEditorTheme() {
|
|
5694
6853
|
if (typeof document !== "undefined" && !document.getElementById(STYLE_ID)) {
|
|
@@ -5720,7 +6879,7 @@ function ToolbarDecorationsSwitch({
|
|
|
5720
6879
|
/* @__PURE__ */ React.createElement("span", { className: "ssml-editor-switch-thumb", style: editorStyles.toolbarSwitchThumb, "aria-hidden": "true" })
|
|
5721
6880
|
));
|
|
5722
6881
|
}
|
|
5723
|
-
var SsmlEditor = (0,
|
|
6882
|
+
var SsmlEditor = (0, import_react4.forwardRef)(function SsmlEditor2({
|
|
5724
6883
|
document: document2,
|
|
5725
6884
|
onChange,
|
|
5726
6885
|
onSsmlChange,
|
|
@@ -5758,8 +6917,11 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
|
|
|
5758
6917
|
toolbarStyle,
|
|
5759
6918
|
displayClassName,
|
|
5760
6919
|
displayStyle,
|
|
5761
|
-
loadingFallback
|
|
6920
|
+
loadingFallback,
|
|
6921
|
+
editMode: editModeProp = "code"
|
|
5762
6922
|
}, ref) {
|
|
6923
|
+
const [editMode, setEditMode] = (0, import_react4.useState)(editModeProp);
|
|
6924
|
+
(0, import_react4.useEffect)(() => setEditMode(editModeProp), [editModeProp]);
|
|
5763
6925
|
const resolvedEditorOptions = {
|
|
5764
6926
|
...settings,
|
|
5765
6927
|
...editorOptions,
|
|
@@ -5837,6 +6999,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
|
|
|
5837
6999
|
);
|
|
5838
7000
|
const {
|
|
5839
7001
|
editorRef,
|
|
7002
|
+
draftDocument,
|
|
5840
7003
|
text,
|
|
5841
7004
|
selectionOverlay,
|
|
5842
7005
|
activeTags,
|
|
@@ -5848,6 +7011,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
|
|
|
5848
7011
|
syntaxError,
|
|
5849
7012
|
setSyntaxError,
|
|
5850
7013
|
helpPanelId,
|
|
7014
|
+
commit,
|
|
5851
7015
|
handleTextChange,
|
|
5852
7016
|
handleInsert,
|
|
5853
7017
|
handleInsertBreak,
|
|
@@ -5895,7 +7059,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
|
|
|
5895
7059
|
enableCodeLens,
|
|
5896
7060
|
onOpenPopover: handleCodeLensAction
|
|
5897
7061
|
});
|
|
5898
|
-
(0,
|
|
7062
|
+
(0, import_react4.useImperativeHandle)(
|
|
5899
7063
|
ref,
|
|
5900
7064
|
() => ({
|
|
5901
7065
|
getSelectedSsml: getSelectedSsml2,
|
|
@@ -6078,7 +7242,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
|
|
|
6078
7242
|
previousRenderedGroupId = groupId;
|
|
6079
7243
|
hasRenderedToolbarItem = true;
|
|
6080
7244
|
renderedItems.push(
|
|
6081
|
-
/* @__PURE__ */ React.createElement(
|
|
7245
|
+
/* @__PURE__ */ React.createElement(import_react4.Fragment, { key: id }, showSeparator && /* @__PURE__ */ React.createElement("span", { style: editorStyles.toolbarSeparator, "aria-hidden": "true" }), render())
|
|
6082
7246
|
);
|
|
6083
7247
|
}
|
|
6084
7248
|
return renderedItems;
|
|
@@ -6107,11 +7271,40 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
|
|
|
6107
7271
|
"aria-label": copy.toolbarAriaLabel,
|
|
6108
7272
|
"data-ssml-editor-toolbar-actions": ""
|
|
6109
7273
|
},
|
|
6110
|
-
renderToolbarItems()
|
|
7274
|
+
renderToolbarItems(),
|
|
7275
|
+
/* @__PURE__ */ React.createElement("span", { style: editorStyles.toolbarSeparator, "aria-hidden": "true" }),
|
|
7276
|
+
/* @__PURE__ */ React.createElement(
|
|
7277
|
+
"button",
|
|
7278
|
+
{
|
|
7279
|
+
type: "button",
|
|
7280
|
+
style: editMode === "visual" ? { ...toolbarButtonStyle, ...editorStyles.toolbarButtonActive } : toolbarButtonStyle,
|
|
7281
|
+
"aria-pressed": editMode === "visual",
|
|
7282
|
+
onClick: () => setEditMode("visual")
|
|
7283
|
+
},
|
|
7284
|
+
"Visual"
|
|
7285
|
+
),
|
|
7286
|
+
/* @__PURE__ */ React.createElement(
|
|
7287
|
+
"button",
|
|
7288
|
+
{
|
|
7289
|
+
type: "button",
|
|
7290
|
+
style: editMode === "code" ? { ...toolbarButtonStyle, ...editorStyles.toolbarButtonActive } : toolbarButtonStyle,
|
|
7291
|
+
"aria-pressed": editMode === "code",
|
|
7292
|
+
onClick: () => setEditMode("code")
|
|
7293
|
+
},
|
|
7294
|
+
"Code"
|
|
7295
|
+
)
|
|
6111
7296
|
)
|
|
6112
7297
|
),
|
|
6113
|
-
/* @__PURE__ */ React.createElement("div", { className: displayClassName, style: { ...editorStyles.display, ...displayStyle }, "data-ssml-editor-display": "" }, isHelpOpen && isSsmlEditorButtonVisible(buttonVisibility, "help") && /* @__PURE__ */ React.createElement("section", { id: helpPanelId, style: editorStyles.helpPanel, "aria-label": copy.helpHeading }, /* @__PURE__ */ React.createElement("h3", { style: editorStyles.helpHeading }, copy.helpHeading), /* @__PURE__ */ React.createElement("p", { style: editorStyles.helpDescription }, copy.helpDescription), helpInsertions.length > 0 && /* @__PURE__ */ React.createElement("ul", { style: editorStyles.helpList }, helpInsertions.map((insertion) => /* @__PURE__ */ React.createElement("li", { key: insertion.id, style: editorStyles.helpItem }, /* @__PURE__ */ React.createElement("details", { className: "ssml-editor-help-settings-accordion", style: editorStyles.helpSettingsAccordion }, /* @__PURE__ */ React.createElement("summary", { className: "ssml-editor-help-settings-summary", style: editorStyles.helpSettingsSummary }, /* @__PURE__ */ React.createElement("span", { style: editorStyles.helpIcon, "aria-hidden": "true" }, insertion.icon), /* @__PURE__ */ React.createElement("span", { style: editorStyles.helpSettingsSummaryContent }, /* @__PURE__ */ React.createElement("strong", null, insertion.labels[language]), " ", insertion.tagName && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("code", null, `<${insertion.tagName}${insertion.selfClosing ? "/>" : ">"}`), " \u2014", " "), insertion.descriptions[language])), /* @__PURE__ */ React.createElement("p", { style: editorStyles.helpSettingsDescription }, /* @__PURE__ */ React.createElement("strong", null, copy.parameters, ":"), " ", insertion.parameterDescription[language]), /* @__PURE__ */ React.createElement("ul", { style: editorStyles.helpSettingsList }, insertion.options.map((option) => /* @__PURE__ */ React.createElement("li", { key: option.value }, /* @__PURE__ */ React.createElement("strong", null, option.labels[language]), option.descriptions && /* @__PURE__ */ React.createElement(React.Fragment, null, " \u2014 ", option.descriptions[language]))))))))), /* @__PURE__ */ React.createElement("div", { style:
|
|
6114
|
-
|
|
7298
|
+
/* @__PURE__ */ React.createElement("div", { className: displayClassName, style: { ...editorStyles.display, ...displayStyle }, "data-ssml-editor-display": "" }, isHelpOpen && isSsmlEditorButtonVisible(buttonVisibility, "help") && /* @__PURE__ */ React.createElement("section", { id: helpPanelId, style: editorStyles.helpPanel, "aria-label": copy.helpHeading }, /* @__PURE__ */ React.createElement("h3", { style: editorStyles.helpHeading }, copy.helpHeading), /* @__PURE__ */ React.createElement("p", { style: editorStyles.helpDescription }, copy.helpDescription), helpInsertions.length > 0 && /* @__PURE__ */ React.createElement("ul", { style: editorStyles.helpList }, helpInsertions.map((insertion) => /* @__PURE__ */ React.createElement("li", { key: insertion.id, style: editorStyles.helpItem }, /* @__PURE__ */ React.createElement("details", { className: "ssml-editor-help-settings-accordion", style: editorStyles.helpSettingsAccordion }, /* @__PURE__ */ React.createElement("summary", { className: "ssml-editor-help-settings-summary", style: editorStyles.helpSettingsSummary }, /* @__PURE__ */ React.createElement("span", { style: editorStyles.helpIcon, "aria-hidden": "true" }, insertion.icon), /* @__PURE__ */ React.createElement("span", { style: editorStyles.helpSettingsSummaryContent }, /* @__PURE__ */ React.createElement("strong", null, insertion.labels[language]), " ", insertion.tagName && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("code", null, `<${insertion.tagName}${insertion.selfClosing ? "/>" : ">"}`), " \u2014", " "), insertion.descriptions[language])), /* @__PURE__ */ React.createElement("p", { style: editorStyles.helpSettingsDescription }, /* @__PURE__ */ React.createElement("strong", null, copy.parameters, ":"), " ", insertion.parameterDescription[language]), /* @__PURE__ */ React.createElement("ul", { style: editorStyles.helpSettingsList }, insertion.options.map((option) => /* @__PURE__ */ React.createElement("li", { key: option.value }, /* @__PURE__ */ React.createElement("strong", null, option.labels[language]), option.descriptions && /* @__PURE__ */ React.createElement(React.Fragment, null, " \u2014 ", option.descriptions[language]))))))))), editMode === "visual" ? /* @__PURE__ */ React.createElement("div", { style: editorStyles.visual }, /* @__PURE__ */ React.createElement(
|
|
7299
|
+
VisualSsmlEditor,
|
|
7300
|
+
{
|
|
7301
|
+
document: draftDocument,
|
|
7302
|
+
readOnly: isReadOnly,
|
|
7303
|
+
onChange: commit,
|
|
7304
|
+
onPreviewSelection
|
|
7305
|
+
}
|
|
7306
|
+
)) : /* @__PURE__ */ React.createElement("div", { style: { ...editorStyles.editor, minHeight: editorMinHeight } }, /* @__PURE__ */ React.createElement(
|
|
7307
|
+
import_react5.default,
|
|
6115
7308
|
{
|
|
6116
7309
|
height: editorHeight,
|
|
6117
7310
|
language: "xml",
|
|
@@ -6172,6 +7365,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
|
|
|
6172
7365
|
SSML_HOVER_COPY,
|
|
6173
7366
|
SSML_INSERTIONS,
|
|
6174
7367
|
SsmlEditor,
|
|
7368
|
+
VisualSsmlEditor,
|
|
6175
7369
|
createSsmlEditorInsertionDefinition,
|
|
6176
7370
|
registerSsmlCodeLens,
|
|
6177
7371
|
updateTagAttribute
|