ssml-builder-js 2.14.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/react.js CHANGED
@@ -2960,34 +2960,51 @@ function createAzureUrlValidatorRunner(validator, options = {}) {
2960
2960
  const inFlight = /* @__PURE__ */ new Map();
2961
2961
  const waiters = [];
2962
2962
  let active = 0;
2963
- const acquire = async () => {
2963
+ const configuredSignal = options.signal ?? new AbortController().signal;
2964
+ const acquire = async (signal) => {
2965
+ if (signal.aborted) throw new Error("URL validation was aborted.");
2964
2966
  if (active < concurrency) {
2965
2967
  active += 1;
2966
2968
  return;
2967
2969
  }
2968
- await new Promise((resolve) => waiters.push(resolve));
2970
+ await new Promise((resolve, reject) => {
2971
+ let waiter;
2972
+ const abortHandler = () => {
2973
+ const index = waiters.indexOf(waiter);
2974
+ if (index >= 0) waiters.splice(index, 1);
2975
+ signal.removeEventListener("abort", abortHandler);
2976
+ reject(new Error("URL validation was aborted."));
2977
+ };
2978
+ signal.addEventListener("abort", abortHandler, { once: true });
2979
+ waiter = () => {
2980
+ signal.removeEventListener("abort", abortHandler);
2981
+ resolve();
2982
+ };
2983
+ waiters.push(waiter);
2984
+ });
2969
2985
  active += 1;
2970
2986
  };
2971
2987
  const release = () => {
2972
2988
  active -= 1;
2973
2989
  waiters.shift()?.();
2974
2990
  };
2975
- const check = async (url, context) => {
2976
- if (options.signal?.aborted) throw new Error("URL validation was aborted.");
2977
- const cached = cache.get(url);
2991
+ const check = async (url, context, signal = configuredSignal) => {
2992
+ if (signal.aborted) throw new Error("URL validation was aborted.");
2993
+ const key = `${context.tag}:${context.attribute}:${url}`;
2994
+ const cached = cache.get(key);
2978
2995
  if (cached !== void 0) return cached;
2979
- const existing = inFlight.get(url);
2996
+ const existing = inFlight.get(key);
2980
2997
  if (existing) return existing;
2981
2998
  const promise = (async () => {
2982
- await acquire();
2999
+ await acquire(signal);
2983
3000
  try {
2984
- if (options.signal?.aborted) throw new Error("URL validation was aborted.");
2985
- const validation = Promise.resolve(validator(url, context));
3001
+ if (signal.aborted) throw new Error("URL validation was aborted.");
3002
+ const validation = Promise.resolve(validator(url, context, signal));
2986
3003
  let timer;
2987
3004
  let abortHandler;
2988
3005
  const cancellation = new Promise((_resolve, reject) => {
2989
3006
  abortHandler = () => reject(new Error("URL validation was aborted."));
2990
- options.signal?.addEventListener("abort", abortHandler, { once: true });
3007
+ signal.addEventListener("abort", abortHandler, { once: true });
2991
3008
  if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
2992
3009
  timer = setTimeout(
2993
3010
  () => reject(new Error(`URL validation timed out after ${options.timeoutMs} ms.`)),
@@ -2997,24 +3014,24 @@ function createAzureUrlValidatorRunner(validator, options = {}) {
2997
3014
  });
2998
3015
  try {
2999
3016
  const result = await (timer || abortHandler ? Promise.race([validation, cancellation]) : validation);
3000
- cache.set(url, result);
3017
+ cache.set(key, result);
3001
3018
  return result;
3002
3019
  } finally {
3003
3020
  if (timer) clearTimeout(timer);
3004
- if (abortHandler) options.signal?.removeEventListener("abort", abortHandler);
3021
+ if (abortHandler) signal.removeEventListener("abort", abortHandler);
3005
3022
  }
3006
3023
  } finally {
3007
3024
  release();
3008
3025
  }
3009
3026
  })();
3010
- inFlight.set(url, promise);
3027
+ inFlight.set(key, promise);
3011
3028
  try {
3012
3029
  return await promise;
3013
3030
  } finally {
3014
- inFlight.delete(url);
3031
+ inFlight.delete(key);
3015
3032
  }
3016
3033
  };
3017
- return (url, context) => check(url, context);
3034
+ return (url, context, signal) => check(url, context, signal ?? configuredSignal);
3018
3035
  }
3019
3036
  var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
3020
3037
  var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
@@ -3660,6 +3677,7 @@ function validateAzureSsml(ssml, options = {}) {
3660
3677
  ...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
3661
3678
  ...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
3662
3679
  });
3680
+ const validationSignal = options.urlValidatorSignal ?? options.urlValidation?.signal ?? new AbortController().signal;
3663
3681
  let tokens;
3664
3682
  try {
3665
3683
  tokens = tokenizeElements(ssml);
@@ -3669,7 +3687,11 @@ function validateAzureSsml(ssml, options = {}) {
3669
3687
  const checks = tokens.flatMap(
3670
3688
  (token) => urlAttributes(token).map(async ({ attribute, value }) => {
3671
3689
  try {
3672
- const result = await boundedValidator(value, { tag: token.name, attribute });
3690
+ const result = await boundedValidator(
3691
+ value,
3692
+ { tag: token.name, attribute, ...options.sourceNodePath ? { sourceNodePath: options.sourceNodePath } : {} },
3693
+ validationSignal
3694
+ );
3673
3695
  const valid = typeof result === "boolean" ? result : result.valid;
3674
3696
  if (!valid) {
3675
3697
  const reason = typeof result === "boolean" ? void 0 : result.reason;
@@ -6841,6 +6863,40 @@ function VoiceCapabilityMatrix({
6841
6863
  const status = voice.status ?? (voice.preview ? "preview" : void 0);
6842
6864
  return /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-voice-capabilities", "data-voice-capabilities": "" }, /* @__PURE__ */ React.createElement("div", null, locale === "ja" ? "\u30B9\u30C6\u30FC\u30BF\u30B9" : "Status", ": ", status?.toUpperCase() ?? (locale === "ja" ? "GA" : "GA"), status === "preview" || status === "deprecated" ? /* @__PURE__ */ React.createElement("span", { role: "status", "aria-label": status, className: "ssml-editor-voice-warning-badge" }, status === "preview" ? "\u26A0 Preview" : "\u26A0 Deprecated") : null), (voice.regions?.length || voice.region) && /* @__PURE__ */ React.createElement("div", null, locale === "ja" ? "\u30EA\u30FC\u30B8\u30E7\u30F3" : "Regions", ":", " ", [...voice.regions ?? [], ...voice.region ? [voice.region] : []].filter((region, index, all) => all.indexOf(region) === index).join(", ")), voice.supportedTags && voice.supportedTags.length > 0 && /* @__PURE__ */ React.createElement("div", null, locale === "ja" ? "\u5BFE\u5FDC\u30BF\u30B0" : "Supported tags", ": ", voice.supportedTags.join(", ")), voice.unsupportedTags && voice.unsupportedTags.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-voice-unsupported" }, locale === "ja" ? "\u975E\u5BFE\u5FDC\u30BF\u30B0" : "Unsupported tags", ": ", voice.unsupportedTags.join(", ")));
6843
6865
  }
6866
+ function diagnosticMatchesElement(diagnostic, element) {
6867
+ const name = elementLabel(element).toLowerCase().replace("expressas", "mstts:express-as");
6868
+ if (diagnostic.code === "azure-unsupported-style") return name === "mstts:express-as" || name === "express-as";
6869
+ if (diagnostic.code === "azure-unsupported-tag-for-voice") {
6870
+ return diagnostic.message.toLowerCase().includes(`<${name}>`) || diagnostic.message.toLowerCase().includes(`<${name.replace("mstts:", "")}>`);
6871
+ }
6872
+ if (diagnostic.code === "azure-locale-mismatch" || diagnostic.code === "azure-unsupported-model-for-voice")
6873
+ return name === "voice" || name === "mstts:turn";
6874
+ return false;
6875
+ }
6876
+ function elementWarnings(diagnostics, element) {
6877
+ return diagnostics.filter((diagnostic) => diagnosticMatchesElement(diagnostic, element));
6878
+ }
6879
+ function fieldWarnings(diagnostics, element, field) {
6880
+ return elementWarnings(diagnostics, element).filter((diagnostic) => {
6881
+ if (diagnostic.code === "azure-unsupported-style") return field.key === "style";
6882
+ return field.key === "name" || field.key === "voice";
6883
+ });
6884
+ }
6885
+ function WarningBadge({ messages }) {
6886
+ if (messages.length === 0) return null;
6887
+ return /* @__PURE__ */ React.createElement(
6888
+ "span",
6889
+ {
6890
+ role: "status",
6891
+ "aria-label": messages.map((message) => message.message).join(" "),
6892
+ title: messages.map((message) => message.message).join(" "),
6893
+ "data-ssml-warning-badge": "",
6894
+ "data-testid": "ssml-editor-warning-badge",
6895
+ className: "ssml-editor-warning-badge"
6896
+ },
6897
+ "\u26A0"
6898
+ );
6899
+ }
6844
6900
  function DefaultVoiceSelector({
6845
6901
  value,
6846
6902
  voices,
@@ -6873,7 +6929,8 @@ function VisualElementInspector({
6873
6929
  voiceCatalog,
6874
6930
  voiceLocale,
6875
6931
  voiceRegion,
6876
- voiceStyle
6932
+ voiceStyle,
6933
+ diagnostics
6877
6934
  }) {
6878
6935
  if (customInspector) {
6879
6936
  return /* @__PURE__ */ React.createElement(React.Fragment, null, customInspector({ document: document2, element, path, readOnly, onChange: commit, locale }));
@@ -6884,7 +6941,7 @@ function VisualElementInspector({
6884
6941
  (voice) => voice.name === (element.type === "voice" ? element.name : void 0)
6885
6942
  );
6886
6943
  const renderSelector = renderVoiceSelector ?? DefaultVoiceSelector;
6887
- return /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, `<${elementLabel(element)}>`), fields.length === 0 ? /* @__PURE__ */ React.createElement("p", null, "This element is preserved in the visual tree. Edit its attributes in Code mode.") : fields.map((field) => {
6944
+ return /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, `<${elementLabel(element)}>`, " ", /* @__PURE__ */ React.createElement(WarningBadge, { messages: elementWarnings(diagnostics, element) })), fields.length === 0 ? /* @__PURE__ */ React.createElement("p", null, "This element is preserved in the visual tree. Edit its attributes in Code mode.") : fields.map((field) => {
6888
6945
  const value = element[field.key];
6889
6946
  const inputValue = value === void 0 ? "" : String(value);
6890
6947
  const inputId = `ssml-visual-${field.key}`;
@@ -6921,7 +6978,7 @@ function VisualElementInspector({
6921
6978
  speakerProfileId: "\u8A71\u8005\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB ID",
6922
6979
  url: "\u97F3\u58F0\u5909\u63DB URL",
6923
6980
  profile: "\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB"
6924
- }[field.key] ?? field.label : field.label, field.key === "name" && elementLabel(element) === "voice" && voiceCatalog ? renderSelector({
6981
+ }[field.key] ?? field.label : field.label, /* @__PURE__ */ React.createElement(WarningBadge, { messages: fieldWarnings(diagnostics, element, field) }), field.key === "name" && elementLabel(element) === "voice" && voiceCatalog ? renderSelector({
6925
6982
  value: inputValue,
6926
6983
  voices: availableVoices,
6927
6984
  selectedVoice,
@@ -6999,19 +7056,21 @@ function TreeNode({
6999
7056
  node,
7000
7057
  path,
7001
7058
  selectedPath,
7002
- onSelect
7059
+ onSelect,
7060
+ getWarnings
7003
7061
  }) {
7004
7062
  if (!isElement(node)) return null;
7005
7063
  const name = elementLabel(node);
7006
7064
  const isSelected = selectedPath?.join(".") === path.join(".");
7007
- 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(
7065
+ return /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement("button", { type: "button", "aria-current": isSelected ? "true" : void 0, onClick: () => onSelect(path) }, `<${name}>`, /* @__PURE__ */ React.createElement(WarningBadge, { messages: getWarnings(node) })), (node.children ?? []).length > 0 && /* @__PURE__ */ React.createElement("ul", null, node.children?.map((child, index) => /* @__PURE__ */ React.createElement(
7008
7066
  TreeNode,
7009
7067
  {
7010
7068
  key: `${path.join(".")}/${isElement(child) ? elementLabel(child) : JSON.stringify(child)}`,
7011
7069
  node: child,
7012
7070
  path: [...path, index],
7013
7071
  selectedPath,
7014
- onSelect
7072
+ onSelect,
7073
+ getWarnings
7015
7074
  }
7016
7075
  ))));
7017
7076
  }
@@ -7026,7 +7085,9 @@ function VisualSsmlEditor({
7026
7085
  voiceCatalog,
7027
7086
  voiceLocale,
7028
7087
  voiceRegion,
7029
- voiceStyle
7088
+ voiceStyle,
7089
+ voiceModel,
7090
+ model
7030
7091
  }) {
7031
7092
  const [selectedPath, setSelectedPath] = (0, import_react3.useState)(null);
7032
7093
  const [selection, setSelection] = (0, import_react3.useState)({ start: 0, end: 0 });
@@ -7034,7 +7095,27 @@ function VisualSsmlEditor({
7034
7095
  const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
7035
7096
  const selectedElement = selectedPath ? getNodeAtPath(document2.children ?? [], selectedPath) : void 0;
7036
7097
  const selectedCustomInspector = selectedElement && isElement(selectedElement) ? customInspectors?.[elementLabel(selectedElement)] ?? customInspectors?.[selectedElement.type] : void 0;
7037
- const diagnostics = (0, import_react3.useMemo)(() => validateAzureSsml(buildSsml(document2)), [document2]);
7098
+ const validationOptions = (0, import_react3.useMemo)(
7099
+ () => ({
7100
+ model: voiceModel ?? model,
7101
+ customVoiceDefinitions: voiceCatalog?.map((voice) => ({
7102
+ name: voice.name,
7103
+ locale: voice.locale,
7104
+ regions: voice.regions ?? (voice.region ? [voice.region] : void 0),
7105
+ styles: voice.styles,
7106
+ supportedTags: voice.supportedTags,
7107
+ unsupportedTags: voice.unsupportedTags,
7108
+ models: voice.models,
7109
+ status: voice.status ?? (voice.preview ? "preview" : "ga")
7110
+ }))
7111
+ }),
7112
+ [model, voiceCatalog, voiceModel]
7113
+ );
7114
+ const diagnostics = (0, import_react3.useMemo)(
7115
+ () => validateAzureSsml(buildSsml(document2), validationOptions),
7116
+ [document2, validationOptions]
7117
+ );
7118
+ const getWarnings = (element) => elementWarnings(diagnostics, element);
7038
7119
  const commit = (nextDocument) => onChange?.(nextDocument);
7039
7120
  const applyWrapper = (tag, attributes) => {
7040
7121
  if (readOnly || !selectedLeaf) return;
@@ -7049,7 +7130,8 @@ function VisualSsmlEditor({
7049
7130
  node,
7050
7131
  path: [index],
7051
7132
  selectedPath,
7052
- onSelect: setSelectedPath
7133
+ onSelect: setSelectedPath,
7134
+ getWarnings
7053
7135
  }
7054
7136
  )))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-form" }, selectedCustomInspector && selectedElement && isElement(selectedElement) ? selectedCustomInspector({
7055
7137
  document: document2,
@@ -7066,7 +7148,7 @@ function VisualSsmlEditor({
7066
7148
  onClick: () => commit(addDialogTurn(document2, selectedPath ?? []))
7067
7149
  },
7068
7150
  locale === "ja" ? "\u30BF\u30FC\u30F3\u3092\u8FFD\u52A0" : "Add turn"
7069
- )) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:turn" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, localizedElementLabel(selectedElement, locale)), /* @__PURE__ */ React.createElement("label", { htmlFor: "ssml-visual-turn-voice" }, locale === "ja" ? "\u97F3\u58F0" : "Voice", voiceCatalog ? (renderVoiceSelector ?? DefaultVoiceSelector)({
7151
+ )) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:turn" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, localizedElementLabel(selectedElement, locale)), /* @__PURE__ */ React.createElement("label", { htmlFor: "ssml-visual-turn-voice" }, locale === "ja" ? "\u97F3\u58F0" : "Voice", /* @__PURE__ */ React.createElement(WarningBadge, { messages: elementWarnings(diagnostics, selectedElement) }), voiceCatalog ? (renderVoiceSelector ?? DefaultVoiceSelector)({
7070
7152
  value: selectedElement.voice ?? "",
7071
7153
  voices: filteredVoices(voiceCatalog, voiceLocale, voiceRegion, voiceStyle),
7072
7154
  selectedVoice: voiceCatalog?.find((voice) => voice.name === selectedElement.voice),
@@ -7143,7 +7225,8 @@ function VisualSsmlEditor({
7143
7225
  voiceCatalog,
7144
7226
  voiceLocale,
7145
7227
  voiceRegion,
7146
- voiceStyle
7228
+ voiceStyle,
7229
+ diagnostics
7147
7230
  }
7148
7231
  ) : selectedLeaf ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, "Text", /* @__PURE__ */ React.createElement(
7149
7232
  "textarea",
@@ -7767,6 +7850,8 @@ var SsmlEditor = (0, import_react4.forwardRef)(function SsmlEditor2({
7767
7850
  voiceLocale,
7768
7851
  voiceRegion,
7769
7852
  voiceStyle,
7853
+ voiceModel,
7854
+ model,
7770
7855
  emotionStyles,
7771
7856
  className,
7772
7857
  style,
@@ -8165,7 +8250,9 @@ var SsmlEditor = (0, import_react4.forwardRef)(function SsmlEditor2({
8165
8250
  voiceCatalog,
8166
8251
  voiceLocale,
8167
8252
  voiceRegion,
8168
- voiceStyle
8253
+ voiceStyle,
8254
+ voiceModel,
8255
+ model
8169
8256
  }
8170
8257
  )) : /* @__PURE__ */ React.createElement("div", { style: { ...editorStyles.editor, minHeight: editorMinHeight } }, /* @__PURE__ */ React.createElement(
8171
8258
  import_react5.default,