@braze/sanity-plugin-ai-translation 0.2.87 → 0.3.1
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/README.md +62 -15
- package/dist/_chunks-es/retry.js +142 -0
- package/dist/_chunks-es/retry.js.map +1 -0
- package/dist/index.d.ts +67 -10
- package/dist/index.js +210 -466
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +87 -0
- package/dist/server.js +144 -0
- package/dist/server.js.map +1 -0
- package/package.json +42 -31
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { getPublishedId, pathToString,
|
|
1
|
+
import { useClient, getPublishedId, pathToString, useWorkspace, validateDocument, defineType, defineField, definePlugin } from "sanity";
|
|
2
2
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { WarningOutlineIcon, ErrorOutlineIcon, CheckmarkIcon, SyncIcon, PanelRightIcon, SearchIcon, CircleIcon, ArrowRightIcon, ResetIcon, TranslateIcon } from "@sanity/icons";
|
|
4
|
-
import { createContext, useReducer, useRef,
|
|
4
|
+
import { createContext, useEffect, useReducer, useRef, useContext, useMemo, useState, useCallback } from "react";
|
|
5
|
+
import { devLog, retryWithBackoff, isNetworkError, isSanityMutationError } from "./_chunks-es/retry.js";
|
|
5
6
|
import defaults from "lodash.defaults";
|
|
6
|
-
import { Card, Stack, Flex, Text, Box, Heading, Button, Spinner, Badge, Inline, Radio, Checkbox, Grid, TextInput, Select, Label
|
|
7
|
+
import { Card, Stack, Flex, Text, Box, Heading, Button, Spinner, Badge, Inline, Radio, Checkbox, Grid, TextInput, Select, Label } from "@sanity/ui";
|
|
7
8
|
import { IntentLink } from "sanity/router";
|
|
8
9
|
import { useOpenInNewPane, useListeningQuery } from "sanity-plugin-utils";
|
|
9
10
|
import { toPlainText } from "@portabletext/react";
|
|
@@ -11,59 +12,57 @@ import { uuid } from "@sanity/uuid";
|
|
|
11
12
|
import cloneDeep from "lodash.clonedeep";
|
|
12
13
|
import get from "lodash.get";
|
|
13
14
|
import set from "lodash.set";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
15
|
+
import { Code } from "@sanity/ui/code";
|
|
16
|
+
import { Tooltip } from "@sanity/ui/tooltip";
|
|
17
|
+
const DEFAULT_TRANSLATION_FUNCTION_NAME = "ai-translation", DEFAULT_MODEL = "gpt-5", TRANSLATION_FUNCTION_TIMEOUT_MS = 9e5, isOpenAIConfigured = (openaiConfig) => !!(openaiConfig?.enabled && typeof openaiConfig.stackId == "string" && openaiConfig.stackId.trim() !== ""), supportsFunctionInvoke = (client) => typeof client.functions?.invoke == "function", toNumber = (value) => typeof value == "number" && Number.isFinite(value) ? value : 0, parseResult = (result, functionName) => {
|
|
18
|
+
const value = result ?? {};
|
|
19
|
+
if (typeof value.content != "string")
|
|
20
|
+
throw new Error(`OpenAI translation function "${functionName}" returned no translated content`);
|
|
21
|
+
const tokensUsed = value.tokensUsed ?? {};
|
|
22
|
+
return {
|
|
23
|
+
content: value.content,
|
|
24
|
+
tokensUsed: {
|
|
25
|
+
prompt: toNumber(tokensUsed.prompt),
|
|
26
|
+
completion: toNumber(tokensUsed.completion),
|
|
27
|
+
reasoning: toNumber(tokensUsed.reasoning),
|
|
28
|
+
total: toNumber(tokensUsed.total),
|
|
29
|
+
cached: toNumber(tokensUsed.cached)
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}, invokeTranslationFunction = async ({
|
|
33
|
+
client,
|
|
34
|
+
openaiConfig,
|
|
35
|
+
messages
|
|
36
|
+
}) => {
|
|
37
|
+
if (!isOpenAIConfigured(openaiConfig))
|
|
38
|
+
throw new Error("OpenAI mode requires openai.enabled and openai.stackId");
|
|
39
|
+
if (!supportsFunctionInvoke(client))
|
|
40
|
+
throw new Error("OpenAI mode requires @sanity/client 8.1 or newer in Studio to invoke the translation function");
|
|
41
|
+
const functionName = openaiConfig.functionName || DEFAULT_TRANSLATION_FUNCTION_NAME, payload = {
|
|
42
|
+
model: openaiConfig.model || DEFAULT_MODEL,
|
|
43
|
+
messages,
|
|
44
|
+
reasoningEffort: openaiConfig.reasoningEffort
|
|
45
|
+
};
|
|
46
|
+
devLog.debug("Invoking OpenAI translation function", {
|
|
47
|
+
functionName,
|
|
48
|
+
stackId: openaiConfig.stackId,
|
|
49
|
+
model: payload.model,
|
|
50
|
+
reasoningEffort: payload.reasoningEffort,
|
|
51
|
+
messages: messages.map((m) => ({ role: m.role, contentLength: m.content.length }))
|
|
52
|
+
});
|
|
53
|
+
let result;
|
|
40
54
|
try {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
47
|
-
if (!response.ok)
|
|
48
|
-
return {
|
|
49
|
-
valid: !1,
|
|
50
|
-
error: (await response.json()).error?.message || "Invalid API key"
|
|
51
|
-
};
|
|
52
|
-
const availableModels = (await response.json()).data.map((m) => m.id);
|
|
53
|
-
return availableModels.includes(model) ? {
|
|
54
|
-
valid: !0,
|
|
55
|
-
modelAvailable: availableModels
|
|
56
|
-
} : {
|
|
57
|
-
valid: !1,
|
|
58
|
-
error: `Model ${model} not available. Try: ${availableModels.slice(0, 5).join(", ")}`
|
|
59
|
-
};
|
|
55
|
+
result = await client.functions.invoke(
|
|
56
|
+
functionName,
|
|
57
|
+
{ event: { data: payload }, stackId: openaiConfig.stackId, timeout: TRANSLATION_FUNCTION_TIMEOUT_MS },
|
|
58
|
+
{ sync: !0 }
|
|
59
|
+
);
|
|
60
60
|
} catch (error) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
error: error instanceof Error ? error.message : "Network error"
|
|
64
|
-
};
|
|
61
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
62
|
+
throw new Error(`OpenAI translation function "${functionName}" failed: ${message}`);
|
|
65
63
|
}
|
|
66
|
-
|
|
64
|
+
return parseResult(result, functionName);
|
|
65
|
+
}, undraftId = (id) => id.replace(/^drafts\./, ""), draftId = (id) => `drafts.${undraftId(id)}`, activeTranslations = /* @__PURE__ */ new Map(), registerActiveTranslation = (documentId, releaseId) => {
|
|
67
66
|
const entry = {
|
|
68
67
|
state: {
|
|
69
68
|
documentId,
|
|
@@ -102,7 +101,7 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
|
|
|
102
101
|
try {
|
|
103
102
|
subscriber(entry.state);
|
|
104
103
|
} catch (error) {
|
|
105
|
-
|
|
104
|
+
devLog.error("Error in active translation subscriber", error);
|
|
106
105
|
}
|
|
107
106
|
}, forceCleanupActiveTranslation = (documentId) => {
|
|
108
107
|
activeTranslations.delete(documentId);
|
|
@@ -253,7 +252,14 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
|
|
|
253
252
|
supportedLanguages: config.supportedLanguages?.map((l) => l.id),
|
|
254
253
|
component: "TranslationContextProvider"
|
|
255
254
|
});
|
|
256
|
-
const defaultReleaseTitle = generateDefaultReleaseTitle(document),
|
|
255
|
+
const defaultReleaseTitle = generateDefaultReleaseTitle(document), client = useClient({ apiVersion: "vX" }), openaiConfigured = isOpenAIConfigured(config?.openai), openaiAvailable = openaiConfigured && supportsFunctionInvoke(client);
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
openaiConfigured && !openaiAvailable && devLog.warn("OpenAI mode is configured but the Studio client cannot invoke functions", {
|
|
258
|
+
requires: "@sanity/client 8.1 or newer",
|
|
259
|
+
component: "TranslationContextProvider"
|
|
260
|
+
});
|
|
261
|
+
}, [openaiConfigured, openaiAvailable]);
|
|
262
|
+
const activeTranslation = getActiveTranslation(document._id), initialContext = {
|
|
257
263
|
document,
|
|
258
264
|
config,
|
|
259
265
|
// If there's an active translation, restore its state
|
|
@@ -266,8 +272,8 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
|
|
|
266
272
|
selectedReferenceIds: /* @__PURE__ */ new Set(),
|
|
267
273
|
// Restore jobs from active translation if available
|
|
268
274
|
translationJobs: activeTranslation?.translationJobs ?? /* @__PURE__ */ new Map(),
|
|
269
|
-
translationMode: config
|
|
270
|
-
openaiValidated:
|
|
275
|
+
translationMode: openaiAvailable && config.openai?.defaultMode || "sanity-only",
|
|
276
|
+
openaiValidated: openaiAvailable,
|
|
271
277
|
releaseConfig: {
|
|
272
278
|
mode: "new",
|
|
273
279
|
newReleaseTitle: defaultReleaseTitle
|
|
@@ -302,30 +308,6 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
|
|
|
302
308
|
unsubscribe();
|
|
303
309
|
};
|
|
304
310
|
}, [document._id]), useEffect(() => {
|
|
305
|
-
(async () => {
|
|
306
|
-
if (config?.openai?.enabled && config?.openai?.apiKey)
|
|
307
|
-
try {
|
|
308
|
-
const validation = await validateOpenAIApiKey(config.openai.apiKey, config.openai.model || "gpt-5");
|
|
309
|
-
dispatch({
|
|
310
|
-
type: "SET_OPENAI_VALIDATED",
|
|
311
|
-
payload: validation.valid
|
|
312
|
-
}), validation.valid ? devLog.info("OpenAI API key validated successfully", {
|
|
313
|
-
model: config.openai.model || "gpt-5",
|
|
314
|
-
component: "TranslationContextProvider"
|
|
315
|
-
}) : devLog.error("OpenAI API key validation failed", {
|
|
316
|
-
error: validation.error,
|
|
317
|
-
component: "TranslationContextProvider"
|
|
318
|
-
});
|
|
319
|
-
} catch (error) {
|
|
320
|
-
devLog.error("OpenAI validation error", error instanceof Error ? error : new Error(String(error)), {
|
|
321
|
-
component: "TranslationContextProvider"
|
|
322
|
-
}), dispatch({
|
|
323
|
-
type: "SET_OPENAI_VALIDATED",
|
|
324
|
-
payload: !1
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
})();
|
|
328
|
-
}, [config?.openai?.apiKey, config?.openai?.enabled, config?.openai?.model]), useEffect(() => {
|
|
329
311
|
const currentRefsKey = context.references.map((ref) => ref._id).sort().join(",");
|
|
330
312
|
if (currentRefsKey !== lastProcessedRefsRef.current) {
|
|
331
313
|
if (context.references.length > 0) {
|
|
@@ -361,7 +343,12 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
|
|
|
361
343
|
"useTranslationContext must be used within a TranslationContextProvider with document and config props"
|
|
362
344
|
);
|
|
363
345
|
return context;
|
|
364
|
-
}
|
|
346
|
+
};
|
|
347
|
+
async function fetchSourceDocument(client, documentId, perspective) {
|
|
348
|
+
const publishedId = undraftId(documentId), [draft, published] = await client.getDocuments([draftId(publishedId), publishedId]);
|
|
349
|
+
return perspective === "published" ? published : draft ?? published;
|
|
350
|
+
}
|
|
351
|
+
const checkSourceDrafts = async ({
|
|
365
352
|
client,
|
|
366
353
|
referenceIds
|
|
367
354
|
}) => {
|
|
@@ -449,10 +436,10 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
|
|
|
449
436
|
return devLog.debug("No stale drafts found", { sourceDocumentId }), [];
|
|
450
437
|
const draftMap = new Map(drafts.map((d) => [d._id, d])), results = [];
|
|
451
438
|
for (const entry of deduped) {
|
|
452
|
-
const
|
|
439
|
+
const draftId2 = `drafts.${entry.targetId}`, draft = draftMap.get(draftId2);
|
|
453
440
|
draft && results.push({
|
|
454
441
|
targetId: entry.targetId,
|
|
455
|
-
draftId,
|
|
442
|
+
draftId: draftId2,
|
|
456
443
|
_type: draft._type,
|
|
457
444
|
title: draft.title || draft.name,
|
|
458
445
|
sourceDocumentId: entry.sourceDocumentId,
|
|
@@ -498,7 +485,7 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
|
|
|
498
485
|
translationHistory: []
|
|
499
486
|
});
|
|
500
487
|
} catch (error) {
|
|
501
|
-
throw
|
|
488
|
+
throw devLog.error("Error creating translation metadata", error, { documentId: sourceDocument._id }), error;
|
|
502
489
|
}
|
|
503
490
|
}, checkDocumentConflicts = async ({
|
|
504
491
|
client,
|
|
@@ -587,115 +574,6 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
|
|
|
587
574
|
}
|
|
588
575
|
return allConflicts;
|
|
589
576
|
};
|
|
590
|
-
function isNetworkError(error) {
|
|
591
|
-
if (!error) return !1;
|
|
592
|
-
if (error instanceof TypeError) {
|
|
593
|
-
const message = error.message.toLowerCase();
|
|
594
|
-
return message.includes("fetch") || message.includes("network") || message.includes("connection") || message.includes("failed to fetch") || message.includes("networkerror");
|
|
595
|
-
}
|
|
596
|
-
if (error && typeof error == "object" && "code" in error) {
|
|
597
|
-
const code = error.code;
|
|
598
|
-
return ["ECONNRESET", "ETIMEDOUT", "ENOTFOUND", "ECONNREFUSED", "EHOSTUNREACH"].includes(code);
|
|
599
|
-
}
|
|
600
|
-
if (error && typeof error == "object" && "response" in error) {
|
|
601
|
-
const response = error.response;
|
|
602
|
-
if (response?.status)
|
|
603
|
-
return response.status >= 500 || response.status === 429;
|
|
604
|
-
}
|
|
605
|
-
if (error instanceof Error) {
|
|
606
|
-
const message = error.message.toLowerCase();
|
|
607
|
-
if (/\b(50[0-9]|429)\b/.test(message) || message.includes("rate limit"))
|
|
608
|
-
return !0;
|
|
609
|
-
}
|
|
610
|
-
return !1;
|
|
611
|
-
}
|
|
612
|
-
function isSanityMutationError(error) {
|
|
613
|
-
if (!error) return !1;
|
|
614
|
-
if (error instanceof Error) {
|
|
615
|
-
const message = error.message.toLowerCase();
|
|
616
|
-
return [
|
|
617
|
-
"validation",
|
|
618
|
-
"schema",
|
|
619
|
-
"invalid document",
|
|
620
|
-
"invalid field",
|
|
621
|
-
"type mismatch",
|
|
622
|
-
"reference",
|
|
623
|
-
"patch",
|
|
624
|
-
"mutation",
|
|
625
|
-
"document structure",
|
|
626
|
-
"required field",
|
|
627
|
-
"field type",
|
|
628
|
-
"sanity"
|
|
629
|
-
].some((pattern) => message.includes(pattern));
|
|
630
|
-
}
|
|
631
|
-
if (error && typeof error == "object") {
|
|
632
|
-
const errorObj = error;
|
|
633
|
-
if (("statusCode" in errorObj || "response" in errorObj || "details" in errorObj) && "statusCode" in errorObj) {
|
|
634
|
-
const statusCode = errorObj.statusCode;
|
|
635
|
-
if (statusCode >= 400 && statusCode < 500 && statusCode !== 429)
|
|
636
|
-
return !0;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
return !1;
|
|
640
|
-
}
|
|
641
|
-
const DEFAULT_RETRY_OPTIONS = {
|
|
642
|
-
maxAttempts: 2,
|
|
643
|
-
initialDelayMs: 0,
|
|
644
|
-
maxDelayMs: 5e3,
|
|
645
|
-
backoffMultiplier: 2,
|
|
646
|
-
retryableErrors: isNetworkError,
|
|
647
|
-
logRetries: !0
|
|
648
|
-
};
|
|
649
|
-
function calculateDelay(attempt, options) {
|
|
650
|
-
const delay = options.initialDelayMs * options.backoffMultiplier ** attempt;
|
|
651
|
-
return Math.min(delay, options.maxDelayMs);
|
|
652
|
-
}
|
|
653
|
-
function sleep(ms) {
|
|
654
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
655
|
-
}
|
|
656
|
-
async function retryWithBackoff(operation, options = {}) {
|
|
657
|
-
const opts = {
|
|
658
|
-
...DEFAULT_RETRY_OPTIONS,
|
|
659
|
-
...options,
|
|
660
|
-
retryableErrors: options.retryableErrors || DEFAULT_RETRY_OPTIONS.retryableErrors
|
|
661
|
-
};
|
|
662
|
-
let lastError;
|
|
663
|
-
const maxAttempts = opts.maxAttempts + 1;
|
|
664
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++)
|
|
665
|
-
try {
|
|
666
|
-
const result = await operation();
|
|
667
|
-
return attempt > 1 && opts.logRetries && devLog.info(`Operation succeeded after ${attempt} attempts`, {
|
|
668
|
-
...opts.context,
|
|
669
|
-
attempt,
|
|
670
|
-
totalAttempts: attempt
|
|
671
|
-
}), result;
|
|
672
|
-
} catch (error) {
|
|
673
|
-
if (lastError = error, !opts.retryableErrors(error))
|
|
674
|
-
throw opts.logRetries && devLog.warn("Error is not retryable, aborting retries", {
|
|
675
|
-
...opts.context,
|
|
676
|
-
attempt,
|
|
677
|
-
error: error instanceof Error ? error.message : String(error)
|
|
678
|
-
}), error;
|
|
679
|
-
if (attempt >= maxAttempts)
|
|
680
|
-
throw opts.logRetries && devLog.error(
|
|
681
|
-
`Operation failed after ${maxAttempts} attempts`,
|
|
682
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
683
|
-
{
|
|
684
|
-
...opts.context,
|
|
685
|
-
totalAttempts: maxAttempts
|
|
686
|
-
}
|
|
687
|
-
), error;
|
|
688
|
-
const delay = calculateDelay(attempt - 1, opts);
|
|
689
|
-
opts.logRetries && devLog.warn(`Operation failed, retrying in ${delay}ms (attempt ${attempt}/${maxAttempts})`, {
|
|
690
|
-
...opts.context,
|
|
691
|
-
attempt,
|
|
692
|
-
totalAttempts: maxAttempts,
|
|
693
|
-
delayMs: delay,
|
|
694
|
-
error: error instanceof Error ? error.message : String(error)
|
|
695
|
-
}), delay > 0 && await sleep(delay);
|
|
696
|
-
}
|
|
697
|
-
throw lastError;
|
|
698
|
-
}
|
|
699
577
|
class Semaphore {
|
|
700
578
|
permits;
|
|
701
579
|
queue = [];
|
|
@@ -769,7 +647,10 @@ function documentToPlainText(document) {
|
|
|
769
647
|
) ? result[key] = toPlainText(value) : result[key] = value;
|
|
770
648
|
return JSON.stringify(result);
|
|
771
649
|
}
|
|
772
|
-
|
|
650
|
+
let glossaryDataset;
|
|
651
|
+
const setGlossaryDataset = (dataset) => {
|
|
652
|
+
glossaryDataset = typeof dataset == "string" && dataset.trim() ? dataset.trim() : void 0;
|
|
653
|
+
}, getGlossaryDataset = () => glossaryDataset, DEFAULTS = {
|
|
773
654
|
windowSlack: 2,
|
|
774
655
|
minTokenMatchRatio: 1,
|
|
775
656
|
stopwords: [
|
|
@@ -885,7 +766,11 @@ const fetchTranslationPrompts = async ({
|
|
|
885
766
|
protectedPhrasesCount: translationSettings2?.protectedPhrases?.length,
|
|
886
767
|
targetLanguages
|
|
887
768
|
});
|
|
888
|
-
const
|
|
769
|
+
const glossaryDataset2 = getGlossaryDataset(), glossaryClient = glossaryDataset2 ? client.withConfig({ dataset: glossaryDataset2 }) : client;
|
|
770
|
+
devLog.debug("Fetching glossary terms", {
|
|
771
|
+
glossaryDataset: glossaryDataset2 || "document dataset"
|
|
772
|
+
});
|
|
773
|
+
const translationGlossaryTerms = await glossaryClient.fetch(
|
|
889
774
|
`*[
|
|
890
775
|
_type == "translationGlossaryTerm"
|
|
891
776
|
&& count(translations[_key in $targetLanguages]) > 0
|
|
@@ -1662,127 +1547,7 @@ class TransformRegistry {
|
|
|
1662
1547
|
}), matches[0];
|
|
1663
1548
|
}
|
|
1664
1549
|
}
|
|
1665
|
-
const globalTransformRegistry = new TransformRegistry(),
|
|
1666
|
-
const model = request.model || "gpt-5", requestBody = {
|
|
1667
|
-
model: model === "gpt-5" ? "gpt-5.1" : model,
|
|
1668
|
-
// Map gpt-5 to gpt-5.1, use others as-is
|
|
1669
|
-
input: request.messages
|
|
1670
|
-
// Uses 'input' instead of 'messages'
|
|
1671
|
-
};
|
|
1672
|
-
request.reasoningEffort && (requestBody.reasoning = {
|
|
1673
|
-
effort: request.reasoningEffort,
|
|
1674
|
-
summary: "auto"
|
|
1675
|
-
// Enable automatic reasoning summaries
|
|
1676
|
-
}), devLog.debug("OpenAI GPT-5 Request", {
|
|
1677
|
-
model: requestBody.model,
|
|
1678
|
-
reasoningEffort: request.reasoningEffort,
|
|
1679
|
-
messages: request.messages.map((m) => ({
|
|
1680
|
-
role: m.role,
|
|
1681
|
-
contentLength: m.content.length,
|
|
1682
|
-
contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? "..." : "")
|
|
1683
|
-
}))
|
|
1684
|
-
}), devLog.debug("OpenAI GPT-5 Full Prompt", {
|
|
1685
|
-
messages: request.messages
|
|
1686
|
-
});
|
|
1687
|
-
const response = await retryWithBackoff(
|
|
1688
|
-
async () => {
|
|
1689
|
-
const fetchResponse = await fetch("https://api.openai.com/v1/responses", {
|
|
1690
|
-
method: "POST",
|
|
1691
|
-
headers: {
|
|
1692
|
-
"Content-Type": "application/json",
|
|
1693
|
-
Authorization: `Bearer ${apiKey}`
|
|
1694
|
-
},
|
|
1695
|
-
body: JSON.stringify(requestBody)
|
|
1696
|
-
});
|
|
1697
|
-
if (!fetchResponse.ok) {
|
|
1698
|
-
const errorMessage = `GPT-5 API error: ${(await fetchResponse.json()).error?.message || "Unknown error"}`, errorObj = new Error(errorMessage);
|
|
1699
|
-
throw errorObj.response = { status: fetchResponse.status }, errorObj;
|
|
1700
|
-
}
|
|
1701
|
-
return fetchResponse;
|
|
1702
|
-
},
|
|
1703
|
-
{
|
|
1704
|
-
maxAttempts: 2,
|
|
1705
|
-
initialDelayMs: 0,
|
|
1706
|
-
retryableErrors: isNetworkError,
|
|
1707
|
-
context: { model: requestBody.model, endpoint: "responses" }
|
|
1708
|
-
}
|
|
1709
|
-
);
|
|
1710
|
-
if (!response.ok) {
|
|
1711
|
-
const error = await response.json();
|
|
1712
|
-
throw new Error(`GPT-5 API error: ${error.error?.message || "Unknown error"}`);
|
|
1713
|
-
}
|
|
1714
|
-
const result = await response.json(), tokensUsed = {
|
|
1715
|
-
prompt: result.usage?.input_tokens || 0,
|
|
1716
|
-
completion: result.usage?.output_tokens || 0,
|
|
1717
|
-
reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,
|
|
1718
|
-
total: result.usage?.total_tokens || 0,
|
|
1719
|
-
cached: result.usage?.input_tokens_details?.cached_tokens || 0
|
|
1720
|
-
};
|
|
1721
|
-
let content = "";
|
|
1722
|
-
if (result.output && Array.isArray(result.output)) {
|
|
1723
|
-
const messageItem = result.output.find((item) => item.type === "message");
|
|
1724
|
-
messageItem?.content && Array.isArray(messageItem.content) && (content = messageItem.content.find((item) => item.type === "output_text")?.text || "");
|
|
1725
|
-
}
|
|
1726
|
-
return content || (content = result.output_text || result.text || ""), {
|
|
1727
|
-
content,
|
|
1728
|
-
tokensUsed
|
|
1729
|
-
};
|
|
1730
|
-
}, callChatCompletionsAPI = async (apiKey, request) => {
|
|
1731
|
-
const model = request.model || "gpt-4o", requestBody = {
|
|
1732
|
-
model,
|
|
1733
|
-
messages: request.messages,
|
|
1734
|
-
temperature: request.temperature ?? 0.3
|
|
1735
|
-
};
|
|
1736
|
-
devLog.debug("OpenAI Chat Completions Request", {
|
|
1737
|
-
model,
|
|
1738
|
-
temperature: request.temperature ?? 0.3,
|
|
1739
|
-
messages: request.messages.map((m) => ({
|
|
1740
|
-
role: m.role,
|
|
1741
|
-
contentLength: m.content.length,
|
|
1742
|
-
contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? "..." : "")
|
|
1743
|
-
}))
|
|
1744
|
-
}), devLog.debug("OpenAI Chat Completions Full Prompt", {
|
|
1745
|
-
messages: request.messages
|
|
1746
|
-
});
|
|
1747
|
-
const response = await retryWithBackoff(
|
|
1748
|
-
async () => {
|
|
1749
|
-
const fetchResponse = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
1750
|
-
method: "POST",
|
|
1751
|
-
headers: {
|
|
1752
|
-
"Content-Type": "application/json",
|
|
1753
|
-
Authorization: `Bearer ${apiKey}`
|
|
1754
|
-
},
|
|
1755
|
-
body: JSON.stringify(requestBody)
|
|
1756
|
-
});
|
|
1757
|
-
if (!fetchResponse.ok) {
|
|
1758
|
-
const errorMessage = `OpenAI API error: ${(await fetchResponse.json()).error?.message || "Unknown error"}`, errorObj = new Error(errorMessage);
|
|
1759
|
-
throw errorObj.response = { status: fetchResponse.status }, errorObj;
|
|
1760
|
-
}
|
|
1761
|
-
return fetchResponse;
|
|
1762
|
-
},
|
|
1763
|
-
{
|
|
1764
|
-
maxAttempts: 2,
|
|
1765
|
-
initialDelayMs: 0,
|
|
1766
|
-
retryableErrors: isNetworkError,
|
|
1767
|
-
context: { model, endpoint: "chat/completions" }
|
|
1768
|
-
}
|
|
1769
|
-
);
|
|
1770
|
-
if (!response.ok) {
|
|
1771
|
-
const error = await response.json();
|
|
1772
|
-
throw new Error(`OpenAI API error: ${error.error?.message || "Unknown error"}`);
|
|
1773
|
-
}
|
|
1774
|
-
const result = await response.json(), usage = result.usage || {}, tokensUsed = {
|
|
1775
|
-
prompt: usage.prompt_tokens || 0,
|
|
1776
|
-
completion: usage.completion_tokens || 0,
|
|
1777
|
-
reasoning: usage.reasoning_tokens || 0,
|
|
1778
|
-
total: usage.total_tokens || 0,
|
|
1779
|
-
cached: usage.prompt_tokens_details?.cached_tokens || 0
|
|
1780
|
-
};
|
|
1781
|
-
return {
|
|
1782
|
-
content: result.choices[0].message.content || "",
|
|
1783
|
-
tokensUsed
|
|
1784
|
-
};
|
|
1785
|
-
}, deserializeDocument = (originalDoc, reviewedText) => {
|
|
1550
|
+
const globalTransformRegistry = new TransformRegistry(), deserializeDocument = (originalDoc, reviewedText) => {
|
|
1786
1551
|
devLog.debug("Raw GPT-5 response received", {
|
|
1787
1552
|
length: reviewedText.length,
|
|
1788
1553
|
preview: reviewedText.substring(0, 1e3),
|
|
@@ -2382,6 +2147,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2382
2147
|
}
|
|
2383
2148
|
}, getOpenAITranslation = async ({
|
|
2384
2149
|
dispatch,
|
|
2150
|
+
client,
|
|
2385
2151
|
sourceDocument,
|
|
2386
2152
|
languageFieldName,
|
|
2387
2153
|
translationPrompts,
|
|
@@ -2410,16 +2176,16 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2410
2176
|
targetLanguage,
|
|
2411
2177
|
translationPrompts,
|
|
2412
2178
|
errorContext
|
|
2413
|
-
}), response = await
|
|
2179
|
+
}), response = await invokeTranslationFunction({
|
|
2180
|
+
client,
|
|
2181
|
+
openaiConfig,
|
|
2414
2182
|
messages: [
|
|
2415
2183
|
{
|
|
2416
2184
|
role: "developer",
|
|
2417
2185
|
content: "You are a translation quality reviewer. Review and refine translations while preserving structure."
|
|
2418
2186
|
},
|
|
2419
2187
|
{ role: "user", content: prompt }
|
|
2420
|
-
]
|
|
2421
|
-
model: openaiConfig.model,
|
|
2422
|
-
reasoningEffort: openaiConfig.reasoningEffort
|
|
2188
|
+
]
|
|
2423
2189
|
});
|
|
2424
2190
|
openaiResponseContent = response.content, devLog.info("OpenAI translation response received", {
|
|
2425
2191
|
...logContext,
|
|
@@ -2727,25 +2493,18 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2727
2493
|
languageFieldName
|
|
2728
2494
|
});
|
|
2729
2495
|
const documentAfterUnset = unsetDocumentFields(sourceDocument, safeUnsetFields), { preserveFieldPatterns } = normalizeUnsetFields(safeUnsetFields);
|
|
2730
|
-
let
|
|
2496
|
+
let preservedValues = {};
|
|
2731
2497
|
if (preserveFieldPatterns.length > 0 && existingTranslationId)
|
|
2732
2498
|
try {
|
|
2733
2499
|
const publishedTarget = await client.getDocument(existingTranslationId);
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
preservedFields: Object.keys(preserveValues)
|
|
2743
|
-
}));
|
|
2744
|
-
} else
|
|
2745
|
-
devLog.debug("Published target not found for preserveExisting fields", {
|
|
2746
|
-
documentId: sourceDocument._id,
|
|
2747
|
-
existingTranslationId
|
|
2748
|
-
});
|
|
2500
|
+
publishedTarget ? (preservedValues = filterExcludedFields(publishedTarget, preserveFieldPatterns).excludedValues, Object.keys(preservedValues).length > 0 && devLog.info("Preserving fields from published target during re-translation", {
|
|
2501
|
+
documentId: sourceDocument._id,
|
|
2502
|
+
existingTranslationId,
|
|
2503
|
+
preservedFields: Object.keys(preservedValues)
|
|
2504
|
+
})) : devLog.debug("Published target not found for preserveExisting fields", {
|
|
2505
|
+
documentId: sourceDocument._id,
|
|
2506
|
+
existingTranslationId
|
|
2507
|
+
});
|
|
2749
2508
|
} catch (error) {
|
|
2750
2509
|
devLog.warn("Failed to fetch published target for preserveExisting fields", {
|
|
2751
2510
|
documentId: sourceDocument._id,
|
|
@@ -2764,10 +2523,10 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2764
2523
|
documentType: sourceDocument._type,
|
|
2765
2524
|
excludeFields: safeExcludeFields,
|
|
2766
2525
|
excludeFieldsLength: safeExcludeFields?.length ?? 0,
|
|
2767
|
-
sourceHasTitle: "title" in
|
|
2768
|
-
sourceTitleValue:
|
|
2526
|
+
sourceHasTitle: "title" in documentAfterUnset,
|
|
2527
|
+
sourceTitleValue: documentAfterUnset.title
|
|
2769
2528
|
});
|
|
2770
|
-
const { filteredDocument, excludedValues } = filterExcludedFields(
|
|
2529
|
+
const { filteredDocument, excludedValues } = filterExcludedFields(documentAfterUnset, safeExcludeFields);
|
|
2771
2530
|
devLog.info("After filtering excluded fields", {
|
|
2772
2531
|
documentId: sourceDocument._id,
|
|
2773
2532
|
filteredHasTitle: "title" in filteredDocument,
|
|
@@ -2830,7 +2589,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2830
2589
|
}
|
|
2831
2590
|
}
|
|
2832
2591
|
case "openai-only": {
|
|
2833
|
-
if (!openaiConfig
|
|
2592
|
+
if (!isOpenAIConfigured(openaiConfig))
|
|
2834
2593
|
throw new Error("OpenAI is not configured but openai-only mode was selected");
|
|
2835
2594
|
setJobStatus(
|
|
2836
2595
|
dispatch,
|
|
@@ -2842,6 +2601,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2842
2601
|
);
|
|
2843
2602
|
const result = await getOpenAITranslation({
|
|
2844
2603
|
dispatch,
|
|
2604
|
+
client,
|
|
2845
2605
|
sourceDocument: documentToTranslate,
|
|
2846
2606
|
targetLanguage,
|
|
2847
2607
|
languageFieldName,
|
|
@@ -2861,32 +2621,39 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2861
2621
|
};
|
|
2862
2622
|
if (translatedDoc = (await performTranslation()).document, !translatedDoc)
|
|
2863
2623
|
return null;
|
|
2864
|
-
let transformedResult;
|
|
2865
|
-
Object.keys(originalStructure).length > 0 ? (devLog.debug("Restoring field transforms after translation", {
|
|
2866
|
-
documentId: sourceDocument._id,
|
|
2867
|
-
documentType: sourceDocument._type
|
|
2868
|
-
}), transformedResult = restoreTransforms({
|
|
2869
|
-
translatedDocument: translatedDoc,
|
|
2870
|
-
originalStructure,
|
|
2871
|
-
registry: globalTransformRegistry,
|
|
2872
|
-
schemaType: sourceDocument._type
|
|
2873
|
-
})) : transformedResult = translatedDoc;
|
|
2874
2624
|
const excludedValuesToRestore = { ...excludedValues };
|
|
2875
|
-
delete excludedValuesToRestore[languageFieldName]
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2625
|
+
delete excludedValuesToRestore[languageFieldName];
|
|
2626
|
+
const finalizeDocument = (translated) => {
|
|
2627
|
+
let transformedResult;
|
|
2628
|
+
Object.keys(originalStructure).length > 0 ? (devLog.debug("Restoring field transforms after translation", {
|
|
2629
|
+
documentId: sourceDocument._id,
|
|
2630
|
+
documentType: sourceDocument._type
|
|
2631
|
+
}), transformedResult = restoreTransforms({
|
|
2632
|
+
translatedDocument: translated,
|
|
2633
|
+
originalStructure,
|
|
2634
|
+
registry: globalTransformRegistry,
|
|
2635
|
+
schemaType: sourceDocument._type
|
|
2636
|
+
})) : transformedResult = translated;
|
|
2637
|
+
const withoutUnsetFields = unsetDocumentFields(transformedResult, safeUnsetFields);
|
|
2638
|
+
devLog.info("Before restoring excluded fields", {
|
|
2639
|
+
documentId: sourceDocument._id,
|
|
2640
|
+
excludedFieldCount: Object.keys(excludedValues).length,
|
|
2641
|
+
excludedValuesKeys: Object.keys(excludedValues),
|
|
2642
|
+
languageFieldExcluded: languageFieldName in excludedValues,
|
|
2643
|
+
transformedResultHasTitle: "title" in withoutUnsetFields,
|
|
2644
|
+
transformedResultTitle: withoutUnsetFields.title
|
|
2645
|
+
});
|
|
2646
|
+
const finalDoc2 = restoreExcludedFields(
|
|
2647
|
+
restoreExcludedFields(withoutUnsetFields, excludedValuesToRestore),
|
|
2648
|
+
preservedValues
|
|
2649
|
+
);
|
|
2650
|
+
return devLog.info("After restoring excluded fields", {
|
|
2651
|
+
documentId: sourceDocument._id,
|
|
2652
|
+
finalDocHasTitle: "title" in finalDoc2,
|
|
2653
|
+
finalDocTitle: finalDoc2.title,
|
|
2654
|
+
preservedFields: Object.keys(preservedValues)
|
|
2655
|
+
}), finalDoc2;
|
|
2656
|
+
}, finalDoc = finalizeDocument(translatedDoc), targetDocumentPublishedId = existingTranslationId || uuid(), targetDocumentVersionId = `versions.${releaseId}.${targetDocumentPublishedId}`, newDoc = {
|
|
2890
2657
|
...finalDoc,
|
|
2891
2658
|
_id: targetDocumentVersionId
|
|
2892
2659
|
};
|
|
@@ -2925,15 +2692,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2925
2692
|
const errorContext = `Previous translation attempt failed with Sanity validation error: ${previousError.message}. Please ensure the translated document structure exactly matches the source document structure.`;
|
|
2926
2693
|
if (translatedDoc = (await performTranslation(errorContext)).document, !translatedDoc)
|
|
2927
2694
|
throw new Error("Translation retry failed: no document returned");
|
|
2928
|
-
let retryTransformedResult;
|
|
2929
|
-
Object.keys(originalStructure).length > 0 ? retryTransformedResult = restoreTransforms({
|
|
2930
|
-
translatedDocument: translatedDoc,
|
|
2931
|
-
originalStructure,
|
|
2932
|
-
registry: globalTransformRegistry,
|
|
2933
|
-
schemaType: sourceDocument._type
|
|
2934
|
-
}) : retryTransformedResult = translatedDoc;
|
|
2935
2695
|
const retryNewDoc = {
|
|
2936
|
-
...
|
|
2696
|
+
...finalizeDocument(translatedDoc),
|
|
2937
2697
|
_id: targetDocumentVersionId
|
|
2938
2698
|
};
|
|
2939
2699
|
return await retryWithBackoff(
|
|
@@ -3224,9 +2984,11 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3224
2984
|
}
|
|
3225
2985
|
try {
|
|
3226
2986
|
await translationSemaphore.withPermit(async () => {
|
|
3227
|
-
const document = await
|
|
3228
|
-
|
|
3229
|
-
|
|
2987
|
+
const document = await fetchSourceDocument(
|
|
2988
|
+
client,
|
|
2989
|
+
documentId,
|
|
2990
|
+
sourcePerspective ?? "drafts"
|
|
2991
|
+
);
|
|
3230
2992
|
if (document || devLog.error("\u274C SOURCE DOCUMENT NOT FOUND", { documentId }), document && releaseId) {
|
|
3231
2993
|
const versionedTargetDocumentId = await translateDocument({
|
|
3232
2994
|
dispatch,
|
|
@@ -3343,7 +3105,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3343
3105
|
onSkipConflicts
|
|
3344
3106
|
}) => {
|
|
3345
3107
|
const uniqueDocuments = useMemo(() => new Set(conflicts.map((c) => c.sourceDocumentId)).size, [conflicts]), nonConflictingCount = totalTranslations - conflicts.length, hasNonConflicting = nonConflictingCount > 0, summaryText = useMemo(() => conflicts.length === 1 ? "1 translation" : uniqueDocuments === 1 ? `${conflicts.length} translations` : `${conflicts.length} translations across ${uniqueDocuments} documents`, [conflicts.length, uniqueDocuments]);
|
|
3346
|
-
return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Stack, {
|
|
3108
|
+
return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
|
|
3347
3109
|
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
3348
3110
|
/* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(WarningOutlineIcon, {}) }),
|
|
3349
3111
|
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: "This release already contains translations for this content and target language(s)." })
|
|
@@ -3353,13 +3115,13 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3353
3115
|
summaryText,
|
|
3354
3116
|
" that will be overwritten if you continue:"
|
|
3355
3117
|
] }),
|
|
3356
|
-
/* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "500px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, {
|
|
3118
|
+
/* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "500px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 2, children: conflicts.map((conflict) => /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
3357
3119
|
conflict.sourceDocumentTitle || conflict.sourceDocumentId,
|
|
3358
3120
|
conflict.sourceDocumentType && ` (${conflict.sourceDocumentType})`,
|
|
3359
3121
|
" \u2192 ",
|
|
3360
3122
|
conflict.targetLanguage
|
|
3361
3123
|
] }, conflict.existingVersionId)) }) }) }),
|
|
3362
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
3124
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
|
|
3363
3125
|
/* @__PURE__ */ jsx(Heading, { size: 0, children: "Choose an action:" }),
|
|
3364
3126
|
/* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
3365
3127
|
/* @__PURE__ */ jsx("strong", { children: "Cancel" }),
|
|
@@ -3415,12 +3177,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3415
3177
|
hasRun.current || (hasRun.current = !0, (async () => {
|
|
3416
3178
|
onResult(documentId, { isValidating: !0, errors: [] });
|
|
3417
3179
|
try {
|
|
3418
|
-
const
|
|
3419
|
-
|
|
3420
|
-
if (perspective === "published" ? doc = await client.fetch("*[_id == $publishedId][0]", { publishedId }) : doc = await client.fetch(
|
|
3421
|
-
"*[_id in [$draftId, $publishedId]] | order(_id desc) [0]",
|
|
3422
|
-
{ draftId, publishedId }
|
|
3423
|
-
), !doc) {
|
|
3180
|
+
const doc = await fetchSourceDocument(client, documentId, perspective);
|
|
3181
|
+
if (!doc) {
|
|
3424
3182
|
devLog.warn("Document not found for validation", { documentId, perspective }), onResult(documentId, { isValidating: !1, errors: [] });
|
|
3425
3183
|
return;
|
|
3426
3184
|
}
|
|
@@ -3436,14 +3194,13 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3436
3194
|
component: "DocumentValidator"
|
|
3437
3195
|
}), onResult(documentId, { isValidating: !1, errors });
|
|
3438
3196
|
} catch (error) {
|
|
3439
|
-
devLog.error(
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
), onResult(documentId, { isValidating: !1, errors: [] });
|
|
3197
|
+
devLog.error("Failed to validate document", error instanceof Error ? error : new Error(String(error)), {
|
|
3198
|
+
documentId,
|
|
3199
|
+
component: "DocumentValidator"
|
|
3200
|
+
}), onResult(documentId, { isValidating: !1, errors: [] });
|
|
3444
3201
|
}
|
|
3445
3202
|
})());
|
|
3446
|
-
}, [documentId,
|
|
3203
|
+
}, [documentId, perspective, onResult, client, workspace]), null;
|
|
3447
3204
|
}, PreflightView = () => {
|
|
3448
3205
|
const {
|
|
3449
3206
|
document,
|
|
@@ -3605,23 +3362,21 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3605
3362
|
}, []), sourceRefDraftIds = useMemo(
|
|
3606
3363
|
() => new Set(sourceRefsWithDrafts.map((d) => d.publishedId)),
|
|
3607
3364
|
[sourceRefsWithDrafts]
|
|
3608
|
-
), isLoading = someStillValidating || isSourceDraftCheckLoading || isDraftCheckLoading || isCheckingConflicts, hasStaleDrafts = staleDrafts.length > 0, draftsDismissed = draftAction === "continue", canStartTranslation = !isLoading && !hasValidationErrors && !conflictContext && (!hasStaleDrafts || draftsDismissed),
|
|
3609
|
-
setConflictContext(null);
|
|
3610
|
-
}, []), handleReplaceConflict = useCallback(() => {
|
|
3365
|
+
), isLoading = someStillValidating || isSourceDraftCheckLoading || isDraftCheckLoading || isCheckingConflicts, hasStaleDrafts = staleDrafts.length > 0, draftsDismissed = draftAction === "continue", canStartTranslation = !isLoading && !hasValidationErrors && !conflictContext && (!hasStaleDrafts || draftsDismissed), handleReplaceConflict = useCallback(() => {
|
|
3611
3366
|
conflictContext && startTranslation(!0, conflictContext.releaseId);
|
|
3612
3367
|
}, [startTranslation, conflictContext]), handleSkipConflicts = useCallback(() => {
|
|
3613
3368
|
conflictContext && startTranslation(!1, conflictContext.releaseId, conflictContext.conflicts);
|
|
3614
3369
|
}, [startTranslation, conflictContext]), totalTranslations = useMemo(() => (1 + filteredReferences.length) * selectedTargetLanguages.length, [filteredReferences.length, selectedTargetLanguages.length]);
|
|
3615
|
-
return conflictContext ? /* @__PURE__ */ jsx(Stack, {
|
|
3370
|
+
return conflictContext ? /* @__PURE__ */ jsx(Stack, { gap: 4, children: /* @__PURE__ */ jsx(
|
|
3616
3371
|
ReleaseConflictWarning,
|
|
3617
3372
|
{
|
|
3618
3373
|
conflicts: conflictContext.conflicts,
|
|
3619
3374
|
totalTranslations,
|
|
3620
|
-
onCancel:
|
|
3375
|
+
onCancel: handleBack,
|
|
3621
3376
|
onReplace: handleReplaceConflict,
|
|
3622
3377
|
onSkipConflicts: handleSkipConflicts
|
|
3623
3378
|
}
|
|
3624
|
-
) }) : /* @__PURE__ */ jsxs(Stack, {
|
|
3379
|
+
) }) : /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
|
|
3625
3380
|
activeValidators.map((doc) => /* @__PURE__ */ jsx(
|
|
3626
3381
|
DocumentValidator,
|
|
3627
3382
|
{
|
|
@@ -3645,14 +3400,14 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3645
3400
|
" document",
|
|
3646
3401
|
filteredReferences.length !== 0 ? "s" : ""
|
|
3647
3402
|
] }),
|
|
3648
|
-
/* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasValidationErrors ? "critical" : void 0, children: /* @__PURE__ */ jsxs(Stack, {
|
|
3403
|
+
/* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasValidationErrors ? "critical" : void 0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
3649
3404
|
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
3650
3405
|
someStillValidating ? /* @__PURE__ */ jsx(Spinner, { muted: !0 }) : hasValidationErrors ? /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, {}) }) : /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(CheckmarkIcon, {}) }),
|
|
3651
3406
|
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: someStillValidating ? `Validating documents (${validationResults.size}/${documentsToValidate.length})\u2026` : hasValidationErrors ? `Validation errors in ${documentsWithErrors.length} document${documentsWithErrors.length !== 1 ? "s" : ""}` : "All documents pass validation" })
|
|
3652
3407
|
] }),
|
|
3653
3408
|
documentsWithErrors.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3654
3409
|
/* @__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." }),
|
|
3655
|
-
/* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, {
|
|
3410
|
+
/* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 3, children: documentsWithErrors.map((doc) => {
|
|
3656
3411
|
const publishedId = getPublishedId(doc._id), displayName = doc._id === document._id ? "This document" : doc.title || doc._id, hasUnpublishedChanges = sourceRefDraftIds.has(publishedId);
|
|
3657
3412
|
return /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
|
|
3658
3413
|
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
|
|
@@ -3680,14 +3435,14 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3680
3435
|
}) }) }) })
|
|
3681
3436
|
] })
|
|
3682
3437
|
] }) }),
|
|
3683
|
-
filteredReferences.length > 0 && /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: sourceRefsWithDrafts.length > 0 ? "caution" : void 0, children: /* @__PURE__ */ jsxs(Stack, {
|
|
3438
|
+
filteredReferences.length > 0 && /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: sourceRefsWithDrafts.length > 0 ? "caution" : void 0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
3684
3439
|
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
3685
3440
|
isSourceDraftCheckLoading ? /* @__PURE__ */ jsx(Spinner, { muted: !0 }) : sourceRefsWithDrafts.length > 0 ? /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(WarningOutlineIcon, {}) }) : /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(CheckmarkIcon, {}) }),
|
|
3686
3441
|
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: isSourceDraftCheckLoading ? "Checking referenced documents\u2026" : sourceRefsWithDrafts.length > 0 ? `${sourceRefsWithDrafts.length} referenced document${sourceRefsWithDrafts.length !== 1 ? "s have" : " has"} unpublished changes` : "All referenced documents are in sync" })
|
|
3687
3442
|
] }),
|
|
3688
3443
|
sourceRefsWithDrafts.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3689
3444
|
/* @__PURE__ */ jsx(Text, { size: 1, children: documentSource === "draft" ? 'You are translating from drafts. These unpublished changes will be included in the translation. If this is not intended, go back and switch to "Translate from published".' : 'You are translating from published. Unpublished changes on these documents will be ignored. If you want to include them, go back and switch to "Translate from drafts".' }),
|
|
3690
|
-
/* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, {
|
|
3445
|
+
/* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 3, children: sourceRefsWithDrafts.map((ref) => /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
|
|
3691
3446
|
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
|
|
3692
3447
|
/* @__PURE__ */ jsx(Text, { size: 1, children: ref.title || ref.publishedId }),
|
|
3693
3448
|
/* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: ref._type })
|
|
@@ -3696,14 +3451,14 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3696
3451
|
] }, ref.publishedId)) }) }) })
|
|
3697
3452
|
] })
|
|
3698
3453
|
] }) }),
|
|
3699
|
-
(isDraftCheckLoading || hasStaleDrafts) && /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasStaleDrafts && !draftsDismissed ? "caution" : void 0, children: /* @__PURE__ */ jsxs(Stack, {
|
|
3454
|
+
(isDraftCheckLoading || hasStaleDrafts) && /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasStaleDrafts && !draftsDismissed ? "caution" : void 0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
3700
3455
|
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
3701
3456
|
isDraftCheckLoading ? /* @__PURE__ */ jsx(Spinner, { muted: !0 }) : draftsDismissed ? /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(CheckmarkIcon, {}) }) : /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(WarningOutlineIcon, {}) }),
|
|
3702
3457
|
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: isDraftCheckLoading ? "Checking for stale drafts on target documents\u2026" : draftsDismissed ? "Stale drafts acknowledged" : `${staleDrafts.length} draft${staleDrafts.length !== 1 ? "s" : ""} found on existing translated document${staleDrafts.length !== 1 ? "s" : ""}` })
|
|
3703
3458
|
] }),
|
|
3704
3459
|
hasStaleDrafts && !draftsDismissed && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3705
3460
|
/* @__PURE__ */ jsx(Text, { size: 1, children: "These existing translations have unpublished changes that will be out-of-date after this translation is completed. Use the links below to either publish each draft or discard the changes." }),
|
|
3706
|
-
/* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, {
|
|
3461
|
+
/* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 3, children: staleDrafts.map((draft) => /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
|
|
3707
3462
|
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
|
|
3708
3463
|
/* @__PURE__ */ jsx(Text, { size: 1, children: draft.title || draft.targetId }),
|
|
3709
3464
|
/* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: draft.targetLanguage }),
|
|
@@ -3711,14 +3466,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3711
3466
|
] }),
|
|
3712
3467
|
/* @__PURE__ */ jsx(DocumentOpenerButton, { documentId: draft.targetId, documentType: draft._type })
|
|
3713
3468
|
] }, draft.draftId)) }) }) }),
|
|
3714
|
-
/* @__PURE__ */ jsx(
|
|
3715
|
-
Button,
|
|
3716
|
-
{
|
|
3717
|
-
mode: "ghost",
|
|
3718
|
-
text: "Continue Anyway",
|
|
3719
|
-
onClick: () => setDraftAction("continue")
|
|
3720
|
-
}
|
|
3721
|
-
)
|
|
3469
|
+
/* @__PURE__ */ jsx(Button, { mode: "ghost", text: "Continue Anyway", onClick: () => setDraftAction("continue") })
|
|
3722
3470
|
] })
|
|
3723
3471
|
] }) }),
|
|
3724
3472
|
/* @__PURE__ */ jsxs(Flex, { justify: "space-between", gap: 2, children: [
|
|
@@ -3754,17 +3502,16 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3754
3502
|
}, [dispatch]);
|
|
3755
3503
|
return /* @__PURE__ */ jsxs(Flex, { justify: "space-between", gap: 2, children: [
|
|
3756
3504
|
/* @__PURE__ */ jsxs(Flex, { justify: "flex-start", gap: 2, children: [
|
|
3757
|
-
/* @__PURE__ */ jsx(Button, { mode: "ghost",
|
|
3758
|
-
/* @__PURE__ */ jsx(Button, { mode: "ghost",
|
|
3505
|
+
/* @__PURE__ */ jsx(Button, { mode: "ghost", onClick: handleSelectAll, children: "Select All" }),
|
|
3506
|
+
/* @__PURE__ */ jsx(Button, { mode: "ghost", onClick: handleSelectNone, children: "Select None" })
|
|
3759
3507
|
] }),
|
|
3760
3508
|
/* @__PURE__ */ jsxs(Flex, { justify: "flex-end", gap: 2, children: [
|
|
3761
|
-
/* @__PURE__ */ jsx(Button, { onClick: () => setIsDialogOpen(!1), mode: "ghost",
|
|
3509
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => setIsDialogOpen(!1), mode: "ghost", children: "Cancel" }),
|
|
3762
3510
|
/* @__PURE__ */ jsx(
|
|
3763
3511
|
Button,
|
|
3764
3512
|
{
|
|
3765
3513
|
mode: "ghost",
|
|
3766
3514
|
tone: "positive",
|
|
3767
|
-
size: 2,
|
|
3768
3515
|
onClick: handleTranslate,
|
|
3769
3516
|
disabled: selectedTargetLanguages.length === 0 || !["idle", "completed"].includes(overallStatus) || releaseConfig.mode === "existing" && !releaseConfig.existingReleaseId,
|
|
3770
3517
|
children: "Translate"
|
|
@@ -3784,9 +3531,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3784
3531
|
payload: source
|
|
3785
3532
|
});
|
|
3786
3533
|
};
|
|
3787
|
-
return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, {
|
|
3534
|
+
return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
3788
3535
|
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: "This document has unpublished changes" }),
|
|
3789
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
3536
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
3790
3537
|
/* @__PURE__ */ jsx(
|
|
3791
3538
|
Button,
|
|
3792
3539
|
{
|
|
@@ -3794,9 +3541,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3794
3541
|
mode: documentSource === "draft" ? "ghost" : "bleed",
|
|
3795
3542
|
tone: documentSource === "draft" ? "primary" : "default",
|
|
3796
3543
|
onClick: () => handleSourceChange("draft"),
|
|
3797
|
-
children: /* @__PURE__ */ jsxs(Inline, {
|
|
3544
|
+
children: /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
|
|
3798
3545
|
/* @__PURE__ */ jsx(Radio, { checked: documentSource === "draft", readOnly: !0 }),
|
|
3799
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
3546
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
3800
3547
|
/* @__PURE__ */ jsx(Text, { children: "Translate from drafts" }),
|
|
3801
3548
|
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Uses the latest draft of this document and all referenced documents" })
|
|
3802
3549
|
] })
|
|
@@ -3810,9 +3557,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3810
3557
|
mode: documentSource === "published" ? "ghost" : "bleed",
|
|
3811
3558
|
tone: documentSource === "published" ? "primary" : "default",
|
|
3812
3559
|
onClick: () => handleSourceChange("published"),
|
|
3813
|
-
children: /* @__PURE__ */ jsxs(Inline, {
|
|
3560
|
+
children: /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
|
|
3814
3561
|
/* @__PURE__ */ jsx(Radio, { checked: documentSource === "published", readOnly: !0 }),
|
|
3815
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
3562
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
3816
3563
|
/* @__PURE__ */ jsx(Text, { children: "Translate from published" }),
|
|
3817
3564
|
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Uses only published content for this document and all referenced documents" })
|
|
3818
3565
|
] })
|
|
@@ -3857,17 +3604,16 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3857
3604
|
payload: updatedLanguages
|
|
3858
3605
|
});
|
|
3859
3606
|
};
|
|
3860
|
-
return isLoading ? /* @__PURE__ */ jsx(Button, { type: "button", mode: "bleed",
|
|
3607
|
+
return isLoading ? /* @__PURE__ */ jsx(Button, { type: "button", mode: "bleed", onClick: handleChange, disabled: !0, muted: !0, children: /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", paddingY: 3, children: /* @__PURE__ */ jsx(Spinner, { size: 1 }) }) }) : /* @__PURE__ */ jsx(
|
|
3861
3608
|
Button,
|
|
3862
3609
|
{
|
|
3863
3610
|
type: "button",
|
|
3864
3611
|
mode: isChecked ? "ghost" : "bleed",
|
|
3865
|
-
size: 2,
|
|
3866
3612
|
onClick: handleChange,
|
|
3867
3613
|
disabled: isDisabled,
|
|
3868
3614
|
tone: isChecked ? "primary" : "default",
|
|
3869
3615
|
muted: !0,
|
|
3870
|
-
children: /* @__PURE__ */ jsx(Flex, { align: "center", gap: 2, style: { cursor: "pointer" }, children: /* @__PURE__ */ jsxs(Inline, {
|
|
3616
|
+
children: /* @__PURE__ */ jsx(Flex, { align: "center", gap: 2, style: { cursor: "pointer" }, children: /* @__PURE__ */ jsxs(Inline, { gap: 3, children: [
|
|
3871
3617
|
/* @__PURE__ */ jsx(Checkbox, { checked: isChecked, onChange: handleChange, label: lang.title, disabled: isDisabled }),
|
|
3872
3618
|
/* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
|
|
3873
3619
|
/* @__PURE__ */ jsx(Text, { children: lang.title }),
|
|
@@ -3880,7 +3626,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3880
3626
|
const { document, config } = useTranslationContext(), { loading } = useTranslationMetadata({
|
|
3881
3627
|
documentId: document._id
|
|
3882
3628
|
});
|
|
3883
|
-
return /* @__PURE__ */ jsx(Card, { padding: 4, muted: !0, border: !0, children: /* @__PURE__ */ jsx(Grid, {
|
|
3629
|
+
return /* @__PURE__ */ jsx(Card, { padding: 4, muted: !0, border: !0, children: /* @__PURE__ */ jsx(Grid, { gridTemplateColumns: [1, 2], gap: 3, children: config?.supportedLanguages.map((lang) => /* @__PURE__ */ jsx(LanguageCheckbox, { lang, isLoading: loading }, lang.id)) }) });
|
|
3884
3630
|
}, reconcileReleaseReferences = async ({
|
|
3885
3631
|
client,
|
|
3886
3632
|
releaseId,
|
|
@@ -4023,9 +3769,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4023
3769
|
if (releaseConfig.mode !== "existing" || !releaseId)
|
|
4024
3770
|
return null;
|
|
4025
3771
|
const isTranslating = overallStatus === "translating" || overallStatus === "linking_references", isReconciling = reconcilingStatus === "reconciling";
|
|
4026
|
-
return /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, tone: reconcilingStatus === "error" ? "critical" : "default", children: /* @__PURE__ */ jsxs(Stack, {
|
|
3772
|
+
return /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, tone: reconcilingStatus === "error" ? "critical" : "default", children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
4027
3773
|
/* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 3, children: [
|
|
4028
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
3774
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
4029
3775
|
/* @__PURE__ */ jsx(Text, { size: 1, weight: "medium", children: "Reconcile References" }),
|
|
4030
3776
|
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: isLoadingCount ? "Loading..." : documentCount === null ? "Unable to load document count" : documentCount === 0 ? "No documents in release" : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4031
3777
|
documentCount,
|
|
@@ -4115,8 +3861,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4115
3861
|
day: "numeric",
|
|
4116
3862
|
year: "numeric"
|
|
4117
3863
|
});
|
|
4118
|
-
return /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, {
|
|
4119
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
3864
|
+
return /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
|
|
3865
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
4120
3866
|
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, as: "label", style: { cursor: "pointer" }, onClick: () => handleModeChange("new"), children: [
|
|
4121
3867
|
/* @__PURE__ */ jsx(Radio, { checked: releaseConfig.mode === "new", readOnly: !0 }),
|
|
4122
3868
|
/* @__PURE__ */ jsx(Text, { weight: "medium", children: "Create new release" })
|
|
@@ -4144,7 +3890,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4144
3890
|
placeholder: "Release title..."
|
|
4145
3891
|
}
|
|
4146
3892
|
),
|
|
4147
|
-
releaseConfig.mode === "existing" && /* @__PURE__ */ jsxs(Stack, {
|
|
3893
|
+
releaseConfig.mode === "existing" && /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
4148
3894
|
isLoading && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
4149
3895
|
/* @__PURE__ */ jsx(Spinner, { muted: !0 }),
|
|
4150
3896
|
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Loading..." })
|
|
@@ -4161,7 +3907,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4161
3907
|
placeholder: "Search releases..."
|
|
4162
3908
|
}
|
|
4163
3909
|
),
|
|
4164
|
-
/* @__PURE__ */ jsx(Box, { style: { maxHeight: "200px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, {
|
|
3910
|
+
/* @__PURE__ */ jsx(Box, { style: { maxHeight: "200px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 1, children: filteredReleases.length === 0 ? /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "No releases match your search." }) : filteredReleases.map((release) => {
|
|
4165
3911
|
const releaseName = release._id.replace("_.releases.", ""), isSelected = releaseConfig.existingReleaseId === releaseName;
|
|
4166
3912
|
return /* @__PURE__ */ jsx(
|
|
4167
3913
|
Card,
|
|
@@ -4207,12 +3953,12 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4207
3953
|
] }) });
|
|
4208
3954
|
}, TranslationModePanel = () => {
|
|
4209
3955
|
const { config, translationMode, openaiValidated, dispatch } = useTranslationContext();
|
|
4210
|
-
if (!config?.openai
|
|
3956
|
+
if (!isOpenAIConfigured(config?.openai))
|
|
4211
3957
|
return null;
|
|
4212
3958
|
if (!openaiValidated)
|
|
4213
|
-
return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, {
|
|
4214
|
-
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: "OpenAI
|
|
4215
|
-
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "
|
|
3959
|
+
return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
3960
|
+
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: "OpenAI Unavailable" }),
|
|
3961
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "This Studio's Sanity client cannot invoke the translation function. OpenAI mode requires @sanity/client 8.1 or newer." })
|
|
4216
3962
|
] }) });
|
|
4217
3963
|
const handleModeChange = (mode) => {
|
|
4218
3964
|
dispatch({
|
|
@@ -4220,12 +3966,12 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4220
3966
|
payload: mode
|
|
4221
3967
|
});
|
|
4222
3968
|
};
|
|
4223
|
-
return /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, {
|
|
4224
|
-
/* @__PURE__ */ jsxs(Inline, {
|
|
3969
|
+
return /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
3970
|
+
/* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
|
|
4225
3971
|
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: "Translation Mode" }),
|
|
4226
3972
|
/* @__PURE__ */ jsx(Badge, { tone: "positive", children: "OpenAI Available" })
|
|
4227
3973
|
] }),
|
|
4228
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
3974
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
4229
3975
|
/* @__PURE__ */ jsx(
|
|
4230
3976
|
Button,
|
|
4231
3977
|
{
|
|
@@ -4233,9 +3979,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4233
3979
|
mode: translationMode === "sanity-only" ? "ghost" : "bleed",
|
|
4234
3980
|
tone: translationMode === "sanity-only" ? "primary" : "default",
|
|
4235
3981
|
onClick: () => handleModeChange("sanity-only"),
|
|
4236
|
-
children: /* @__PURE__ */ jsxs(Inline, {
|
|
3982
|
+
children: /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
|
|
4237
3983
|
/* @__PURE__ */ jsx(Radio, { checked: translationMode === "sanity-only", readOnly: !0 }),
|
|
4238
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
3984
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
4239
3985
|
/* @__PURE__ */ jsx(Text, { children: "Sanity Agent Only" }),
|
|
4240
3986
|
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Fast, reliable, and included with Sanity." })
|
|
4241
3987
|
] })
|
|
@@ -4249,10 +3995,10 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4249
3995
|
mode: translationMode === "openai-only" ? "ghost" : "bleed",
|
|
4250
3996
|
tone: translationMode === "openai-only" ? "primary" : "default",
|
|
4251
3997
|
onClick: () => handleModeChange("openai-only"),
|
|
4252
|
-
children: /* @__PURE__ */ jsxs(Inline, {
|
|
3998
|
+
children: /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
|
|
4253
3999
|
/* @__PURE__ */ jsx(Radio, { checked: translationMode === "openai-only", readOnly: !0 }),
|
|
4254
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
4255
|
-
/* @__PURE__ */ jsxs(Inline, {
|
|
4000
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
4001
|
+
/* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
|
|
4256
4002
|
/* @__PURE__ */ jsx(Text, { children: "OpenAI Only" }),
|
|
4257
4003
|
/* @__PURE__ */ jsx(Badge, { tone: "caution", children: "Beta" })
|
|
4258
4004
|
] }),
|
|
@@ -4371,7 +4117,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4371
4117
|
payload: newSelection
|
|
4372
4118
|
});
|
|
4373
4119
|
};
|
|
4374
|
-
return references.length === 0 ? null : /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, muted: !0, children: /* @__PURE__ */ jsxs(Stack, {
|
|
4120
|
+
return references.length === 0 ? null : /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, muted: !0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
4375
4121
|
/* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", gap: 2, children: [
|
|
4376
4122
|
/* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", children: [
|
|
4377
4123
|
/* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: "Select References" }),
|
|
@@ -4384,8 +4130,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4384
4130
|
] })
|
|
4385
4131
|
] }),
|
|
4386
4132
|
/* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
4387
|
-
/* @__PURE__ */ jsx(Button, { mode: "ghost",
|
|
4388
|
-
/* @__PURE__ */ jsx(Button, { mode: "ghost",
|
|
4133
|
+
/* @__PURE__ */ jsx(Button, { mode: "ghost", onClick: handleSelectAll, text: "Select All" }),
|
|
4134
|
+
/* @__PURE__ */ jsx(Button, { mode: "ghost", onClick: handleSelectNone, text: "Select None" })
|
|
4389
4135
|
] })
|
|
4390
4136
|
] }),
|
|
4391
4137
|
documentTypes.length > 1 && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
@@ -4415,7 +4161,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4415
4161
|
}
|
|
4416
4162
|
)
|
|
4417
4163
|
] }),
|
|
4418
|
-
/* @__PURE__ */ jsx(Box, { style: { maxHeight: "400px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, {
|
|
4164
|
+
/* @__PURE__ */ jsx(Box, { style: { maxHeight: "400px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 2, children: filteredReferences.length === 0 ? /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "No references match the selected filter." }) : filteredReferences.map((reference) => {
|
|
4419
4165
|
const isSelected = selectedReferenceIds.has(reference._id), displayTitle = reference.title || reference._id;
|
|
4420
4166
|
return /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, children: [
|
|
4421
4167
|
/* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: () => handleToggleReference(reference._id) }),
|
|
@@ -4460,17 +4206,16 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4460
4206
|
payload: !shouldTranslateReferences
|
|
4461
4207
|
});
|
|
4462
4208
|
};
|
|
4463
|
-
return /* @__PURE__ */ jsxs(Stack, {
|
|
4209
|
+
return /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
4464
4210
|
/* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
|
|
4465
4211
|
/* @__PURE__ */ jsx(
|
|
4466
4212
|
Button,
|
|
4467
4213
|
{
|
|
4468
4214
|
type: "button",
|
|
4469
4215
|
mode: shouldTranslateReferences ? "ghost" : "bleed",
|
|
4470
|
-
size: 2,
|
|
4471
4216
|
onClick: handleShouldTranslateChange,
|
|
4472
4217
|
tone: shouldTranslateReferences ? "primary" : "neutral",
|
|
4473
|
-
children: /* @__PURE__ */ jsxs(Inline, {
|
|
4218
|
+
children: /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
|
|
4474
4219
|
/* @__PURE__ */ jsx(
|
|
4475
4220
|
Checkbox,
|
|
4476
4221
|
{
|
|
@@ -4483,7 +4228,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4483
4228
|
] })
|
|
4484
4229
|
}
|
|
4485
4230
|
),
|
|
4486
|
-
shouldTranslateReferences ? /* @__PURE__ */ jsxs(Inline, {
|
|
4231
|
+
shouldTranslateReferences ? /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
|
|
4487
4232
|
/* @__PURE__ */ jsx(Text, { children: "Max Depth:" }),
|
|
4488
4233
|
/* @__PURE__ */ jsx(
|
|
4489
4234
|
Select,
|
|
@@ -4507,7 +4252,6 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4507
4252
|
Button,
|
|
4508
4253
|
{
|
|
4509
4254
|
mode: "ghost",
|
|
4510
|
-
size: 1,
|
|
4511
4255
|
text: showSelectionPanel ? "Hide selection" : "Select specific references...",
|
|
4512
4256
|
onClick: () => setShowSelectionPanel(!showSelectionPanel)
|
|
4513
4257
|
}
|
|
@@ -4516,21 +4260,21 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4516
4260
|
!isLoadingReferences && references.length > 0 && showSelectionPanel && /* @__PURE__ */ jsx(ReferenceSelectionPanel, {})
|
|
4517
4261
|
] }) : null
|
|
4518
4262
|
] });
|
|
4519
|
-
}, WriteSettingsPanel = () => /* @__PURE__ */ jsx(ReferenceConfigPanel, {}), TranslationConfigView = ({ setIsDialogOpen }) => /* @__PURE__ */ jsxs(Stack, {
|
|
4263
|
+
}, WriteSettingsPanel = () => /* @__PURE__ */ jsx(ReferenceConfigPanel, {}), TranslationConfigView = ({ setIsDialogOpen }) => /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
|
|
4520
4264
|
/* @__PURE__ */ jsx(DocumentSourcePanel, {}),
|
|
4521
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
4265
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
4522
4266
|
/* @__PURE__ */ jsx(Label, { children: "Languages" }),
|
|
4523
4267
|
/* @__PURE__ */ jsx(LanguageSelectionPanel, {})
|
|
4524
4268
|
] }),
|
|
4525
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
4269
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
4526
4270
|
/* @__PURE__ */ jsx(Label, { children: "Translation Method" }),
|
|
4527
4271
|
/* @__PURE__ */ jsx(TranslationModePanel, {})
|
|
4528
4272
|
] }),
|
|
4529
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
4273
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
4530
4274
|
/* @__PURE__ */ jsx(Label, { children: "Content Release" }),
|
|
4531
4275
|
/* @__PURE__ */ jsx(ReleaseSelectionPanel, {})
|
|
4532
4276
|
] }),
|
|
4533
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
4277
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
4534
4278
|
/* @__PURE__ */ jsx(Label, { children: "Settings" }),
|
|
4535
4279
|
/* @__PURE__ */ jsx(Card, { padding: 4, border: !0, muted: !0, children: /* @__PURE__ */ jsx(WriteSettingsPanel, {}) })
|
|
4536
4280
|
] }),
|
|
@@ -4539,16 +4283,16 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4539
4283
|
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(() => {
|
|
4540
4284
|
forceCleanupActiveTranslation(document._id), dispatch({ type: "RESET_TRANSLATION_STATE" });
|
|
4541
4285
|
}, [dispatch, document._id]);
|
|
4542
|
-
return /* @__PURE__ */ jsxs(Stack, {
|
|
4286
|
+
return /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
|
|
4543
4287
|
/* @__PURE__ */ jsx(Heading, { size: 2, children: headline }),
|
|
4544
4288
|
/* @__PURE__ */ jsx(ProgressBar, { progress: jobs.filter((job) => TERMINAL_STATUSES.includes(job.status)).length, max: jobs.length }),
|
|
4545
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
4289
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
4546
4290
|
/* @__PURE__ */ jsx(Label, { children: "Translations" }),
|
|
4547
|
-
/* @__PURE__ */ jsx(Card, { padding: 2, tone: "transparent", children: /* @__PURE__ */ jsx(Stack, {
|
|
4548
|
-
/* @__PURE__ */ jsxs(Inline, {
|
|
4291
|
+
/* @__PURE__ */ jsx(Card, { padding: 2, tone: "transparent", children: /* @__PURE__ */ jsx(Stack, { gap: 2, children: /* @__PURE__ */ jsx("ul", { style: { listStyleType: "none", paddingInlineStart: "0" }, children: jobs.map((job) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Box, { padding: 3, children: /* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
|
|
4292
|
+
/* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
|
|
4549
4293
|
/* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", style: { width: "24px", height: "24px" }, children: ["completed", "skipped"].includes(job.status) ? /* @__PURE__ */ jsx(CheckmarkIcon, { fontSize: 24 }) : job.status === "error" ? /* @__PURE__ */ jsx(Box, { style: { color: "var(--card-critical-fg-color)" }, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, { fontSize: 24 }) }) : job.status === "queued" ? /* @__PURE__ */ jsx(CircleIcon, { fontSize: 24 }) : /* @__PURE__ */ jsx(Spinner, {}) }),
|
|
4550
|
-
/* @__PURE__ */ jsxs(Stack, {
|
|
4551
|
-
/* @__PURE__ */ jsxs(Inline, {
|
|
4294
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
4295
|
+
/* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
|
|
4552
4296
|
/* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: job.title || "Untitled Document" }),
|
|
4553
4297
|
/* @__PURE__ */ jsx(ArrowRightIcon, { fontSize: 18 }),
|
|
4554
4298
|
/* @__PURE__ */ jsx(Code, { size: 1, children: job.language })
|
|
@@ -4584,7 +4328,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
4584
4328
|
] });
|
|
4585
4329
|
}, ProgressBar = ({ progress, max = 100 }) => {
|
|
4586
4330
|
const percentage = Math.min(progress / max * 100, 100);
|
|
4587
|
-
return /* @__PURE__ */ jsxs(Stack, {
|
|
4331
|
+
return /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
4588
4332
|
/* @__PURE__ */ jsx(
|
|
4589
4333
|
"div",
|
|
4590
4334
|
{
|
|
@@ -5437,7 +5181,7 @@ function getDocumentLanguage(document) {
|
|
|
5437
5181
|
}
|
|
5438
5182
|
const aiTranslationPlugin = definePlugin((config) => (config.fieldTransforms?.length && (devLog.info(`Registering ${config.fieldTransforms.length} custom field transforms`), config.fieldTransforms.forEach((transform) => {
|
|
5439
5183
|
globalTransformRegistry.register(transform);
|
|
5440
|
-
})), {
|
|
5184
|
+
})), setGlossaryDataset(config.glossaryDataset), {
|
|
5441
5185
|
name: "sanity-plugin-ai-translation",
|
|
5442
5186
|
document: {
|
|
5443
5187
|
actions: [getTranslateAction(config)]
|