@parhelia/localization 0.1.12485 → 0.1.12515
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 +206 -55
- 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
|
});
|
|
@@ -22,32 +25,66 @@ const MemoizedJsonView = memo(({ data }) => (_jsx(JsonView, { data: data, should
|
|
|
22
25
|
function normalizeBatchInfo(raw) {
|
|
23
26
|
if (!raw)
|
|
24
27
|
return null;
|
|
25
|
-
const pick = (a, b) => (a !== undefined ? a : b);
|
|
26
28
|
const toNum = (v) => typeof v === "number" ? v : v != null ? parseInt(v, 10) : undefined;
|
|
27
29
|
const info = {
|
|
28
|
-
batchId:
|
|
29
|
-
createdAtUtc:
|
|
30
|
-
startedAtUtc:
|
|
31
|
-
completedAtUtc:
|
|
32
|
-
initiatedByUser:
|
|
33
|
-
initiatorSessionId:
|
|
34
|
-
provider:
|
|
35
|
-
status:
|
|
36
|
-
expectedJobs: toNum(
|
|
37
|
-
completedJobs: toNum(
|
|
38
|
-
errorJobs: toNum(
|
|
39
|
-
metadata:
|
|
40
|
-
lastUpdatedUtc:
|
|
41
|
-
exists:
|
|
30
|
+
batchId: raw?.batchId,
|
|
31
|
+
createdAtUtc: raw?.createdAtUtc,
|
|
32
|
+
startedAtUtc: raw?.startedAtUtc,
|
|
33
|
+
completedAtUtc: raw?.completedAtUtc,
|
|
34
|
+
initiatedByUser: raw?.initiatedByUser,
|
|
35
|
+
initiatorSessionId: raw?.initiatorSessionId,
|
|
36
|
+
provider: raw?.provider,
|
|
37
|
+
status: raw?.status,
|
|
38
|
+
expectedJobs: toNum(raw?.expectedJobs),
|
|
39
|
+
completedJobs: toNum(raw?.completedJobs),
|
|
40
|
+
errorJobs: toNum(raw?.errorJobs),
|
|
41
|
+
metadata: raw?.metadata,
|
|
42
|
+
lastUpdatedUtc: raw?.lastUpdatedUtc,
|
|
43
|
+
exists: raw?.exists,
|
|
42
44
|
};
|
|
43
45
|
return info.batchId ? info : null;
|
|
44
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
|
+
}
|
|
45
77
|
export function BatchTranslationView({ batchId, onBack, }) {
|
|
46
78
|
const editContext = useEditContext();
|
|
79
|
+
const router = useRouter();
|
|
80
|
+
const pathname = usePathname();
|
|
81
|
+
const searchParams = useSearchParams();
|
|
47
82
|
const [batchJobs, setBatchJobs] = useState([]);
|
|
48
83
|
const [batchInfo, setBatchInfo] = useState(null);
|
|
49
84
|
const [providers, setProviders] = useState([]);
|
|
50
85
|
const [isLoading, setIsLoading] = useState(false);
|
|
86
|
+
const [isRetryingAll, setIsRetryingAll] = useState(false);
|
|
87
|
+
const [retryingJobKeys, setRetryingJobKeys] = useState(new Set());
|
|
51
88
|
const [translationProgress, setTranslationProgress] = useState(new Map());
|
|
52
89
|
const [itemNames, setItemNames] = useState(new Map());
|
|
53
90
|
const [isMetadataExpanded, setIsMetadataExpanded] = useState(false);
|
|
@@ -65,6 +102,90 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
65
102
|
const provider = providers.find((p) => p.name === serviceName);
|
|
66
103
|
return provider?.displayName || serviceName;
|
|
67
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]);
|
|
68
189
|
// Parse metadata for display
|
|
69
190
|
const parsedMetadata = useMemo(() => {
|
|
70
191
|
if (!batchInfo?.metadata)
|
|
@@ -97,43 +218,45 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
97
218
|
try {
|
|
98
219
|
// Use efficient batch-specific endpoint
|
|
99
220
|
const res = await listBatchTranslationJobs(batchId);
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
[]);
|
|
221
|
+
const apiJobsRaw = (res?.data ?? res ?? []);
|
|
222
|
+
const apiJobs = apiJobsRaw.map(normalizeJobRecord);
|
|
103
223
|
// Merge API response with existing state to prevent flickering
|
|
104
224
|
setBatchJobs((prevJobs) => {
|
|
105
225
|
// Create a map of existing jobs keyed by itemId-targetLanguage
|
|
106
226
|
const existingMap = new Map();
|
|
107
227
|
for (const job of prevJobs) {
|
|
108
|
-
const key =
|
|
228
|
+
const key = getJobKey(job.itemId, job.targetLanguage);
|
|
109
229
|
existingMap.set(key, job);
|
|
110
230
|
}
|
|
111
231
|
// Merge API jobs into the map (API data takes precedence)
|
|
112
232
|
for (const apiJob of apiJobs) {
|
|
113
|
-
const
|
|
233
|
+
const normalizedJob = normalizeJobRecord(apiJob);
|
|
234
|
+
const key = getJobKey(normalizedJob.itemId, normalizedJob.targetLanguage);
|
|
114
235
|
const existingJob = existingMap.get(key);
|
|
115
236
|
if (existingJob) {
|
|
116
237
|
// Prefer API data if timestamp is newer or status changed
|
|
117
|
-
const apiTimestamp =
|
|
118
|
-
? new Date(
|
|
238
|
+
const apiTimestamp = normalizedJob.timestamp
|
|
239
|
+
? new Date(normalizedJob.timestamp).getTime()
|
|
119
240
|
: 0;
|
|
120
241
|
const existingTimestamp = existingJob.timestamp
|
|
121
242
|
? new Date(existingJob.timestamp).getTime()
|
|
122
243
|
: 0;
|
|
123
244
|
if (apiTimestamp >= existingTimestamp ||
|
|
124
|
-
|
|
245
|
+
normalizedJob.status !== existingJob.status) {
|
|
125
246
|
// Preserve sourceLanguage from API if available, otherwise keep existing
|
|
126
247
|
existingMap.set(key, {
|
|
127
|
-
...
|
|
128
|
-
sourceLanguage:
|
|
248
|
+
...normalizedJob,
|
|
249
|
+
sourceLanguage: normalizedJob.sourceLanguage ||
|
|
250
|
+
existingJob.sourceLanguage ||
|
|
251
|
+
"",
|
|
129
252
|
});
|
|
130
253
|
}
|
|
131
254
|
}
|
|
132
255
|
else {
|
|
133
256
|
// New job from API - ensure sourceLanguage is set
|
|
134
257
|
existingMap.set(key, {
|
|
135
|
-
...
|
|
136
|
-
sourceLanguage:
|
|
258
|
+
...normalizedJob,
|
|
259
|
+
sourceLanguage: normalizedJob.sourceLanguage || "",
|
|
137
260
|
});
|
|
138
261
|
}
|
|
139
262
|
}
|
|
@@ -244,7 +367,9 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
244
367
|
if (message.payload.batchId === batchId) {
|
|
245
368
|
// Update the specific job in our local state instead of refetching all jobs
|
|
246
369
|
setBatchJobs((prevJobs) => {
|
|
247
|
-
const
|
|
370
|
+
const normalizedItemId = normalizeJobItemId(message.payload.itemId);
|
|
371
|
+
const payloadMessage = getSocketPayloadMessage(message.payload);
|
|
372
|
+
const jobIndex = prevJobs.findIndex((job) => normalizeJobItemId(job.itemId) === normalizedItemId &&
|
|
248
373
|
job.targetLanguage === message.payload.language);
|
|
249
374
|
if (jobIndex >= 0) {
|
|
250
375
|
// Update existing job
|
|
@@ -253,9 +378,10 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
253
378
|
if (existingJob) {
|
|
254
379
|
updatedJobs[jobIndex] = {
|
|
255
380
|
...existingJob,
|
|
381
|
+
itemId: normalizedItemId,
|
|
256
382
|
status: message.payload.status,
|
|
257
383
|
timestamp: message.payload.timestamp || existingJob.timestamp,
|
|
258
|
-
message:
|
|
384
|
+
message: payloadMessage || existingJob.message,
|
|
259
385
|
// Preserve sourceLanguage from existing job if not provided in message
|
|
260
386
|
sourceLanguage: message.payload.sourceLanguage ||
|
|
261
387
|
existingJob.sourceLanguage ||
|
|
@@ -264,34 +390,32 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
264
390
|
}
|
|
265
391
|
return updatedJobs;
|
|
266
392
|
}
|
|
267
|
-
else
|
|
268
|
-
// 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.
|
|
269
395
|
const newJob = {
|
|
270
|
-
itemId:
|
|
396
|
+
itemId: normalizedItemId,
|
|
271
397
|
targetLanguage: message.payload.language,
|
|
272
398
|
sourceLanguage: message.payload.sourceLanguage || "", // Keep this, but we'll try to populate from API
|
|
273
399
|
status: message.payload.status,
|
|
274
400
|
timestamp: message.payload.timestamp || new Date().toISOString(),
|
|
275
401
|
batchId: batchId,
|
|
276
|
-
message:
|
|
402
|
+
message: payloadMessage,
|
|
277
403
|
};
|
|
278
404
|
return [...prevJobs, newJob];
|
|
279
405
|
}
|
|
280
|
-
return prevJobs;
|
|
281
406
|
});
|
|
282
407
|
}
|
|
283
408
|
}
|
|
284
409
|
if (message.type === "translation-progress") {
|
|
285
410
|
// Only track progress for jobs in this batch
|
|
286
411
|
if (message.payload.batchId === batchId) {
|
|
287
|
-
const itemId = (message.payload.itemId
|
|
288
|
-
.toString()
|
|
289
|
-
.toLowerCase();
|
|
412
|
+
const itemId = normalizeJobItemId(message.payload.itemId);
|
|
290
413
|
const language = message.payload.language;
|
|
291
|
-
const progressKey =
|
|
414
|
+
const progressKey = getJobKey(itemId, language);
|
|
292
415
|
// Ensure a row exists; if not, add a placeholder "In Progress" job
|
|
293
416
|
setBatchJobs((prevJobs) => {
|
|
294
|
-
const idx = prevJobs.findIndex((j) => j.itemId === itemId &&
|
|
417
|
+
const idx = prevJobs.findIndex((j) => normalizeJobItemId(j.itemId) === itemId &&
|
|
418
|
+
j.targetLanguage === language);
|
|
295
419
|
if (idx >= 0)
|
|
296
420
|
return prevJobs;
|
|
297
421
|
const newJob = {
|
|
@@ -498,7 +622,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
498
622
|
let totalActualProgress = completedCount * 100;
|
|
499
623
|
for (const job of batchJobs) {
|
|
500
624
|
if (job.status === "In Progress") {
|
|
501
|
-
const progressKey =
|
|
625
|
+
const progressKey = getJobKey(job.itemId, job.targetLanguage);
|
|
502
626
|
const jobProgress = translationProgress.get(progressKey);
|
|
503
627
|
totalActualProgress += jobProgress?.progress || 0;
|
|
504
628
|
}
|
|
@@ -569,7 +693,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
569
693
|
? { backgroundColor: "#f6eeff", color: "#9650fb" }
|
|
570
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
|
|
571
695
|
? `, ${progressSegments.errorCount} errors`
|
|
572
|
-
: ""] }), _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: {
|
|
573
697
|
width: `${progressSegments.completed}%`,
|
|
574
698
|
backgroundColor: "#8ae048",
|
|
575
699
|
} })), progressSegments.inProgress > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: {
|
|
@@ -589,7 +713,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
589
713
|
}
|
|
590
714
|
else if (job.status === "In Progress") {
|
|
591
715
|
// Get progress for this specific job
|
|
592
|
-
const progressKey =
|
|
716
|
+
const progressKey = getJobKey(job.itemId, job.targetLanguage);
|
|
593
717
|
const jobProgress = translationProgress.get(progressKey);
|
|
594
718
|
totalProgress += jobProgress?.progress || 0; // Use actual progress or 0
|
|
595
719
|
}
|
|
@@ -600,13 +724,15 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
600
724
|
const itemProgress = Math.round(totalProgress / jobs.length);
|
|
601
725
|
const itemInProgress = jobs.some((j) => j.status === "In Progress");
|
|
602
726
|
const itemError = jobs.some((j) => j.status === "Error");
|
|
727
|
+
const itemTerminalError = itemError && !itemInProgress;
|
|
728
|
+
const itemVisualProgress = itemTerminalError ? 100 : itemProgress;
|
|
603
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: (() => {
|
|
604
730
|
const itemName = itemNames.get(itemId.toLowerCase());
|
|
605
|
-
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)" })] }));
|
|
606
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: {
|
|
607
733
|
backgroundColor: "#f6eeff",
|
|
608
734
|
color: "#9650fb",
|
|
609
|
-
}, 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
|
|
610
736
|
? undefined
|
|
611
737
|
: itemProgress === 100
|
|
612
738
|
? { backgroundColor: "#8ae048" }
|
|
@@ -629,9 +755,15 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
629
755
|
return priorityA - priorityB;
|
|
630
756
|
})
|
|
631
757
|
.map((job) => {
|
|
632
|
-
const progressKey =
|
|
758
|
+
const progressKey = getJobKey(job.itemId, job.targetLanguage);
|
|
633
759
|
const progress = translationProgress.get(progressKey);
|
|
634
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);
|
|
635
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"
|
|
636
768
|
? "bg-green-100 text-green-700"
|
|
637
769
|
: job.status === "In Progress"
|
|
@@ -643,12 +775,12 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
643
775
|
backgroundColor: "#f6eeff",
|
|
644
776
|
color: "#9650fb",
|
|
645
777
|
}
|
|
646
|
-
: 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: {
|
|
647
779
|
backgroundColor: "#9650fb",
|
|
648
|
-
}, 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([], {
|
|
649
781
|
hour: "2-digit",
|
|
650
782
|
minute: "2-digit",
|
|
651
|
-
}) })] }, 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));
|
|
652
784
|
}) })) : (_jsx(SimpleTable, { columns: [
|
|
653
785
|
{
|
|
654
786
|
header: "Language",
|
|
@@ -659,8 +791,12 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
659
791
|
header: "Status",
|
|
660
792
|
className: "w-32", // Fixed width for status column
|
|
661
793
|
body: (job) => {
|
|
662
|
-
const progressKey =
|
|
794
|
+
const progressKey = getJobKey(job.itemId, job.targetLanguage);
|
|
663
795
|
const progress = translationProgress.get(progressKey);
|
|
796
|
+
const displayMessage = job.message ||
|
|
797
|
+
(job.status === "Error"
|
|
798
|
+
? progress?.message || "Translation failed"
|
|
799
|
+
: "");
|
|
664
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"
|
|
665
801
|
? "bg-green-100 text-green-700"
|
|
666
802
|
: job.status === "In Progress"
|
|
@@ -676,7 +812,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
676
812
|
progress &&
|
|
677
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: {
|
|
678
814
|
backgroundColor: "#9650fb",
|
|
679
|
-
}, 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" }) }))] }));
|
|
680
816
|
},
|
|
681
817
|
},
|
|
682
818
|
{
|
|
@@ -686,8 +822,23 @@ export function BatchTranslationView({ batchId, onBack, }) {
|
|
|
686
822
|
},
|
|
687
823
|
{
|
|
688
824
|
header: "Message",
|
|
689
|
-
className: "w-
|
|
690
|
-
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
|
+
},
|
|
691
842
|
},
|
|
692
843
|
{
|
|
693
844
|
header: "Updated",
|