@ssml-builder-js/ssml-editor-react 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/dist/index.mjs CHANGED
@@ -19,6 +19,7 @@ var ssmlPresets_exports = {};
19
19
  __export(ssmlPresets_exports, {
20
20
  AUDIO_DURATION_DESCRIPTIONS: () => AUDIO_DURATION_DESCRIPTIONS,
21
21
  AUDIO_DURATION_PRESETS: () => AUDIO_DURATION_PRESETS,
22
+ BACKGROUND_AUDIO_DURATION_PRESETS: () => BACKGROUND_AUDIO_DURATION_PRESETS,
22
23
  BREAK_STRENGTH_PRESETS: () => BREAK_STRENGTH_PRESETS,
23
24
  BREAK_TIME_DESCRIPTIONS: () => BREAK_TIME_DESCRIPTIONS,
24
25
  BREAK_TIME_PRESETS: () => BREAK_TIME_PRESETS,
@@ -100,6 +101,9 @@ var AZURE_VOICE_STYLE_MAP = {
100
101
  ],
101
102
  "es-ES-ElviraNeural": [],
102
103
  "fil-PH-AngeloNeural": [],
104
+ "fil-PH-Angelo:DragonHDLatestNeural": [],
105
+ "fil-PH-BlessicaNeural": [],
106
+ "fil-PH-Blessica:DragonHDLatestNeural": [],
103
107
  "fr-FR-DeniseNeural": ["cheerful", "sad"],
104
108
  "fr-FR-HenriNeural": ["cheerful", "sad"],
105
109
  "id-ID-GadisNeural": [],
@@ -377,6 +381,7 @@ var SAY_AS_PRESETS = [
377
381
  var LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"];
378
382
  var SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"];
379
383
  var AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"];
384
+ var BACKGROUND_AUDIO_DURATION_PRESETS = ["0", "500", "1000", "3000", "10000"];
380
385
  var SILENCE_TYPE_PRESETS = [
381
386
  "Leading",
382
387
  "Tailing",
@@ -438,8 +443,8 @@ var SSML_ATTRIBUTE_PRESETS = {
438
443
  },
439
444
  "mstts:backgroundaudio": {
440
445
  volume: PROSODY_VOLUME_PRESETS,
441
- fadein: AUDIO_DURATION_PRESETS,
442
- fadeout: AUDIO_DURATION_PRESETS
446
+ fadein: BACKGROUND_AUDIO_DURATION_PRESETS,
447
+ fadeout: BACKGROUND_AUDIO_DURATION_PRESETS
443
448
  },
444
449
  silence: {
445
450
  type: SILENCE_TYPE_PRESETS,
@@ -3288,7 +3293,7 @@ var SSML_COMPLETION_SNIPPETS = [
3288
3293
  },
3289
3294
  {
3290
3295
  label: "mstts:backgroundaudio",
3291
- insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:-3dB}" />`
3296
+ insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:70}" fadein="\${3:1000}" fadeout="\${4:1000}" />`
3292
3297
  },
3293
3298
  {
3294
3299
  label: "mstts:ttsembedding",
@@ -3989,18 +3994,18 @@ var SSML_TAG_DEFINITIONS = [
3989
3994
  },
3990
3995
  {
3991
3996
  name: "volume",
3992
- description: "The background audio volume, for example `-3dB` or `medium`.",
3993
- example: "-3dB"
3997
+ description: "The background audio volume from 0 to 100.",
3998
+ example: "70"
3994
3999
  },
3995
4000
  {
3996
4001
  name: "fadein",
3997
- description: "The fade-in duration, for example `1s`.",
3998
- example: "1s"
4002
+ description: "The fade-in duration in milliseconds from 0 to 10000.",
4003
+ example: "1000"
3999
4004
  },
4000
4005
  {
4001
4006
  name: "fadeout",
4002
- description: "The fade-out duration, for example `500ms`.",
4003
- example: "500ms"
4007
+ description: "The fade-out duration in milliseconds from 0 to 10000.",
4008
+ example: "1000"
4004
4009
  }
4005
4010
  ]
4006
4011
  },
@@ -4659,6 +4664,181 @@ function updateNodesAtPath(nodes, path, update) {
4659
4664
  function updateTextAtPath(document2, path, value) {
4660
4665
  return { ...document2, children: updateNodesAtPath(document2.children ?? [], path, () => value) };
4661
4666
  }
4667
+ function getNodeAtPath(nodes, path) {
4668
+ let current;
4669
+ let currentNodes = nodes;
4670
+ for (const index of path) {
4671
+ current = currentNodes[index];
4672
+ if (current === void 0) return void 0;
4673
+ if (!isElement(current)) {
4674
+ currentNodes = [];
4675
+ } else {
4676
+ currentNodes = current.children ?? [];
4677
+ }
4678
+ }
4679
+ return current;
4680
+ }
4681
+ function updateElementAtPath(document2, path, update) {
4682
+ return {
4683
+ ...document2,
4684
+ children: updateNodesAtPath(document2.children ?? [], path, (node) => isElement(node) ? update(node) : node)
4685
+ };
4686
+ }
4687
+ function updateElementText(element, value) {
4688
+ return { ...element, children: [value] };
4689
+ }
4690
+ function updateOptionalElementProperty(document2, path, property, value) {
4691
+ return updateElementAtPath(document2, path, (element) => {
4692
+ const next = { ...element };
4693
+ if (value.trim()) next[property] = value;
4694
+ else delete next[property];
4695
+ return next;
4696
+ });
4697
+ }
4698
+ var VISUAL_ELEMENT_FIELDS = {
4699
+ voice: [
4700
+ { key: "name", label: "Voice name" },
4701
+ { key: "effect", label: "Effect" }
4702
+ ],
4703
+ prosody: [
4704
+ { key: "rate", label: "Rate" },
4705
+ { key: "pitch", label: "Pitch" },
4706
+ { key: "volume", label: "Volume" },
4707
+ { key: "contour", label: "Contour" },
4708
+ { key: "range", label: "Range" }
4709
+ ],
4710
+ "express-as": [
4711
+ { key: "style", label: "Style" },
4712
+ { key: "styleDegree", label: "Style degree" },
4713
+ { key: "role", label: "Role" }
4714
+ ],
4715
+ expressAs: [
4716
+ { key: "style", label: "Style" },
4717
+ { key: "styleDegree", label: "Style degree" },
4718
+ { key: "role", label: "Role" }
4719
+ ],
4720
+ "mstts:express-as": [
4721
+ { key: "style", label: "Style" },
4722
+ { key: "styleDegree", label: "Style degree" },
4723
+ { key: "role", label: "Role" }
4724
+ ],
4725
+ "say-as": [
4726
+ { key: "interpretAs", label: "Interpret as" },
4727
+ { key: "format", label: "Format" },
4728
+ { key: "detail", label: "Detail" }
4729
+ ],
4730
+ sayAs: [
4731
+ { key: "interpretAs", label: "Interpret as" },
4732
+ { key: "format", label: "Format" },
4733
+ { key: "detail", label: "Detail" }
4734
+ ],
4735
+ phoneme: [
4736
+ { key: "alphabet", label: "Alphabet" },
4737
+ { key: "ph", label: "Pronunciation" }
4738
+ ],
4739
+ emphasis: [{ key: "level", label: "Level" }],
4740
+ audio: [
4741
+ { key: "src", label: "Source URL" },
4742
+ { key: "desc", label: "Description" },
4743
+ { key: "clipBegin", label: "Clip begin" },
4744
+ { key: "clipEnd", label: "Clip end" },
4745
+ { key: "speed", label: "Speed" },
4746
+ { key: "repeatCount", label: "Repeat count" },
4747
+ { key: "repeatDuration", label: "Repeat duration" },
4748
+ { key: "soundLevel", label: "Sound level" }
4749
+ ],
4750
+ mark: [{ key: "name", label: "Mark name" }],
4751
+ bookmark: [{ key: "mark", label: "Bookmark name" }],
4752
+ sub: [{ key: "alias", label: "Alias" }],
4753
+ lang: [{ key: "lang", label: "Language" }],
4754
+ lexicon: [{ key: "uri", label: "Lexicon URI" }],
4755
+ "mstts:silence": [
4756
+ { key: "typeValue", label: "Silence type" },
4757
+ { key: "value", label: "Value" }
4758
+ ],
4759
+ silence: [
4760
+ { key: "typeValue", label: "Silence type" },
4761
+ { key: "value", label: "Value" }
4762
+ ],
4763
+ "mstts:audioduration": [{ key: "value", label: "Duration" }],
4764
+ "mstts:ttsembedding": [{ key: "speakerProfileId", label: "Speaker profile ID" }],
4765
+ "mstts:embedding": [
4766
+ { key: "id", label: "Embedding ID" },
4767
+ { key: "speakerProfileId", label: "Speaker profile ID" }
4768
+ ],
4769
+ "mstts:voiceconversion": [
4770
+ { key: "url", label: "Source URL" },
4771
+ { key: "profile", label: "Profile" },
4772
+ { key: "speakerProfileId", label: "Speaker profile ID" }
4773
+ ]
4774
+ };
4775
+ function getElementFields(element) {
4776
+ return VISUAL_ELEMENT_FIELDS[elementLabel(element)] ?? [];
4777
+ }
4778
+ function VisualElementInspector({
4779
+ document: document2,
4780
+ element,
4781
+ path,
4782
+ readOnly,
4783
+ commit
4784
+ }) {
4785
+ const fields = getElementFields(element);
4786
+ return /* @__PURE__ */ jsxs2("fieldset", { children: [
4787
+ /* @__PURE__ */ jsx6("legend", { children: `<${elementLabel(element)}>` }),
4788
+ fields.length === 0 ? /* @__PURE__ */ jsx6("p", { children: "This element is preserved in the visual tree. Edit its attributes in Code mode." }) : fields.map((field) => {
4789
+ const value = element[field.key];
4790
+ const inputValue = value === void 0 ? "" : String(value);
4791
+ const inputId = `ssml-visual-${field.key}`;
4792
+ return /* @__PURE__ */ jsxs2("label", { htmlFor: inputId, children: [
4793
+ field.label,
4794
+ field.multiline ? /* @__PURE__ */ jsx6(
4795
+ "textarea",
4796
+ {
4797
+ id: inputId,
4798
+ value: inputValue,
4799
+ readOnly,
4800
+ onChange: (event) => commit(updateOptionalElementProperty(document2, path, field.key, event.target.value))
4801
+ }
4802
+ ) : /* @__PURE__ */ jsx6(
4803
+ "input",
4804
+ {
4805
+ id: inputId,
4806
+ value: inputValue,
4807
+ readOnly,
4808
+ onChange: (event) => commit(updateOptionalElementProperty(document2, path, field.key, event.target.value))
4809
+ }
4810
+ )
4811
+ ] }, field.key);
4812
+ })
4813
+ ] });
4814
+ }
4815
+ function getElementAncestors(nodes, path, ancestors = []) {
4816
+ const [index, ...rest] = path;
4817
+ const node = nodes[index];
4818
+ if (!node || !isElement(node)) return ancestors;
4819
+ if (rest.length === 0) return ancestors;
4820
+ return getElementAncestors(node.children ?? [], rest, [...ancestors, node]);
4821
+ }
4822
+ function buildVisualPreview(document2, leaf, selection) {
4823
+ const start = Math.max(0, Math.min(selection.start, leaf.value.length));
4824
+ const end = Math.max(start, Math.min(selection.end, leaf.value.length));
4825
+ const value = start === end ? leaf.value : leaf.value.slice(start, end);
4826
+ const ancestors = getElementAncestors(document2.children ?? [], leaf.path);
4827
+ const children = ancestors.reduceRight(
4828
+ (current, ancestor) => [{ ...ancestor, children: current }],
4829
+ [value]
4830
+ );
4831
+ return buildSsml3({ ...document2, children });
4832
+ }
4833
+ function addDialogTurn(document2, path) {
4834
+ return updateElementAtPath(document2, path, (element) => ({
4835
+ ...element,
4836
+ children: [
4837
+ ...element.children ?? [],
4838
+ { type: "mstts:turn", voice: "en-US-JennyNeural", children: ["New dialog turn"] }
4839
+ ]
4840
+ }));
4841
+ }
4662
4842
  function wrapTextAtPath(document2, path, start, end, tag, attributes) {
4663
4843
  return {
4664
4844
  ...document2,
@@ -4713,6 +4893,7 @@ function VisualSsmlEditor({
4713
4893
  const [selection, setSelection] = useState2({ start: 0, end: 0 });
4714
4894
  const textLeaves = useMemo(() => collectTextLeaves(document2.children ?? []), [document2]);
4715
4895
  const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
4896
+ const selectedElement = selectedPath ? getNodeAtPath(document2.children ?? [], selectedPath) : void 0;
4716
4897
  const diagnostics = useMemo(() => validateAzureSsml(buildSsml3(document2)), [document2]);
4717
4898
  const commit = (nextDocument) => onChange?.(nextDocument);
4718
4899
  const applyWrapper = (tag, attributes) => {
@@ -4747,7 +4928,115 @@ function VisualSsmlEditor({
4747
4928
  isElement(node) ? elementLabel(node) : JSON.stringify(node)
4748
4929
  )) })
4749
4930
  ] }),
4750
- /* @__PURE__ */ jsx6("div", { className: "ssml-editor-visual-form", children: selectedLeaf ? /* @__PURE__ */ jsxs2(Fragment3, { children: [
4931
+ /* @__PURE__ */ jsx6("div", { className: "ssml-editor-visual-form", children: selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:dialog" ? /* @__PURE__ */ jsxs2("fieldset", { children: [
4932
+ /* @__PURE__ */ jsx6("legend", { children: "Dialog" }),
4933
+ /* @__PURE__ */ jsx6("p", { children: "Add and edit speaker turns in this dialog." }),
4934
+ /* @__PURE__ */ jsx6(
4935
+ "button",
4936
+ {
4937
+ type: "button",
4938
+ disabled: readOnly,
4939
+ onClick: () => commit(addDialogTurn(document2, selectedPath ?? [])),
4940
+ children: "Add turn"
4941
+ }
4942
+ )
4943
+ ] }) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:turn" ? /* @__PURE__ */ jsxs2("fieldset", { children: [
4944
+ /* @__PURE__ */ jsx6("legend", { children: "Dialog turn" }),
4945
+ /* @__PURE__ */ jsxs2("label", { children: [
4946
+ "Voice",
4947
+ /* @__PURE__ */ jsx6(
4948
+ "input",
4949
+ {
4950
+ value: selectedElement.voice ?? "",
4951
+ readOnly,
4952
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "voice", event.target.value))
4953
+ }
4954
+ )
4955
+ ] }),
4956
+ /* @__PURE__ */ jsxs2("label", { children: [
4957
+ "Speaker",
4958
+ /* @__PURE__ */ jsx6(
4959
+ "input",
4960
+ {
4961
+ value: selectedElement.speaker ?? "",
4962
+ readOnly,
4963
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "speaker", event.target.value))
4964
+ }
4965
+ )
4966
+ ] }),
4967
+ /* @__PURE__ */ jsxs2("label", { children: [
4968
+ "Turn text",
4969
+ /* @__PURE__ */ jsx6(
4970
+ "textarea",
4971
+ {
4972
+ value: selectedElement.children?.filter((child) => typeof child === "string").join("") ?? "",
4973
+ readOnly,
4974
+ onChange: (event) => commit(
4975
+ updateElementAtPath(
4976
+ document2,
4977
+ selectedPath ?? [],
4978
+ (element) => updateElementText(element, event.target.value)
4979
+ )
4980
+ )
4981
+ }
4982
+ )
4983
+ ] })
4984
+ ] }) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:backgroundaudio" ? /* @__PURE__ */ jsxs2("fieldset", { children: [
4985
+ /* @__PURE__ */ jsx6("legend", { children: "Background audio" }),
4986
+ /* @__PURE__ */ jsxs2("label", { children: [
4987
+ "Source URL",
4988
+ /* @__PURE__ */ jsx6(
4989
+ "input",
4990
+ {
4991
+ value: selectedElement.src ?? "",
4992
+ readOnly,
4993
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "src", event.target.value))
4994
+ }
4995
+ )
4996
+ ] }),
4997
+ /* @__PURE__ */ jsxs2("label", { children: [
4998
+ "Volume",
4999
+ /* @__PURE__ */ jsx6(
5000
+ "input",
5001
+ {
5002
+ value: selectedElement.volume === void 0 ? "" : String(selectedElement.volume),
5003
+ readOnly,
5004
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "volume", event.target.value))
5005
+ }
5006
+ )
5007
+ ] }),
5008
+ /* @__PURE__ */ jsxs2("label", { children: [
5009
+ "Fade in",
5010
+ /* @__PURE__ */ jsx6(
5011
+ "input",
5012
+ {
5013
+ value: selectedElement.fadeIn === void 0 ? "" : String(selectedElement.fadeIn),
5014
+ readOnly,
5015
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "fadeIn", event.target.value))
5016
+ }
5017
+ )
5018
+ ] }),
5019
+ /* @__PURE__ */ jsxs2("label", { children: [
5020
+ "Fade out",
5021
+ /* @__PURE__ */ jsx6(
5022
+ "input",
5023
+ {
5024
+ value: selectedElement.fadeOut === void 0 ? "" : String(selectedElement.fadeOut),
5025
+ readOnly,
5026
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "fadeOut", event.target.value))
5027
+ }
5028
+ )
5029
+ ] })
5030
+ ] }) : selectedElement && isElement(selectedElement) ? /* @__PURE__ */ jsx6(
5031
+ VisualElementInspector,
5032
+ {
5033
+ document: document2,
5034
+ element: selectedElement,
5035
+ path: selectedPath ?? [],
5036
+ readOnly,
5037
+ commit
5038
+ }
5039
+ ) : selectedLeaf ? /* @__PURE__ */ jsxs2(Fragment3, { children: [
4751
5040
  /* @__PURE__ */ jsxs2("label", { children: [
4752
5041
  "Text",
4753
5042
  /* @__PURE__ */ jsx6(
@@ -4787,7 +5076,7 @@ function VisualSsmlEditor({
4787
5076
  "button",
4788
5077
  {
4789
5078
  type: "button",
4790
- onClick: () => onPreviewSelection(buildSsml3({ ...document2, children: [selectedLeaf.value] })),
5079
+ onClick: () => onPreviewSelection(buildVisualPreview(document2, selectedLeaf, selection)),
4791
5080
  children: "Preview selection"
4792
5081
  }
4793
5082
  )