@parhelia/localization 0.1.11092 → 0.1.11143
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 +12 -2
- package/dist/LocalizeItemUtils.d.ts.map +1 -1
- package/dist/LocalizeItemUtils.js +17 -1
- package/dist/api/discovery.d.ts +1 -0
- package/dist/api/discovery.d.ts.map +1 -1
- package/dist/api/discovery.js +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/steps/PromptCustomizationStep.d.ts +3 -0
- package/dist/steps/PromptCustomizationStep.d.ts.map +1 -0
- package/dist/steps/PromptCustomizationStep.js +102 -0
- package/dist/steps/ServiceLanguageSelectionStep.d.ts.map +1 -1
- package/dist/steps/ServiceLanguageSelectionStep.js +45 -1
- package/dist/steps/SubitemDiscoveryStep.d.ts.map +1 -1
- package/dist/steps/SubitemDiscoveryStep.js +90 -38
- package/dist/steps/types.d.ts +2 -0
- package/dist/steps/types.d.ts.map +1 -1
- package/dist/translation-center/BatchTranslationView.d.ts +1 -0
- package/dist/translation-center/BatchTranslationView.d.ts.map +1 -1
- package/dist/translation-center/BatchTranslationView.js +19 -1
- 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,2CA8VtE"}
|
|
@@ -41,11 +41,17 @@ export function LocalizeItemDialog(props) {
|
|
|
41
41
|
}, [props.onClose]);
|
|
42
42
|
// Memoize activeSteps to prevent unnecessary recalculations and new object references
|
|
43
43
|
// This prevents infinite loops when wizardData changes but skip conditions don't
|
|
44
|
-
// The skip condition for subitem-discovery step
|
|
44
|
+
// The skip condition for subitem-discovery step checks includeSubitems
|
|
45
|
+
// The skip condition for prompt-customization step checks serviceCustomData and translationProvider
|
|
45
46
|
const activeSteps = useMemo(() => {
|
|
46
47
|
const steps = configuration.steps.filter((step) => !step.skipCondition || !step.skipCondition(wizardData));
|
|
47
48
|
return steps;
|
|
48
|
-
}, [
|
|
49
|
+
}, [
|
|
50
|
+
configuration.steps,
|
|
51
|
+
wizardData.includeSubitems,
|
|
52
|
+
wizardData.serviceCustomData,
|
|
53
|
+
wizardData.translationProvider
|
|
54
|
+
]); // Depend on what affects skip conditions
|
|
49
55
|
const currentStep = activeSteps[currentStepIndex];
|
|
50
56
|
// Refs for stable callbacks - updated during render to avoid dependency issues
|
|
51
57
|
const currentStepIdRef = useRef(currentStep?.id);
|
|
@@ -137,12 +143,16 @@ export function LocalizeItemDialog(props) {
|
|
|
137
143
|
const setWizardDataStable = useCallback((newData) => {
|
|
138
144
|
// Create a stable key from the data to detect actual changes
|
|
139
145
|
// Only compare the fields that matter for re-renders
|
|
146
|
+
const serviceCustomDataKey = newData.serviceCustomData
|
|
147
|
+
? JSON.stringify(Array.from(newData.serviceCustomData.entries()))
|
|
148
|
+
: null;
|
|
140
149
|
const dataKey = JSON.stringify({
|
|
141
150
|
translationProvider: newData.translationProvider,
|
|
142
151
|
targetLanguages: [...newData.targetLanguages].sort(),
|
|
143
152
|
includeSubitems: newData.includeSubitems,
|
|
144
153
|
discoveredItemsCount: newData.discoveredItems?.length || 0,
|
|
145
154
|
itemsCount: newData.items?.length || 0,
|
|
155
|
+
serviceCustomData: serviceCustomDataKey,
|
|
146
156
|
});
|
|
147
157
|
// Skip if data hasn't actually changed
|
|
148
158
|
if (lastSetWizardDataRef.current === dataKey) {
|
|
@@ -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,CAiG5B;AAGD,eAAO,MAAM,mBAAmB,GAAU,eAAe,MAAM,EAAE,EAAE,mBAAmB,iBAAiB,EAAE,EAAE,WAAW,MAAM,EAAE,MAAM,QAAQ,EAAE,qBAAqB,MAAM,+BA0BxK,CAAC"}
|
|
@@ -41,12 +41,28 @@ export async function performDefaultTranslation(wizardData, editContext, predefi
|
|
|
41
41
|
error.name = 'NoTranslationsNeededError';
|
|
42
42
|
throw error;
|
|
43
43
|
}
|
|
44
|
+
// Prepare batch metadata including service-specific custom data
|
|
45
|
+
let batchMetadata = wizardData.metadata || null;
|
|
46
|
+
if (wizardData.serviceCustomData && wizardData.serviceCustomData.size > 0) {
|
|
47
|
+
const serviceData = wizardData.serviceCustomData.get(wizardData.translationProvider);
|
|
48
|
+
if (serviceData && serviceData.enableCustomPrompt) {
|
|
49
|
+
// Merge service-specific custom data into batch metadata
|
|
50
|
+
const metadataObj = batchMetadata ? (typeof batchMetadata === 'string' ? JSON.parse(batchMetadata) : batchMetadata) : {};
|
|
51
|
+
metadataObj.serviceCustomData = {
|
|
52
|
+
[wizardData.translationProvider]: {
|
|
53
|
+
enableCustomPrompt: serviceData.enableCustomPrompt,
|
|
54
|
+
customPrompt: serviceData.customPrompt || ""
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
batchMetadata = JSON.stringify(metadataObj);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
44
60
|
try {
|
|
45
61
|
const batchResult = await requestBatchTranslation({
|
|
46
62
|
sessionId: editContext.sessionId,
|
|
47
63
|
batchId,
|
|
48
64
|
provider: wizardData.translationProvider,
|
|
49
|
-
batchMetadata:
|
|
65
|
+
batchMetadata: batchMetadata,
|
|
50
66
|
translations: translationRequests,
|
|
51
67
|
});
|
|
52
68
|
if (batchResult.type === "error") {
|
package/dist/api/discovery.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/api/discovery.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAC;AAInG,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,wBAAwB,EAC7B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,yBAAyB,CAAC,CAWpC;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,QAAQ,EAAE,CAQlF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,cAAc,EAAE,CAQvF"}
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/api/discovery.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAC;AAInG,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,wBAAwB,EAC7B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,yBAAyB,CAAC,CAWpC;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,QAAQ,EAAE,CAQlF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,cAAc,EAAE,CAQvF"}
|
package/dist/api/discovery.js
CHANGED
|
@@ -12,7 +12,7 @@ export function convertBackendTreeToTreeNodes(trees) {
|
|
|
12
12
|
const convert = (n) => ({
|
|
13
13
|
key: n.id,
|
|
14
14
|
label: n.name,
|
|
15
|
-
data: { id: n.id, name: n.name, path: n.path, hasLayout: n.hasLayout, icon: n.icon, parentId: undefined, hasChildren: (n.children?.length ?? 0) > 0 },
|
|
15
|
+
data: { id: n.id, name: n.name, path: n.path, hasLayout: n.hasLayout, isTranslatable: n.isTranslatable, icon: n.icon, parentId: undefined, hasChildren: (n.children?.length ?? 0) > 0 },
|
|
16
16
|
children: n.children?.map(convert) || [],
|
|
17
17
|
});
|
|
18
18
|
return trees.map(convert);
|
|
@@ -20,7 +20,7 @@ export function convertBackendTreeToTreeNodes(trees) {
|
|
|
20
20
|
export function flattenPagesFromBackendTrees(trees) {
|
|
21
21
|
const out = [];
|
|
22
22
|
const walk = (n) => {
|
|
23
|
-
if (n.
|
|
23
|
+
if (n.isTranslatable)
|
|
24
24
|
out.push({ id: n.id, name: n.name, path: n.path, hasChildren: (n.children?.length ?? 0) > 0, parentId: undefined });
|
|
25
25
|
n.children?.forEach(walk);
|
|
26
26
|
};
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAWtD,OAAO,EAAE,mBAAmB,EAAsB,MAAM,gBAAgB,CAAC;AASzE,QAAA,MAAM,wCAAwC;;;;;;;;;;;;8BAalB,qBAAqB;;CAchD,CAAC;AAEF,QAAA,MAAM,4CAA4C;;;;;;;CAUjD,CAAC;AAEF,OAAO,EAAE,wCAAwC,EAAE,4CAA4C,EAAE,CAAC;AAClG,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,YAAY,EACV,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,yBAAyB,EACzB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,YAAY,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEhF,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,mBAAmB,EAClC,OAAO,CAAC,EAAE,yBAAyB,uBAmGpC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { ServiceLanguageSelectionStep } from "./steps/ServiceLanguageSelectionStep";
|
|
3
3
|
import { SubitemDiscoveryStep } from "./steps/SubitemDiscoveryStep";
|
|
4
|
+
import { PromptCustomizationStep } from "./steps/PromptCustomizationStep";
|
|
4
5
|
import { localizeItemCommand } from "./LocalizeItemCommand";
|
|
5
6
|
import { TranslationSidebar } from "./sidebar/TranslationSidebar";
|
|
6
7
|
import { TranslationManagement } from "./translation-center/TranslationManagement";
|
|
@@ -25,6 +26,17 @@ const DEFAULT_TRANSLATION_WIZARD_CONFIGURATION = {
|
|
|
25
26
|
component: SubitemDiscoveryStep,
|
|
26
27
|
skipCondition: (data) => !data.includeSubitems,
|
|
27
28
|
},
|
|
29
|
+
{
|
|
30
|
+
id: "prompt-customization",
|
|
31
|
+
name: "Customize Prompt",
|
|
32
|
+
description: "Optionally customize the translation prompt.",
|
|
33
|
+
component: PromptCustomizationStep,
|
|
34
|
+
skipCondition: (data) => {
|
|
35
|
+
// Skip if custom prompt is not enabled (checkbox unchecked)
|
|
36
|
+
const serviceData = data.serviceCustomData?.get(data.translationProvider);
|
|
37
|
+
return !serviceData || !serviceData.enableCustomPrompt;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
28
40
|
],
|
|
29
41
|
};
|
|
30
42
|
const SINGLE_ITEM_TRANSLATION_WIZARD_CONFIGURATION = {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { TranslationStepProps } from "./types";
|
|
2
|
+
export declare function PromptCustomizationStep({ stepIndex, isActive, data, setData, onStepCompleted, editContext, }: TranslationStepProps): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
//# sourceMappingURL=PromptCustomizationStep.d.ts.map
|
|
@@ -0,0 +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,2CA6MtB"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useMemo } from "react";
|
|
3
|
+
export function PromptCustomizationStep({ stepIndex, isActive = true, data, setData, onStepCompleted, editContext, }) {
|
|
4
|
+
const [customPrompt, setCustomPrompt] = useState("");
|
|
5
|
+
const [customizationType, setCustomizationType] = useState("extend");
|
|
6
|
+
// Get service-specific custom data for the selected provider
|
|
7
|
+
const serviceData = useMemo(() => {
|
|
8
|
+
if (!data.serviceCustomData || !data.translationProvider)
|
|
9
|
+
return null;
|
|
10
|
+
return data.serviceCustomData.get(data.translationProvider);
|
|
11
|
+
}, [data.serviceCustomData, data.translationProvider]);
|
|
12
|
+
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
|
+
// Get default prompt from provider settings (no fallback)
|
|
25
|
+
const defaultPrompt = useMemo(() => {
|
|
26
|
+
const provider = data.translationProviders.find(p => p.name === data.translationProvider);
|
|
27
|
+
const prompt = provider?.defaultPrompt;
|
|
28
|
+
// Return null if prompt is null, undefined, or empty string
|
|
29
|
+
return prompt && prompt.trim() ? prompt : null;
|
|
30
|
+
}, [data.translationProviders, data.translationProvider]);
|
|
31
|
+
const hasDefaultPrompt = defaultPrompt != null && defaultPrompt.trim().length > 0;
|
|
32
|
+
// Preview of final prompt
|
|
33
|
+
const previewPrompt = useMemo(() => {
|
|
34
|
+
if (!enableCustomPrompt || !customPrompt.trim()) {
|
|
35
|
+
return defaultPrompt || "";
|
|
36
|
+
}
|
|
37
|
+
// If no default prompt, just show custom prompt
|
|
38
|
+
if (!hasDefaultPrompt) {
|
|
39
|
+
return customPrompt;
|
|
40
|
+
}
|
|
41
|
+
// If default prompt exists, show based on customization type
|
|
42
|
+
if (customizationType === "replace") {
|
|
43
|
+
return customPrompt;
|
|
44
|
+
}
|
|
45
|
+
return `${defaultPrompt}\n\n${customPrompt}`;
|
|
46
|
+
}, [enableCustomPrompt, customPrompt, customizationType, defaultPrompt, hasDefaultPrompt]);
|
|
47
|
+
// Update wizard data when settings change
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!isActive)
|
|
50
|
+
return;
|
|
51
|
+
const newServiceCustomData = new Map(data.serviceCustomData || new Map());
|
|
52
|
+
// Compute final custom prompt based on extend/replace of Default Prompt
|
|
53
|
+
let finalCustomPrompt = "";
|
|
54
|
+
if (enableCustomPrompt && customPrompt.trim()) {
|
|
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
|
+
}
|
|
71
|
+
if (enableCustomPrompt) {
|
|
72
|
+
newServiceCustomData.set(data.translationProvider, {
|
|
73
|
+
enableCustomPrompt: true,
|
|
74
|
+
customPrompt: finalCustomPrompt,
|
|
75
|
+
// Note: promptCustomizationType is no longer sent to backend
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Remove custom data if disabled
|
|
80
|
+
newServiceCustomData.delete(data.translationProvider);
|
|
81
|
+
}
|
|
82
|
+
const newData = {
|
|
83
|
+
...data,
|
|
84
|
+
serviceCustomData: newServiceCustomData,
|
|
85
|
+
};
|
|
86
|
+
setData(newData);
|
|
87
|
+
}, [enableCustomPrompt, customPrompt, customizationType, defaultPrompt, hasDefaultPrompt, data.translationProvider, isActive, setData]);
|
|
88
|
+
// Update completion status
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (!isActive)
|
|
91
|
+
return;
|
|
92
|
+
// Step is always complete (it's optional)
|
|
93
|
+
onStepCompleted(true);
|
|
94
|
+
}, [isActive, onStepCompleted]);
|
|
95
|
+
// Skip condition: hide step when checkbox is disabled
|
|
96
|
+
// This is handled by skipCondition in wizard config
|
|
97
|
+
return (_jsxs("div", { className: "p-6 space-y-6 h-full flex flex-col", "data-testid": "prompt-customization-step", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-xl font-semibold mb-2", children: "Customize Translation Prompt" }), _jsxs("p", { className: "text-sm text-gray-600 mb-6", children: ["Optionally customize the prompt used for translation. This allows you to provide specific instructions or context for the translation service.", _jsx("br", {}), _jsx("span", { className: "text-xs text-gray-500 mt-1 block", children: "Note: Your custom prompt will be appended to the system instructions that ensure proper translation structure." })] })] }), _jsxs("div", { className: "space-y-6 flex-1", children: [!enableCustomPrompt && (_jsx("div", { className: "border border-gray-200 rounded-md p-4 bg-gray-50", children: _jsx("p", { className: "text-sm text-gray-600", children: "Enable \"Customize translation prompt\" in the previous step to customize the prompt." }) })), enableCustomPrompt && (_jsxs(_Fragment, { children: [hasDefaultPrompt && (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 mb-2", children: "Default Prompt" }), _jsx("div", { className: "border border-gray-200 rounded-md p-3 bg-gray-50", children: _jsx("pre", { className: "text-xs text-gray-700 whitespace-pre-wrap font-mono", children: defaultPrompt }) })] }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 mb-2", children: "Customization Type" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "radio", name: "customizationType", value: "extend", checked: customizationType === "extend", onChange: () => setCustomizationType("extend"), className: "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300", "data-testid": "customization-type-extend" }), _jsx("span", { className: "ml-2 text-sm text-gray-900", children: "Extend (append to default)" })] }), _jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "radio", name: "customizationType", value: "replace", checked: customizationType === "replace", onChange: () => setCustomizationType("replace"), className: "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300", "data-testid": "customization-type-replace" }), _jsx("span", { className: "ml-2 text-sm text-gray-900", children: "Replace (use custom prompt only)" })] })] })] })] })), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 mb-2", children: "Custom Prompt" }), _jsx("textarea", { value: customPrompt, onChange: (e) => setCustomPrompt(e.target.value), className: "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm font-mono", rows: 6, placeholder: "Enter your custom prompt instructions here...", "data-testid": "custom-prompt-textarea" }), _jsx("p", { className: "text-xs text-gray-500 mt-1", children: hasDefaultPrompt
|
|
98
|
+
? customizationType === "extend"
|
|
99
|
+
? "This will be appended to the default prompt. The final prompt (default + custom) will then be appended to the system instructions on the backend."
|
|
100
|
+
: "This will replace the default prompt. The custom prompt will then be appended to the system instructions on the backend."
|
|
101
|
+
: "Your custom prompt will be appended to the system instructions on the backend." })] }), hasDefaultPrompt && _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 mb-2", children: "Preview" }), _jsx("div", { className: "border border-gray-200 rounded-md p-3 bg-gray-50 max-h-64 overflow-y-auto", children: _jsx("pre", { className: "text-xs text-gray-700 whitespace-pre-wrap font-mono", children: previewPrompt }) }), _jsx("p", { className: "text-xs text-gray-500 mt-1", children: "This is how the final prompt will look when sent to the translation service." })] })] }))] })] }));
|
|
102
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServiceLanguageSelectionStep.d.ts","sourceRoot":"","sources":["../../src/steps/ServiceLanguageSelectionStep.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAyB,MAAM,SAAS,CAAC;AAGtE,wBAAgB,4BAA4B,CAAC,EAAE,SAAS,EAAE,QAAe,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"ServiceLanguageSelectionStep.d.ts","sourceRoot":"","sources":["../../src/steps/ServiceLanguageSelectionStep.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAyB,MAAM,SAAS,CAAC;AAGtE,wBAAgB,4BAA4B,CAAC,EAAE,SAAS,EAAE,QAAe,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,EAAE,oBAAoB,2CA4Y7K"}
|
|
@@ -121,9 +121,20 @@ export function ServiceLanguageSelectionStep({ stepIndex, isActive = true, data,
|
|
|
121
121
|
}, [languageSelection, setData, data.targetLanguages]);
|
|
122
122
|
const handleProviderChange = (e) => {
|
|
123
123
|
const newProvider = e.target.value;
|
|
124
|
+
// Always create a new Map instance to ensure React detects the change
|
|
125
|
+
const newServiceCustomData = new Map();
|
|
126
|
+
if (data.serviceCustomData) {
|
|
127
|
+
data.serviceCustomData.forEach((value, key) => {
|
|
128
|
+
// Only preserve custom data if not switching away from OpenAI
|
|
129
|
+
if (key !== "OpenAI" || newProvider === "OpenAI") {
|
|
130
|
+
newServiceCustomData.set(key, value);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
124
134
|
const newData = {
|
|
125
135
|
...data,
|
|
126
136
|
translationProvider: newProvider,
|
|
137
|
+
serviceCustomData: newServiceCustomData,
|
|
127
138
|
// Clear target languages when provider changes since language availability might change
|
|
128
139
|
targetLanguages: []
|
|
129
140
|
};
|
|
@@ -167,7 +178,40 @@ export function ServiceLanguageSelectionStep({ stepIndex, isActive = true, data,
|
|
|
167
178
|
const areSomeLanguagesSelected = useMemo(() => {
|
|
168
179
|
return allLanguages.some(lang => languageSelection[lang.code]);
|
|
169
180
|
}, [allLanguages, languageSelection]);
|
|
170
|
-
|
|
181
|
+
// Compute checkbox state for prompt customization
|
|
182
|
+
const isPromptCustomizationEnabled = useMemo(() => {
|
|
183
|
+
if (data.translationProvider !== "OpenAI") {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
const serviceData = data.serviceCustomData?.get("OpenAI");
|
|
187
|
+
return serviceData?.enableCustomPrompt === true;
|
|
188
|
+
}, [data.translationProvider, data.serviceCustomData]);
|
|
189
|
+
return (_jsxs("div", { className: "p-6 space-y-6 h-full flex flex-col", "data-testid": "service-language-selection-step", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-xl font-semibold mb-2", children: "Configure Translation" }), _jsx("p", { className: "text-sm text-gray-600 mb-6", children: "Select translation provider and target languages for your content." })] }), _jsxs("div", { className: "space-y-6 flex-1", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 mb-2", children: "Translation Provider" }), _jsx("p", { className: "text-xs text-gray-600 mb-3", children: "Choose how to translate your content. \"Create Versions\" will create new language versions without automatic translation." }), _jsxs("select", { value: data.translationProvider || "", onChange: handleProviderChange, className: "block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm", "data-testid": "translation-provider-select", children: [_jsx("option", { value: "", disabled: true, children: "Select a provider..." }), data.translationProviders.map((provider) => (_jsx("option", { value: provider.name, children: provider.displayName || provider.name }, provider.name)))] })] }), multiItemEnabled && (_jsxs("div", { children: [_jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "checkbox", checked: data.includeSubitems, onChange: handleSubitemsToggle, className: "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded", "data-testid": "include-subitems-checkbox" }), _jsx("span", { className: "ml-2 text-sm text-gray-900", children: "Include subitems" })] }), _jsx("p", { className: "text-xs text-gray-500 mt-1 ml-6", children: "Also translate any child components and nested content within this item." })] })), data.translationProvider === "OpenAI" && (_jsxs("div", { children: [_jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "checkbox", checked: isPromptCustomizationEnabled, onChange: (e) => {
|
|
190
|
+
const isChecked = e.target.checked;
|
|
191
|
+
// Always create a new Map instance to ensure React detects the change
|
|
192
|
+
const newServiceCustomData = new Map();
|
|
193
|
+
if (data.serviceCustomData) {
|
|
194
|
+
data.serviceCustomData.forEach((value, key) => {
|
|
195
|
+
newServiceCustomData.set(key, value);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (isChecked) {
|
|
199
|
+
newServiceCustomData.set("OpenAI", {
|
|
200
|
+
enableCustomPrompt: true,
|
|
201
|
+
customPrompt: "",
|
|
202
|
+
promptCustomizationType: "extend",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
// Remove the service data when unchecked
|
|
207
|
+
newServiceCustomData.delete("OpenAI");
|
|
208
|
+
}
|
|
209
|
+
const newData = {
|
|
210
|
+
...data,
|
|
211
|
+
serviceCustomData: newServiceCustomData
|
|
212
|
+
};
|
|
213
|
+
setData(newData);
|
|
214
|
+
}, className: "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded", "data-testid": "enable-custom-prompt-checkbox" }), _jsx("span", { className: "ml-2 text-sm text-gray-900", children: "Customize translation prompt" })] }), _jsx("p", { className: "text-xs text-gray-500 mt-1 ml-6", children: "Enable advanced prompt customization for the translation service." })] })), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("h3", { className: "text-sm font-medium text-gray-900", children: "Target Languages" }), allLanguages.length > 1 && (_jsxs("label", { className: "flex items-center cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: areAllLanguagesSelected, ref: (input) => {
|
|
171
215
|
if (input) {
|
|
172
216
|
input.indeterminate = areSomeLanguagesSelected && !areAllLanguagesSelected;
|
|
173
217
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubitemDiscoveryStep.d.ts","sourceRoot":"","sources":["../../src/steps/SubitemDiscoveryStep.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAW/C,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,QAAe,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,YAAY,EAAE,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"SubitemDiscoveryStep.d.ts","sourceRoot":"","sources":["../../src/steps/SubitemDiscoveryStep.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAW/C,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,QAAe,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,YAAY,EAAE,EAAE,oBAAoB,2CAuf5L"}
|
|
@@ -14,6 +14,8 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
14
14
|
const [allDiscoveredItems, setAllDiscoveredItems] = useState([]);
|
|
15
15
|
const [userHasInteracted, setUserHasInteracted] = useState(false);
|
|
16
16
|
const [discoveredCount, setDiscoveredCount] = useState(0);
|
|
17
|
+
const [totalItemsCount, setTotalItemsCount] = useState(0);
|
|
18
|
+
const [translatableItemsCount, setTranslatableItemsCount] = useState(0);
|
|
17
19
|
const [treeInitialized, setTreeInitialized] = useState(false);
|
|
18
20
|
const [expandedIds, setExpandedIds] = useState(new Set());
|
|
19
21
|
// Refs for async safety
|
|
@@ -45,6 +47,26 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
45
47
|
const nodes = buildTreeNodes(stubs);
|
|
46
48
|
setTreeNodes(nodes);
|
|
47
49
|
setExpandedIds(new Set(nodes.map(n => n.key)));
|
|
50
|
+
// Count total items and translatable items in the tree
|
|
51
|
+
const countItems = (nodeList) => {
|
|
52
|
+
let total = 0;
|
|
53
|
+
let translatable = 0;
|
|
54
|
+
const walk = (nodeList) => {
|
|
55
|
+
nodeList.forEach(node => {
|
|
56
|
+
total++;
|
|
57
|
+
if (node?.data?.isTranslatable === true) {
|
|
58
|
+
translatable++;
|
|
59
|
+
}
|
|
60
|
+
if (node.children)
|
|
61
|
+
walk(node.children);
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
walk(nodeList);
|
|
65
|
+
return { total, translatable };
|
|
66
|
+
};
|
|
67
|
+
const counts = countItems(nodes);
|
|
68
|
+
setTotalItemsCount(counts.total);
|
|
69
|
+
setTranslatableItemsCount(counts.translatable);
|
|
48
70
|
setDiscoveryComplete(true);
|
|
49
71
|
setTreeInitialized(true);
|
|
50
72
|
return;
|
|
@@ -87,6 +109,26 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
87
109
|
const nodes = convertBackendTreeToTreeNodes(resp.trees);
|
|
88
110
|
setTreeNodes(nodes);
|
|
89
111
|
setExpandedIds(new Set(nodes.map(n => n.key)));
|
|
112
|
+
// Count total items and translatable items in the tree
|
|
113
|
+
const countItems = (nodeList) => {
|
|
114
|
+
let total = 0;
|
|
115
|
+
let translatable = 0;
|
|
116
|
+
const walk = (nodeList) => {
|
|
117
|
+
nodeList.forEach(node => {
|
|
118
|
+
total++;
|
|
119
|
+
if (node?.data?.isTranslatable === true) {
|
|
120
|
+
translatable++;
|
|
121
|
+
}
|
|
122
|
+
if (node.children)
|
|
123
|
+
walk(node.children);
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
walk(nodeList);
|
|
127
|
+
return { total, translatable };
|
|
128
|
+
};
|
|
129
|
+
const counts = countItems(nodes);
|
|
130
|
+
setTotalItemsCount(counts.total);
|
|
131
|
+
setTranslatableItemsCount(counts.translatable);
|
|
90
132
|
setDiscoveryComplete(true);
|
|
91
133
|
setTreeInitialized(true);
|
|
92
134
|
}
|
|
@@ -111,19 +153,19 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
111
153
|
initialSelection = new Set(data.discoveredItems.map(item => item.descriptor.id));
|
|
112
154
|
}
|
|
113
155
|
else {
|
|
114
|
-
// Default: select all
|
|
115
|
-
const
|
|
156
|
+
// Default: select all translatable items in the tree
|
|
157
|
+
const allTranslatableIds = new Set();
|
|
116
158
|
const walk = (nodes) => {
|
|
117
159
|
nodes.forEach(node => {
|
|
118
|
-
if (node?.data?.
|
|
119
|
-
|
|
160
|
+
if (node?.data?.isTranslatable) {
|
|
161
|
+
allTranslatableIds.add(node.key);
|
|
120
162
|
}
|
|
121
163
|
if (node.children)
|
|
122
164
|
walk(node.children);
|
|
123
165
|
});
|
|
124
166
|
};
|
|
125
167
|
walk(treeNodes);
|
|
126
|
-
initialSelection =
|
|
168
|
+
initialSelection = allTranslatableIds;
|
|
127
169
|
}
|
|
128
170
|
setSelectedItemIds(initialSelection);
|
|
129
171
|
}, [isActive, discoveryComplete, treeNodes, data.discoveredItems, userHasInteracted]);
|
|
@@ -187,7 +229,7 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
187
229
|
traverse(node.children);
|
|
188
230
|
return descendants;
|
|
189
231
|
};
|
|
190
|
-
const
|
|
232
|
+
const getDescendantTranslatableIds = (nodeKey) => {
|
|
191
233
|
const findNode = (nodes) => {
|
|
192
234
|
for (const node of nodes) {
|
|
193
235
|
if (node.key === nodeKey)
|
|
@@ -206,8 +248,8 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
206
248
|
const out = [];
|
|
207
249
|
const walk = (children) => {
|
|
208
250
|
children.forEach(child => {
|
|
209
|
-
const
|
|
210
|
-
if (
|
|
251
|
+
const isTranslatable = child?.data?.isTranslatable === true;
|
|
252
|
+
if (isTranslatable)
|
|
211
253
|
out.push(child.key);
|
|
212
254
|
if (child.children)
|
|
213
255
|
walk(child.children);
|
|
@@ -224,13 +266,24 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
224
266
|
};
|
|
225
267
|
return (_jsxs("div", { className: "flex flex-col gap-4 p-6 h-full", "data-testid": "subitem-discovery-step", children: [_jsxs("div", { className: "mb-2", children: [_jsx("h3", { className: "text-lg font-medium mb-1", children: "Discover Subitems" }), _jsx("p", { className: "text-sm text-gray-600", children: "Scanning for subitems to include in translation..." })] }), _jsxs("div", { className: "border rounded p-4 bg-gray-50 min-h-[120px]", "data-testid": "discovery-status-container", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [isDiscovering && _jsx(Spinner, { "data-testid": "discovery-spinner" }), _jsx("span", { className: "font-medium", "data-testid": "discovery-status-text", children: isDiscovering ? 'Discovering subitems...' :
|
|
226
268
|
discoveryComplete ? 'Discovery Complete' :
|
|
227
|
-
'Ready to discover' })] }), isDiscovering && (_jsx(Button, { size: "sm", onClick: handleCancel, "data-testid": "discovery-cancel-button", children: "Cancel" }))] }), _jsx("div", { className: "text-sm text-gray-600 mb-3", "data-testid": "discovery-summary", children: discoveredCount > 0 ? (_jsxs("p", { "data-testid": "discovery-total-count", children: [_jsxs("span", { className: "font-medium text-blue-600", children: [
|
|
269
|
+
'Ready to discover' })] }), isDiscovering && (_jsx(Button, { size: "sm", onClick: handleCancel, "data-testid": "discovery-cancel-button", children: "Cancel" }))] }), _jsx("div", { className: "text-sm text-gray-600 mb-3", "data-testid": "discovery-summary", children: discoveredCount > 0 ? (_jsxs("p", { "data-testid": "discovery-total-count", children: [_jsxs("span", { className: "font-medium text-blue-600", children: [totalItemsCount, " total items found"] }), totalItemsCount !== translatableItemsCount && (_jsxs(_Fragment, { children: [_jsx("br", {}), _jsxs("span", { className: "text-xs", children: ["Translatable items: ", translatableItemsCount, " | Non-translatable: ", totalItemsCount - translatableItemsCount] })] })), _jsx("br", {}), _jsxs("span", { className: "text-xs", children: ["Original items: ", data.items.length, " | Subitems discovered: ", Math.max(0, totalItemsCount - data.items.length)] }), isDiscovering && (_jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "text-xs text-gray-500 italic", children: "Item tree will appear when discovery completes" })] }))] })) : (_jsx("p", { children: "No items discovered yet." })) }), _jsx("div", { className: "mt-4 min-h-[400px]", children: !isDiscovering && discoveryComplete && treeInitialized && allDiscoveredItems.length > 0 && treeNodes.length > 0 ? (_jsx("div", { children: _jsxs("div", { className: "border-t pt-4", "data-testid": "item-selection-section", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h3", { className: "text-lg font-medium", children: "Select Items to Translate" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("span", { className: "text-sm text-gray-600", "data-testid": "selection-summary-header", children: selectedItemIds.size > 0 ? `${selectedItemIds.size} selected` : "No items selected" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
228
270
|
if (!isActive)
|
|
229
271
|
return;
|
|
230
|
-
|
|
272
|
+
// Only select translatable items
|
|
273
|
+
const translatableIds = new Set();
|
|
274
|
+
const walk = (nodes) => {
|
|
275
|
+
nodes.forEach(node => {
|
|
276
|
+
if (node?.data?.isTranslatable === true) {
|
|
277
|
+
translatableIds.add(node.key);
|
|
278
|
+
}
|
|
279
|
+
if (node.children)
|
|
280
|
+
walk(node.children);
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
walk(treeNodes);
|
|
231
284
|
setUserHasInteracted(true);
|
|
232
|
-
setSelectedItemIds(
|
|
233
|
-
onStepCompleted(
|
|
285
|
+
setSelectedItemIds(translatableIds);
|
|
286
|
+
onStepCompleted(translatableIds.size > 0);
|
|
234
287
|
}, "data-testid": "select-all-items-button", children: "Select All" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
235
288
|
if (!isActive)
|
|
236
289
|
return;
|
|
@@ -253,7 +306,7 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
253
306
|
const targetNode = key;
|
|
254
307
|
const shift = event?.shiftKey === true;
|
|
255
308
|
const next = new Set(selectedItemIds);
|
|
256
|
-
// Only select
|
|
309
|
+
// Only select translatable items
|
|
257
310
|
const findNode = (nodes) => {
|
|
258
311
|
for (const n of nodes) {
|
|
259
312
|
if (n.key === targetNode)
|
|
@@ -267,35 +320,34 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
267
320
|
return null;
|
|
268
321
|
};
|
|
269
322
|
const n = findNode(treeNodes);
|
|
270
|
-
const
|
|
271
|
-
if (
|
|
323
|
+
const isTranslatable = n?.data?.isTranslatable === true;
|
|
324
|
+
if (isTranslatable) {
|
|
272
325
|
const ids = shift ? [key, ...getDescendantIds(key)] : [key];
|
|
273
326
|
if (!next.has(key))
|
|
274
|
-
ids.forEach(id => next.add(id));
|
|
327
|
+
ids.forEach((id) => next.add(id));
|
|
275
328
|
else
|
|
276
|
-
ids.forEach(id => next.delete(id));
|
|
329
|
+
ids.forEach((id) => next.delete(id));
|
|
277
330
|
}
|
|
278
331
|
else {
|
|
279
|
-
// Folder row click toggles all descendant
|
|
280
|
-
const
|
|
281
|
-
const allSelected =
|
|
332
|
+
// Folder row click toggles all descendant translatable items
|
|
333
|
+
const translatableIds = getDescendantTranslatableIds(key);
|
|
334
|
+
const allSelected = translatableIds.length > 0 && translatableIds.every((id) => next.has(id));
|
|
282
335
|
if (allSelected)
|
|
283
|
-
|
|
336
|
+
translatableIds.forEach((id) => next.delete(id));
|
|
284
337
|
else
|
|
285
|
-
|
|
338
|
+
translatableIds.forEach((id) => next.add(id));
|
|
286
339
|
}
|
|
287
340
|
setSelectedItemIds(next);
|
|
288
341
|
// Update completion when user changes selection
|
|
289
342
|
onStepCompleted(next.size > 0);
|
|
290
343
|
}, renderNode: (node) => {
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
// Simple checkbox logic: if it's a page, check direct selection; otherwise check descendant pages
|
|
344
|
+
const isTranslatable = node?.data?.isTranslatable === true;
|
|
345
|
+
const translatableIds = !isTranslatable ? getDescendantTranslatableIds(node.key) : [];
|
|
346
|
+
const allSelected = !isTranslatable ? (translatableIds.length > 0 && translatableIds.every((id) => selectedItemIds.has(id))) : false;
|
|
347
|
+
const someSelected = !isTranslatable ? (translatableIds.some((id) => selectedItemIds.has(id)) && !allSelected) : false;
|
|
348
|
+
// Simple checkbox logic: if it's translatable, check direct selection; otherwise check descendant translatable items
|
|
297
349
|
// Base items are treated like any other selected item - if selected, show checked
|
|
298
|
-
const isChecked =
|
|
350
|
+
const isChecked = isTranslatable ? selectedItemIds.has(node.key) : allSelected;
|
|
299
351
|
const icon = node?.data?.icon;
|
|
300
352
|
return (_jsxs("div", { className: `flex items-center gap-2 ${!isTranslatable ? 'opacity-70' : ''}`, children: [icon && (_jsx("img", { src: icon, alt: "", className: `w-4 h-4 ${!isTranslatable ? 'opacity-50' : ''}` })), _jsx("input", { type: "checkbox", checked: isChecked, disabled: !isTranslatable, "data-testid": `tree-item-checkbox-${node.key}`, ref: (el) => { if (el)
|
|
301
353
|
el.indeterminate = someSelected; }, onMouseDown: (e) => {
|
|
@@ -307,27 +359,27 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
|
|
|
307
359
|
return; // Don't handle changes when inactive or untranslatable
|
|
308
360
|
setUserHasInteracted(true);
|
|
309
361
|
const next = new Set(selectedItemIds);
|
|
310
|
-
if (
|
|
362
|
+
if (isTranslatable) {
|
|
311
363
|
const withDesc = shiftToggleRef.current && (node.children?.length ?? 0) > 0;
|
|
312
364
|
const ids = withDesc ? [node.key, ...getDescendantIds(node.key)] : [node.key];
|
|
313
365
|
if (e.currentTarget.checked)
|
|
314
|
-
ids.forEach(id => next.add(id));
|
|
366
|
+
ids.forEach((id) => next.add(id));
|
|
315
367
|
else
|
|
316
|
-
ids.forEach(id => next.delete(id));
|
|
368
|
+
ids.forEach((id) => next.delete(id));
|
|
317
369
|
}
|
|
318
370
|
else {
|
|
319
|
-
const ids =
|
|
320
|
-
const allSel = ids.length > 0 && ids.every(id => next.has(id));
|
|
371
|
+
const ids = getDescendantTranslatableIds(node.key);
|
|
372
|
+
const allSel = ids.length > 0 && ids.every((id) => next.has(id));
|
|
321
373
|
if (e.currentTarget.checked && !allSel)
|
|
322
|
-
ids.forEach(id => next.add(id));
|
|
374
|
+
ids.forEach((id) => next.add(id));
|
|
323
375
|
else if (!e.currentTarget.checked && allSel)
|
|
324
|
-
ids.forEach(id => next.delete(id));
|
|
376
|
+
ids.forEach((id) => next.delete(id));
|
|
325
377
|
else {
|
|
326
378
|
// Toggle based on current
|
|
327
379
|
if (allSel)
|
|
328
|
-
ids.forEach(id => next.delete(id));
|
|
380
|
+
ids.forEach((id) => next.delete(id));
|
|
329
381
|
else
|
|
330
|
-
ids.forEach(id => next.add(id));
|
|
382
|
+
ids.forEach((id) => next.add(id));
|
|
331
383
|
}
|
|
332
384
|
}
|
|
333
385
|
setSelectedItemIds(next);
|
package/dist/steps/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type TranslationProviderInfo = {
|
|
|
3
3
|
name: string;
|
|
4
4
|
displayName: string;
|
|
5
5
|
supportedLanguages: string[];
|
|
6
|
+
defaultPrompt?: string;
|
|
6
7
|
};
|
|
7
8
|
export type TranslationStatus = {
|
|
8
9
|
targetLanguage: string;
|
|
@@ -43,6 +44,7 @@ export type TranslationWizardData = {
|
|
|
43
44
|
translationProviders: TranslationProviderInfo[];
|
|
44
45
|
itemMetadata: Map<string, Map<string, string>>;
|
|
45
46
|
metadata?: any;
|
|
47
|
+
serviceCustomData?: Map<string, any>;
|
|
46
48
|
[key: string]: any;
|
|
47
49
|
};
|
|
48
50
|
export type TranslationStepProps = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/steps/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAE3E,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/steps/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAE3E,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,QAAQ,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,yBAAyB,CAAC,EAAE,MAAM,EAAE,CAAC;IACrC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,GAAG,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,QAAQ,EAAE,CAAC;IAC5B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACxC,oBAAoB,EAAE,uBAAuB,EAAE,CAAC;IAChD,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,OAAO,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAC/C,WAAW,EAAE,eAAe,CAAC;IAC7B,eAAe,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5E,gBAAgB,CAAC,EAAE,CACjB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,KACnG,IAAI,CAAC;IACV,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI,KAAK,IAAI,CAAC;CACjE,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,eAAe,CAAC;IAC7B,aAAa,CAAC,EAAE,8BAA8B,CAAC;CAChD,CAAC"}
|
|
@@ -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":"AAwBA,OAAO,qCAAqC,CAAC;AAa7C,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,2CA07BlF"}
|
|
@@ -2,8 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import React, { useEffect, useState, useMemo, useRef, useCallback, } 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
|
+
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
6
|
+
import { JsonView, defaultStyles } from "react-json-view-lite";
|
|
7
|
+
import "react-json-view-lite/dist/index.css";
|
|
5
8
|
// Wrapper so React 19 sees a plain function component instead of a forwardRef exotic component.
|
|
6
9
|
const Progress = (props) => React.createElement(CoreProgress, props);
|
|
10
|
+
// Wrappers for lucide-react icons to work with React 19
|
|
11
|
+
const ChevronUpIcon = (props) => React.createElement(ChevronUp, props);
|
|
12
|
+
const ChevronDownIcon = (props) => React.createElement(ChevronDown, props);
|
|
7
13
|
// Normalize API shape/casing to what the UI expects
|
|
8
14
|
function normalizeBatchInfo(raw) {
|
|
9
15
|
if (!raw)
|
|
@@ -39,6 +45,7 @@ export function BatchTranslationView({ batchId, onBack }) {
|
|
|
39
45
|
const [isLoading, setIsLoading] = useState(false);
|
|
40
46
|
const [translationProgress, setTranslationProgress] = useState(new Map());
|
|
41
47
|
const [itemNames, setItemNames] = useState(new Map());
|
|
48
|
+
const [isMetadataExpanded, setIsMetadataExpanded] = useState(false);
|
|
42
49
|
const lastWsAtRef = useRef(0);
|
|
43
50
|
const batchJobsRef = useRef([]);
|
|
44
51
|
const hasLoadedRef = useRef(false);
|
|
@@ -52,6 +59,17 @@ export function BatchTranslationView({ batchId, onBack }) {
|
|
|
52
59
|
const provider = providers.find(p => p.name === serviceName);
|
|
53
60
|
return provider?.displayName || serviceName;
|
|
54
61
|
}, [providers]);
|
|
62
|
+
// Parse metadata for display
|
|
63
|
+
const parsedMetadata = useMemo(() => {
|
|
64
|
+
if (!batchInfo?.metadata)
|
|
65
|
+
return null;
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(batchInfo.metadata);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}, [batchInfo?.metadata]);
|
|
55
73
|
// Keep refs in sync with state
|
|
56
74
|
useEffect(() => {
|
|
57
75
|
batchJobsRef.current = batchJobs;
|
|
@@ -482,7 +500,7 @@ export function BatchTranslationView({ batchId, onBack }) {
|
|
|
482
500
|
? 'bg-blue-100 text-blue-700'
|
|
483
501
|
: effectiveStatus === 'Error'
|
|
484
502
|
? 'bg-red-100 text-red-700'
|
|
485
|
-
: 'bg-gray-100 text-gray-700'}`, children: effectiveStatus })), batchInfo.provider && (_jsxs("span", { className: "text-gray-700", children: ["Provider: ", _jsx("span", { className: "font-medium", children: getProviderDisplayName(batchInfo.provider) })] })), batchInfo.initiatedByUser && (_jsxs("span", { className: "text-gray-700", children: ["By: ", _jsx("span", { className: "font-medium", children: batchInfo.initiatedByUser })] })), batchInfo.includeSubitems !== undefined && batchInfo.includeSubitems !== null && (_jsxs("span", { className: "text-gray-700", children: ["Include subitems: ", _jsx("span", { className: "font-medium", children: batchInfo.includeSubitems ? 'Yes' : 'No' })] })), batchInfo.scopeItemPath && (_jsxs("span", { className: "text-gray-700 truncate max-w-[40rem]", children: ["Scope: ", _jsx("span", { className: "font-mono", children: batchInfo.scopeItemPath })] })), (batchInfo.startedAtUtc || batchInfo.completedAtUtc || batchInfo.lastUpdatedUtc) && (_jsxs("span", { className: "text-gray-500", 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-gray-600", children: [`${progressSegments.completedCount}/${progressSegments.total} completed`, progressSegments.errorCount ? `, ${progressSegments.errorCount} errors` : ''] }), _jsxs(Button, { size: "sm", onClick: loadBatchJobs, disabled: isLoading, title: "Manual refresh - normally updates come through websocket subscriptions", children: [_jsx("i", { className: `pi pi-refresh ${isLoading ? 'pi-spin' : ''}` }), "Refresh"] })] })] }), _jsxs("div", { className: "mt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-medium", children: "Overall Progress" }), _jsxs("span", { className: "text-sm text-gray-600", children: [progressSegments.completedCount, " / ", progressSegments.total, " completed"] })] }), _jsxs("div", { className: "relative h-4 bg-gray-200 rounded-full overflow-hidden", children: [_jsxs("div", { className: "absolute inset-0 flex", children: [progressSegments.completed > 0 && (_jsx("div", { className: "bg-green-500 h-full transition-all duration-300", style: { width: `${progressSegments.completed}%` } })), progressSegments.inProgress > 0 && (_jsx("div", { className: "bg-blue-500 h-full transition-all duration-300", style: { width: `${progressSegments.inProgress}%` } })), progressSegments.error > 0 && (_jsx("div", { className: "bg-red-500 h-full transition-all duration-300", style: { width: `${progressSegments.error}%` } })), progressSegments.pending > 0 && (_jsx("div", { className: "bg-gray-300 h-full transition-all duration-300", style: { width: `${progressSegments.pending}%` } }))] }), _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 gap-3 mt-2 text-xs", children: [progressSegments.completedCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-green-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.completedCount, " completed"] })] })), progressSegments.inProgressCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-blue-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.inProgressCount, " in progress"] })] })), progressSegments.errorCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-red-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.errorCount, " errors"] })] })), progressSegments.pendingCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-gray-300 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.pendingCount, " pending"] })] })), typeof batchInfo?.expectedJobs === 'number' && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-gray-300 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [(batchInfo.expectedJobs || 0) - (batchInfo.completedJobs || 0) - (batchInfo.errorJobs || 0), " pending (server)"] })] }))] })] })] }), _jsx("div", { className: "flex-1 overflow-auto p-4 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-gray-500", children: [_jsx("i", { className: "pi pi-spin pi-spinner" }), "Loading batch translations..."] }) })) : batchJobs.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-gray-500", children: [_jsx("i", { className: "pi pi-language text-2xl block mb-2" }), _jsx("p", { children: "No translations found for this batch" }), _jsx("p", { className: "text-sm", children: "The batch may not exist or no translation jobs have started yet" })] }) })) : (_jsx("div", { className: "space-y-6", children: sortedItemGroups.map(([itemId, jobs]) => {
|
|
503
|
+
: 'bg-gray-100 text-gray-700'}`, children: effectiveStatus })), batchInfo.provider && (_jsxs("span", { className: "text-gray-700", children: ["Provider: ", _jsx("span", { className: "font-medium", children: getProviderDisplayName(batchInfo.provider) })] })), batchInfo.initiatedByUser && (_jsxs("span", { className: "text-gray-700", children: ["By: ", _jsx("span", { className: "font-medium", children: batchInfo.initiatedByUser })] })), batchInfo.includeSubitems !== undefined && batchInfo.includeSubitems !== null && (_jsxs("span", { className: "text-gray-700", children: ["Include subitems: ", _jsx("span", { className: "font-medium", children: batchInfo.includeSubitems ? 'Yes' : 'No' })] })), batchInfo.scopeItemPath && (_jsxs("span", { className: "text-gray-700 truncate max-w-[40rem]", children: ["Scope: ", _jsx("span", { className: "font-mono", children: batchInfo.scopeItemPath })] })), (batchInfo.startedAtUtc || batchInfo.completedAtUtc || batchInfo.lastUpdatedUtc) && (_jsxs("span", { className: "text-gray-500", 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-gray-600", children: [`${progressSegments.completedCount}/${progressSegments.total} completed`, progressSegments.errorCount ? `, ${progressSegments.errorCount} errors` : ''] }), _jsxs(Button, { size: "sm", 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-gray-200 bg-gray-50 mt-4", children: [_jsxs("button", { onClick: () => setIsMetadataExpanded(!isMetadataExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2 text-left transition-colors hover:bg-gray-100", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("i", { className: "pi pi-info-circle text-gray-500" }), _jsx("span", { className: "text-xs font-medium text-gray-700", children: "Batch Metadata" })] }), isMetadataExpanded ? (_jsx(ChevronUpIcon, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 })) : (_jsx(ChevronDownIcon, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }))] }), isMetadataExpanded && (_jsx("div", { className: "max-h-96 overflow-y-auto px-4 pb-3", children: _jsx("div", { className: "rounded-md border border-gray-200 bg-white p-3 text-xs shadow-sm mt-2", children: _jsx(JsonView, { data: parsedMetadata, shouldExpandNode: (level) => level < 2, style: defaultStyles }) }) }))] })), _jsxs("div", { className: "mt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-medium", children: "Overall Progress" }), _jsxs("span", { className: "text-sm text-gray-600", children: [progressSegments.completedCount, " / ", progressSegments.total, " completed"] })] }), _jsxs("div", { className: "relative h-4 bg-gray-200 rounded-full overflow-hidden", children: [_jsxs("div", { className: "absolute inset-0 flex", children: [progressSegments.completed > 0 && (_jsx("div", { className: "bg-green-500 h-full transition-all duration-300", style: { width: `${progressSegments.completed}%` } })), progressSegments.inProgress > 0 && (_jsx("div", { className: "bg-blue-500 h-full transition-all duration-300", style: { width: `${progressSegments.inProgress}%` } })), progressSegments.error > 0 && (_jsx("div", { className: "bg-red-500 h-full transition-all duration-300", style: { width: `${progressSegments.error}%` } })), progressSegments.pending > 0 && (_jsx("div", { className: "bg-gray-300 h-full transition-all duration-300", style: { width: `${progressSegments.pending}%` } }))] }), _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 gap-3 mt-2 text-xs", children: [progressSegments.completedCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-green-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.completedCount, " completed"] })] })), progressSegments.inProgressCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-blue-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.inProgressCount, " in progress"] })] })), progressSegments.errorCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-red-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.errorCount, " errors"] })] })), progressSegments.pendingCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-gray-300 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.pendingCount, " pending"] })] })), typeof batchInfo?.expectedJobs === 'number' && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-gray-300 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [(batchInfo.expectedJobs || 0) - (batchInfo.completedJobs || 0) - (batchInfo.errorJobs || 0), " pending (server)"] })] }))] })] })] }), _jsx("div", { className: "flex-1 overflow-auto p-4 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-gray-500", children: [_jsx("i", { className: "pi pi-spin pi-spinner" }), "Loading batch translations..."] }) })) : batchJobs.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-gray-500", children: [_jsx("i", { className: "pi pi-language text-2xl block mb-2" }), _jsx("p", { children: "No translations found for this batch" }), _jsx("p", { className: "text-sm", children: "The batch may not exist or no translation jobs have started yet" })] }) })) : (_jsx("div", { className: "space-y-6", children: sortedItemGroups.map(([itemId, jobs]) => {
|
|
486
504
|
const itemCompleted = jobs.filter(j => j.status === "Completed").length;
|
|
487
505
|
// Calculate item progress including in-progress job progress
|
|
488
506
|
// Performance: item-level calculation is always fast (typically 2-10 jobs per item)
|