@parhelia/localization 0.1.11464 → 0.1.11519
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/LocalizeItemDialog.d.ts.map +1 -1
- package/dist/LocalizeItemDialog.js +3 -1
- package/dist/LocalizeItemUtils.d.ts.map +1 -1
- package/dist/LocalizeItemUtils.js +16 -1
- package/dist/steps/PromptCustomizationStep.d.ts.map +1 -1
- package/dist/steps/PromptCustomizationStep.js +67 -36
- package/dist/translation-center/BatchTranslationView.d.ts.map +1 -1
- package/dist/translation-center/BatchTranslationView.js +27 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalizeItemDialog.d.ts","sourceRoot":"","sources":["../src/LocalizeItemDialog.tsx"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7C,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EAExB,MAAM,eAAe,CAAC;AAiBvB,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,uBAAuB,GAAG,WAAW,CAAC,uBAAuB,CAAC,
|
|
1
|
+
{"version":3,"file":"LocalizeItemDialog.d.ts","sourceRoot":"","sources":["../src/LocalizeItemDialog.tsx"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7C,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EAExB,MAAM,eAAe,CAAC;AAiBvB,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,uBAAuB,GAAG,WAAW,CAAC,uBAAuB,CAAC,2CAmWtE"}
|
|
@@ -186,7 +186,9 @@ export function LocalizeItemDialog(props) {
|
|
|
186
186
|
minHeight: '700px'
|
|
187
187
|
}, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { "data-testid": "translation-wizard-title", children: "Translation Wizard" }), _jsx("div", { id: "translation-wizard-description", className: "sr-only", children: currentStep?.description || "Configure and start translation for your content." })] }), _jsxs("div", { className: "flex flex-1 flex-col min-h-0", children: [_jsx("div", { className: "border-b border-[var(--color-gray-3)] bg-background px-6", "data-testid": "translation-wizard-step-navigation", children: _jsx("div", { className: "flex items-center gap-3 py-3", children: activeSteps.map((step, index) => (_jsxs("div", { className: "flex items-center gap-2", "data-testid": `step-indicator-${step.id}`, children: [_jsx("div", { className: cn("flex h-8 w-8 items-center justify-center rounded-full border text-sm font-medium transition-colors", currentStepIndex === index &&
|
|
188
188
|
"border-[#9650fb] text-[#9650fb] bg-[#f6eeff]", currentStepIndex < index && "border-[var(--color-gray-3)] text-[var(--color-gray-2)]", currentStepIndex > index &&
|
|
189
|
-
"bg-[#9650fb] text-white border-[#9650fb]"),
|
|
189
|
+
"bg-[#9650fb] text-white border-[#9650fb]"), style: currentStepIndex > index
|
|
190
|
+
? { backgroundColor: "#9650fb", color: "#ffffff" }
|
|
191
|
+
: undefined, "data-testid": `step-indicator-circle-${step.id}`, children: currentStepIndex > index ? "✓" : index + 1 }), _jsx("span", { className: cn("text-sm", currentStepIndex === index
|
|
190
192
|
? "text-[#9650fb] font-medium"
|
|
191
193
|
: currentStepIndex > index
|
|
192
194
|
? "text-[#9650fb]"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalizeItemUtils.d.ts","sourceRoot":"","sources":["../src/LocalizeItemUtils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAC,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIjD,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,MAAM,MAAM,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAIF,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,qBAAqB,EACjC,WAAW,EAAE,eAAe,EAC5B,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"LocalizeItemUtils.d.ts","sourceRoot":"","sources":["../src/LocalizeItemUtils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAC,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIjD,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,MAAM,MAAM,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAIF,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,qBAAqB,EACjC,WAAW,EAAE,eAAe,EAC5B,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAqH5B;AAGD,eAAO,MAAM,mBAAmB,GAAU,eAAe,MAAM,EAAE,EAAE,mBAAmB,iBAAiB,EAAE,EAAE,WAAW,MAAM,EAAE,MAAM,QAAQ,EAAE,qBAAqB,MAAM,+BA0BxK,CAAC"}
|
|
@@ -46,12 +46,27 @@ export async function performDefaultTranslation(wizardData, editContext, predefi
|
|
|
46
46
|
if (wizardData.serviceCustomData && wizardData.serviceCustomData.size > 0) {
|
|
47
47
|
const serviceData = wizardData.serviceCustomData.get(wizardData.translationProvider);
|
|
48
48
|
if (serviceData && serviceData.enableCustomPrompt) {
|
|
49
|
+
const provider = wizardData.translationProviders.find(p => p.name === wizardData.translationProvider);
|
|
50
|
+
const defaultPrompt = provider?.defaultPrompt && provider.defaultPrompt.trim()
|
|
51
|
+
? provider.defaultPrompt
|
|
52
|
+
: null;
|
|
53
|
+
const customizationType = serviceData.promptCustomizationType === "replace" ? "replace" : "extend";
|
|
54
|
+
const rawCustomPrompt = (serviceData.customPrompt || "").trim();
|
|
55
|
+
let finalCustomPrompt = "";
|
|
56
|
+
if (rawCustomPrompt) {
|
|
57
|
+
if (defaultPrompt && customizationType === "extend") {
|
|
58
|
+
finalCustomPrompt = `${defaultPrompt}\n\n${rawCustomPrompt}`;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
finalCustomPrompt = rawCustomPrompt;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
49
64
|
// Merge service-specific custom data into batch metadata
|
|
50
65
|
const metadataObj = batchMetadata ? (typeof batchMetadata === 'string' ? JSON.parse(batchMetadata) : batchMetadata) : {};
|
|
51
66
|
metadataObj.serviceCustomData = {
|
|
52
67
|
[wizardData.translationProvider]: {
|
|
53
68
|
enableCustomPrompt: serviceData.enableCustomPrompt,
|
|
54
|
-
customPrompt:
|
|
69
|
+
customPrompt: finalCustomPrompt
|
|
55
70
|
}
|
|
56
71
|
};
|
|
57
72
|
batchMetadata = JSON.stringify(metadataObj);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromptCustomizationStep.d.ts","sourceRoot":"","sources":["../../src/steps/PromptCustomizationStep.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAyB,MAAM,SAAS,CAAC;AAEtE,wBAAgB,uBAAuB,CAAC,EACtC,SAAS,EACT,QAAe,EACf,IAAI,EACJ,OAAO,EACP,eAAe,EACf,WAAW,GACZ,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"PromptCustomizationStep.d.ts","sourceRoot":"","sources":["../../src/steps/PromptCustomizationStep.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAyB,MAAM,SAAS,CAAC;AAEtE,wBAAgB,uBAAuB,CAAC,EACtC,SAAS,EACT,QAAe,EACf,IAAI,EACJ,OAAO,EACP,eAAe,EACf,WAAW,GACZ,EAAE,oBAAoB,2CAwPtB"}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useMemo } from "react";
|
|
2
|
+
import { useState, useEffect, useMemo, useRef } from "react";
|
|
3
3
|
export function PromptCustomizationStep({ stepIndex, isActive = true, data, setData, onStepCompleted, editContext, }) {
|
|
4
4
|
const [customPrompt, setCustomPrompt] = useState("");
|
|
5
5
|
const [customizationType, setCustomizationType] = useState("extend");
|
|
6
|
+
const customPromptRef = useRef(customPrompt);
|
|
7
|
+
const customizationTypeRef = useRef(customizationType);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
customPromptRef.current = customPrompt;
|
|
10
|
+
}, [customPrompt]);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
customizationTypeRef.current = customizationType;
|
|
13
|
+
}, [customizationType]);
|
|
6
14
|
// Get service-specific custom data for the selected provider
|
|
7
15
|
const serviceData = useMemo(() => {
|
|
8
16
|
if (!data.serviceCustomData || !data.translationProvider)
|
|
@@ -10,17 +18,6 @@ export function PromptCustomizationStep({ stepIndex, isActive = true, data, setD
|
|
|
10
18
|
return data.serviceCustomData.get(data.translationProvider);
|
|
11
19
|
}, [data.serviceCustomData, data.translationProvider]);
|
|
12
20
|
const enableCustomPrompt = serviceData?.enableCustomPrompt === true;
|
|
13
|
-
// Initialize from existing data
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (enableCustomPrompt && serviceData) {
|
|
16
|
-
setCustomPrompt(serviceData.customPrompt || "");
|
|
17
|
-
setCustomizationType(serviceData.promptCustomizationType || "extend");
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
setCustomPrompt("");
|
|
21
|
-
setCustomizationType("extend");
|
|
22
|
-
}
|
|
23
|
-
}, [enableCustomPrompt, serviceData]);
|
|
24
21
|
// Get default prompt from provider settings (no fallback)
|
|
25
22
|
const defaultPrompt = useMemo(() => {
|
|
26
23
|
const provider = data.translationProviders.find(p => p.name === data.translationProvider);
|
|
@@ -29,6 +26,37 @@ export function PromptCustomizationStep({ stepIndex, isActive = true, data, setD
|
|
|
29
26
|
return prompt && prompt.trim() ? prompt : null;
|
|
30
27
|
}, [data.translationProviders, data.translationProvider]);
|
|
31
28
|
const hasDefaultPrompt = defaultPrompt != null && defaultPrompt.trim().length > 0;
|
|
29
|
+
// Initialize from existing data
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const nextCustomizationType = serviceData?.promptCustomizationType || "extend";
|
|
32
|
+
let nextCustomPrompt = serviceData?.customPrompt || "";
|
|
33
|
+
// If stored prompt already includes default (legacy), strip it for editing
|
|
34
|
+
if (hasDefaultPrompt && nextCustomizationType === "extend" && nextCustomPrompt.startsWith(defaultPrompt || "")) {
|
|
35
|
+
nextCustomPrompt = nextCustomPrompt.slice((defaultPrompt || "").length);
|
|
36
|
+
nextCustomPrompt = nextCustomPrompt.replace(/^\s*\n\s*\n?/, "");
|
|
37
|
+
}
|
|
38
|
+
if (enableCustomPrompt && serviceData) {
|
|
39
|
+
if (customPromptRef.current !== nextCustomPrompt) {
|
|
40
|
+
setCustomPrompt(nextCustomPrompt);
|
|
41
|
+
}
|
|
42
|
+
if (customizationTypeRef.current !== nextCustomizationType) {
|
|
43
|
+
setCustomizationType(nextCustomizationType);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (customPromptRef.current !== "") {
|
|
48
|
+
setCustomPrompt("");
|
|
49
|
+
}
|
|
50
|
+
if (customizationTypeRef.current !== "extend") {
|
|
51
|
+
setCustomizationType("extend");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}, [
|
|
55
|
+
enableCustomPrompt,
|
|
56
|
+
serviceData,
|
|
57
|
+
hasDefaultPrompt,
|
|
58
|
+
defaultPrompt,
|
|
59
|
+
]);
|
|
32
60
|
// Preview of final prompt
|
|
33
61
|
const previewPrompt = useMemo(() => {
|
|
34
62
|
if (!enableCustomPrompt || !customPrompt.trim()) {
|
|
@@ -49,34 +77,29 @@ export function PromptCustomizationStep({ stepIndex, isActive = true, data, setD
|
|
|
49
77
|
if (!isActive)
|
|
50
78
|
return;
|
|
51
79
|
const newServiceCustomData = new Map(data.serviceCustomData || new Map());
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (hasDefaultPrompt) {
|
|
56
|
-
// If default prompt exists, apply extend/replace logic
|
|
57
|
-
if (customizationType === "replace") {
|
|
58
|
-
// Replace Default Prompt entirely
|
|
59
|
-
finalCustomPrompt = customPrompt.trim();
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
// Extend Default Prompt
|
|
63
|
-
finalCustomPrompt = `${defaultPrompt}\n\n${customPrompt.trim()}`;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
// No default prompt, just use custom prompt as-is
|
|
68
|
-
finalCustomPrompt = customPrompt.trim();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
80
|
+
const trimmedCustomPrompt = customPrompt.trim();
|
|
81
|
+
const nextCustomizationType = customizationType === "replace" ? "replace" : "extend";
|
|
82
|
+
const currentServiceData = data.serviceCustomData?.get(data.translationProvider);
|
|
71
83
|
if (enableCustomPrompt) {
|
|
72
|
-
|
|
84
|
+
const nextServiceData = {
|
|
73
85
|
enableCustomPrompt: true,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
// Store raw custom prompt only; final prompt is composed when sending metadata.
|
|
87
|
+
customPrompt: trimmedCustomPrompt,
|
|
88
|
+
promptCustomizationType: nextCustomizationType,
|
|
89
|
+
};
|
|
90
|
+
const isSame = currentServiceData?.enableCustomPrompt === true &&
|
|
91
|
+
(currentServiceData.customPrompt || "") === trimmedCustomPrompt &&
|
|
92
|
+
(currentServiceData.promptCustomizationType || "extend") === nextCustomizationType;
|
|
93
|
+
if (isSame) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
newServiceCustomData.set(data.translationProvider, nextServiceData);
|
|
77
97
|
}
|
|
78
98
|
else {
|
|
79
99
|
// Remove custom data if disabled
|
|
100
|
+
if (!currentServiceData) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
80
103
|
newServiceCustomData.delete(data.translationProvider);
|
|
81
104
|
}
|
|
82
105
|
const newData = {
|
|
@@ -84,7 +107,15 @@ export function PromptCustomizationStep({ stepIndex, isActive = true, data, setD
|
|
|
84
107
|
serviceCustomData: newServiceCustomData,
|
|
85
108
|
};
|
|
86
109
|
setData(newData);
|
|
87
|
-
}, [
|
|
110
|
+
}, [
|
|
111
|
+
enableCustomPrompt,
|
|
112
|
+
customPrompt,
|
|
113
|
+
customizationType,
|
|
114
|
+
data.serviceCustomData,
|
|
115
|
+
data.translationProvider,
|
|
116
|
+
isActive,
|
|
117
|
+
setData,
|
|
118
|
+
]);
|
|
88
119
|
// Update completion status
|
|
89
120
|
useEffect(() => {
|
|
90
121
|
if (!isActive)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BatchTranslationView.d.ts","sourceRoot":"","sources":["../../src/translation-center/BatchTranslationView.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BatchTranslationView.d.ts","sourceRoot":"","sources":["../../src/translation-center/BatchTranslationView.tsx"],"names":[],"mappings":"AAyBA,OAAO,qCAAqC,CAAC;AAsB7C,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAoCD,wBAAgB,oBAAoB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,yBAAyB,2CAo9BlF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React, { useEffect, useState, useMemo, useRef, useCallback, } from "react";
|
|
2
|
+
import React, { useEffect, useState, useMemo, useRef, useCallback, memo, } from "react";
|
|
3
3
|
import { useEditContext, SimpleTable, Progress as CoreProgress, Button, } from "@parhelia/core";
|
|
4
4
|
import { listBatchTranslationJobs, subscribeToBatch, unsubscribeFromBatch, getBatchInfo, getTranslationProviders } from "../services/translationService";
|
|
5
5
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
@@ -10,6 +10,9 @@ const Progress = (props) => React.createElement(CoreProgress, props);
|
|
|
10
10
|
// Wrappers for lucide-react icons to work with React 19
|
|
11
11
|
const ChevronUpIcon = (props) => React.createElement(ChevronUp, props);
|
|
12
12
|
const ChevronDownIcon = (props) => React.createElement(ChevronDown, props);
|
|
13
|
+
const MemoizedJsonView = memo(({ data }) => (_jsx(JsonView, { data: data, shouldExpandNode: (level) => level < 2, style: defaultStyles })), (prevProps, nextProps) => {
|
|
14
|
+
return JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data);
|
|
15
|
+
});
|
|
13
16
|
// Normalize API shape/casing to what the UI expects
|
|
14
17
|
function normalizeBatchInfo(raw) {
|
|
15
18
|
if (!raw)
|
|
@@ -52,6 +55,7 @@ export function BatchTranslationView({ batchId, onBack }) {
|
|
|
52
55
|
const isSubscribedRef = useRef(false);
|
|
53
56
|
const initGuardRef = useRef(false);
|
|
54
57
|
const lastBatchIdRef = useRef(null);
|
|
58
|
+
const completionRefreshRef = useRef(null);
|
|
55
59
|
// Helper function to get display name from service name
|
|
56
60
|
const getProviderDisplayName = useCallback((serviceName) => {
|
|
57
61
|
if (!serviceName)
|
|
@@ -165,6 +169,7 @@ export function BatchTranslationView({ batchId, onBack }) {
|
|
|
165
169
|
hasLoadedRef.current = false;
|
|
166
170
|
isSubscribedRef.current = false;
|
|
167
171
|
initGuardRef.current = false;
|
|
172
|
+
completionRefreshRef.current = null;
|
|
168
173
|
}
|
|
169
174
|
let cancelled = false;
|
|
170
175
|
const init = async () => {
|
|
@@ -302,6 +307,7 @@ export function BatchTranslationView({ batchId, onBack }) {
|
|
|
302
307
|
const info = normalizeBatchInfo(raw);
|
|
303
308
|
if (info)
|
|
304
309
|
setBatchInfo(info);
|
|
310
|
+
await loadBatchJobs();
|
|
305
311
|
}
|
|
306
312
|
catch { }
|
|
307
313
|
})();
|
|
@@ -495,13 +501,32 @@ export function BatchTranslationView({ batchId, onBack }) {
|
|
|
495
501
|
progressSegments.inProgressCount === 0
|
|
496
502
|
? "Completed"
|
|
497
503
|
: batchInfo?.status;
|
|
504
|
+
// Ensure terminal state is reflected in UI even if a websocket message was missed.
|
|
505
|
+
useEffect(() => {
|
|
506
|
+
if (effectiveStatus !== "Completed" && effectiveStatus !== "Error")
|
|
507
|
+
return;
|
|
508
|
+
if (completionRefreshRef.current === batchId)
|
|
509
|
+
return;
|
|
510
|
+
completionRefreshRef.current = batchId;
|
|
511
|
+
(async () => {
|
|
512
|
+
try {
|
|
513
|
+
const resInfo = await getBatchInfo(batchId);
|
|
514
|
+
const raw = resInfo?.data ?? resInfo;
|
|
515
|
+
const info = normalizeBatchInfo(raw);
|
|
516
|
+
if (info)
|
|
517
|
+
setBatchInfo(info);
|
|
518
|
+
await loadBatchJobs();
|
|
519
|
+
}
|
|
520
|
+
catch { }
|
|
521
|
+
})();
|
|
522
|
+
}, [effectiveStatus, batchId, loadBatchJobs]);
|
|
498
523
|
return (_jsxs("div", { className: "flex h-full flex-col min-h-0 bg-[var(--color-gray-5)]", "data-testid": "batch-translation-view", children: [_jsxs("div", { className: "flex-shrink-0 border-b border-[var(--color-gray-3)] bg-background p-4 md:p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [onBack && (_jsxs(Button, { size: "sm", variant: "outline", onClick: onBack, children: [_jsx("i", { className: "pi pi-arrow-left" }), "Back"] })), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold text-[var(--color-dark)]", children: "Translation Batch" }), _jsxs("p", { className: "text-sm text-[var(--color-gray-2)] mt-1", children: ["Batch ID: ", _jsx("span", { className: "font-mono text-xs bg-[var(--color-gray-4)] px-2 py-0.5 rounded-md", children: batchId })] }), batchInfo && (_jsxs("div", { className: "mt-2 flex flex-wrap gap-2 items-center text-xs", children: [effectiveStatus && (_jsx("span", { "data-testid": "translation-job-status", className: `inline-flex items-center rounded-full px-2.5 py-1 font-medium ${effectiveStatus === 'Completed'
|
|
499
524
|
? 'bg-green-100 text-green-700'
|
|
500
525
|
: effectiveStatus === 'In Progress'
|
|
501
526
|
? ''
|
|
502
527
|
: effectiveStatus === 'Error'
|
|
503
528
|
? 'bg-red-100 text-red-600'
|
|
504
|
-
: 'bg-[var(--color-gray-4)] text-[var(--color-gray-1)]'}`, style: effectiveStatus === 'In Progress' ? { backgroundColor: '#f6eeff', color: '#9650fb' } : undefined, children: effectiveStatus })), batchInfo.provider && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["Provider: ", _jsx("span", { className: "font-medium", children: getProviderDisplayName(batchInfo.provider) })] })), batchInfo.initiatedByUser && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["By: ", _jsx("span", { className: "font-medium", children: batchInfo.initiatedByUser })] })), batchInfo.includeSubitems !== undefined && batchInfo.includeSubitems !== null && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["Include subitems: ", _jsx("span", { className: "font-medium", children: batchInfo.includeSubitems ? 'Yes' : 'No' })] })), batchInfo.scopeItemPath && (_jsxs("span", { className: "text-[var(--color-gray-1)] truncate max-w-[40rem]", children: ["Scope: ", _jsx("span", { className: "font-mono", children: batchInfo.scopeItemPath })] })), (batchInfo.startedAtUtc || batchInfo.completedAtUtc || batchInfo.lastUpdatedUtc) && (_jsxs("span", { className: "text-[var(--color-gray-2)]", children: [batchInfo.startedAtUtc && (_jsxs(_Fragment, { children: ["Started: ", new Date(batchInfo.startedAtUtc).toLocaleString()] })), batchInfo.completedAtUtc && (_jsxs(_Fragment, { children: [batchInfo.startedAtUtc ? ' • ' : '', "Completed: ", new Date(batchInfo.completedAtUtc).toLocaleString()] })), !batchInfo.completedAtUtc && batchInfo.lastUpdatedUtc && (_jsxs(_Fragment, { children: [batchInfo.startedAtUtc ? ' • ' : '', "Updated: ", new Date(batchInfo.lastUpdatedUtc).toLocaleString()] }))] }))] }))] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "text-sm text-[var(--color-gray-2)]", children: [`${progressSegments.completedCount}/${progressSegments.total} completed`, progressSegments.errorCount ? `, ${progressSegments.errorCount} errors` : ''] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: loadBatchJobs, disabled: isLoading, title: "Manual refresh - normally updates come through websocket subscriptions", children: [_jsx("i", { className: `pi pi-refresh ${isLoading ? 'pi-spin' : ''}` }), "Refresh"] })] })] }), parsedMetadata && (_jsxs("div", { className: "border-t border-[var(--color-gray-3)] bg-[var(--color-gray-5)] mt-4 rounded-b-lg", children: [_jsxs("button", { onClick: () => setIsMetadataExpanded(!isMetadataExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2.5 text-left transition-colors hover:bg-[var(--color-gray-4)]", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("i", { className: "pi pi-info-circle text-[var(--color-gray-2)]" }), _jsx("span", { className: "text-xs font-medium text-[var(--color-gray-1)]", children: "Batch Metadata" })] }), isMetadataExpanded ? (_jsx(ChevronUpIcon, { className: "h-4 w-4 text-[var(--color-gray-2)]", strokeWidth: 1 })) : (_jsx(ChevronDownIcon, { className: "h-4 w-4 text-[var(--color-gray-2)]", strokeWidth: 1 }))] }), isMetadataExpanded && (_jsx("div", { className: "max-h-96 overflow-y-auto px-4 pb-3", children: _jsx("div", { className: "rounded-lg border border-[var(--color-gray-3)] bg-background p-3 text-xs shadow-sm mt-2", children: _jsx(
|
|
529
|
+
: 'bg-[var(--color-gray-4)] text-[var(--color-gray-1)]'}`, style: effectiveStatus === 'In Progress' ? { backgroundColor: '#f6eeff', color: '#9650fb' } : undefined, children: effectiveStatus })), batchInfo.provider && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["Provider: ", _jsx("span", { className: "font-medium", children: getProviderDisplayName(batchInfo.provider) })] })), batchInfo.initiatedByUser && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["By: ", _jsx("span", { className: "font-medium", children: batchInfo.initiatedByUser })] })), batchInfo.includeSubitems !== undefined && batchInfo.includeSubitems !== null && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["Include subitems: ", _jsx("span", { className: "font-medium", children: batchInfo.includeSubitems ? 'Yes' : 'No' })] })), batchInfo.scopeItemPath && (_jsxs("span", { className: "text-[var(--color-gray-1)] truncate max-w-[40rem]", children: ["Scope: ", _jsx("span", { className: "font-mono", children: batchInfo.scopeItemPath })] })), (batchInfo.startedAtUtc || batchInfo.completedAtUtc || batchInfo.lastUpdatedUtc) && (_jsxs("span", { className: "text-[var(--color-gray-2)]", children: [batchInfo.startedAtUtc && (_jsxs(_Fragment, { children: ["Started: ", new Date(batchInfo.startedAtUtc).toLocaleString()] })), batchInfo.completedAtUtc && (_jsxs(_Fragment, { children: [batchInfo.startedAtUtc ? ' • ' : '', "Completed: ", new Date(batchInfo.completedAtUtc).toLocaleString()] })), !batchInfo.completedAtUtc && batchInfo.lastUpdatedUtc && (_jsxs(_Fragment, { children: [batchInfo.startedAtUtc ? ' • ' : '', "Updated: ", new Date(batchInfo.lastUpdatedUtc).toLocaleString()] }))] }))] }))] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "text-sm text-[var(--color-gray-2)]", children: [`${progressSegments.completedCount}/${progressSegments.total} completed`, progressSegments.errorCount ? `, ${progressSegments.errorCount} errors` : ''] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: loadBatchJobs, disabled: isLoading, title: "Manual refresh - normally updates come through websocket subscriptions", children: [_jsx("i", { className: `pi pi-refresh ${isLoading ? 'pi-spin' : ''}` }), "Refresh"] })] })] }), parsedMetadata && (_jsxs("div", { className: "border-t border-[var(--color-gray-3)] bg-[var(--color-gray-5)] mt-4 rounded-b-lg", children: [_jsxs("button", { onClick: () => setIsMetadataExpanded(!isMetadataExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2.5 text-left transition-colors hover:bg-[var(--color-gray-4)]", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("i", { className: "pi pi-info-circle text-[var(--color-gray-2)]" }), _jsx("span", { className: "text-xs font-medium text-[var(--color-gray-1)]", children: "Batch Metadata" })] }), isMetadataExpanded ? (_jsx(ChevronUpIcon, { className: "h-4 w-4 text-[var(--color-gray-2)]", strokeWidth: 1 })) : (_jsx(ChevronDownIcon, { className: "h-4 w-4 text-[var(--color-gray-2)]", strokeWidth: 1 }))] }), isMetadataExpanded && (_jsx("div", { className: "max-h-96 overflow-y-auto px-4 pb-3", children: _jsx("div", { className: "rounded-lg border border-[var(--color-gray-3)] bg-background p-3 text-xs shadow-sm mt-2", children: _jsx(MemoizedJsonView, { data: parsedMetadata }) }) }))] })), _jsxs("div", { className: "mt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-medium text-[var(--color-dark)]", children: "Overall Progress" }), _jsxs("span", { className: "text-sm text-[var(--color-gray-2)]", children: [progressSegments.completedCount, " / ", progressSegments.total, " completed"] })] }), _jsxs("div", { className: "relative h-4 rounded-full overflow-hidden", style: { backgroundColor: 'var(--color-gray-3)' }, children: [_jsxs("div", { className: "absolute inset-0 flex", children: [progressSegments.completed > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: { width: `${progressSegments.completed}%`, backgroundColor: '#8ae048' } })), progressSegments.inProgress > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: { width: `${progressSegments.inProgress}%`, backgroundColor: '#9650fb' } })), progressSegments.error > 0 && (_jsx("div", { className: "bg-destructive h-full transition-all duration-300", style: { width: `${progressSegments.error}%` } })), progressSegments.pending > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: { width: `${progressSegments.pending}%`, backgroundColor: 'var(--color-gray-4)' } }))] }), _jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsxs("span", { className: "text-xs font-medium text-white drop-shadow-sm", children: [overallProgress, "%"] }) })] }), _jsxs("div", { className: "flex flex-wrap gap-3 mt-2 text-xs", children: [progressSegments.completedCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 rounded-sm", style: { backgroundColor: '#8ae048' } }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.completedCount, " completed"] })] })), progressSegments.inProgressCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 rounded-sm", style: { backgroundColor: '#9650fb' } }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.inProgressCount, " in progress"] })] })), progressSegments.errorCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 bg-destructive rounded-sm" }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.errorCount, " errors"] })] })), progressSegments.pendingCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 bg-[var(--color-gray-4)] rounded-sm" }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.pendingCount, " pending"] })] })), typeof batchInfo?.expectedJobs === 'number' && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 bg-[var(--color-gray-4)] rounded-sm" }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [(batchInfo.expectedJobs || 0) - (batchInfo.completedJobs || 0) - (batchInfo.errorJobs || 0), " pending (server)"] })] }))] })] })] }), _jsx("div", { className: "flex-1 overflow-auto p-4 md:p-6 min-h-0", children: isLoading && batchJobs.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "flex items-center gap-2 text-[var(--color-gray-2)]", children: [_jsx("i", { className: "pi pi-spin pi-spinner text-[#9650fb]" }), "Loading batch translations..."] }) })) : batchJobs.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-[var(--color-gray-2)]", children: [_jsx("i", { className: "pi pi-language text-2xl block mb-2 text-[var(--color-gray-3)]" }), _jsx("p", { className: "font-medium text-[var(--color-gray-1)]", children: "No translations found for this batch" }), _jsx("p", { className: "text-sm mt-1", children: "The batch may not exist or no translation jobs have started yet" })] }) })) : (_jsx("div", { className: "space-y-4", children: sortedItemGroups.map(([itemId, jobs]) => {
|
|
505
530
|
const itemCompleted = jobs.filter(j => j.status === "Completed").length;
|
|
506
531
|
// Calculate item progress including in-progress job progress
|
|
507
532
|
// Performance: item-level calculation is always fast (typically 2-10 jobs per item)
|