ssml-builder-js 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/react.mjs CHANGED
@@ -42,13 +42,13 @@ import {
42
42
  resolveExpressAsStyles,
43
43
  updateEditableText,
44
44
  updateTagAttribute
45
- } from "./chunk-LCTE26LS.mjs";
45
+ } from "./chunk-BDY2Q2JL.mjs";
46
46
  import {
47
47
  buildPartialSsml,
48
48
  buildSsml,
49
49
  validateAzureSsml,
50
50
  validateSsml
51
- } from "./chunk-CZ2F3TET.mjs";
51
+ } from "./chunk-WXFLUCLR.mjs";
52
52
  import "./chunk-6S5ODO6A.mjs";
53
53
 
54
54
  // packages/ssml-editor-react/src/SsmlEditor.tsx
@@ -1960,13 +1960,65 @@ function localizedElementLabel(element, locale) {
1960
1960
  function filteredVoices(voiceCatalog, locale, region, style) {
1961
1961
  return (voiceCatalog ?? []).filter((voice) => {
1962
1962
  if (locale && voice.locale.toLowerCase() !== locale.toLowerCase()) return false;
1963
- if (region && voice.region && voice.region.toLowerCase() !== region.toLowerCase()) return false;
1963
+ const regions = [voice.region, ...voice.regions ?? []].filter(
1964
+ (candidate) => Boolean(candidate)
1965
+ );
1966
+ if (region && regions.length > 0 && !regions.some((candidate) => candidate.toLowerCase() === region.toLowerCase()))
1967
+ return false;
1964
1968
  if (style && !voice.styles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase())) return false;
1965
1969
  return true;
1966
1970
  });
1967
1971
  }
1968
- function DefaultVoiceSelector({ value, voices, readOnly, locale, onChange }) {
1972
+ function VoiceCapabilityMatrix({
1973
+ voice,
1974
+ locale
1975
+ }) {
1976
+ const status = voice.status ?? (voice.preview ? "preview" : void 0);
1977
+ 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(", ")));
1978
+ }
1979
+ function diagnosticMatchesElement(diagnostic, element) {
1980
+ const name = elementLabel(element).toLowerCase().replace("expressas", "mstts:express-as");
1981
+ if (diagnostic.code === "azure-unsupported-style") return name === "mstts:express-as" || name === "express-as";
1982
+ if (diagnostic.code === "azure-unsupported-tag-for-voice") {
1983
+ return diagnostic.message.toLowerCase().includes(`<${name}>`) || diagnostic.message.toLowerCase().includes(`<${name.replace("mstts:", "")}>`);
1984
+ }
1985
+ if (diagnostic.code === "azure-locale-mismatch" || diagnostic.code === "azure-unsupported-model-for-voice")
1986
+ return name === "voice" || name === "mstts:turn";
1987
+ return false;
1988
+ }
1989
+ function elementWarnings(diagnostics, element) {
1990
+ return diagnostics.filter((diagnostic) => diagnosticMatchesElement(diagnostic, element));
1991
+ }
1992
+ function fieldWarnings(diagnostics, element, field) {
1993
+ return elementWarnings(diagnostics, element).filter((diagnostic) => {
1994
+ if (diagnostic.code === "azure-unsupported-style") return field.key === "style";
1995
+ return field.key === "name" || field.key === "voice";
1996
+ });
1997
+ }
1998
+ function WarningBadge({ messages }) {
1999
+ if (messages.length === 0) return null;
1969
2000
  return /* @__PURE__ */ React.createElement(
2001
+ "span",
2002
+ {
2003
+ role: "status",
2004
+ "aria-label": messages.map((message) => message.message).join(" "),
2005
+ title: messages.map((message) => message.message).join(" "),
2006
+ "data-ssml-warning-badge": "",
2007
+ "data-testid": "ssml-editor-warning-badge",
2008
+ className: "ssml-editor-warning-badge"
2009
+ },
2010
+ "\u26A0"
2011
+ );
2012
+ }
2013
+ function DefaultVoiceSelector({
2014
+ value,
2015
+ voices,
2016
+ selectedVoice,
2017
+ readOnly,
2018
+ locale,
2019
+ onChange
2020
+ }) {
2021
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
1970
2022
  "select",
1971
2023
  {
1972
2024
  "aria-label": locale === "ja" ? "\u97F3\u58F0" : "Voice",
@@ -1976,7 +2028,7 @@ function DefaultVoiceSelector({ value, voices, readOnly, locale, onChange }) {
1976
2028
  },
1977
2029
  /* @__PURE__ */ React.createElement("option", { value: "" }, locale === "ja" ? "\u97F3\u58F0\u3092\u9078\u629E" : "Select a voice"),
1978
2030
  voices.map((voice) => /* @__PURE__ */ React.createElement("option", { key: voice.name, value: voice.name }, voice.name, voice.preview || voice.status === "preview" ? " (preview)" : ""))
1979
- );
2031
+ ), selectedVoice ? /* @__PURE__ */ React.createElement(VoiceCapabilityMatrix, { voice: selectedVoice, locale }) : null);
1980
2032
  }
