@parhelia/localization 0.1.12496 → 0.1.12517
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/services/translationService.d.ts +48 -7
- package/dist/services/translationService.d.ts.map +1 -1
- package/dist/services/translationService.js +3 -0
- package/dist/settings/TranslationServicesPanel.d.ts.map +1 -1
- package/dist/settings/TranslationServicesPanel.js +14 -23
- package/dist/setup/LocalizationSetupStep.d.ts.map +1 -1
- package/dist/setup/LocalizationSetupStep.js +8 -7
- package/dist/translation-center/BatchTranslationView.d.ts.map +1 -1
- package/dist/translation-center/BatchTranslationView.js +192 -40
- package/package.json +1 -1
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { TranslationStatus } from "../types";
|
|
2
2
|
import { TranslationProviderInfo } from "../steps/types";
|
|
3
|
+
export type TranslationApiResponse<T> = {
|
|
4
|
+
success: boolean;
|
|
5
|
+
data?: T;
|
|
6
|
+
error?: string;
|
|
7
|
+
message?: string;
|
|
8
|
+
};
|
|
3
9
|
export type TranslationRequestResponse = {
|
|
4
10
|
jobId: string;
|
|
5
11
|
};
|
|
@@ -23,13 +29,11 @@ export type AvailableTranslationService = {
|
|
|
23
29
|
templateId: string;
|
|
24
30
|
templateName: string;
|
|
25
31
|
};
|
|
26
|
-
export declare function getAvailableTranslationServices(): Promise<import("@parhelia/core").ExecutionResult<AvailableTranslationService[]
|
|
27
|
-
export declare function createProviderSettings(serviceName: string, templateId: string, language?: string, structureSettings?: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<{
|
|
28
|
-
success: boolean;
|
|
32
|
+
export declare function getAvailableTranslationServices(): Promise<import("@parhelia/core").ExecutionResult<TranslationApiResponse<AvailableTranslationService[]>>>;
|
|
33
|
+
export declare function createProviderSettings(serviceName: string, templateId: string, language?: string, structureSettings?: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<TranslationApiResponse<{
|
|
29
34
|
itemId: string;
|
|
30
35
|
itemPath: string;
|
|
31
|
-
|
|
32
|
-
}>>;
|
|
36
|
+
}>>>;
|
|
33
37
|
export type TranslationStructureSettings = {
|
|
34
38
|
translationFolderPath: string;
|
|
35
39
|
translationFolderTemplateId: string;
|
|
@@ -46,9 +50,10 @@ export type TranslationStructureResponse = {
|
|
|
46
50
|
translationFolder: TranslationStructureItemInfo;
|
|
47
51
|
translationProvidersFolder: TranslationStructureItemInfo;
|
|
48
52
|
};
|
|
49
|
-
export declare function getTranslationStructure(settings: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<TranslationStructureResponse
|
|
50
|
-
export declare function ensureTranslationStructure(settings: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<TranslationStructureResponse
|
|
53
|
+
export declare function getTranslationStructure(settings: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<TranslationApiResponse<TranslationStructureResponse>>>;
|
|
54
|
+
export declare function ensureTranslationStructure(settings: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<TranslationApiResponse<TranslationStructureResponse>>>;
|
|
51
55
|
export type TranslationJobRecord = {
|
|
56
|
+
id?: number;
|
|
52
57
|
itemId: string;
|
|
53
58
|
targetLanguage: string;
|
|
54
59
|
sourceLanguage: string;
|
|
@@ -57,6 +62,7 @@ export type TranslationJobRecord = {
|
|
|
57
62
|
hash?: string;
|
|
58
63
|
message?: string;
|
|
59
64
|
batchId?: string;
|
|
65
|
+
metadata?: string | null;
|
|
60
66
|
};
|
|
61
67
|
export declare function listBatchTranslationJobs(batchId: string): Promise<import("@parhelia/core").ExecutionResult<TranslationJobRecord[]>>;
|
|
62
68
|
export type TranslationBatchInfo = {
|
|
@@ -112,4 +118,39 @@ export declare function requestBatchTranslation({ sessionId, batchId, provider,
|
|
|
112
118
|
languageMappings: LanguageMapping[];
|
|
113
119
|
itemIds: string[];
|
|
114
120
|
}): Promise<import("@parhelia/core").ExecutionResult<BatchTranslationResponse>>;
|
|
121
|
+
export type RetryBatchTranslationJobRequest = {
|
|
122
|
+
sourceTranslationId?: number;
|
|
123
|
+
itemId: string;
|
|
124
|
+
sourceLanguage: string;
|
|
125
|
+
targetLanguage: string;
|
|
126
|
+
metadata?: string | null;
|
|
127
|
+
};
|
|
128
|
+
export type RetryBatchTranslationStartedJob = {
|
|
129
|
+
sourceTranslationId?: number;
|
|
130
|
+
itemId: string;
|
|
131
|
+
sourceLanguage: string;
|
|
132
|
+
targetLanguage: string;
|
|
133
|
+
metadata?: string | null;
|
|
134
|
+
jobId: string;
|
|
135
|
+
};
|
|
136
|
+
export type RetryBatchTranslationSkippedJob = {
|
|
137
|
+
sourceTranslationId?: number;
|
|
138
|
+
itemId: string;
|
|
139
|
+
sourceLanguage: string;
|
|
140
|
+
targetLanguage: string;
|
|
141
|
+
error: string;
|
|
142
|
+
};
|
|
143
|
+
export type RetryBatchTranslationResponse = {
|
|
144
|
+
sourceBatchId: string;
|
|
145
|
+
retryBatchId: string;
|
|
146
|
+
provider: string;
|
|
147
|
+
started: RetryBatchTranslationStartedJob[];
|
|
148
|
+
skipped: RetryBatchTranslationSkippedJob[];
|
|
149
|
+
};
|
|
150
|
+
export declare function retryBatchTranslation({ sessionId, sourceBatchId, provider, jobs, }: {
|
|
151
|
+
sessionId: string;
|
|
152
|
+
sourceBatchId: string;
|
|
153
|
+
provider?: string;
|
|
154
|
+
jobs: RetryBatchTranslationJobRequest[];
|
|
155
|
+
}): Promise<import("@parhelia/core").ExecutionResult<RetryBatchTranslationResponse>>;
|
|
115
156
|
//# sourceMappingURL=translationService.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translationService.d.ts","sourceRoot":"","sources":["../../src/services/translationService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,MAAM,MAAM,0BAA0B,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,wBAAsB,kBAAkB,CAAC,EAAC,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAC,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,iFAKnQ;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,0EAIxD;AAED,wBAAsB,uBAAuB,iFAE5C;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAsB,+BAA+B,
|
|
1
|
+
{"version":3,"file":"translationService.d.ts","sourceRoot":"","sources":["../../src/services/translationService.ts"],"names":[],"mappings":"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"}
|
|
@@ -56,3 +56,6 @@ export async function unsubscribeFromBatch(batchId, sessionId) {
|
|
|
56
56
|
export async function requestBatchTranslation({ sessionId, batchId, provider, batchMetadata, languageMappings, itemIds }) {
|
|
57
57
|
return await post("/parhelia/translation/requestBatchTranslation", { sessionId, batchId, provider, batchMetadata: batchMetadata || "", languageMappings, itemIds });
|
|
58
58
|
}
|
|
59
|
+
export async function retryBatchTranslation({ sessionId, sourceBatchId, provider, jobs, }) {
|
|
60
|
+
return await post("/parhelia/translation/retryBatchTranslation", { sessionId, sourceBatchId, provider, jobs });
|
|
61
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TranslationServicesPanel.d.ts","sourceRoot":"","sources":["../../src/settings/TranslationServicesPanel.tsx"],"names":[],"mappings":"AAgEA;;;GAGG;AACH,wBAAgB,wBAAwB,
|
|
1
|
+
{"version":3,"file":"TranslationServicesPanel.d.ts","sourceRoot":"","sources":["../../src/settings/TranslationServicesPanel.tsx"],"names":[],"mappings":"AAgEA;;;GAGG;AACH,wBAAgB,wBAAwB,4CAusBvC;AAED,eAAe,wBAAwB,CAAC"}
|
|
@@ -53,12 +53,8 @@ export function TranslationServicesPanel() {
|
|
|
53
53
|
setStructureLoading(true);
|
|
54
54
|
const result = await getTranslationStructure(structureSettings);
|
|
55
55
|
if (result.type === "success" && result.data) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
? result.data.data
|
|
59
|
-
: result.data;
|
|
60
|
-
if (success && response) {
|
|
61
|
-
setStructureState(response);
|
|
56
|
+
if (result.data.success && result.data.data) {
|
|
57
|
+
setStructureState(result.data.data);
|
|
62
58
|
}
|
|
63
59
|
else {
|
|
64
60
|
throw new Error(result.data.error ||
|
|
@@ -83,12 +79,8 @@ export function TranslationServicesPanel() {
|
|
|
83
79
|
setError(null);
|
|
84
80
|
const result = await ensureTranslationStructure(structureSettings);
|
|
85
81
|
if (result.type === "success" && result.data) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
? result.data.data
|
|
89
|
-
: result.data;
|
|
90
|
-
if (success && response) {
|
|
91
|
-
setStructureState(response);
|
|
82
|
+
if (result.data.success && result.data.data) {
|
|
83
|
+
setStructureState(result.data.data);
|
|
92
84
|
return true;
|
|
93
85
|
}
|
|
94
86
|
throw new Error(result.data.error ||
|
|
@@ -112,10 +104,12 @@ export function TranslationServicesPanel() {
|
|
|
112
104
|
await loadStructure();
|
|
113
105
|
const result = await getAvailableTranslationServices();
|
|
114
106
|
if (result.type === "success" && result.data) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
107
|
+
if (!result.data.success || !result.data.data) {
|
|
108
|
+
throw new Error(result.data.error ||
|
|
109
|
+
result.details ||
|
|
110
|
+
"Failed to load translation services. The translation API may not be available.");
|
|
111
|
+
}
|
|
112
|
+
const availableServices = result.data.data;
|
|
119
113
|
const serviceStates = availableServices.map((svc) => ({
|
|
120
114
|
...svc,
|
|
121
115
|
creating: false,
|
|
@@ -150,18 +144,15 @@ export function TranslationServicesPanel() {
|
|
|
150
144
|
"en";
|
|
151
145
|
const result = await createProviderSettings(serviceName, templateId, language, structureSettings);
|
|
152
146
|
if (result.type === "success" && result.data) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
: result.data;
|
|
157
|
-
const success = result.data.success !== false;
|
|
158
|
-
if (success && response) {
|
|
147
|
+
const payload = result.data.data;
|
|
148
|
+
if (result.data.success && payload) {
|
|
149
|
+
const settingsItemId = payload.itemId;
|
|
159
150
|
setServices((prev) => prev.map((s) => s.serviceName === serviceName
|
|
160
151
|
? {
|
|
161
152
|
...s,
|
|
162
153
|
isConfigured: true,
|
|
163
154
|
creating: false,
|
|
164
|
-
settingsItemId
|
|
155
|
+
settingsItemId,
|
|
165
156
|
}
|
|
166
157
|
: s));
|
|
167
158
|
// Reload data to ensure consistency
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalizationSetupStep.d.ts","sourceRoot":"","sources":["../../src/setup/LocalizationSetupStep.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"LocalizationSetupStep.d.ts","sourceRoot":"","sources":["../../src/setup/LocalizationSetupStep.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,qBAAqB,4CA6NpC;AAED,eAAe,qBAAqB,CAAC"}
|
|
@@ -29,8 +29,12 @@ export function LocalizationSetupStep() {
|
|
|
29
29
|
setServices([]);
|
|
30
30
|
const result = await getAvailableTranslationServices();
|
|
31
31
|
if (result.type === "success" && result.data) {
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
if (!result.data.success || !result.data.data) {
|
|
33
|
+
throw new Error(result.data.error ||
|
|
34
|
+
result.details ||
|
|
35
|
+
"Failed to check translation services. The translation API may not be available.");
|
|
36
|
+
}
|
|
37
|
+
const availableServices = result.data.data;
|
|
34
38
|
if (availableServices.length === 0) {
|
|
35
39
|
setState("error");
|
|
36
40
|
setError("No translation services registered in dependency injection.");
|
|
@@ -75,12 +79,9 @@ export function LocalizationSetupStep() {
|
|
|
75
79
|
"en";
|
|
76
80
|
const result = await createProviderSettings(serviceName, templateId, language);
|
|
77
81
|
if (result.type === "success" && result.data) {
|
|
78
|
-
|
|
79
|
-
const response = result.data.success ? result.data.data : result.data;
|
|
80
|
-
const success = result.data.success !== false;
|
|
81
|
-
if (success && response) {
|
|
82
|
+
if (result.data.success && result.data.data) {
|
|
82
83
|
setServices(prev => prev.map(s => s.serviceName === serviceName
|
|
83
|
-
? { ...s, isConfigured: true, creating: false, settingsItemId:
|
|
84
|
+
? { ...s, isConfigured: true, creating: false, settingsItemId: result.data?.data?.itemId }
|
|
84
85
|
: s));
|
|
85
86
|
setTimeout(() => checkLocalization(), 500);
|
|
86
87
|
}
|
|
@@ -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":"AA0CA,OAAO,qCAAqC,CAAC;AA4C7C,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAwED,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,MAAM,GACP,EAAE,yBAAyB,2CAmoD3B"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useEffect, useState, useMemo, useRef, useCallback, memo, } from "react";
|
|
3
|
-
import { useEditContext, SimpleTable, Progress as CoreProgress, Button, } from "@parhelia/core";
|
|
4
|
-
import { listBatchTranslationJobs, subscribeToBatch, unsubscribeFromBatch, getBatchInfo, getTranslationProviders, } from "../services/translationService";
|
|
5
|
-
import { ChevronDown, ChevronUp, RefreshCw, ArrowLeft, Info, Loader2, Languages, } from "lucide-react";
|
|
3
|
+
import { useEditContext, SimpleTable, Progress as CoreProgress, Button, SimpleIconButton, CopyButton, } from "@parhelia/core";
|
|
4
|
+
import { listBatchTranslationJobs, retryBatchTranslation, subscribeToBatch, unsubscribeFromBatch, getBatchInfo, getTranslationProviders, } from "../services/translationService";
|
|
5
|
+
import { ChevronDown, ChevronUp, RefreshCw, ArrowLeft, Info, Loader2, Languages, ExternalLink, } from "lucide-react";
|
|
6
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
7
|
+
import { toast } from "sonner";
|
|
6
8
|
import { JsonView, defaultStyles } from "react-json-view-lite";
|
|
7
9
|
import "react-json-view-lite/dist/index.css";
|
|
8
10
|
// Wrapper so React 19 sees a plain function component instead of a forwardRef exotic component.
|
|
@@ -15,6 +17,7 @@ const ArrowLeftIcon = (props) => React.createElement(ArrowLeft, props);
|
|
|
15
17
|
const InfoIcon = (props) => React.createElement(Info, props);
|
|
16
18
|
const LoaderIcon = (props) => React.createElement(Loader2, props);
|
|
17
19
|
const LanguagesIcon = (props) => React.createElement(Languages, props);
|
|
20
|
+
const ExternalLinkIcon = (props) => React.createElement(ExternalLink, props);
|
|
18
21
|
const MemoizedJsonView = memo(({ data }) => (_jsx(JsonView, { data: data, shouldExpandNode: (level) => level < 2, style: defaultStyles })), (prevProps, nextProps) => {
|
|
19
22
|
return JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data);
|
|
20
23
|
});
|
|
@@ -41,12 +44,47 @@ function normalizeBatchInfo(raw) {
|
|
|
41
44
|
};
|
|
42
45
|
return info.batchId ? info : null;
|
|
43
46
|
}
|
|
47
|
+
function normalizeJobItemId(itemId) {
|
|
48
|
+
return (itemId ?? "").toString().toLowerCase();
|
|
49
|
+
}
|
|
50
|
+
function getJobKey(itemId, language) {
|
|
51
|
+
return `${normalizeJobItemId(itemId)}-${(language ?? "").toString()}`;
|
|
52
|
+
}
|
|
53
|
+
function getSocketPayloadMessage(payload) {
|
|
54
|
+
return payload?.message || payload?.error || "";
|
|
55
|
+
}
|
|
56
|
+
function normalizeJobRecord(raw) {
|
|
57
|
+
return {
|
|
58
|
+
id: typeof raw?.id === "number"
|
|
59
|
+
? raw.id
|
|
60
|
+
: typeof raw?.Id === "number"
|
|
61
|
+
? raw.Id
|
|
62
|
+
: undefined,
|
|
63
|
+
itemId: normalizeJobItemId(raw?.itemId ?? raw?.ItemId),
|
|
64
|
+
targetLanguage: raw?.targetLanguage ?? raw?.TargetLanguage ?? "",
|
|
65
|
+
sourceLanguage: raw?.sourceLanguage ?? raw?.SourceLanguage ?? "",
|
|
66
|
+
status: raw?.status ?? raw?.Status ?? "",
|
|
67
|
+
timestamp: raw?.timestamp ?? raw?.Timestamp ?? "",
|
|
68
|
+
hash: raw?.hash ?? raw?.Hash,
|
|
69
|
+
message: raw?.message ?? raw?.Message,
|
|
70
|
+
batchId: raw?.batchId ?? raw?.BatchId,
|
|
71
|
+
metadata: raw?.metadata ?? raw?.Metadata ?? null,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function getRetryJobKey(job) {
|
|
75
|
+
return `${job.id ?? "no-id"}|${normalizeJobItemId(job.itemId)}|${job.sourceLanguage}|${job.targetLanguage}`;
|
|
76
|
+
}
|
|
44
77
|
export function BatchTranslationView({ batchId, onBack, }) {
|
|
45
78
|
const editContext = useEditContext();
|
|
79
|
+
const router = useRouter();
|
|
80
|
+
const pathname = usePathname();
|
|
81
|
+
const searchParams = useSearchParams();
|
|
46
82
|
const [batchJobs, setBatchJobs] = useState([]);
|
|
47
83
|
const [batchInfo, setBatchInfo] = useState(null);
|
|
48
84
|
const [providers, setProviders] = useState([]);
|
|
49
85
|
const [isLoading, setIsLoading] = useState(false);
|
|
86
|
+
const [isRetryingAll, setIsRetryingAll] = useState(false);
|
|
87
|
+
const [retryingJobKeys, setRetryingJobKeys] = useState(new Set());
|
|
50
88
|
const [translationProgress, setTranslationProgress] = useState(new Map());
|
|
51
89
|
const [itemNames, setItemNames] = useState(new Map());
|
|
52
90
|
const [isMetadataExpanded, setIsMetadataExpanded] = useState(false);
|
|
@@ -64,6 +102,90 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
64
102
|
const provider = providers.find((p) => p.name === serviceName);
|
|
65
103
|
return provider?.displayName || serviceName;
|
|
66
104
|
}, [providers]);
|
|
105
|
+
const openItemInEditor = useCallback((itemId, language) => {
|
|
106
|
+
editContext?.loadItem({
|
|
107
|
+
id: normalizeJobItemId(itemId),
|
|
108
|
+
language,
|
|
109
|
+
version: 0,
|
|
110
|
+
});
|
|
111
|
+
editContext?.switchWorkspace?.("editor");
|
|
112
|
+
}, [editContext]);
|
|
113
|
+
const openBatchInView = useCallback((nextBatchId) => {
|
|
114
|
+
const current = new URLSearchParams(Array.from(searchParams.entries()));
|
|
115
|
+
current.set("batchId", nextBatchId);
|
|
116
|
+
router.push(`${pathname}?${current.toString()}`, { scroll: false });
|
|
117
|
+
}, [pathname, router, searchParams]);
|
|
118
|
+
const buildRetryJobs = useCallback((jobs) => {
|
|
119
|
+
const uniqueJobs = new Map();
|
|
120
|
+
for (const job of jobs) {
|
|
121
|
+
if (job.status !== "Error")
|
|
122
|
+
continue;
|
|
123
|
+
const key = getRetryJobKey(job);
|
|
124
|
+
if (uniqueJobs.has(key))
|
|
125
|
+
continue;
|
|
126
|
+
uniqueJobs.set(key, {
|
|
127
|
+
sourceTranslationId: job.id,
|
|
128
|
+
itemId: normalizeJobItemId(job.itemId),
|
|
129
|
+
sourceLanguage: job.sourceLanguage,
|
|
130
|
+
targetLanguage: job.targetLanguage,
|
|
131
|
+
metadata: job.metadata ?? undefined,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return Array.from(uniqueJobs.values());
|
|
135
|
+
}, []);
|
|
136
|
+
const handleRetryJobs = useCallback(async (jobs, retryAll) => {
|
|
137
|
+
const retryJobs = buildRetryJobs(jobs);
|
|
138
|
+
if (retryJobs.length === 0) {
|
|
139
|
+
toast.error("No failed translations available to retry.");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!editContext?.sessionId) {
|
|
143
|
+
toast.error("Cannot retry without an active session.");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (!batchInfo?.provider) {
|
|
147
|
+
toast.error("Retry provider could not be resolved for this batch.");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (retryAll) {
|
|
151
|
+
setIsRetryingAll(true);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
setRetryingJobKeys(new Set(jobs.map((job) => getRetryJobKey(job)).filter((key) => retryJobs.some((retryJob) => key ===
|
|
155
|
+
`${retryJob.sourceTranslationId ?? "no-id"}|${retryJob.itemId}|${retryJob.sourceLanguage}|${retryJob.targetLanguage}`))));
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const result = await retryBatchTranslation({
|
|
159
|
+
sessionId: editContext.sessionId,
|
|
160
|
+
sourceBatchId: batchId,
|
|
161
|
+
provider: batchInfo.provider,
|
|
162
|
+
jobs: retryJobs,
|
|
163
|
+
});
|
|
164
|
+
if (result.type !== "success" || !result.data) {
|
|
165
|
+
toast.error(result.details ||
|
|
166
|
+
result.summary ||
|
|
167
|
+
"Failed to start retry translations.");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const response = result.data;
|
|
171
|
+
const startedCount = response.started?.length ?? 0;
|
|
172
|
+
const skippedCount = response.skipped?.length ?? 0;
|
|
173
|
+
if (startedCount === 0) {
|
|
174
|
+
toast.error(skippedCount > 0
|
|
175
|
+
? response.skipped[0]?.error || "No retries were started."
|
|
176
|
+
: "No retries were started.");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
toast.success(retryAll
|
|
180
|
+
? `Started retry batch with ${startedCount} translation${startedCount === 1 ? "" : "s"}.`
|
|
181
|
+
: "Started retry batch for failed translation.");
|
|
182
|
+
openBatchInView(response.retryBatchId);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
setIsRetryingAll(false);
|
|
186
|
+
setRetryingJobKeys(new Set());
|
|
187
|
+
}
|
|
188
|
+
}, [batchId, batchInfo?.provider, buildRetryJobs, editContext?.sessionId, openBatchInView]);
|
|
67
189
|
// Parse metadata for display
|
|
68
190
|
const parsedMetadata = useMemo(() => {
|
|
69
191
|
if (!batchInfo?.metadata)
|
|
@@ -96,43 +218,45 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
96
218
|
try {
|
|
97
219
|
// Use efficient batch-specific endpoint
|
|
98
220
|
const res = await listBatchTranslationJobs(batchId);
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
[]);
|
|
221
|
+
const apiJobsRaw = (res?.data ?? res ?? []);
|
|
222
|
+
const apiJobs = apiJobsRaw.map(normalizeJobRecord);
|
|
102
223
|
// Merge API response with existing state to prevent flickering
|
|
103
224
|
setBatchJobs((prevJobs) => {
|
|
104
225
|
// Create a map of existing jobs keyed by itemId-targetLanguage
|
|
105
226
|
const existingMap = new Map();
|
|
106
227
|
for (const job of prevJobs) {
|
|
107
|
-
const key =
|
|
228
|
+
const key = getJobKey(job.itemId, job.targetLanguage);
|
|
108
229
|
existingMap.set(key, job);
|
|
109
230
|
}
|
|
110
231
|
// Merge API jobs into the map (API data takes precedence)
|
|
111
232
|
for (const apiJob of apiJobs) {
|
|
112
|
-
const
|
|
233
|
+
const normalizedJob = normalizeJobRecord(apiJob);
|
|
234
|
+
const key = getJobKey(normalizedJob.itemId, normalizedJob.targetLanguage);
|
|
113
235
|
const existingJob = existingMap.get(key);
|
|
114
236
|
if (existingJob) {
|
|
115
237
|
// Prefer API data if timestamp is newer or status changed
|
|
116
|
-
const apiTimestamp =
|
|
117
|
-
? new Date(
|
|
238
|
+
const apiTimestamp = normalizedJob.timestamp
|
|
239
|
+
? new Date(normalizedJob.timestamp).getTime()
|
|
118
240
|
: 0;
|
|
119
241
|
const existingTimestamp = existingJob.timestamp
|
|
120
242
|
? new Date(existingJob.timestamp).getTime()
|
|
121
243
|
: 0;
|
|
122
244
|
if (apiTimestamp >= existingTimestamp ||
|
|
123
|
-
|
|
245
|
+
normalizedJob.status !== existingJob.status) {
|
|
124
246
|
// Preserve sourceLanguage from API if available, otherwise keep existing
|
|
125
247
|
existingMap.set(key, {
|
|
126
|
-
...
|
|
127
|
-
sourceLanguage:
|
|
248
|
+
...normalizedJob,
|
|
249
|
+
sourceLanguage: normalizedJob.sourceLanguage ||
|
|
250
|
+
existingJob.sourceLanguage ||
|
|
251
|
+
"",
|
|
128
252
|
});
|
|
129
253
|
}
|
|
130
254
|
}
|
|
131
255
|
else {
|
|
132
256
|
// New job from API - ensure sourceLanguage is set
|
|
133
257
|
existingMap.set(key, {
|
|
134
|
-
...
|
|
135
|
-
sourceLanguage:
|
|
258
|
+
...normalizedJob,
|
|
259
|
+
sourceLanguage: normalizedJob.sourceLanguage || "",
|
|
136
260
|
});
|
|
137
261
|
}
|
|
138
262
|
}
|
|
@@ -243,7 +367,9 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
243
367
|
if (message.payload.batchId === batchId) {
|
|
244
368
|
// Update the specific job in our local state instead of refetching all jobs
|
|
245
369
|
setBatchJobs((prevJobs) => {
|
|
246
|
-
const
|
|
370
|
+
const normalizedItemId = normalizeJobItemId(message.payload.itemId);
|
|
371
|
+
const payloadMessage = getSocketPayloadMessage(message.payload);
|
|
372
|
+
const jobIndex = prevJobs.findIndex((job) => normalizeJobItemId(job.itemId) === normalizedItemId &&
|
|
247
373
|
job.targetLanguage === message.payload.language);
|
|
248
374
|
if (jobIndex >= 0) {
|
|
249
375
|
// Update existing job
|
|
@@ -252,9 +378,10 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
252
378
|
if (existingJob) {
|
|
253
379
|
updatedJobs[jobIndex] = {
|
|
254
380
|
...existingJob,
|
|
381
|
+
itemId: normalizedItemId,
|
|
255
382
|
status: message.payload.status,
|
|
256
383
|
timestamp: message.payload.timestamp || existingJob.timestamp,
|
|
257
|
-
message:
|
|
384
|
+
message: payloadMessage || existingJob.message,
|
|
258
385
|
// Preserve sourceLanguage from existing job if not provided in message
|
|
259
386
|
sourceLanguage: message.payload.sourceLanguage ||
|
|
260
387
|
existingJob.sourceLanguage ||
|
|
@@ -263,34 +390,32 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
263
390
|
}
|
|
264
391
|
return updatedJobs;
|
|
265
392
|
}
|
|
266
|
-
else
|
|
267
|
-
// Add new job if it doesn't exist
|
|
393
|
+
else {
|
|
394
|
+
// Add new job if it doesn't exist yet so terminal updates are still visible.
|
|
268
395
|
const newJob = {
|
|
269
|
-
itemId:
|
|
396
|
+
itemId: normalizedItemId,
|
|
270
397
|
targetLanguage: message.payload.language,
|
|
271
398
|
sourceLanguage: message.payload.sourceLanguage || "", // Keep this, but we'll try to populate from API
|
|
272
399
|
status: message.payload.status,
|
|
273
400
|
timestamp: message.payload.timestamp || new Date().toISOString(),
|
|
274
401
|
batchId: batchId,
|
|
275
|
-
message:
|
|
402
|
+
message: payloadMessage,
|
|
276
403
|
};
|
|
277
404
|
return [...prevJobs, newJob];
|
|
278
405
|
}
|
|
279
|
-
return prevJobs;
|
|
280
406
|
});
|
|
281
407
|
}
|
|
282
408
|
}
|
|
283
409
|
if (message.type === "translation-progress") {
|
|
284
410
|
// Only track progress for jobs in this batch
|
|
285
411
|
if (message.payload.batchId === batchId) {
|
|
286
|
-
const itemId = (message.payload.itemId
|
|
287
|
-
.toString()
|
|
288
|
-
.toLowerCase();
|
|
412
|
+
const itemId = normalizeJobItemId(message.payload.itemId);
|
|
289
413
|
const language = message.payload.language;
|
|
290
|
-
const progressKey =
|
|
414
|
+
const progressKey = getJobKey(itemId, language);
|
|
291
415
|
// Ensure a row exists; if not, add a placeholder "In Progress" job
|
|
292
416
|
setBatchJobs((prevJobs) => {
|
|
293
|
-
const idx = prevJobs.findIndex((j) => j.itemId === itemId &&
|
|
417
|
+
const idx = prevJobs.findIndex((j) => normalizeJobItemId(j.itemId) === itemId &&
|
|
418
|
+
j.targetLanguage === language);
|
|
294
419
|
if (idx >= 0)
|
|
295
420
|
return prevJobs;
|
|
296
421
|
const newJob = {
|
|
@@ -497,7 +622,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
497
622
|
let totalActualProgress = completedCount * 100;
|
|
498
623
|
for (const job of batchJobs) {
|
|
499
624
|
if (job.status === "In Progress") {
|
|
500
|
-
const progressKey =
|
|
625
|
+
const progressKey = getJobKey(job.itemId, job.targetLanguage);
|
|
501
626
|
const jobProgress = translationProgress.get(progressKey);
|
|
502
627
|
totalActualProgress += jobProgress?.progress || 0;
|
|
503
628
|
}
|
|
@@ -568,7 +693,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
568
693
|
? { backgroundColor: "#f6eeff", color: "#9650fb" }
|
|
569
694
|
: undefined, children: effectiveStatus }))] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-[10px] text-gray-2 uppercase font-bold tracking-wider", children: "Provider" }), _jsx("span", { className: "text-xs font-medium text-(--color-gray-1) truncate", children: getProviderDisplayName(batchInfo.provider) })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-[10px] text-gray-2 uppercase font-bold tracking-wider", children: "Initiated By" }), _jsx("span", { className: "text-xs font-medium text-(--color-gray-1) truncate", children: batchInfo.initiatedByUser || "System" })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-[10px] text-gray-2 uppercase font-bold tracking-wider", children: "Activity" }), _jsx("span", { className: "text-[10px] text-gray-2 leading-tight", children: batchInfo.completedAtUtc ? (_jsxs(_Fragment, { children: ["Done:", " ", new Date(batchInfo.completedAtUtc).toLocaleTimeString()] })) : batchInfo.startedAtUtc ? (_jsxs(_Fragment, { children: ["Started:", " ", new Date(batchInfo.startedAtUtc).toLocaleTimeString()] })) : batchInfo.lastUpdatedUtc ? (_jsxs(_Fragment, { children: ["Updated:", " ", new Date(batchInfo.lastUpdatedUtc).toLocaleTimeString()] })) : (_jsx(_Fragment, { children: "Created" })) })] })] })), _jsxs("div", { className: `flex items-center ${isMobile ? "w-full justify-between border-t border-gray-3 pt-3 mt-1" : "gap-3"}`, children: [_jsxs("div", { className: "text-xs md:text-sm text-gray-2", children: [_jsx("span", { className: "font-semibold text-(--color-dark)", children: progressSegments.completedCount }), "/", progressSegments.total, " completed", progressSegments.errorCount
|
|
570
695
|
? `, ${progressSegments.errorCount} errors`
|
|
571
|
-
: ""] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: loadBatchJobs, disabled: isLoading, title: "Manual refresh - normally updates come through websocket subscriptions", className: "md:w-auto w-8 h-8 md:h-8 p-0 md:px-3", children: [_jsx(RefreshIcon, { className: `h-4 w-4 ${isLoading ? "animate-spin" : ""}` }), _jsx("span", { className: "hidden md:inline", children: "Refresh" })] })] })] }), parsedMetadata && (_jsxs("div", { className: `border-t border-gray-3 bg-gray-5 mt-4 rounded-b-lg ${isMobile ? "-mx-4 border-x-0" : ""}`, children: [_jsxs("button", { onClick: () => setIsMetadataExpanded(!isMetadataExpanded), className: `flex w-full cursor-pointer items-center justify-between ${isMobile ? "px-4 py-3" : "px-4 py-2.5"} text-left transition-colors hover:bg-gray-4`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(InfoIcon, { className: "h-4 w-4 text-gray-2" }), _jsx("span", { className: "text-xs font-medium text-(--color-gray-1)", children: "Batch Metadata" })] }), isMetadataExpanded ? (_jsx(ChevronUpIcon, { className: "h-4 w-4 text-gray-2", strokeWidth: 1 })) : (_jsx(ChevronDownIcon, { className: "h-4 w-4 text-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-gray-3 bg-background p-3 text-xs shadow-sm mt-2", children: _jsx(MemoizedJsonView, { data: parsedMetadata }) }) }))] })), _jsxs("div", { className: "mt-6 md:mt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-medium text-(--color-dark)", children: "Overall Progress" }), _jsxs("span", { className: "text-sm text-gray-2", children: [_jsx("span", { className: "font-semibold text-(--color-dark)", children: progressSegments.completedCount }), " ", "/ ", progressSegments.total, " completed"] })] }), _jsxs("div", { className: `relative ${isMobile ? "h-5" : "h-4"} rounded-full overflow-hidden bg-gray-3`, children: [_jsxs("div", { className: "absolute inset-0 flex", children: [progressSegments.completed > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: {
|
|
696
|
+
: ""] }), _jsxs("div", { className: "flex items-center gap-2", children: [progressSegments.errorCount > 0 && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => void handleRetryJobs(batchJobs.filter((job) => job.status === "Error"), true), disabled: isRetryingAll || isLoading, className: "md:w-auto h-8 px-3", children: [_jsx(RefreshIcon, { className: `h-4 w-4 ${isRetryingAll ? "animate-spin" : ""}` }), _jsx("span", { children: "Retry All Failed" })] })), _jsxs(Button, { size: "sm", variant: "outline", onClick: loadBatchJobs, disabled: isLoading || isRetryingAll, title: "Manual refresh - normally updates come through websocket subscriptions", className: "md:w-auto w-8 h-8 md:h-8 p-0 md:px-3", children: [_jsx(RefreshIcon, { className: `h-4 w-4 ${isLoading ? "animate-spin" : ""}` }), _jsx("span", { className: "hidden md:inline", children: "Refresh" })] })] })] })] }), parsedMetadata && (_jsxs("div", { className: `border-t border-gray-3 bg-gray-5 mt-4 rounded-b-lg ${isMobile ? "-mx-4 border-x-0" : ""}`, children: [_jsxs("button", { onClick: () => setIsMetadataExpanded(!isMetadataExpanded), className: `flex w-full cursor-pointer items-center justify-between ${isMobile ? "px-4 py-3" : "px-4 py-2.5"} text-left transition-colors hover:bg-gray-4`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(InfoIcon, { className: "h-4 w-4 text-gray-2" }), _jsx("span", { className: "text-xs font-medium text-(--color-gray-1)", children: "Batch Metadata" })] }), isMetadataExpanded ? (_jsx(ChevronUpIcon, { className: "h-4 w-4 text-gray-2", strokeWidth: 1 })) : (_jsx(ChevronDownIcon, { className: "h-4 w-4 text-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-gray-3 bg-background p-3 text-xs shadow-sm mt-2", children: _jsx(MemoizedJsonView, { data: parsedMetadata }) }) }))] })), _jsxs("div", { className: "mt-6 md:mt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-medium text-(--color-dark)", children: "Overall Progress" }), _jsxs("span", { className: "text-sm text-gray-2", children: [_jsx("span", { className: "font-semibold text-(--color-dark)", children: progressSegments.completedCount }), " ", "/ ", progressSegments.total, " completed"] })] }), _jsxs("div", { className: `relative ${isMobile ? "h-5" : "h-4"} rounded-full overflow-hidden bg-gray-3`, children: [_jsxs("div", { className: "absolute inset-0 flex", children: [progressSegments.completed > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: {
|
|
572
697
|
width: `${progressSegments.completed}%`,
|
|
573
698
|
backgroundColor: "#8ae048",
|
|
574
699
|
} })), progressSegments.inProgress > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: {
|
|
@@ -588,7 +713,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
588
713
|
}
|
|
589
714
|
else if (job.status === "In Progress") {
|
|
590
715
|
// Get progress for this specific job
|
|
591
|
-
const progressKey =
|
|
716
|
+
const progressKey = getJobKey(job.itemId, job.targetLanguage);
|
|
592
717
|
const jobProgress = translationProgress.get(progressKey);
|
|
593
718
|
totalProgress += jobProgress?.progress || 0; // Use actual progress or 0
|
|
594
719
|
}
|
|
@@ -599,13 +724,15 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
599
724
|
const itemProgress = Math.round(totalProgress / jobs.length);
|
|
600
725
|
const itemInProgress = jobs.some((j) => j.status === "In Progress");
|
|
601
726
|
const itemError = jobs.some((j) => j.status === "Error");
|
|
727
|
+
const itemTerminalError = itemError && !itemInProgress;
|
|
728
|
+
const itemVisualProgress = itemTerminalError ? 100 : itemProgress;
|
|
602
729
|
return (_jsxs("div", { className: `border border-gray-3 rounded-lg bg-background ${isMobile ? "p-4" : "p-6"} shadow-sm`, children: [_jsxs("div", { className: "mb-4", children: [_jsx("div", { className: "mb-3", children: (() => {
|
|
603
730
|
const itemName = itemNames.get(itemId.toLowerCase());
|
|
604
|
-
return itemName ? (_jsxs(_Fragment, { children: [_jsx("div", { className: `${isMobile ? "text-sm" : "text-base"} font-bold text-(--color-dark) mb-1`, children: itemName }), _jsxs("div", { className: `text-[10px] md:text-xs text-gray-2 font-mono break-all`, children: ["Item ID: ", itemId] })] })) : (_jsxs("div", { className: `text-[10px] md:text-xs text-gray-2 font-mono break-all`, children: ["Item ID: ", itemId] }));
|
|
731
|
+
return itemName ? (_jsxs(_Fragment, { children: [_jsx("div", { className: `${isMobile ? "text-sm" : "text-base"} font-bold text-(--color-dark) mb-1`, children: itemName }), _jsxs("div", { className: `flex items-center gap-1.5 text-[10px] md:text-xs text-gray-2 font-mono break-all`, children: [_jsxs("span", { className: "break-all", children: ["Item ID: ", itemId] }), _jsx(SimpleIconButton, { onClick: () => openItemInEditor(itemId, "en"), icon: _jsx(ExternalLinkIcon, { className: "h-3.5 w-3.5", strokeWidth: 1 }), label: "Open item in English", className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })] })] })) : (_jsxs("div", { className: `flex items-center gap-1.5 text-[10px] md:text-xs text-gray-2 font-mono break-all`, children: [_jsxs("span", { className: "break-all", children: ["Item ID: ", itemId] }), _jsx(SimpleIconButton, { onClick: () => openItemInEditor(itemId, "en"), icon: _jsx(ExternalLinkIcon, { className: "h-3.5 w-3.5", strokeWidth: 1 }), label: "Open item in English", className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })] }));
|
|
605
732
|
})() }), _jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: `${isMobile ? "text-xs" : "text-sm"} text-gray-2`, children: [itemCompleted, " / ", jobs.length, " languages"] }), itemInProgress && (_jsx("span", { className: `${isMobile ? "text-[10px]" : "text-xs"} px-2 py-0.5 md:px-2.5 md:py-1 rounded-full font-medium`, style: {
|
|
606
733
|
backgroundColor: "#f6eeff",
|
|
607
734
|
color: "#9650fb",
|
|
608
|
-
}, children: "In Progress" })), itemError && (_jsx("span", { className: `${isMobile ? "text-[10px]" : "text-xs"} bg-red-100 text-red-600 px-2 py-0.5 md:px-2.5 md:py-1 rounded-full font-medium`, children: "Error" }))] }), _jsxs("span", { className: `${isMobile ? "text-xs" : "text-sm"} font-bold text-(--color-dark)`, children: [
|
|
735
|
+
}, children: "In Progress" })), itemError && (_jsx("span", { className: `${isMobile ? "text-[10px]" : "text-xs"} bg-red-100 text-red-600 px-2 py-0.5 md:px-2.5 md:py-1 rounded-full font-medium`, children: "Error" }))] }), _jsxs("span", { className: `${isMobile ? "text-xs" : "text-sm"} font-bold text-(--color-dark)`, children: [itemVisualProgress, "%"] })] }), _jsx("div", { className: "relative", children: _jsx(Progress, { value: itemVisualProgress, className: `${isMobile ? "h-2" : "h-3"} mb-2`, indicatorClassName: itemError ? "bg-destructive" : undefined, indicatorStyle: itemError
|
|
609
736
|
? undefined
|
|
610
737
|
: itemProgress === 100
|
|
611
738
|
? { backgroundColor: "#8ae048" }
|
|
@@ -628,9 +755,15 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
628
755
|
return priorityA - priorityB;
|
|
629
756
|
})
|
|
630
757
|
.map((job) => {
|
|
631
|
-
const progressKey =
|
|
758
|
+
const progressKey = getJobKey(job.itemId, job.targetLanguage);
|
|
632
759
|
const progress = translationProgress.get(progressKey);
|
|
633
760
|
const date = new Date(job.timestamp);
|
|
761
|
+
const displayMessage = job.message ||
|
|
762
|
+
(job.status === "Error"
|
|
763
|
+
? progress?.message || "Translation failed"
|
|
764
|
+
: "");
|
|
765
|
+
const jobRetryKey = getRetryJobKey(job);
|
|
766
|
+
const isRetryingJob = isRetryingAll || retryingJobKeys.has(jobRetryKey);
|
|
634
767
|
return (_jsxs("div", { className: "rounded-md border border-gray-4 bg-gray-5 p-3", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-bold text-(--color-dark)", children: job.targetLanguage }), _jsx("span", { className: "text-gray-2", children: "\u2190" }), _jsx("span", { className: "text-xs text-(--color-gray-1)", children: job.sourceLanguage || "—" })] }), _jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-bold ${job.status === "Completed"
|
|
635
768
|
? "bg-green-100 text-green-700"
|
|
636
769
|
: job.status === "In Progress"
|
|
@@ -642,12 +775,12 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
642
775
|
backgroundColor: "#f6eeff",
|
|
643
776
|
color: "#9650fb",
|
|
644
777
|
}
|
|
645
|
-
: undefined, children: job.status })] }),
|
|
778
|
+
: undefined, children: job.status })] }), displayMessage && (_jsxs("div", { className: "mb-2 flex items-start gap-1.5", children: [_jsx("div", { className: "min-w-0 flex-1 text-xs text-gray-2 break-words [overflow-wrap:anywhere] whitespace-normal leading-relaxed line-clamp-3", title: displayMessage, children: displayMessage }), _jsx(CopyButton, { textToCopy: displayMessage, iconOnly: true, className: "mt-0.5 shrink-0 text-gray-2 hover:text-(--color-gray-1)" })] })), job.status === "In Progress" && progress && (_jsxs("div", { className: "mb-2", children: [_jsxs("div", { className: "flex items-center justify-between mb-1", children: [_jsx("span", { className: "text-[10px] text-gray-2 truncate pr-2", children: progress.message }), _jsxs("span", { className: "text-[10px] font-bold text-(--color-gray-1) shrink-0", children: [Math.round(progress.progress), "%"] })] }), _jsx(Progress, { value: progress.progress, className: "h-2", indicatorStyle: {
|
|
646
779
|
backgroundColor: "#9650fb",
|
|
647
|
-
}, showValue: false })] })), _jsx("div", { className: "text-[10px] text-gray-2 text-right mt-1", children: date.toLocaleTimeString([], {
|
|
780
|
+
}, showValue: false })] })), job.status === "Error" && (_jsx("div", { className: "mb-2", children: _jsx(Progress, { value: 100, className: "h-2", indicatorClassName: "bg-destructive", showValue: false }) })), _jsx("div", { className: "text-[10px] text-gray-2 text-right mt-1", children: date.toLocaleTimeString([], {
|
|
648
781
|
hour: "2-digit",
|
|
649
782
|
minute: "2-digit",
|
|
650
|
-
}) })] }, job.targetLanguage));
|
|
783
|
+
}) }), _jsx("div", { className: "mt-2 flex justify-end", children: _jsxs("div", { className: "flex items-center gap-2", children: [job.status === "Error" && (_jsx(SimpleIconButton, { onClick: () => void handleRetryJobs([job], false), icon: _jsx(RefreshIcon, { className: `h-3.5 w-3.5 ${isRetryingJob ? "animate-spin" : ""}`, strokeWidth: 1 }), label: "Retry translation as new batch", disabled: isRetryingJob, className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })), _jsx(SimpleIconButton, { onClick: () => openItemInEditor(job.itemId, job.targetLanguage), icon: _jsx(ExternalLinkIcon, { className: "h-3.5 w-3.5", strokeWidth: 1 }), label: `Open item in ${job.targetLanguage}`, disabled: isRetryingAll, className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })] }) })] }, job.targetLanguage));
|
|
651
784
|
}) })) : (_jsx(SimpleTable, { columns: [
|
|
652
785
|
{
|
|
653
786
|
header: "Language",
|
|
@@ -658,8 +791,12 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
658
791
|
header: "Status",
|
|
659
792
|
className: "w-32", // Fixed width for status column
|
|
660
793
|
body: (job) => {
|
|
661
|
-
const progressKey =
|
|
794
|
+
const progressKey = getJobKey(job.itemId, job.targetLanguage);
|
|
662
795
|
const progress = translationProgress.get(progressKey);
|
|
796
|
+
const displayMessage = job.message ||
|
|
797
|
+
(job.status === "Error"
|
|
798
|
+
? progress?.message || "Translation failed"
|
|
799
|
+
: "");
|
|
663
800
|
return (_jsxs("div", { children: [_jsx("span", { className: `inline-flex items-center rounded-full px-2.5 py-1 text-xs font-medium ${job.status === "Completed"
|
|
664
801
|
? "bg-green-100 text-green-700"
|
|
665
802
|
: job.status === "In Progress"
|
|
@@ -675,7 +812,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
675
812
|
progress &&
|
|
676
813
|
Math.round(progress.progress || 0) === 0 && (_jsx("div", { className: "mt-1 text-xs text-gray-2", children: "Version created" })), progress && job.status === "In Progress" && (_jsxs("div", { className: "mt-1.5", children: [_jsxs("div", { className: "flex items-center justify-between mb-1", children: [_jsx("span", { className: "text-xs text-gray-2 truncate", "data-testid": "translation-progress-text", children: progress.message }), _jsxs("span", { className: "text-xs font-medium text-(--color-gray-1) ml-1", children: [Math.round(progress.progress), "%"] })] }), _jsx(Progress, { value: progress.progress, className: "h-2", indicatorStyle: {
|
|
677
814
|
backgroundColor: "#9650fb",
|
|
678
|
-
}, showValue: false, "data-testid": "translation-progress-bar" })] }))] }));
|
|
815
|
+
}, showValue: false, "data-testid": "translation-progress-bar" })] })), job.status === "Error" && (_jsx("div", { className: "mt-1.5", children: _jsx(Progress, { value: 100, className: "h-2", indicatorClassName: "bg-destructive", showValue: false, "data-testid": "translation-progress-bar" }) }))] }));
|
|
679
816
|
},
|
|
680
817
|
},
|
|
681
818
|
{
|
|
@@ -685,8 +822,23 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
685
822
|
},
|
|
686
823
|
{
|
|
687
824
|
header: "Message",
|
|
688
|
-
className: "w-
|
|
689
|
-
body: (job) =>
|
|
825
|
+
className: "w-full min-w-0",
|
|
826
|
+
body: (job) => {
|
|
827
|
+
const message = job.message ||
|
|
828
|
+
(job.status === "Error"
|
|
829
|
+
? "Translation failed"
|
|
830
|
+
: "—");
|
|
831
|
+
return (_jsxs("div", { className: "flex items-start gap-1.5", children: [_jsx("div", { className: "min-w-0 w-full flex-1 text-sm text-gray-2 break-words [overflow-wrap:anywhere] whitespace-normal leading-relaxed line-clamp-3", title: message, children: message }), message !== "—" && (_jsx(CopyButton, { textToCopy: message, iconOnly: true, className: "mt-0.5 shrink-0 text-gray-2 hover:text-(--color-gray-1)" }))] }));
|
|
832
|
+
},
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
header: "Actions",
|
|
836
|
+
className: "w-24",
|
|
837
|
+
body: (job) => {
|
|
838
|
+
const jobRetryKey = getRetryJobKey(job);
|
|
839
|
+
const isRetryingJob = isRetryingAll || retryingJobKeys.has(jobRetryKey);
|
|
840
|
+
return (_jsxs("div", { className: "flex items-center justify-center gap-2", children: [job.status === "Error" && (_jsx(SimpleIconButton, { onClick: () => void handleRetryJobs([job], false), icon: _jsx(RefreshIcon, { className: `h-3.5 w-3.5 ${isRetryingJob ? "animate-spin" : ""}`, strokeWidth: 1 }), label: "Retry translation as new batch", disabled: isRetryingJob, className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })), _jsx(SimpleIconButton, { onClick: () => openItemInEditor(job.itemId, job.targetLanguage), icon: _jsx(ExternalLinkIcon, { className: "h-3.5 w-3.5", strokeWidth: 1 }), label: `Open item in ${job.targetLanguage}`, disabled: isRetryingAll, className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })] }));
|
|
841
|
+
},
|
|
690
842
|
},
|
|
691
843
|
{
|
|
692
844
|
header: "Updated",
|