@braze/sanity-plugin-ai-translation 0.2.76 → 0.2.77
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.d.ts +0 -3
- package/dist/index.js +148 -143
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -284,7 +284,6 @@ declare interface OpenAIApiCallEvent extends TelemetryEventBase {
|
|
|
284
284
|
total: number;
|
|
285
285
|
cached: number;
|
|
286
286
|
};
|
|
287
|
-
cost: number;
|
|
288
287
|
durationMs: number;
|
|
289
288
|
};
|
|
290
289
|
}
|
|
@@ -694,8 +693,6 @@ declare interface TranslationJobCompletedEvent extends TelemetryEventBase {
|
|
|
694
693
|
targetLanguage: string;
|
|
695
694
|
translationMode: TranslationMode;
|
|
696
695
|
durationMs: number;
|
|
697
|
-
tokensUsed?: number;
|
|
698
|
-
estimatedCost?: number;
|
|
699
696
|
};
|
|
700
697
|
}
|
|
701
698
|
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getPublishedId, pathToString, useClient, defineType, defineField, definePlugin } from "sanity";
|
|
1
|
+
import { getPublishedId, pathToString, useClient, useValidationStatus, defineType, defineField, definePlugin } from "sanity";
|
|
2
2
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { WarningOutlineIcon, SyncIcon, SearchIcon,
|
|
3
|
+
import { WarningOutlineIcon, SyncIcon, SearchIcon, ErrorOutlineIcon, CheckmarkIcon, CircleIcon, ArrowRightIcon, ResetIcon, PanelRightIcon, TranslateIcon } from "@sanity/icons";
|
|
4
4
|
import { createContext, useReducer, useRef, useEffect, useContext, useMemo, useState, useCallback } from "react";
|
|
5
5
|
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";
|
|
@@ -10,6 +10,7 @@ import cloneDeep from "lodash.clonedeep";
|
|
|
10
10
|
import get from "lodash.get";
|
|
11
11
|
import set from "lodash.set";
|
|
12
12
|
import { useListeningQuery, useOpenInNewPane } from "sanity-plugin-utils";
|
|
13
|
+
import { IntentLink } from "sanity/router";
|
|
13
14
|
const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING === "true" || process.env.SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING === "true", devLog = {
|
|
14
15
|
info: (message, context) => {
|
|
15
16
|
isLoggingEnabled() && console.info("[AI Translation]", message, context ? JSON.stringify(context, null, 2) : "");
|
|
@@ -1596,7 +1597,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1596
1597
|
reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,
|
|
1597
1598
|
total: result.usage?.total_tokens || 0,
|
|
1598
1599
|
cached: result.usage?.input_tokens_details?.cached_tokens || 0
|
|
1599
|
-
}
|
|
1600
|
+
};
|
|
1600
1601
|
let content = "";
|
|
1601
1602
|
if (result.output && Array.isArray(result.output)) {
|
|
1602
1603
|
const messageItem = result.output.find((item) => item.type === "message");
|
|
@@ -1604,8 +1605,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1604
1605
|
}
|
|
1605
1606
|
return content || (content = result.output_text || result.text || ""), {
|
|
1606
1607
|
content,
|
|
1607
|
-
tokensUsed
|
|
1608
|
-
cost
|
|
1608
|
+
tokensUsed
|
|
1609
1609
|
};
|
|
1610
1610
|
}, callChatCompletionsAPI = async (apiKey, request) => {
|
|
1611
1611
|
const model = request.model || "gpt-4o", requestBody = {
|
|
@@ -1657,56 +1657,11 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1657
1657
|
reasoning: usage.reasoning_tokens || 0,
|
|
1658
1658
|
total: usage.total_tokens || 0,
|
|
1659
1659
|
cached: usage.prompt_tokens_details?.cached_tokens || 0
|
|
1660
|
-
}
|
|
1660
|
+
};
|
|
1661
1661
|
return {
|
|
1662
1662
|
content: result.choices[0].message.content || "",
|
|
1663
|
-
tokensUsed
|
|
1664
|
-
cost
|
|
1663
|
+
tokensUsed
|
|
1665
1664
|
};
|
|
1666
|
-
}, calculateCost = (tokens, model) => {
|
|
1667
|
-
const pricing = {
|
|
1668
|
-
"gpt-5": {
|
|
1669
|
-
input: 125e-8,
|
|
1670
|
-
output: 1e-5,
|
|
1671
|
-
reasoning: 0
|
|
1672
|
-
},
|
|
1673
|
-
"gpt-5.1": {
|
|
1674
|
-
input: 125e-8,
|
|
1675
|
-
output: 1e-5,
|
|
1676
|
-
reasoning: 0
|
|
1677
|
-
},
|
|
1678
|
-
"gpt-5-mini": {
|
|
1679
|
-
input: 25e-8,
|
|
1680
|
-
output: 2e-6,
|
|
1681
|
-
reasoning: 0
|
|
1682
|
-
},
|
|
1683
|
-
"gpt-5-nano": {
|
|
1684
|
-
input: 50000000000000004e-24,
|
|
1685
|
-
output: 40000000000000003e-23,
|
|
1686
|
-
reasoning: 0
|
|
1687
|
-
},
|
|
1688
|
-
"gpt-5-pro": {
|
|
1689
|
-
input: 15e-6,
|
|
1690
|
-
output: 12e-5,
|
|
1691
|
-
reasoning: 0
|
|
1692
|
-
},
|
|
1693
|
-
"gpt-4o": {
|
|
1694
|
-
input: 25e-7,
|
|
1695
|
-
output: 1e-5,
|
|
1696
|
-
reasoning: 0
|
|
1697
|
-
},
|
|
1698
|
-
"gpt-4-turbo": {
|
|
1699
|
-
input: 1e-5,
|
|
1700
|
-
output: 3e-5,
|
|
1701
|
-
reasoning: 0
|
|
1702
|
-
},
|
|
1703
|
-
"gpt-4": {
|
|
1704
|
-
input: 3e-5,
|
|
1705
|
-
output: 6e-5,
|
|
1706
|
-
reasoning: 0
|
|
1707
|
-
}
|
|
1708
|
-
}, modelPricing = pricing[model] || pricing["gpt-5"], cachedInputRate = model === "gpt-5.1" ? 0.125 / 1e6 : modelPricing.input;
|
|
1709
|
-
return Math.max(0, tokens.prompt - tokens.cached) * modelPricing.input + tokens.cached * cachedInputRate + tokens.completion * modelPricing.output + tokens.reasoning * modelPricing.reasoning;
|
|
1710
1665
|
}, deserializeDocument = (originalDoc, reviewedText) => {
|
|
1711
1666
|
devLog.debug("Raw GPT-5 response received", {
|
|
1712
1667
|
length: reviewedText.length,
|
|
@@ -2349,7 +2304,6 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2349
2304
|
openaiResponseContent = response.content, devLog.info("OpenAI translation response received", {
|
|
2350
2305
|
...logContext,
|
|
2351
2306
|
tokensUsed: response.tokensUsed,
|
|
2352
|
-
cost: response.cost,
|
|
2353
2307
|
responseLength: response.content.length,
|
|
2354
2308
|
responsePreview: response.content.substring(0, 500)
|
|
2355
2309
|
});
|
|
@@ -2381,13 +2335,8 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2381
2335
|
originalValue: field.originalValue,
|
|
2382
2336
|
translatedValue: field.translatedValue
|
|
2383
2337
|
}))
|
|
2384
|
-
})), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating_with_openai", {
|
|
2385
|
-
|
|
2386
|
-
estimatedCost: response.cost
|
|
2387
|
-
}), {
|
|
2388
|
-
document: result,
|
|
2389
|
-
tokensUsed: response.tokensUsed.total,
|
|
2390
|
-
cost: response.cost
|
|
2338
|
+
})), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating_with_openai"), {
|
|
2339
|
+
document: result
|
|
2391
2340
|
};
|
|
2392
2341
|
} catch (error) {
|
|
2393
2342
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
@@ -2723,7 +2672,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2723
2672
|
documentId: sourceDocument._id,
|
|
2724
2673
|
documentType: sourceDocument._type
|
|
2725
2674
|
}), documentToTranslate = filteredDocument;
|
|
2726
|
-
let translatedDoc,
|
|
2675
|
+
let translatedDoc, previousError = null;
|
|
2727
2676
|
const performTranslation = async (errorContext) => {
|
|
2728
2677
|
switch (translationMode) {
|
|
2729
2678
|
case "sanity-only": {
|
|
@@ -2783,19 +2732,14 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2783
2732
|
telemetryConfig
|
|
2784
2733
|
});
|
|
2785
2734
|
return result ? {
|
|
2786
|
-
document: result.document
|
|
2787
|
-
tokensUsed: result.tokensUsed,
|
|
2788
|
-
cost: result.cost
|
|
2735
|
+
document: result.document
|
|
2789
2736
|
} : { document: null };
|
|
2790
2737
|
}
|
|
2791
2738
|
default:
|
|
2792
2739
|
throw new Error(`Invalid translation mode: ${translationMode}`);
|
|
2793
2740
|
}
|
|
2794
|
-
}
|
|
2795
|
-
if (translatedDoc =
|
|
2796
|
-
tokensUsed: translationResult.tokensUsed,
|
|
2797
|
-
cost: translationResult.cost
|
|
2798
|
-
}, !translatedDoc)
|
|
2741
|
+
};
|
|
2742
|
+
if (translatedDoc = (await performTranslation()).document, !translatedDoc)
|
|
2799
2743
|
return null;
|
|
2800
2744
|
let transformedResult;
|
|
2801
2745
|
Object.keys(originalStructure).length > 0 ? (devLog.debug("Restoring field transforms after translation", {
|
|
@@ -2858,13 +2802,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2858
2802
|
targetLanguage,
|
|
2859
2803
|
error: error instanceof Error ? error.message : String(error)
|
|
2860
2804
|
}), previousError = error instanceof Error ? error : new Error(String(error));
|
|
2861
|
-
const errorContext = `Previous translation attempt failed with Sanity validation error: ${previousError.message}. Please ensure the translated document structure exactly matches the source document structure
|
|
2862
|
-
if (translatedDoc =
|
|
2805
|
+
const errorContext = `Previous translation attempt failed with Sanity validation error: ${previousError.message}. Please ensure the translated document structure exactly matches the source document structure.`;
|
|
2806
|
+
if (translatedDoc = (await performTranslation(errorContext)).document, !translatedDoc)
|
|
2863
2807
|
throw new Error("Translation retry failed: no document returned");
|
|
2864
|
-
translationMetrics = {
|
|
2865
|
-
tokensUsed: retryResult.tokensUsed,
|
|
2866
|
-
cost: retryResult.cost
|
|
2867
|
-
};
|
|
2868
2808
|
let retryTransformedResult;
|
|
2869
2809
|
Object.keys(originalStructure).length > 0 ? retryTransformedResult = restoreTransforms({
|
|
2870
2810
|
translatedDocument: translatedDoc,
|
|
@@ -2896,17 +2836,13 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2896
2836
|
}
|
|
2897
2837
|
}
|
|
2898
2838
|
), {
|
|
2899
|
-
document: retryNewDoc
|
|
2900
|
-
tokensUsed: translationMetrics.tokensUsed,
|
|
2901
|
-
cost: translationMetrics.cost
|
|
2839
|
+
document: retryNewDoc
|
|
2902
2840
|
};
|
|
2903
2841
|
}
|
|
2904
2842
|
throw error;
|
|
2905
2843
|
}
|
|
2906
2844
|
return {
|
|
2907
|
-
document: newDoc
|
|
2908
|
-
tokensUsed: translationMetrics.tokensUsed,
|
|
2909
|
-
cost: translationMetrics.cost
|
|
2845
|
+
document: newDoc
|
|
2910
2846
|
};
|
|
2911
2847
|
}, translateDocument = async ({
|
|
2912
2848
|
dispatch,
|
|
@@ -2938,7 +2874,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2938
2874
|
client,
|
|
2939
2875
|
sourceDocument,
|
|
2940
2876
|
sourceLanguage
|
|
2941
|
-
}), existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage),
|
|
2877
|
+
}), existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage), newDocument = (await handleTranslation({
|
|
2942
2878
|
dispatch,
|
|
2943
2879
|
client,
|
|
2944
2880
|
schemaId,
|
|
@@ -2955,7 +2891,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2955
2891
|
replaceExisting,
|
|
2956
2892
|
mainSourceDocumentId,
|
|
2957
2893
|
telemetryConfig
|
|
2958
|
-
})
|
|
2894
|
+
}))?.document;
|
|
2959
2895
|
if (!newDocument) {
|
|
2960
2896
|
const failureError = new Error("Translation returned null");
|
|
2961
2897
|
devLog.error("Translation failed: no document returned", failureError, logContext), setJobStatus(
|
|
@@ -3006,9 +2942,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3006
2942
|
targetLanguage,
|
|
3007
2943
|
"completed",
|
|
3008
2944
|
{
|
|
3009
|
-
targetId: newDocument._id
|
|
3010
|
-
tokensUsed: translationResult?.tokensUsed,
|
|
3011
|
-
estimatedCost: translationResult?.cost
|
|
2945
|
+
targetId: newDocument._id
|
|
3012
2946
|
},
|
|
3013
2947
|
mainSourceDocumentId
|
|
3014
2948
|
), devLog.info("Translation completed successfully", {
|
|
@@ -3023,9 +2957,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3023
2957
|
sourceLanguage,
|
|
3024
2958
|
targetLanguage,
|
|
3025
2959
|
translationMode,
|
|
3026
|
-
durationMs: Date.now() - jobStartTime
|
|
3027
|
-
tokensUsed: translationResult?.tokensUsed,
|
|
3028
|
-
estimatedCost: translationResult?.cost
|
|
2960
|
+
durationMs: Date.now() - jobStartTime
|
|
3029
2961
|
}
|
|
3030
2962
|
}), devLog.timeEnd("translateDocument"), devLog.groupEnd(), newDocument._id;
|
|
3031
2963
|
} catch (error) {
|
|
@@ -3343,7 +3275,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3343
3275
|
] })
|
|
3344
3276
|
] })
|
|
3345
3277
|
] }) });
|
|
3346
|
-
}, ActionButtonBar = ({ setIsDialogOpen }) => {
|
|
3278
|
+
}, ActionButtonBar = ({ setIsDialogOpen, hasValidationErrors }) => {
|
|
3347
3279
|
const {
|
|
3348
3280
|
document,
|
|
3349
3281
|
config,
|
|
@@ -3475,7 +3407,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3475
3407
|
tone: "positive",
|
|
3476
3408
|
size: 2,
|
|
3477
3409
|
onClick: handleTranslate,
|
|
3478
|
-
disabled: selectedTargetLanguages.length === 0 || !["idle", "completed"].includes(overallStatus) || releaseConfig.mode === "existing" && !releaseConfig.existingReleaseId || isCheckingConflicts,
|
|
3410
|
+
disabled: selectedTargetLanguages.length === 0 || !["idle", "completed"].includes(overallStatus) || releaseConfig.mode === "existing" && !releaseConfig.existingReleaseId || isCheckingConflicts || hasValidationErrors,
|
|
3479
3411
|
children: isCheckingConflicts ? "Checking..." : "Translate"
|
|
3480
3412
|
}
|
|
3481
3413
|
)
|
|
@@ -3921,13 +3853,106 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3921
3853
|
] })
|
|
3922
3854
|
}
|
|
3923
3855
|
)
|
|
3856
|
+
] })
|
|
3857
|
+
] }) });
|
|
3858
|
+
}, DocumentValidator = ({ documentId, documentType, onResult }) => {
|
|
3859
|
+
const publishedId = getPublishedId(documentId), { isValidating, validation } = useValidationStatus(publishedId, documentType);
|
|
3860
|
+
return useEffect(() => {
|
|
3861
|
+
const errors = validation.filter((v) => v.level === "error");
|
|
3862
|
+
onResult(documentId, { isValidating, errors });
|
|
3863
|
+
}, [isValidating, validation, documentId, onResult]), null;
|
|
3864
|
+
}, ValidationPanel = ({ onValidationChange }) => {
|
|
3865
|
+
const { document, references, selectedReferenceIds, shouldTranslateReferences } = useTranslationContext(), [results, setResults] = useState(/* @__PURE__ */ new Map()), documentsToValidate = useMemo(() => {
|
|
3866
|
+
const docs = [
|
|
3867
|
+
{
|
|
3868
|
+
_id: document._id,
|
|
3869
|
+
_type: document._type,
|
|
3870
|
+
title: document.title || document.name || document._id
|
|
3871
|
+
}
|
|
3872
|
+
];
|
|
3873
|
+
if (shouldTranslateReferences)
|
|
3874
|
+
for (const ref of references)
|
|
3875
|
+
selectedReferenceIds.has(ref._id) && docs.push(ref);
|
|
3876
|
+
return docs;
|
|
3877
|
+
}, [document, references, selectedReferenceIds, shouldTranslateReferences]), handleResult = useCallback((id, result) => {
|
|
3878
|
+
setResults((prev) => {
|
|
3879
|
+
const next = new Map(prev);
|
|
3880
|
+
return next.set(id, result), next;
|
|
3881
|
+
});
|
|
3882
|
+
}, []), { shouldBlock, someStillValidating, documentsWithErrors } = useMemo(() => {
|
|
3883
|
+
const allValidated = documentsToValidate.every((doc) => results.has(doc._id)), validating = [...results.values()].some((r) => r.isValidating) || !allValidated, docsWithErrors = [];
|
|
3884
|
+
for (const doc of documentsToValidate) {
|
|
3885
|
+
const result = results.get(doc._id);
|
|
3886
|
+
result && result.errors.length > 0 && docsWithErrors.push({ ...doc, errors: result.errors });
|
|
3887
|
+
}
|
|
3888
|
+
const hasErrors = docsWithErrors.length > 0;
|
|
3889
|
+
return {
|
|
3890
|
+
shouldBlock: validating || hasErrors,
|
|
3891
|
+
someStillValidating: validating,
|
|
3892
|
+
documentsWithErrors: docsWithErrors
|
|
3893
|
+
};
|
|
3894
|
+
}, [documentsToValidate, results]);
|
|
3895
|
+
useEffect(() => {
|
|
3896
|
+
onValidationChange(shouldBlock);
|
|
3897
|
+
}, [shouldBlock, onValidationChange]), useEffect(() => {
|
|
3898
|
+
documentsWithErrors.length > 0 && devLog.warn("Documents have validation errors that block translation", {
|
|
3899
|
+
documentsWithErrors: documentsWithErrors.map((d) => ({
|
|
3900
|
+
id: d._id,
|
|
3901
|
+
type: d._type,
|
|
3902
|
+
errorCount: d.errors.length
|
|
3903
|
+
})),
|
|
3904
|
+
component: "ValidationPanel"
|
|
3905
|
+
});
|
|
3906
|
+
}, [documentsWithErrors]);
|
|
3907
|
+
const totalErrors = useMemo(
|
|
3908
|
+
() => documentsWithErrors.reduce((sum, doc) => sum + doc.errors.length, 0),
|
|
3909
|
+
[documentsWithErrors]
|
|
3910
|
+
);
|
|
3911
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3912
|
+
documentsToValidate.map((doc) => /* @__PURE__ */ jsx(DocumentValidator, { documentId: doc._id, documentType: doc._type, onResult: handleResult }, doc._id)),
|
|
3913
|
+
someStillValidating && documentsWithErrors.length === 0 && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, padding: 2, children: [
|
|
3914
|
+
/* @__PURE__ */ jsx(Spinner, { muted: !0 }),
|
|
3915
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Validating documents\u2026" })
|
|
3924
3916
|
] }),
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3917
|
+
documentsWithErrors.length > 0 && /* @__PURE__ */ jsx(Card, { tone: "critical", padding: 4, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
|
|
3918
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
3919
|
+
/* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, {}) }),
|
|
3920
|
+
/* @__PURE__ */ jsxs(Text, { weight: "semibold", children: [
|
|
3921
|
+
totalErrors,
|
|
3922
|
+
" validation error",
|
|
3923
|
+
totalErrors !== 1 ? "s" : "",
|
|
3924
|
+
" found in ",
|
|
3925
|
+
documentsWithErrors.length,
|
|
3926
|
+
" ",
|
|
3927
|
+
"document",
|
|
3928
|
+
documentsWithErrors.length !== 1 ? "s" : ""
|
|
3929
|
+
] })
|
|
3930
|
+
] }),
|
|
3931
|
+
/* @__PURE__ */ jsx(Text, { size: 1, children: "Documents must pass validation before they can be translated. Click a document to open it and fix the errors." }),
|
|
3932
|
+
/* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { space: 3, children: documentsWithErrors.map((doc) => {
|
|
3933
|
+
const publishedId = getPublishedId(doc._id);
|
|
3934
|
+
return /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
|
|
3935
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
|
|
3936
|
+
/* @__PURE__ */ jsx(Text, { size: 1, children: /* @__PURE__ */ jsx(
|
|
3937
|
+
IntentLink,
|
|
3938
|
+
{
|
|
3939
|
+
intent: "edit",
|
|
3940
|
+
params: { id: publishedId, type: doc._type },
|
|
3941
|
+
style: { color: "inherit" },
|
|
3942
|
+
children: doc.title || doc._id
|
|
3943
|
+
}
|
|
3944
|
+
) }),
|
|
3945
|
+
/* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: doc._type })
|
|
3946
|
+
] }),
|
|
3947
|
+
/* @__PURE__ */ jsxs(Badge, { tone: "critical", fontSize: 0, children: [
|
|
3948
|
+
doc.errors.length,
|
|
3949
|
+
" error",
|
|
3950
|
+
doc.errors.length !== 1 ? "s" : ""
|
|
3951
|
+
] })
|
|
3952
|
+
] }, doc._id);
|
|
3953
|
+
}) }) }) })
|
|
3929
3954
|
] }) })
|
|
3930
|
-
] })
|
|
3955
|
+
] });
|
|
3931
3956
|
}, assembleReferences = async ({
|
|
3932
3957
|
client,
|
|
3933
3958
|
sourceDocument,
|
|
@@ -4181,32 +4206,34 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4181
4206
|
!isLoadingReferences && references.length > 0 && showSelectionPanel && /* @__PURE__ */ jsx(ReferenceSelectionPanel, {})
|
|
4182
4207
|
] }) : null
|
|
4183
4208
|
] });
|
|
4184
|
-
}, WriteSettingsPanel = () => /* @__PURE__ */ jsx(ReferenceConfigPanel, {}), TranslationConfigView = ({ setIsDialogOpen }) =>
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4209
|
+
}, WriteSettingsPanel = () => /* @__PURE__ */ jsx(ReferenceConfigPanel, {}), TranslationConfigView = ({ setIsDialogOpen }) => {
|
|
4210
|
+
const [hasValidationErrors, setHasValidationErrors] = useState(!0), handleValidationChange = useCallback((shouldBlock) => {
|
|
4211
|
+
setHasValidationErrors(shouldBlock);
|
|
4212
|
+
}, []);
|
|
4213
|
+
return /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
|
|
4214
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
4215
|
+
/* @__PURE__ */ jsx(Label, { children: "Languages" }),
|
|
4216
|
+
/* @__PURE__ */ jsx(LanguageSelectionPanel, {})
|
|
4217
|
+
] }),
|
|
4218
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
4219
|
+
/* @__PURE__ */ jsx(Label, { children: "Translation Method" }),
|
|
4220
|
+
/* @__PURE__ */ jsx(TranslationModePanel, {})
|
|
4221
|
+
] }),
|
|
4222
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
4223
|
+
/* @__PURE__ */ jsx(Label, { children: "Content Release" }),
|
|
4224
|
+
/* @__PURE__ */ jsx(ReleaseSelectionPanel, {})
|
|
4225
|
+
] }),
|
|
4226
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
4227
|
+
/* @__PURE__ */ jsx(Label, { children: "Settings" }),
|
|
4228
|
+
/* @__PURE__ */ jsx(Card, { padding: 4, border: !0, muted: !0, children: /* @__PURE__ */ jsx(WriteSettingsPanel, {}) })
|
|
4229
|
+
] }),
|
|
4230
|
+
/* @__PURE__ */ jsx(ValidationPanel, { onValidationChange: handleValidationChange }),
|
|
4231
|
+
/* @__PURE__ */ jsx(ActionButtonBar, { setIsDialogOpen, hasValidationErrors })
|
|
4232
|
+
] });
|
|
4233
|
+
}, TERMINAL_STATUSES = ["completed", "skipped", "error"], TranslationStatusPanel = () => {
|
|
4234
|
+
const { overallStatus, translationJobs, dispatch, document } = useTranslationContext(), headline = useMemo(() => overallStatus === "completed" ? "Translation completed" : overallStatus === "error" ? "Translation failed" : "Translation in progress", [overallStatus]), jobs = useMemo(() => Array.from(translationJobs.values()), [translationJobs]), isTerminalState = overallStatus === "completed" || overallStatus === "error", handleReset = useCallback(() => {
|
|
4204
4235
|
forceCleanupActiveTranslation(document._id), dispatch({ type: "RESET_TRANSLATION_STATE" });
|
|
4205
|
-
}, [dispatch, document._id])
|
|
4206
|
-
if (translationMode === "sanity-only") return null;
|
|
4207
|
-
const completedJobs = jobs.filter((j) => j.status === "completed"), totalTokens = completedJobs.reduce((sum, job) => sum + (job.tokensUsed || 0), 0), totalCost = completedJobs.reduce((sum, job) => sum + (job.estimatedCost || 0), 0);
|
|
4208
|
-
return { totalTokens, totalCost, jobsWithUsage: completedJobs.filter((j) => j.tokensUsed).length };
|
|
4209
|
-
}, [jobs, translationMode]);
|
|
4236
|
+
}, [dispatch, document._id]);
|
|
4210
4237
|
return /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
|
|
4211
4238
|
/* @__PURE__ */ jsx(Heading, { size: 2, children: headline }),
|
|
4212
4239
|
/* @__PURE__ */ jsx(ProgressBar, { progress: jobs.filter((job) => TERMINAL_STATUSES.includes(job.status)).length, max: jobs.length }),
|
|
@@ -4227,12 +4254,6 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4227
4254
|
job._id?.slice(-6) || "unknown"
|
|
4228
4255
|
] }),
|
|
4229
4256
|
!["completed", "skipped", "error"].includes(job.status) && /* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: getStatusLabel(job.status) }),
|
|
4230
|
-
job.status === "completed" && job.tokensUsed ? /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
4231
|
-
job.tokensUsed.toLocaleString(),
|
|
4232
|
-
" tokens (~$",
|
|
4233
|
-
job.estimatedCost?.toFixed(4),
|
|
4234
|
-
")"
|
|
4235
|
-
] }) : null,
|
|
4236
4257
|
job.status === "error" && job.errorMessage ? /* @__PURE__ */ jsx(Card, { tone: "critical", padding: 2, radius: 2, children: /* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
4237
4258
|
"Error: ",
|
|
4238
4259
|
job.errorMessage
|
|
@@ -4254,22 +4275,6 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4254
4275
|
] })
|
|
4255
4276
|
] }) }) }, job._id)) }) }) })
|
|
4256
4277
|
] }),
|
|
4257
|
-
openaiStats && openaiStats.jobsWithUsage > 0 && /* @__PURE__ */ jsx(Card, { tone: "primary", padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
4258
|
-
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: "OpenAI Usage Summary" }),
|
|
4259
|
-
/* @__PURE__ */ jsxs(Inline, { space: 4, children: [
|
|
4260
|
-
/* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
4261
|
-
"Total Tokens: ",
|
|
4262
|
-
/* @__PURE__ */ jsx("strong", { children: openaiStats.totalTokens.toLocaleString() })
|
|
4263
|
-
] }),
|
|
4264
|
-
/* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
4265
|
-
"Total Cost: ",
|
|
4266
|
-
/* @__PURE__ */ jsxs("strong", { children: [
|
|
4267
|
-
"$",
|
|
4268
|
-
openaiStats.totalCost.toFixed(4)
|
|
4269
|
-
] })
|
|
4270
|
-
] })
|
|
4271
|
-
] })
|
|
4272
|
-
] }) }),
|
|
4273
4278
|
isTerminalState && /* @__PURE__ */ jsx(Flex, { justify: "flex-end", children: /* @__PURE__ */ jsx(Button, { mode: "ghost", tone: "primary", text: "Translate More", icon: ResetIcon, onClick: handleReset }) })
|
|
4274
4279
|
] });
|
|
4275
4280
|
}, ProgressBar = ({ progress, max = 100 }) => {
|