1981
2033
  function VisualElementInspector({
1982
2034
  document: document2,
@@ -1990,15 +2042,19 @@ function VisualElementInspector({
1990
2042
  voiceCatalog,
1991
2043
  voiceLocale,
1992
2044
  voiceRegion,
1993
- voiceStyle
2045
+ voiceStyle,
2046
+ diagnostics
1994
2047
  }) {
1995
2048
  if (customInspector) {
1996
2049
  return /* @__PURE__ */ React.createElement(React.Fragment, null, customInspector({ document: document2, element, path, readOnly, onChange: commit, locale }));
1997
2050
  }
1998
2051
  const fields = getElementFields(element);
1999
2052
  const availableVoices = filteredVoices(voiceCatalog, voiceLocale, voiceRegion, voiceStyle);
2053
+ const selectedVoice = voiceCatalog?.find(
2054
+ (voice) => voice.name === (element.type === "voice" ? element.name : void 0)
2055
+ );
2000
2056
  const renderSelector = renderVoiceSelector ?? DefaultVoiceSelector;
2001
- 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) => {
2057
+ 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) => {
2002
2058
  const value = element[field.key];
2003
2059
  const inputValue = value === void 0 ? "" : String(value);
2004
2060
  const inputId = `ssml-visual-${field.key}`;
@@ -2035,9 +2091,10 @@ function VisualElementInspector({
2035
2091
  speakerProfileId: "\u8A71\u8005\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB ID",
2036
2092
  url: "\u97F3\u58F0\u5909\u63DB URL",
2037
2093
  profile: "\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB"
2038
- }[field.key] ?? field.label : field.label, field.key === "name" && elementLabel(element) === "voice" && voiceCatalog ? renderSelector({
2094
+ }[field.key] ?? field.label : field.label, /* @__PURE__ */ React.createElement(WarningBadge, { messages: fieldWarnings(diagnostics, element, field) }), field.key === "name" && elementLabel(element) === "voice" && voiceCatalog ? renderSelector({
2039
2095
  value: inputValue,
2040
2096
  voices: availableVoices,
2097
+ selectedVoice,
2041
2098
  readOnly,
2042
2099
  locale,
2043
2100
  onChange: (value2) => commit(updateOptionalElementProperty(document2, path, field.key, value2))
@@ -2112,19 +2169,21 @@ function TreeNode({
2112
2169
  node,
2113
2170
  path,
2114
2171
  selectedPath,
2115
- onSelect
2172
+ onSelect,
2173
+ getWarnings
2116
2174
  }) {
2117
2175
  if (!isElement(node)) return null;
2118
2176
  const name = elementLabel(node);
2119
2177
  const isSelected = selectedPath?.join(".") === path.join(".");
2120
- 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(
2178
+ 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(
2121
2179
  TreeNode,
2122
2180
  {
2123
2181
  key: `${path.join(".")}/${isElement(child) ? elementLabel(child) : JSON.stringify(child)}`,
2124
2182
  node: child,
2125
2183
  path: [...path, index],
2126
2184
  selectedPath,
2127
- onSelect
2185
+ onSelect,
2186
+ getWarnings
2128
2187
  }
2129
2188
  ))));
2130
2189
  }
@@ -2139,7 +2198,9 @@ function VisualSsmlEditor({
2139
2198
  voiceCatalog,
2140
2199
  voiceLocale,
2141
2200
  voiceRegion,
2142
- voiceStyle
2201
+ voiceStyle,
2202
+ voiceModel,
2203
+ model
2143
2204
  }) {
2144
2205
  const [selectedPath, setSelectedPath] = useState2(null);
2145
2206
  const [selection, setSelection] = useState2({ start: 0, end: 0 });
@@ -2147,7 +2208,27 @@ function VisualSsmlEditor({
2147
2208
  const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
2148
2209
  const selectedElement = selectedPath ? getNodeAtPath(document2.children ?? [], selectedPath) : void 0;
2149
2210
  const selectedCustomInspector = selectedElement && isElement(selectedElement) ? customInspectors?.[elementLabel(selectedElement)] ?? customInspectors?.[selectedElement.type] : void 0;
2150
- const diagnostics = useMemo(() => validateAzureSsml(buildSsml(document2)), [document2]);
2211
+ const validationOptions = useMemo(
2212
+ () => ({
2213
+ model: voiceModel ?? model,
2214
+ customVoiceDefinitions: voiceCatalog?.map((voice) => ({
2215
+ name: voice.name,
2216
+ locale: voice.locale,
2217
+ regions: voice.regions ?? (voice.region ? [voice.region] : void 0),
2218
+ styles: voice.styles,
2219
+ supportedTags: voice.supportedTags,
2220
+ unsupportedTags: voice.unsupportedTags,
2221
+ models: voice.models,
2222
+ status: voice.status ?? (voice.preview ? "preview" : "ga")
2223
+ }))
2224
+ }),
2225
+ [model, voiceCatalog, voiceModel]
2226
+ );
2227
+ const diagnostics = useMemo(
2228
+ () => validateAzureSsml(buildSsml(document2), validationOptions),
2229
+ [document2, validationOptions]
2230
+ );
2231
+ const getWarnings = (element) => elementWarnings(diagnostics, element);
2151
2232
  const commit = (nextDocument) => onChange?.(nextDocument);
2152
2233
  const applyWrapper = (tag, attributes) => {
2153
2234
  if (readOnly || !selectedLeaf) return;
@@ -2162,7 +2243,8 @@ function VisualSsmlEditor({
2162
2243
  node,
2163
2244
  path: [index],
2164
2245
  selectedPath,
2165
- onSelect: setSelectedPath
2246
+ onSelect: setSelectedPath,
2247
+ getWarnings
2166
2248
  }
2167
2249
  )))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-form" }, selectedCustomInspector && selectedElement && isElement(selectedElement) ? selectedCustomInspector({
2168
2250
  document: document2,
@@ -2179,9 +2261,10 @@ function VisualSsmlEditor({
2179
2261
  onClick: () => commit(addDialogTurn(document2, selectedPath ?? []))
2180
2262
  },
2181
2263
  locale === "ja" ? "\u30BF\u30FC\u30F3\u3092\u8FFD\u52A0" : "Add turn"
2182
- )) : 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)({
2264
+ )) : 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)({
2183
2265
  value: selectedElement.voice ?? "",
2184
2266
  voices: filteredVoices(voiceCatalog, voiceLocale, voiceRegion, voiceStyle),
2267
+ selectedVoice: voiceCatalog?.find((voice) => voice.name === selectedElement.voice),
2185
2268
  readOnly,
2186
2269
  locale,
2187
2270
  onChange: (value) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "voice", value))
@@ -2255,7 +2338,8 @@ function VisualSsmlEditor({
2255
2338
  voiceCatalog,
2256
2339
  voiceLocale,
2257
2340
  voiceRegion,
2258
- voiceStyle
2341
+ voiceStyle,
2342
+ diagnostics
2259
2343
  }
2260
2344
  ) : selectedLeaf ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, "Text", /* @__PURE__ */ React.createElement(
2261
2345
  "textarea",
@@ -2879,6 +2963,8 @@ var SsmlEditor = forwardRef(function SsmlEditor2({
2879
2963
  voiceLocale,
2880
2964
  voiceRegion,
2881
2965
  voiceStyle,
2966
+ voiceModel,
2967
+ model,
2882
2968
  emotionStyles,
2883
2969
  className,
2884
2970
  style,
@@ -3277,7 +3363,9 @@ var SsmlEditor = forwardRef(function SsmlEditor2({
3277
3363
  voiceCatalog,
3278
3364
  voiceLocale,
3279
3365
  voiceRegion,
3280
- voiceStyle
3366
+ voiceStyle,
3367
+ voiceModel,
3368
+ model
3281
3369
  }
3282
3370
  )) : /* @__PURE__ */ React.createElement("div", { style: { ...editorStyles.editor, minHeight: editorMinHeight } }, /* @__PURE__ */ React.createElement(
3283
3371
  Editor,