@ssml-builder-js/ssml-editor-react 2.13.0 → 2.15.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
@@ -4999,29 +4999,107 @@ function localizedElementLabel(element, locale) {
4999
4999
  function filteredVoices(voiceCatalog, locale, region, style) {
5000
5000
  return (voiceCatalog ?? []).filter((voice) => {
5001
5001
  if (locale && voice.locale.toLowerCase() !== locale.toLowerCase()) return false;
5002
- if (region && voice.region && voice.region.toLowerCase() !== region.toLowerCase()) return false;
5002
+ const regions = [voice.region, ...voice.regions ?? []].filter(
5003
+ (candidate) => Boolean(candidate)
5004
+ );
5005
+ if (region && regions.length > 0 && !regions.some((candidate) => candidate.toLowerCase() === region.toLowerCase()))
5006
+ return false;
5003
5007
  if (style && !voice.styles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase())) return false;
5004
5008
  return true;
5005
5009
  });
5006
5010
  }
5007
- function DefaultVoiceSelector({ value, voices, readOnly, locale, onChange }) {
5008
- return /* @__PURE__ */ jsxs2(
5009
- "select",
5011
+ function VoiceCapabilityMatrix({
5012
+ voice,
5013
+ locale
5014
+ }) {
5015
+ const status = voice.status ?? (voice.preview ? "preview" : void 0);
5016
+ return /* @__PURE__ */ jsxs2("div", { className: "ssml-editor-voice-capabilities", "data-voice-capabilities": "", children: [
5017
+ /* @__PURE__ */ jsxs2("div", { children: [
5018
+ locale === "ja" ? "\u30B9\u30C6\u30FC\u30BF\u30B9" : "Status",
5019
+ ": ",
5020
+ status?.toUpperCase() ?? (locale === "ja" ? "GA" : "GA"),
5021
+ status === "preview" || status === "deprecated" ? /* @__PURE__ */ jsx6("span", { role: "status", "aria-label": status, className: "ssml-editor-voice-warning-badge", children: status === "preview" ? "\u26A0 Preview" : "\u26A0 Deprecated" }) : null
5022
+ ] }),
5023
+ (voice.regions?.length || voice.region) && /* @__PURE__ */ jsxs2("div", { children: [
5024
+ locale === "ja" ? "\u30EA\u30FC\u30B8\u30E7\u30F3" : "Regions",
5025
+ ":",
5026
+ " ",
5027
+ [...voice.regions ?? [], ...voice.region ? [voice.region] : []].filter((region, index, all) => all.indexOf(region) === index).join(", ")
5028
+ ] }),
5029
+ voice.supportedTags && voice.supportedTags.length > 0 && /* @__PURE__ */ jsxs2("div", { children: [
5030
+ locale === "ja" ? "\u5BFE\u5FDC\u30BF\u30B0" : "Supported tags",
5031
+ ": ",
5032
+ voice.supportedTags.join(", ")
5033
+ ] }),
5034
+ voice.unsupportedTags && voice.unsupportedTags.length > 0 && /* @__PURE__ */ jsxs2("div", { className: "ssml-editor-voice-unsupported", children: [
5035
+ locale === "ja" ? "\u975E\u5BFE\u5FDC\u30BF\u30B0" : "Unsupported tags",
5036
+ ": ",
5037
+ voice.unsupportedTags.join(", ")
5038
+ ] })
5039
+ ] });
5040
+ }
5041
+ function diagnosticMatchesElement(diagnostic, element) {
5042
+ const name = elementLabel(element).toLowerCase().replace("expressas", "mstts:express-as");
5043
+ if (diagnostic.code === "azure-unsupported-style") return name === "mstts:express-as" || name === "express-as";
5044
+ if (diagnostic.code === "azure-unsupported-tag-for-voice") {
5045
+ return diagnostic.message.toLowerCase().includes(`<${name}>`) || diagnostic.message.toLowerCase().includes(`<${name.replace("mstts:", "")}>`);
5046
+ }
5047
+ if (diagnostic.code === "azure-locale-mismatch" || diagnostic.code === "azure-unsupported-model-for-voice")
5048
+ return name === "voice" || name === "mstts:turn";
5049
+ return false;
5050
+ }
5051
+ function elementWarnings(diagnostics, element) {
5052
+ return diagnostics.filter((diagnostic) => diagnosticMatchesElement(diagnostic, element));
5053
+ }
5054
+ function fieldWarnings(diagnostics, element, field) {
5055
+ return elementWarnings(diagnostics, element).filter((diagnostic) => {
5056
+ if (diagnostic.code === "azure-unsupported-style") return field.key === "style";
5057
+ return field.key === "name" || field.key === "voice";
5058
+ });
5059
+ }
5060
+ function WarningBadge({ messages }) {
5061
+ if (messages.length === 0) return null;
5062
+ return /* @__PURE__ */ jsx6(
5063
+ "span",
5010
5064
  {
5011
- "aria-label": locale === "ja" ? "\u97F3\u58F0" : "Voice",
5012
- value,
5013
- disabled: readOnly,
5014
- onChange: (event) => onChange(event.target.value),
5015
- children: [
5016
- /* @__PURE__ */ jsx6("option", { value: "", children: locale === "ja" ? "\u97F3\u58F0\u3092\u9078\u629E" : "Select a voice" }),
5017
- voices.map((voice) => /* @__PURE__ */ jsxs2("option", { value: voice.name, children: [
5018
- voice.name,
5019
- voice.preview || voice.status === "preview" ? " (preview)" : ""
5020
- ] }, voice.name))
5021
- ]
5065
+ role: "status",
5066
+ "aria-label": messages.map((message) => message.message).join(" "),
5067
+ title: messages.map((message) => message.message).join(" "),
5068
+ "data-ssml-warning-badge": "",
5069
+ "data-testid": "ssml-editor-warning-badge",
5070
+ className: "ssml-editor-warning-badge",
5071
+ children: "\u26A0"
5022
5072
  }
5023
5073
  );
5024
5074
  }
5075
+ function DefaultVoiceSelector({
5076
+ value,
5077
+ voices,
5078
+ selectedVoice,
5079
+ readOnly,
5080
+ locale,
5081
+ onChange
5082
+ }) {
5083
+ return /* @__PURE__ */ jsxs2(Fragment3, { children: [
5084
+ /* @__PURE__ */ jsxs2(
5085
+ "select",
5086
+ {
5087
+ "aria-label": locale === "ja" ? "\u97F3\u58F0" : "Voice",
5088
+ value,
5089
+ disabled: readOnly,
5090
+ onChange: (event) => onChange(event.target.value),
5091
+ children: [
5092
+ /* @__PURE__ */ jsx6("option", { value: "", children: locale === "ja" ? "\u97F3\u58F0\u3092\u9078\u629E" : "Select a voice" }),
5093
+ voices.map((voice) => /* @__PURE__ */ jsxs2("option", { value: voice.name, children: [
5094
+ voice.name,
5095
+ voice.preview || voice.status === "preview" ? " (preview)" : ""
5096
+ ] }, voice.name))
5097
+ ]
5098
+ }
5099
+ ),
5100
+ selectedVoice ? /* @__PURE__ */ jsx6(VoiceCapabilityMatrix, { voice: selectedVoice, locale }) : null
5101
+ ] });
5102
+ }
5025
5103
  function VisualElementInspector({
5026
5104
  document: document2,
5027
5105
  element,
@@ -5034,16 +5112,24 @@ function VisualElementInspector({
5034
5112
  voiceCatalog,
5035
5113
  voiceLocale,
5036
5114
  voiceRegion,
5037
- voiceStyle
5115
+ voiceStyle,
5116
+ diagnostics
5038
5117
  }) {
5039
5118
  if (customInspector) {
5040
5119
  return /* @__PURE__ */ jsx6(Fragment3, { children: customInspector({ document: document2, element, path, readOnly, onChange: commit, locale }) });
5041
5120
  }
5042
5121
  const fields = getElementFields(element);
5043
5122
  const availableVoices = filteredVoices(voiceCatalog, voiceLocale, voiceRegion, voiceStyle);
5123
+ const selectedVoice = voiceCatalog?.find(
5124
+ (voice) => voice.name === (element.type === "voice" ? element.name : void 0)
5125
+ );
5044
5126
  const renderSelector = renderVoiceSelector ?? DefaultVoiceSelector;
5045
5127
  return /* @__PURE__ */ jsxs2("fieldset", { children: [
5046
- /* @__PURE__ */ jsx6("legend", { children: `<${elementLabel(element)}>` }),
5128
+ /* @__PURE__ */ jsxs2("legend", { children: [
5129
+ `<${elementLabel(element)}>`,
5130
+ " ",
5131
+ /* @__PURE__ */ jsx6(WarningBadge, { messages: elementWarnings(diagnostics, element) })
5132
+ ] }),
5047
5133
  fields.length === 0 ? /* @__PURE__ */ jsx6("p", { children: "This element is preserved in the visual tree. Edit its attributes in Code mode." }) : fields.map((field) => {
5048
5134
  const value = element[field.key];
5049
5135
  const inputValue = value === void 0 ? "" : String(value);
@@ -5083,9 +5169,11 @@ function VisualElementInspector({
5083
5169
  url: "\u97F3\u58F0\u5909\u63DB URL",
5084
5170
  profile: "\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB"
5085
5171
  }[field.key] ?? field.label : field.label,
5172
+ /* @__PURE__ */ jsx6(WarningBadge, { messages: fieldWarnings(diagnostics, element, field) }),
5086
5173
  field.key === "name" && elementLabel(element) === "voice" && voiceCatalog ? renderSelector({
5087
5174
  value: inputValue,
5088
5175
  voices: availableVoices,
5176
+ selectedVoice,
5089
5177
  readOnly,
5090
5178
  locale,
5091
5179
  onChange: (value2) => commit(updateOptionalElementProperty(document2, path, field.key, value2))
@@ -5162,20 +5250,25 @@ function TreeNode({
5162
5250
  node,
5163
5251
  path,
5164
5252
  selectedPath,
5165
- onSelect
5253
+ onSelect,
5254
+ getWarnings
5166
5255
  }) {
5167
5256
  if (!isElement(node)) return null;
5168
5257
  const name = elementLabel(node);
5169
5258
  const isSelected = selectedPath?.join(".") === path.join(".");
5170
5259
  return /* @__PURE__ */ jsxs2("li", { children: [
5171
- /* @__PURE__ */ jsx6("button", { type: "button", "aria-current": isSelected ? "true" : void 0, onClick: () => onSelect(path), children: `<${name}>` }),
5260
+ /* @__PURE__ */ jsxs2("button", { type: "button", "aria-current": isSelected ? "true" : void 0, onClick: () => onSelect(path), children: [
5261
+ `<${name}>`,
5262
+ /* @__PURE__ */ jsx6(WarningBadge, { messages: getWarnings(node) })
5263
+ ] }),
5172
5264
  (node.children ?? []).length > 0 && /* @__PURE__ */ jsx6("ul", { children: node.children?.map((child, index) => /* @__PURE__ */ jsx6(
5173
5265
  TreeNode,
5174
5266
  {
5175
5267
  node: child,
5176
5268
  path: [...path, index],
5177
5269
  selectedPath,
5178
- onSelect
5270
+ onSelect,
5271
+ getWarnings
5179
5272
  },
5180
5273
  `${path.join(".")}/${isElement(child) ? elementLabel(child) : JSON.stringify(child)}`
5181
5274
  )) })
@@ -5192,7 +5285,9 @@ function VisualSsmlEditor({
5192
5285
  voiceCatalog,
5193
5286
  voiceLocale,
5194
5287
  voiceRegion,
5195
- voiceStyle
5288
+ voiceStyle,
5289
+ voiceModel,
5290
+ model
5196
5291
  }) {
5197
5292
  const [selectedPath, setSelectedPath] = useState2(null);
5198
5293
  const [selection, setSelection] = useState2({ start: 0, end: 0 });
@@ -5200,7 +5295,27 @@ function VisualSsmlEditor({
5200
5295
  const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
5201
5296
  const selectedElement = selectedPath ? getNodeAtPath(document2.children ?? [], selectedPath) : void 0;
5202
5297
  const selectedCustomInspector = selectedElement && isElement(selectedElement) ? customInspectors?.[elementLabel(selectedElement)] ?? customInspectors?.[selectedElement.type] : void 0;
5203
- const diagnostics = useMemo(() => validateAzureSsml(buildSsml3(document2)), [document2]);
5298
+ const validationOptions = useMemo(
5299
+ () => ({
5300
+ model: voiceModel ?? model,
5301
+ customVoiceDefinitions: voiceCatalog?.map((voice) => ({
5302
+ name: voice.name,
5303
+ locale: voice.locale,
5304
+ regions: voice.regions ?? (voice.region ? [voice.region] : void 0),
5305
+ styles: voice.styles,
5306
+ supportedTags: voice.supportedTags,
5307
+ unsupportedTags: voice.unsupportedTags,
5308
+ models: voice.models,
5309
+ status: voice.status ?? (voice.preview ? "preview" : "ga")
5310
+ }))
5311
+ }),
5312
+ [model, voiceCatalog, voiceModel]
5313
+ );
5314
+ const diagnostics = useMemo(
5315
+ () => validateAzureSsml(buildSsml3(document2), validationOptions),
5316
+ [document2, validationOptions]
5317
+ );
5318
+ const getWarnings = (element) => elementWarnings(diagnostics, element);
5204
5319
  const commit = (nextDocument) => onChange?.(nextDocument);
5205
5320
  const applyWrapper = (tag, attributes) => {
5206
5321
  if (readOnly || !selectedLeaf) return;
@@ -5229,7 +5344,8 @@ function VisualSsmlEditor({
5229
5344
  node,
5230
5345
  path: [index],
5231
5346
  selectedPath,
5232
- onSelect: setSelectedPath
5347
+ onSelect: setSelectedPath,
5348
+ getWarnings
5233
5349
  },
5234
5350
  isElement(node) ? elementLabel(node) : JSON.stringify(node)
5235
5351
  )) })
@@ -5257,9 +5373,11 @@ function VisualSsmlEditor({
5257
5373
  /* @__PURE__ */ jsx6("legend", { children: localizedElementLabel(selectedElement, locale) }),
5258
5374
  /* @__PURE__ */ jsxs2("label", { htmlFor: "ssml-visual-turn-voice", children: [
5259
5375
  locale === "ja" ? "\u97F3\u58F0" : "Voice",
5376
+ /* @__PURE__ */ jsx6(WarningBadge, { messages: elementWarnings(diagnostics, selectedElement) }),
5260
5377
  voiceCatalog ? (renderVoiceSelector ?? DefaultVoiceSelector)({
5261
5378
  value: selectedElement.voice ?? "",
5262
5379
  voices: filteredVoices(voiceCatalog, voiceLocale, voiceRegion, voiceStyle),
5380
+ selectedVoice: voiceCatalog?.find((voice) => voice.name === selectedElement.voice),
5263
5381
  readOnly,
5264
5382
  locale,
5265
5383
  onChange: (value) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "voice", value))
@@ -5361,7 +5479,8 @@ function VisualSsmlEditor({
5361
5479
  voiceCatalog,
5362
5480
  voiceLocale,
5363
5481
  voiceRegion,
5364
- voiceStyle
5482
+ voiceStyle,
5483
+ diagnostics
5365
5484
  }
5366
5485
  ) : selectedLeaf ? /* @__PURE__ */ jsxs2(Fragment3, { children: [
5367
5486
  /* @__PURE__ */ jsxs2("label", { children: [
@@ -6006,6 +6125,8 @@ var SsmlEditor = forwardRef(function SsmlEditor2({
6006
6125
  voiceLocale,
6007
6126
  voiceRegion,
6008
6127
  voiceStyle,
6128
+ voiceModel,
6129
+ model,
6009
6130
  emotionStyles,
6010
6131
  className,
6011
6132
  style,
@@ -6454,7 +6575,9 @@ var SsmlEditor = forwardRef(function SsmlEditor2({
6454
6575
  voiceCatalog,
6455
6576
  voiceLocale,
6456
6577
  voiceRegion,
6457
- voiceStyle
6578
+ voiceStyle,
6579
+ voiceModel,
6580
+ model
6458
6581
  }
6459
6582
  ) }) : /* @__PURE__ */ jsxs3("div", { style: { ...editorStyles.editor, minHeight: editorMinHeight }, children: [
6460
6583
  /* @__PURE__ */ jsx7(