@braze/sanity-plugin-ai-translation 0.2.63 → 0.2.65
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.js +107 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import defaults from "lodash.defaults";
|
|
|
6
6
|
import { Card, Stack, Flex, Text, Box, Heading, Button, Spinner, Inline, Checkbox, Grid, Radio, TextInput, Badge, Select, Label, Code, Tooltip } from "@sanity/ui";
|
|
7
7
|
import { toPlainText } from "@portabletext/react";
|
|
8
8
|
import { uuid } from "@sanity/uuid";
|
|
9
|
+
import { XMLParser } from "fast-xml-parser";
|
|
9
10
|
import cloneDeep from "lodash.clonedeep";
|
|
10
11
|
import get from "lodash.get";
|
|
11
12
|
import set from "lodash.set";
|
|
@@ -1539,7 +1540,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1539
1540
|
cleanedLength: cleanedText.length,
|
|
1540
1541
|
preview: cleanedText.substring(0, 500)
|
|
1541
1542
|
});
|
|
1542
|
-
const parsed =
|
|
1543
|
+
const parsed = parseResponseToNodes(cleanedText, originalDoc);
|
|
1543
1544
|
devLog.debug("Parsed response structure", {
|
|
1544
1545
|
rootNodeCount: parsed.length,
|
|
1545
1546
|
rootNodeNames: parsed.map((n) => n.type === "field" && n.name ? n.name : "unknown")
|
|
@@ -1561,7 +1562,77 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1561
1562
|
codeBlocksRemoved,
|
|
1562
1563
|
preview: cleaned.trim().substring(0, 200)
|
|
1563
1564
|
}), cleaned.trim();
|
|
1564
|
-
},
|
|
1565
|
+
}, TAG_NAMES = ["field", "block", "span"], looksLikeXmlResponse = (text) => TAG_NAMES.some((tag) => text.includes(`<${tag}`)), parseResponseToNodes = (text, originalDoc) => {
|
|
1566
|
+
if (looksLikeXmlResponse(text))
|
|
1567
|
+
try {
|
|
1568
|
+
const wrapped = `<root>${text}</root>`, parsed = new XMLParser({
|
|
1569
|
+
ignoreAttributes: !1,
|
|
1570
|
+
attributeNamePrefix: "@_",
|
|
1571
|
+
textNodeName: "#text",
|
|
1572
|
+
parseTagValue: !1,
|
|
1573
|
+
trimValues: !1,
|
|
1574
|
+
// preserve exact whitespace in text (e.g. trailing space in spans)
|
|
1575
|
+
isArray: (tagName) => TAG_NAMES.includes(tagName)
|
|
1576
|
+
}).parse(wrapped);
|
|
1577
|
+
if (parsed && typeof parsed == "object" && parsed.root) {
|
|
1578
|
+
const nodes = convertXmlObjectToParsedNodes(parsed.root);
|
|
1579
|
+
if (nodes.length > 0)
|
|
1580
|
+
return devLog.debug("Parsed response with XML parser", { rootNodeCount: nodes.length }), nodes;
|
|
1581
|
+
}
|
|
1582
|
+
} catch (err) {
|
|
1583
|
+
devLog.warn("XML parse failed, falling back to regex parser", {
|
|
1584
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
return parseReviewFormat(text, originalDoc);
|
|
1588
|
+
};
|
|
1589
|
+
function normalizeToArray(value) {
|
|
1590
|
+
return Array.isArray(value) ? value : value != null && typeof value == "object" ? [value] : [];
|
|
1591
|
+
}
|
|
1592
|
+
function convertXmlObjectToParsedNodes(root) {
|
|
1593
|
+
const result = [];
|
|
1594
|
+
for (const tagName of TAG_NAMES) {
|
|
1595
|
+
const raw = root[tagName];
|
|
1596
|
+
if (raw !== void 0) {
|
|
1597
|
+
for (const item of normalizeToArray(raw))
|
|
1598
|
+
if (item != null && typeof item == "object") {
|
|
1599
|
+
const node = convertXmlElementToParsedNode(item, tagName);
|
|
1600
|
+
node && result.push(node);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
return result;
|
|
1605
|
+
}
|
|
1606
|
+
function convertXmlElementToParsedNode(obj, tagName) {
|
|
1607
|
+
const attributes = {};
|
|
1608
|
+
for (const [key, value] of Object.entries(obj))
|
|
1609
|
+
if (key.startsWith("@_")) {
|
|
1610
|
+
const attrName = key.slice(2);
|
|
1611
|
+
attributes[attrName] = value != null ? String(value) : "";
|
|
1612
|
+
}
|
|
1613
|
+
let text;
|
|
1614
|
+
const textVal = obj["#text"];
|
|
1615
|
+
textVal != null && (text = typeof textVal == "string" ? textVal : String(textVal));
|
|
1616
|
+
const children = [];
|
|
1617
|
+
for (const childTag of TAG_NAMES) {
|
|
1618
|
+
const raw = obj[childTag];
|
|
1619
|
+
if (raw !== void 0) {
|
|
1620
|
+
for (const item of normalizeToArray(raw))
|
|
1621
|
+
if (item != null && typeof item == "object") {
|
|
1622
|
+
const child = convertXmlElementToParsedNode(item, childTag);
|
|
1623
|
+
child && children.push(child);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return {
|
|
1628
|
+
type: tagName,
|
|
1629
|
+
name: attributes.name,
|
|
1630
|
+
text: text !== void 0 && text !== "" ? unescapeXml(text) : void 0,
|
|
1631
|
+
attributes,
|
|
1632
|
+
children
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
const parseReviewFormat = (text, originalDoc) => {
|
|
1565
1636
|
const root = [], stack = [], lines = text.split(`
|
|
1566
1637
|
`);
|
|
1567
1638
|
devLog.debug("Parsing response text", {
|
|
@@ -1682,7 +1753,24 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1682
1753
|
parent && parent.children.push(node);
|
|
1683
1754
|
} else
|
|
1684
1755
|
root.push(node);
|
|
1685
|
-
else if (attributes.type === "string" && !content) {
|
|
1756
|
+
else if (attributes.type === "string" && content && !content.includes(closingTag)) {
|
|
1757
|
+
const contentLines = [content];
|
|
1758
|
+
for (i++; i < lines.length; ) {
|
|
1759
|
+
const currentLine = lines[i];
|
|
1760
|
+
if (currentLine.includes(closingTag)) {
|
|
1761
|
+
const closingIndex = currentLine.indexOf(closingTag), remainingContent = currentLine.substring(0, closingIndex);
|
|
1762
|
+
remainingContent && contentLines.push(remainingContent);
|
|
1763
|
+
break;
|
|
1764
|
+
}
|
|
1765
|
+
contentLines.push(currentLine), i++;
|
|
1766
|
+
}
|
|
1767
|
+
if (node.text = unescapeXml(contentLines.join(`
|
|
1768
|
+
`)), stack.length > 0) {
|
|
1769
|
+
const parent = stack[stack.length - 1];
|
|
1770
|
+
parent && parent.children.push(node);
|
|
1771
|
+
} else
|
|
1772
|
+
root.push(node);
|
|
1773
|
+
} else if (attributes.type === "string" && !content) {
|
|
1686
1774
|
const contentLines = [];
|
|
1687
1775
|
for (i++; i < lines.length; ) {
|
|
1688
1776
|
const currentLine = lines[i];
|
|
@@ -1880,10 +1968,22 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1880
1968
|
});
|
|
1881
1969
|
return;
|
|
1882
1970
|
}
|
|
1883
|
-
node.text !== void 0 && !node.attributes.readonly
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1971
|
+
if (node.text !== void 0 && !node.attributes.readonly) {
|
|
1972
|
+
const existingValue = get(doc, path);
|
|
1973
|
+
if (existingValue === void 0)
|
|
1974
|
+
devLog.debug("Skipping field not present in original document", {
|
|
1975
|
+
path: path.join("."),
|
|
1976
|
+
attemptedValue: node.text
|
|
1977
|
+
});
|
|
1978
|
+
else if (typeof existingValue == "string") {
|
|
1979
|
+
const value = path.length === 1 ? node.text.trim() : node.text;
|
|
1980
|
+
set(doc, path, value);
|
|
1981
|
+
} else
|
|
1982
|
+
devLog.warn("Skipping text apply at path where value is not a string (prevents structure corruption)", {
|
|
1983
|
+
path: path.join("."),
|
|
1984
|
+
actualType: typeof existingValue
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1887
1987
|
}, applyBlockChanges = (block, blockNode) => {
|
|
1888
1988
|
if (blockNode.attributes._key && block._key !== blockNode.attributes._key) {
|
|
1889
1989
|
devLog.warn("Block key mismatch, skipping", {
|