@parhelia/localization 0.1.12808 → 0.1.12836
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/LocalizeItemCommand.d.ts.map +1 -1
- package/dist/LocalizeItemCommand.js +4 -2
- package/dist/LocalizeItemDialog.d.ts.map +1 -1
- package/dist/LocalizeItemDialog.js +7 -4
- package/dist/LocalizeItemUtils.d.ts.map +1 -1
- package/dist/LocalizeItemUtils.js +35 -25
- package/dist/api/discovery.d.ts.map +1 -1
- package/dist/api/discovery.js +20 -3
- package/dist/hooks/useTranslationWizard.d.ts.map +1 -1
- package/dist/hooks/useTranslationWizard.js +3 -3
- package/dist/services/translationService.d.ts +2 -2
- package/dist/services/translationService.d.ts.map +1 -1
- package/dist/services/translationService.js +21 -5
- package/dist/settings/TranslationServicesPanel.d.ts.map +1 -1
- package/dist/settings/TranslationServicesPanel.js +24 -21
- package/dist/setup/LocalizationSetupStep.d.ts.map +1 -1
- package/dist/setup/LocalizationSetupStep.js +29 -18
- package/dist/sidebar/TranslationSidebar.d.ts.map +1 -1
- package/dist/sidebar/TranslationSidebar.js +17 -10
- package/dist/steps/ItemSelectionStep.d.ts.map +1 -1
- package/dist/steps/ItemSelectionStep.js +2 -1
- package/dist/steps/ItemSelectionTree.d.ts.map +1 -1
- package/dist/steps/ItemSelectionTree.js +2 -1
- package/dist/steps/PromptCustomizationStep.d.ts.map +1 -1
- package/dist/steps/PromptCustomizationStep.js +6 -8
- package/dist/steps/ServiceLanguageSelectionStep.d.ts.map +1 -1
- package/dist/steps/ServiceLanguageSelectionStep.js +6 -4
- package/dist/steps/WizardStepShell.d.ts.map +1 -1
- package/dist/steps/types.d.ts.map +1 -1
- package/dist/translation-center/TranslationBatches.d.ts.map +1 -1
- package/dist/translation-center/TranslationBatches.js +187 -115
- package/dist/translation-center/TranslationManagement.js +4 -4
- package/dist/translation-center/TranslationsTitlebar.d.ts.map +1 -1
- package/dist/translation-center/TranslationsTitlebar.js +2 -2
- package/package.json +1 -1
- package/dist/constants.d.ts +0 -15
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -21
- package/dist/steps/MetadataInputStep.d.ts +0 -4
- package/dist/steps/MetadataInputStep.d.ts.map +0 -1
- package/dist/steps/MetadataInputStep.js +0 -48
- package/dist/steps/SubitemDiscoveryStep.d.ts +0 -3
- package/dist/steps/SubitemDiscoveryStep.d.ts.map +0 -1
- package/dist/steps/SubitemDiscoveryStep.js +0 -313
- package/dist/steps/index.d.ts +0 -6
- package/dist/steps/index.d.ts.map +0 -1
- package/dist/steps/index.js +0 -5
- package/dist/utils/createVersions.d.ts +0 -14
- package/dist/utils/createVersions.d.ts.map +0 -1
- package/dist/utils/createVersions.js +0 -26
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useRef, useState, useMemo } from "react";
|
|
3
3
|
import React from "react";
|
|
4
|
-
import { Button, Select, Input, UserPicker, Popover, PopoverContent, PopoverTrigger, ContentTree, fetchItemStubs, searchUsers as searchBackendUsers, useEditContext, getLanguages, SimpleIconButton, } from "@parhelia/core";
|
|
4
|
+
import { Button, Select, Input, UserPicker, Popover, PopoverContent, PopoverTrigger, ContentTree, fetchItemStubs, searchUsers as searchBackendUsers, useEditContext, getLanguages, SimpleIconButton, DeleteIcon, } from "@parhelia/core";
|
|
5
5
|
import { listBatches, getTranslationProviders, listBatchTranslationJobs, retryBatchTranslation, abortBatch, deleteBatch, } from "../services/translationService";
|
|
6
6
|
import { useDebouncedCallback } from "use-debounce";
|
|
7
|
-
import { X, ChevronDown, Languages, Loader2, Calendar, CheckCircle2, User as UserIcon2, Cloud, Globe, FileText, FolderTree, ExternalLink, Check, AlertCircle, CircleStop,
|
|
7
|
+
import { X, ChevronDown, Languages, Loader2, Calendar, CheckCircle2, User as UserIcon2, Cloud, Globe, FileText, FolderTree, ExternalLink, Check, AlertCircle, CircleStop, Hourglass, Copy, Sparkles, RotateCcw, } from "lucide-react";
|
|
8
8
|
import { toast } from "sonner";
|
|
9
9
|
// Wrappers for lucide-react icons to work with React 19
|
|
10
10
|
const XIcon = (props) => React.createElement(X, props);
|
|
@@ -22,7 +22,6 @@ const ExternalLinkIcon = (props) => React.createElement(ExternalLink, props);
|
|
|
22
22
|
const CheckIcon = (props) => React.createElement(Check, props);
|
|
23
23
|
const AlertCircleIcon = (props) => React.createElement(AlertCircle, props);
|
|
24
24
|
const AbortIcon = (props) => React.createElement(CircleStop, props);
|
|
25
|
-
const TrashIcon = (props) => React.createElement(Trash2, props);
|
|
26
25
|
const QueuedIcon = (props) => React.createElement(Hourglass, props);
|
|
27
26
|
const CopyIcon = (props) => React.createElement(Copy, props);
|
|
28
27
|
const SparklesIcon = (props) => React.createElement(Sparkles, props);
|
|
@@ -73,7 +72,10 @@ function buildRetryJobRequest(job) {
|
|
|
73
72
|
};
|
|
74
73
|
}
|
|
75
74
|
function normalizeGuid(value) {
|
|
76
|
-
return (value ?? "")
|
|
75
|
+
return (value ?? "")
|
|
76
|
+
.toString()
|
|
77
|
+
.replace(/[{}()]/g, "")
|
|
78
|
+
.toLowerCase();
|
|
77
79
|
}
|
|
78
80
|
function dateRangeToFromUtc(range) {
|
|
79
81
|
const now = new Date();
|
|
@@ -146,8 +148,10 @@ export function TranslationBatches() {
|
|
|
146
148
|
});
|
|
147
149
|
// Enforce current user filter for limited preview users
|
|
148
150
|
useEffect(() => {
|
|
149
|
-
if (isLimitedPreviewUser &&
|
|
150
|
-
|
|
151
|
+
if (isLimitedPreviewUser &&
|
|
152
|
+
currentUserName !== "all" &&
|
|
153
|
+
filters.user !== currentUserName) {
|
|
154
|
+
setFilters((prev) => ({ ...prev, user: currentUserName }));
|
|
151
155
|
}
|
|
152
156
|
}, [isLimitedPreviewUser, currentUserName, filters.user]);
|
|
153
157
|
const effectiveFilters = useMemo(() => {
|
|
@@ -155,7 +159,9 @@ export function TranslationBatches() {
|
|
|
155
159
|
const trimmedItem = filters.itemIdOrPath.trim();
|
|
156
160
|
return {
|
|
157
161
|
fromUtc: dateRangeToFromUtc(filters.dateRange),
|
|
158
|
-
status: filters.status === "all"
|
|
162
|
+
status: filters.status === "all"
|
|
163
|
+
? undefined
|
|
164
|
+
: STATUS_FILTER_TO_DB[filters.status],
|
|
159
165
|
user: userFilter === "all" ? undefined : userFilter,
|
|
160
166
|
provider: filters.provider === "all" ? undefined : filters.provider,
|
|
161
167
|
targetLanguage: filters.targetLanguage === "all" ? undefined : filters.targetLanguage,
|
|
@@ -178,9 +184,11 @@ export function TranslationBatches() {
|
|
|
178
184
|
if (myRequestId !== requestIdRef.current)
|
|
179
185
|
return;
|
|
180
186
|
const maybeData = res?.data ?? res;
|
|
181
|
-
const newBatches = Array.isArray(maybeData)
|
|
187
|
+
const newBatches = Array.isArray(maybeData)
|
|
188
|
+
? maybeData
|
|
189
|
+
: [];
|
|
182
190
|
if (append) {
|
|
183
|
-
setBatches(prev => {
|
|
191
|
+
setBatches((prev) => {
|
|
184
192
|
const map = new Map();
|
|
185
193
|
for (const b of prev)
|
|
186
194
|
map.set(b.id, b);
|
|
@@ -222,7 +230,9 @@ export function TranslationBatches() {
|
|
|
222
230
|
const loadProviders = async () => {
|
|
223
231
|
try {
|
|
224
232
|
const res = await getTranslationProviders();
|
|
225
|
-
const providerData = (res?.data ??
|
|
233
|
+
const providerData = (res?.data ??
|
|
234
|
+
res ??
|
|
235
|
+
[]);
|
|
226
236
|
setProviders(providerData);
|
|
227
237
|
}
|
|
228
238
|
catch (error) {
|
|
@@ -284,8 +294,7 @@ export function TranslationBatches() {
|
|
|
284
294
|
await Promise.all(Array.from(unresolved).map(async (userName) => {
|
|
285
295
|
try {
|
|
286
296
|
const results = await searchBackendUsers(userName, 5);
|
|
287
|
-
const exact = results.find((r) => r.userName?.toLowerCase() === userName.toLowerCase()) ??
|
|
288
|
-
results[0];
|
|
297
|
+
const exact = results.find((r) => r.userName?.toLowerCase() === userName.toLowerCase()) ?? results[0];
|
|
289
298
|
updates[userName] = exact?.displayName?.trim() || userName;
|
|
290
299
|
}
|
|
291
300
|
catch {
|
|
@@ -347,7 +356,7 @@ export function TranslationBatches() {
|
|
|
347
356
|
// Languages available in the Target Language dropdown — sourced from Sitecore so
|
|
348
357
|
// the list is stable regardless of the current filter selection.
|
|
349
358
|
const languageOptions = useMemo(() => sitecoreLanguages
|
|
350
|
-
.map(l => l.languageCode || l.name)
|
|
359
|
+
.map((l) => l.languageCode || l.name)
|
|
351
360
|
.filter((code) => !!code)
|
|
352
361
|
.sort(), [sitecoreLanguages]);
|
|
353
362
|
// Group by date. Filtering happens server-side, so we operate on `batches` directly.
|
|
@@ -357,11 +366,14 @@ export function TranslationBatches() {
|
|
|
357
366
|
const dedupMap = new Map();
|
|
358
367
|
for (const b of batches)
|
|
359
368
|
dedupMap.set(b.id, b);
|
|
360
|
-
const sorted = Array.from(dedupMap.values()).sort((a, b) => new Date(b.startedAtUtc || b.createdAtUtc || b.lastUpdatedUtc ||
|
|
361
|
-
new Date(a.startedAtUtc || a.createdAtUtc || a.lastUpdatedUtc ||
|
|
362
|
-
const list = sorted.map(b => ({
|
|
369
|
+
const sorted = Array.from(dedupMap.values()).sort((a, b) => new Date(b.startedAtUtc || b.createdAtUtc || b.lastUpdatedUtc || "").getTime() -
|
|
370
|
+
new Date(a.startedAtUtc || a.createdAtUtc || a.lastUpdatedUtc || "").getTime());
|
|
371
|
+
const list = sorted.map((b) => ({
|
|
363
372
|
id: b.id,
|
|
364
|
-
timestamp: b.startedAtUtc ||
|
|
373
|
+
timestamp: b.startedAtUtc ||
|
|
374
|
+
b.createdAtUtc ||
|
|
375
|
+
b.lastUpdatedUtc ||
|
|
376
|
+
new Date().toISOString(),
|
|
365
377
|
info: b,
|
|
366
378
|
}));
|
|
367
379
|
const now = new Date();
|
|
@@ -396,16 +408,18 @@ export function TranslationBatches() {
|
|
|
396
408
|
return dateGroups;
|
|
397
409
|
}, [batches]);
|
|
398
410
|
const loadBatchJobs = useCallback(async (batchId) => {
|
|
399
|
-
setLoadingJobs(prev => {
|
|
411
|
+
setLoadingJobs((prev) => {
|
|
400
412
|
const next = new Set(prev);
|
|
401
413
|
next.add(batchId);
|
|
402
414
|
return next;
|
|
403
415
|
});
|
|
404
416
|
try {
|
|
405
417
|
const res = await listBatchTranslationJobs(batchId);
|
|
406
|
-
const data = (res?.data ??
|
|
418
|
+
const data = (res?.data ??
|
|
419
|
+
res ??
|
|
420
|
+
[]);
|
|
407
421
|
const jobs = Array.isArray(data) ? data : [];
|
|
408
|
-
setBatchJobs(prev => ({ ...prev, [batchId]: jobs }));
|
|
422
|
+
setBatchJobs((prev) => ({ ...prev, [batchId]: jobs }));
|
|
409
423
|
return jobs;
|
|
410
424
|
}
|
|
411
425
|
catch (error) {
|
|
@@ -413,7 +427,7 @@ export function TranslationBatches() {
|
|
|
413
427
|
return [];
|
|
414
428
|
}
|
|
415
429
|
finally {
|
|
416
|
-
setLoadingJobs(prev => {
|
|
430
|
+
setLoadingJobs((prev) => {
|
|
417
431
|
const next = new Set(prev);
|
|
418
432
|
next.delete(batchId);
|
|
419
433
|
return next;
|
|
@@ -424,7 +438,7 @@ export function TranslationBatches() {
|
|
|
424
438
|
loadBatchJobsRef.current = loadBatchJobs;
|
|
425
439
|
}, [loadBatchJobs]);
|
|
426
440
|
const toggleBatch = useCallback((batchId) => {
|
|
427
|
-
setExpandedBatchId(prev => {
|
|
441
|
+
setExpandedBatchId((prev) => {
|
|
428
442
|
const next = prev === batchId ? null : batchId;
|
|
429
443
|
if (next && !batchJobs[next]) {
|
|
430
444
|
void loadBatchJobs(next);
|
|
@@ -434,7 +448,7 @@ export function TranslationBatches() {
|
|
|
434
448
|
setExpandedItems(new Set());
|
|
435
449
|
}, [batchJobs, loadBatchJobs]);
|
|
436
450
|
const toggleItem = useCallback((key) => {
|
|
437
|
-
setExpandedItems(prev => {
|
|
451
|
+
setExpandedItems((prev) => {
|
|
438
452
|
const next = new Set(prev);
|
|
439
453
|
if (next.has(key))
|
|
440
454
|
next.delete(key);
|
|
@@ -450,11 +464,13 @@ export function TranslationBatches() {
|
|
|
450
464
|
acceptLabel: "Abort",
|
|
451
465
|
showCancel: true,
|
|
452
466
|
accept: async () => {
|
|
453
|
-
setAbortingBatchIds(prev => new Set(prev).add(batchId));
|
|
467
|
+
setAbortingBatchIds((prev) => new Set(prev).add(batchId));
|
|
454
468
|
try {
|
|
455
469
|
const result = await abortBatch(batchId);
|
|
456
470
|
if (result.type !== "success") {
|
|
457
|
-
toast.error(result.details ||
|
|
471
|
+
toast.error(result.details ||
|
|
472
|
+
result.summary ||
|
|
473
|
+
"Failed to abort translation batch.");
|
|
458
474
|
return;
|
|
459
475
|
}
|
|
460
476
|
toast.success("Translation batch aborted.");
|
|
@@ -464,7 +480,7 @@ export function TranslationBatches() {
|
|
|
464
480
|
}
|
|
465
481
|
}
|
|
466
482
|
finally {
|
|
467
|
-
setAbortingBatchIds(prev => {
|
|
483
|
+
setAbortingBatchIds((prev) => {
|
|
468
484
|
const next = new Set(prev);
|
|
469
485
|
next.delete(batchId);
|
|
470
486
|
return next;
|
|
@@ -472,7 +488,13 @@ export function TranslationBatches() {
|
|
|
472
488
|
}
|
|
473
489
|
},
|
|
474
490
|
});
|
|
475
|
-
}, [
|
|
491
|
+
}, [
|
|
492
|
+
editContext,
|
|
493
|
+
effectiveFilters,
|
|
494
|
+
expandedBatchId,
|
|
495
|
+
loadBatchJobs,
|
|
496
|
+
loadRecentBatches,
|
|
497
|
+
]);
|
|
476
498
|
const handleDeleteBatch = useCallback((batchId) => {
|
|
477
499
|
editContext?.confirm({
|
|
478
500
|
header: "Delete Translation History",
|
|
@@ -480,24 +502,26 @@ export function TranslationBatches() {
|
|
|
480
502
|
acceptLabel: "Delete",
|
|
481
503
|
showCancel: true,
|
|
482
504
|
accept: async () => {
|
|
483
|
-
setDeletingBatchIds(prev => new Set(prev).add(batchId));
|
|
505
|
+
setDeletingBatchIds((prev) => new Set(prev).add(batchId));
|
|
484
506
|
try {
|
|
485
507
|
const result = await deleteBatch(batchId);
|
|
486
508
|
if (result.type !== "success") {
|
|
487
|
-
toast.error(result.details ||
|
|
509
|
+
toast.error(result.details ||
|
|
510
|
+
result.summary ||
|
|
511
|
+
"Failed to delete translation batch.");
|
|
488
512
|
return;
|
|
489
513
|
}
|
|
490
514
|
toast.success("Translation batch deleted.");
|
|
491
|
-
setBatches(prev => prev.filter(batch => batch.id !== batchId));
|
|
492
|
-
setBatchJobs(prev => {
|
|
515
|
+
setBatches((prev) => prev.filter((batch) => batch.id !== batchId));
|
|
516
|
+
setBatchJobs((prev) => {
|
|
493
517
|
const next = { ...prev };
|
|
494
518
|
delete next[batchId];
|
|
495
519
|
return next;
|
|
496
520
|
});
|
|
497
|
-
setExpandedBatchId(prev => prev === batchId ? null : prev);
|
|
521
|
+
setExpandedBatchId((prev) => (prev === batchId ? null : prev));
|
|
498
522
|
}
|
|
499
523
|
finally {
|
|
500
|
-
setDeletingBatchIds(prev => {
|
|
524
|
+
setDeletingBatchIds((prev) => {
|
|
501
525
|
const next = new Set(prev);
|
|
502
526
|
next.delete(batchId);
|
|
503
527
|
return next;
|
|
@@ -520,7 +544,7 @@ export function TranslationBatches() {
|
|
|
520
544
|
toast.error("No failed translations are available to retry.");
|
|
521
545
|
return;
|
|
522
546
|
}
|
|
523
|
-
setRetryingKeys(prev => new Set(prev).add(retryKey));
|
|
547
|
+
setRetryingKeys((prev) => new Set(prev).add(retryKey));
|
|
524
548
|
try {
|
|
525
549
|
const result = await retryBatchTranslation({
|
|
526
550
|
sessionId,
|
|
@@ -529,7 +553,9 @@ export function TranslationBatches() {
|
|
|
529
553
|
jobs: retryJobs,
|
|
530
554
|
});
|
|
531
555
|
if (result.type !== "success") {
|
|
532
|
-
toast.error(result.details ||
|
|
556
|
+
toast.error(result.details ||
|
|
557
|
+
result.summary ||
|
|
558
|
+
"Failed to retry translations.");
|
|
533
559
|
return;
|
|
534
560
|
}
|
|
535
561
|
const payload = (result?.data ?? result);
|
|
@@ -552,16 +578,23 @@ export function TranslationBatches() {
|
|
|
552
578
|
}
|
|
553
579
|
}
|
|
554
580
|
finally {
|
|
555
|
-
setRetryingKeys(prev => {
|
|
581
|
+
setRetryingKeys((prev) => {
|
|
556
582
|
const next = new Set(prev);
|
|
557
583
|
next.delete(retryKey);
|
|
558
584
|
return next;
|
|
559
585
|
});
|
|
560
586
|
}
|
|
561
|
-
}, [
|
|
587
|
+
}, [
|
|
588
|
+
batchJobs,
|
|
589
|
+
editContext?.sessionId,
|
|
590
|
+
effectiveFilters,
|
|
591
|
+
expandedBatchId,
|
|
592
|
+
loadBatchJobs,
|
|
593
|
+
loadRecentBatches,
|
|
594
|
+
]);
|
|
562
595
|
const handleRetryBatch = useCallback(async (batch) => {
|
|
563
596
|
const batchId = batch.id;
|
|
564
|
-
const jobs = batchJobs[batchId] ?? await loadBatchJobs(batchId);
|
|
597
|
+
const jobs = batchJobs[batchId] ?? (await loadBatchJobs(batchId));
|
|
565
598
|
await handleRetryJobs(batchId, batch.provider, jobs, `batch:${batchId}`);
|
|
566
599
|
}, [batchJobs, handleRetryJobs, loadBatchJobs]);
|
|
567
600
|
const openItemInEditor = useCallback(async (itemId, language) => {
|
|
@@ -577,8 +610,14 @@ export function TranslationBatches() {
|
|
|
577
610
|
filters.provider !== "all" ||
|
|
578
611
|
filters.targetLanguage !== "all" ||
|
|
579
612
|
filters.itemIdOrPath.trim() !== "";
|
|
580
|
-
const filterLabelClass = "flex items-center gap-1.5 text-[10px] font-semibold
|
|
581
|
-
return (_jsxs("div", { className: "flex h-full flex-col min-h-0 bg-background", "data-testid": "translation-batches", children: [_jsx("div", { className: "shrink-0 px-4 pt-4 pb-3 md:px-6", children: _jsxs("div", { className: "flex flex-wrap items-end gap-x-3 gap-y-2", children: [_jsxs("div", { className: "flex min-w-[160px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(CalendarIcon, { className: "h-3 w-3" }), "Date Range"] }), _jsx(Select, { className: "w-full bg-
|
|
613
|
+
const filterLabelClass = "flex items-center gap-1.5 text-[10px] font-semibold tracking-[0.06em] text-neutral-grey-50";
|
|
614
|
+
return (_jsxs("div", { className: "flex h-full flex-col min-h-0 bg-background", "data-testid": "translation-batches", children: [_jsx("div", { className: "shrink-0 px-4 pt-4 pb-3 md:px-6", children: _jsxs("div", { className: "flex flex-wrap items-end gap-x-3 gap-y-2", children: [_jsxs("div", { className: "flex min-w-[160px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(CalendarIcon, { className: "h-3 w-3" }), "Date Range"] }), _jsx(Select, { className: "w-full bg-neutral-grey-5", options: DATE_RANGE_OPTIONS, value: filters.dateRange, onValueChange: (value) => setFilters((prev) => ({
|
|
615
|
+
...prev,
|
|
616
|
+
dateRange: value,
|
|
617
|
+
})), placeholder: "Select date range" })] }), _jsxs("div", { className: "flex min-w-[140px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(StatusIcon, { className: "h-3 w-3" }), "Status"] }), _jsx(Select, { className: "w-full bg-neutral-grey-5", options: STATUS_OPTIONS, value: filters.status, onValueChange: (value) => setFilters((prev) => ({
|
|
618
|
+
...prev,
|
|
619
|
+
status: value,
|
|
620
|
+
})), placeholder: "Select status" })] }), _jsxs("div", { className: "flex min-w-[200px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(UserFilterIcon, { className: "h-3 w-3" }), "User"] }), _jsx(UserPicker, { value: isLimitedPreviewUser
|
|
582
621
|
? currentUserName
|
|
583
622
|
: filters.user === "all"
|
|
584
623
|
? null
|
|
@@ -587,34 +626,44 @@ export function TranslationBatches() {
|
|
|
587
626
|
: undefined, onChange: (user) => setFilters((prev) => ({
|
|
588
627
|
...prev,
|
|
589
628
|
user: user?.userName ?? "all",
|
|
590
|
-
})), disabled: isLimitedPreviewUser, placeholder: "All Users", buttonClassName: "h-[27px] w-full justify-start rounded-md border bg-
|
|
629
|
+
})), disabled: isLimitedPreviewUser, placeholder: "All Users", buttonClassName: "h-[27px] w-full justify-start rounded-md border bg-neutral-grey-5 text-xs font-medium", allowClear: true, clearLabel: "All Users", searchUsers: searchTranslationUsers })] }), _jsxs("div", { className: "flex min-w-[160px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(ProviderIcon, { className: "h-3 w-3" }), "Provider"] }), _jsx(Select, { className: "w-full bg-neutral-grey-5", options: [
|
|
591
630
|
{ value: "all", label: "All Providers" },
|
|
592
631
|
...providers.map((p) => ({
|
|
593
632
|
value: p.name,
|
|
594
633
|
label: p.displayName || p.name,
|
|
595
634
|
})),
|
|
596
|
-
], value: filters.provider, onValueChange: (value) => setFilters((prev) => ({ ...prev, provider: value })), placeholder: "Select provider" })] }), _jsxs("div", { className: "flex min-w-[160px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(GlobeIcon, { className: "h-3 w-3" }), "Target Language"] }), _jsx(Select, { className: "w-full bg-
|
|
635
|
+
], value: filters.provider, onValueChange: (value) => setFilters((prev) => ({ ...prev, provider: value })), placeholder: "Select provider" })] }), _jsxs("div", { className: "flex min-w-[160px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(GlobeIcon, { className: "h-3 w-3" }), "Target Language"] }), _jsx(Select, { className: "w-full bg-neutral-grey-5", options: [
|
|
597
636
|
{ value: "all", label: "All Languages" },
|
|
598
637
|
...languageOptions.map((lang) => ({
|
|
599
638
|
value: lang,
|
|
600
639
|
label: lang,
|
|
601
640
|
})),
|
|
602
|
-
], value: filters.targetLanguage, onValueChange: (value) => setFilters((prev) => ({ ...prev, targetLanguage: value })), placeholder: "Select language" })] }), _jsxs("div", { className: "flex min-w-[200px] flex-1 flex-col gap-1", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(ItemIcon, { className: "h-3 w-3" }), "Item ID / Path"] }), _jsxs("label", { className: "flex items-center gap-1 text-[10px] text-
|
|
641
|
+
], value: filters.targetLanguage, onValueChange: (value) => setFilters((prev) => ({ ...prev, targetLanguage: value })), placeholder: "Select language" })] }), _jsxs("div", { className: "flex min-w-[200px] flex-1 flex-col gap-1", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(ItemIcon, { className: "h-3 w-3" }), "Item ID / Path"] }), _jsxs("label", { className: "flex items-center gap-1 text-[10px] text-neutral-grey-50 cursor-pointer select-none", children: [_jsx("input", { type: "checkbox", checked: filters.itemIncludeSubitems, onChange: (e) => setFilters((prev) => ({
|
|
642
|
+
...prev,
|
|
643
|
+
itemIncludeSubitems: e.target.checked,
|
|
644
|
+
})), className: "h-3 w-3 accent-highlight-100 cursor-pointer" }), "Include subitems"] })] }), _jsxs("div", { className: "relative", children: [_jsx(Input, { value: itemIdOrPathInput, onChange: (e) => {
|
|
603
645
|
const next = e.target.value;
|
|
604
646
|
setItemIdOrPathInput(next);
|
|
605
647
|
debouncedSetItemFilter(next);
|
|
606
|
-
}, placeholder: "GUID or /sitecore/content/\u2026", className: "h-[27px] w-full bg-
|
|
648
|
+
}, placeholder: "GUID or /sitecore/content/\u2026", className: "h-[27px] w-full bg-neutral-grey-5 text-xs pr-14" }), _jsxs("div", { className: "absolute right-1.5 top-1/2 -translate-y-1/2 flex items-center gap-1", children: [itemIdOrPathInput && (_jsx("button", { type: "button", onClick: () => {
|
|
607
649
|
setItemIdOrPathInput("");
|
|
608
650
|
debouncedSetItemFilter.cancel();
|
|
609
|
-
setFilters((prev) => ({
|
|
610
|
-
|
|
651
|
+
setFilters((prev) => ({
|
|
652
|
+
...prev,
|
|
653
|
+
itemIdOrPath: "",
|
|
654
|
+
itemIncludeSubitems: false,
|
|
655
|
+
}));
|
|
656
|
+
}, className: "text-neutral-grey-50 hover:text-neutral-grey-100 cursor-pointer", "aria-label": "Clear", children: _jsx(XIcon, { className: "h-3.5 w-3.5" }) })), _jsxs(Popover, { open: itemPickerOpen, onOpenChange: setItemPickerOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "text-neutral-grey-50 hover:text-neutral-grey-100 cursor-pointer", "aria-label": "Browse content tree", title: "Browse content tree", children: _jsx(FolderTreeIcon, { className: "h-3.5 w-3.5" }) }) }), _jsxs(PopoverContent, { align: "end", sideOffset: 6, className: "w-[28rem] max-w-[calc(100vw-2rem)] p-0", children: [_jsx("div", { className: "flex items-center justify-between gap-2 border-b border-border-default px-3 py-2 text-[11px] text-neutral-grey-50", children: _jsx("span", { children: "Pick item to filter by" }) }), _jsx("div", { className: "max-h-80 overflow-auto p-2", children: _jsx(ContentTree, { language: "en", selectionMode: "single", rootItemIds: ["0de95ae4-41ab-4d01-9eb0-67441b7c2450"], onSelectionChange: (items) => {
|
|
611
657
|
const picked = items?.[0];
|
|
612
658
|
if (!picked)
|
|
613
659
|
return;
|
|
614
660
|
const applyValue = (value) => {
|
|
615
661
|
setItemIdOrPathInput(value);
|
|
616
662
|
debouncedSetItemFilter.cancel();
|
|
617
|
-
setFilters((prev) => ({
|
|
663
|
+
setFilters((prev) => ({
|
|
664
|
+
...prev,
|
|
665
|
+
itemIdOrPath: value,
|
|
666
|
+
}));
|
|
618
667
|
setItemPickerOpen(false);
|
|
619
668
|
};
|
|
620
669
|
if (picked.path) {
|
|
@@ -623,13 +672,15 @@ export function TranslationBatches() {
|
|
|
623
672
|
}
|
|
624
673
|
// Fall back: resolve the item's path via a stub fetch
|
|
625
674
|
// so the input shows a human-readable path instead of a GUID.
|
|
626
|
-
fetchItemStubs([
|
|
675
|
+
fetchItemStubs([
|
|
676
|
+
{ id: picked.id, language: "en", version: 1 },
|
|
677
|
+
])
|
|
627
678
|
.then((stubs) => {
|
|
628
679
|
const stubPath = stubs?.[0]?.path;
|
|
629
680
|
applyValue(stubPath || picked.id);
|
|
630
681
|
})
|
|
631
682
|
.catch(() => applyValue(picked.id));
|
|
632
|
-
} }) })] })] })] })] })] })] }) }), _jsx("div", { className: "flex-1 overflow-auto p-4 md:p-6 min-h-0", children: isLoading && batches.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "flex items-center gap-2 text-
|
|
683
|
+
} }) })] })] })] })] })] })] }) }), _jsx("div", { className: "flex-1 overflow-auto p-4 md:p-6 min-h-0", children: isLoading && batches.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "flex items-center gap-2 text-neutral-grey-50", children: [_jsx(LoaderIcon, { className: "h-5 w-5 animate-spin text-highlight-100" }), "Loading recent translations..."] }) })) : groupedBatches.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-neutral-grey-50", children: [_jsx(LanguagesIcon, { className: "h-8 w-8 block mx-auto mb-2 text-neutral-grey-15" }), _jsx("p", { className: "font-medium text-neutral-grey-100", children: "No translations found" }), _jsx("p", { className: "text-sm mt-1", children: (() => {
|
|
633
684
|
const hasActiveFilters = filters.dateRange !== "last30days" ||
|
|
634
685
|
filters.status !== "all" ||
|
|
635
686
|
filters.user !== currentUserName ||
|
|
@@ -639,45 +690,53 @@ export function TranslationBatches() {
|
|
|
639
690
|
return hasActiveFilters
|
|
640
691
|
? "Try adjusting your filters or start a translation to see it appear here"
|
|
641
692
|
: "Start a translation to see it appear here";
|
|
642
|
-
})() })] }) })) : (_jsxs("div", { className: "space-y-10", children: [groupedBatches.map((dateGroup) => (_jsxs("div", { children: [_jsxs("div", { className: "flex items-baseline gap-2 mb-2", children: [_jsx("h2", { className: "text-[11px] font-semibold
|
|
693
|
+
})() })] }) })) : (_jsxs("div", { className: "space-y-10", children: [groupedBatches.map((dateGroup) => (_jsxs("div", { children: [_jsxs("div", { className: "flex items-baseline gap-2 mb-2", children: [_jsx("h2", { className: "text-[11px] font-semibold tracking-[0.08em] text-neutral-grey-50", children: dateGroup.label }), _jsx("span", { className: "flex-1 ml-2 border-t border-border-default" })] }), _jsx("div", { className: "divide-y divide-border-default/70", children: dateGroup.batches.map((b) => {
|
|
643
694
|
const batchDate = new Date(b.timestamp);
|
|
644
695
|
const now = new Date();
|
|
645
696
|
const isToday = batchDate.toDateString() === now.toDateString();
|
|
646
697
|
const timeDisplay = isToday
|
|
647
698
|
? batchDate.toLocaleTimeString()
|
|
648
|
-
: batchDate.toLocaleDateString() +
|
|
699
|
+
: batchDate.toLocaleDateString() +
|
|
700
|
+
" " +
|
|
701
|
+
batchDate.toLocaleTimeString();
|
|
649
702
|
const info = b.info;
|
|
650
703
|
const totalJobs = info?.expectedJobs ?? 0;
|
|
651
|
-
const itemsCount = typeof info?.itemsCount ===
|
|
704
|
+
const itemsCount = typeof info?.itemsCount === "number"
|
|
705
|
+
? info.itemsCount
|
|
706
|
+
: undefined;
|
|
652
707
|
const languagesCsv = info?.languages;
|
|
653
|
-
const languages = languagesCsv
|
|
708
|
+
const languages = languagesCsv
|
|
709
|
+
? languagesCsv.split(",")
|
|
710
|
+
: [];
|
|
654
711
|
const completedJobs = info?.completedJobs ?? 0;
|
|
655
712
|
const errorJobs = info?.errorJobs ?? 0;
|
|
656
713
|
const anyError = errorJobs > 0;
|
|
657
714
|
const anyInProgress = isActiveBatchStatus(info?.status);
|
|
658
715
|
const anyAborted = info?.status === "Aborted";
|
|
659
|
-
const progressPct = totalJobs > 0
|
|
716
|
+
const progressPct = totalJobs > 0
|
|
717
|
+
? Math.min(100, Math.round((completedJobs / totalJobs) * 100))
|
|
718
|
+
: 0;
|
|
660
719
|
const showProgress = totalJobs > 0 && (anyInProgress || anyError);
|
|
661
720
|
const statusDotClass = anyError
|
|
662
|
-
? "bg-red
|
|
721
|
+
? "bg-feedback-red"
|
|
663
722
|
: anyInProgress
|
|
664
|
-
? "bg-
|
|
723
|
+
? "bg-highlight-100"
|
|
665
724
|
: anyAborted
|
|
666
|
-
? "bg-
|
|
667
|
-
: "bg-
|
|
725
|
+
? "bg-feedback-orange"
|
|
726
|
+
: "bg-feedback-green/70";
|
|
668
727
|
const metaParts = [];
|
|
669
|
-
metaParts.push(_jsxs("span", { className: "text-
|
|
670
|
-
if (typeof itemsCount ===
|
|
671
|
-
metaParts.push(_jsxs("span", { children: [itemsCount, " item", itemsCount !== 1 ?
|
|
728
|
+
metaParts.push(_jsxs("span", { className: "text-neutral-grey-100", children: [totalJobs, " translation", totalJobs !== 1 ? "s" : ""] }, "trans"));
|
|
729
|
+
if (typeof itemsCount === "number") {
|
|
730
|
+
metaParts.push(_jsxs("span", { children: [itemsCount, " item", itemsCount !== 1 ? "s" : ""] }, "items"));
|
|
672
731
|
}
|
|
673
732
|
if (languages.length > 0) {
|
|
674
|
-
metaParts.push(_jsxs("span", { title: languages.join(", "), children: [languages.length, " language", languages.length !== 1 ?
|
|
733
|
+
metaParts.push(_jsxs("span", { title: languages.join(", "), children: [languages.length, " language", languages.length !== 1 ? "s" : ""] }, "langs"));
|
|
675
734
|
}
|
|
676
735
|
if (info?.provider) {
|
|
677
736
|
metaParts.push(_jsx("span", { children: info.provider }, "prov"));
|
|
678
737
|
}
|
|
679
738
|
if (info?.totalCost != null) {
|
|
680
|
-
metaParts.push(_jsxs("span", { children: ["Cost: ", _jsx("span", { className: "font-medium text-
|
|
739
|
+
metaParts.push(_jsxs("span", { children: ["Cost:", " ", _jsx("span", { className: "font-medium text-neutral-grey-100", children: formatUsdCost(info.totalCost) })] }, "cost"));
|
|
681
740
|
}
|
|
682
741
|
if (info?.initiatedByUser) {
|
|
683
742
|
const resolved = userDisplayNames[info.initiatedByUser];
|
|
@@ -701,12 +760,12 @@ export function TranslationBatches() {
|
|
|
701
760
|
: anyAborted
|
|
702
761
|
? "aborted"
|
|
703
762
|
: "completed";
|
|
704
|
-
return (_jsxs("div", { "data-testid": `batch-row-${b.id}`, "data-batch-status": testStatus, children: [_jsx("div", { role: "button", tabIndex: 0, className: "group w-full text-left px-3 md:px-4 py-3 hover:bg-
|
|
763
|
+
return (_jsxs("div", { "data-testid": `batch-row-${b.id}`, "data-batch-status": testStatus, children: [_jsx("div", { role: "button", tabIndex: 0, className: "group w-full text-left px-3 md:px-4 py-3 hover:bg-neutral-grey-5 transition-colors cursor-pointer", onClick: () => toggleBatch(b.id), onKeyDown: (e) => {
|
|
705
764
|
if (e.key === "Enter" || e.key === " ") {
|
|
706
765
|
e.preventDefault();
|
|
707
766
|
toggleBatch(b.id);
|
|
708
767
|
}
|
|
709
|
-
}, children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [anyInProgress ? (_jsx(LoaderIcon, { className: "h-3 w-3 shrink-0 animate-spin text-
|
|
768
|
+
}, children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [anyInProgress ? (_jsx(LoaderIcon, { className: "h-3 w-3 shrink-0 animate-spin text-highlight-100" })) : (_jsx("span", { className: `h-1.5 w-1.5 shrink-0 rounded-full ${statusDotClass}`, "aria-hidden": "true" })), _jsx("span", { className: "text-neutral-grey-100 truncate", title: timeDisplay, children: (() => {
|
|
710
769
|
let parsedName;
|
|
711
770
|
if (info?.metadata) {
|
|
712
771
|
try {
|
|
@@ -714,7 +773,8 @@ export function TranslationBatches() {
|
|
|
714
773
|
? JSON.parse(info.metadata)
|
|
715
774
|
: info.metadata;
|
|
716
775
|
const candidate = m?.name;
|
|
717
|
-
if (typeof candidate === "string" &&
|
|
776
|
+
if (typeof candidate === "string" &&
|
|
777
|
+
candidate.trim()) {
|
|
718
778
|
parsedName = candidate.trim();
|
|
719
779
|
}
|
|
720
780
|
}
|
|
@@ -722,18 +782,22 @@ export function TranslationBatches() {
|
|
|
722
782
|
// Metadata isn't JSON — fall through to timestamp.
|
|
723
783
|
}
|
|
724
784
|
}
|
|
725
|
-
return parsedName ??
|
|
726
|
-
|
|
785
|
+
return (parsedName ??
|
|
786
|
+
`Translation Batch · ${timeDisplay}`);
|
|
787
|
+
})() }), showProgress && (_jsxs("span", { className: `inline-flex items-center gap-1 text-[11px] tabular-nums ${anyError ? "text-feedback-red" : "text-highlight-100"}`, "data-testid": `batch-progress-${b.id}`, children: [completedJobs, "/", totalJobs, anyError && (_jsxs("span", { className: "text-feedback-red", children: ["\u00B7 ", errorJobs, " error", errorJobs !== 1 ? "s" : ""] }))] }))] }), _jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[12px] text-neutral-grey-50 pl-4", children: [metaParts.map((part, i) => (_jsxs(React.Fragment, { children: [i > 0 && (_jsx("span", { className: "text-neutral-grey-15", children: "\u00B7" })), part] }, i))), info?.lastUpdatedUtc && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-neutral-grey-15", children: "\u00B7" }), _jsx("span", { children: new Date(info.lastUpdatedUtc).toLocaleString() })] }))] })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [canRetry && (_jsx(SimpleIconButton, { onClick: (event) => {
|
|
727
788
|
event.stopPropagation();
|
|
728
789
|
void handleRetryBatch(info);
|
|
729
|
-
}, icon: _jsx(RetryIcon, { className: `h-3.5 w-3.5 ${isRetryingBatch ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Retry failed translations", disabled: isRetryingBatch ||
|
|
790
|
+
}, icon: _jsx(RetryIcon, { className: `h-3.5 w-3.5 ${isRetryingBatch ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Retry failed translations", disabled: isRetryingBatch ||
|
|
791
|
+
isLoadingThis ||
|
|
792
|
+
isAborting ||
|
|
793
|
+
isDeleting, className: "p-0! text-feedback-red hover:text-feedback-red" })), canAbort && (_jsx(SimpleIconButton, { onClick: (event) => {
|
|
730
794
|
event.stopPropagation();
|
|
731
795
|
handleAbortBatch(b.id);
|
|
732
|
-
}, icon: _jsx(AbortIcon, { className: `h-3.5 w-3.5 ${isAborting ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Abort translation batch", disabled: isAborting || isDeleting, className: "p-0! text-
|
|
796
|
+
}, icon: _jsx(AbortIcon, { className: `h-3.5 w-3.5 ${isAborting ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Abort translation batch", disabled: isAborting || isDeleting, className: "p-0! text-feedback-orange hover:text-feedback-orange" })), canDelete && (_jsx(SimpleIconButton, { onClick: (event) => {
|
|
733
797
|
event.stopPropagation();
|
|
734
798
|
handleDeleteBatch(b.id);
|
|
735
|
-
}, icon: _jsx(
|
|
736
|
-
}) })] }, dateGroup.label))), hasMore && (_jsx("div", { className: "flex justify-center pt-6", children: _jsx(Button, { variant: "outline", size: "sm", onClick: loadMore, disabled: isLoadingMore, children: isLoadingMore ? (_jsxs(_Fragment, { children: [_jsx(LoaderIcon, { className: "h-4 w-4 animate-spin text-
|
|
799
|
+
}, icon: _jsx(DeleteIcon, { size: "md" }), label: "Delete translation history", disabled: isDeleting || isAborting, className: "p-0! text-neutral-grey-50 hover:bg-neutral-grey-5 hover:text-neutral-grey-50" })), _jsx("div", { className: `text-neutral-grey-15 transition-transform ${isExpanded ? "rotate-180" : ""} group-hover:text-neutral-grey-50`, children: _jsx(ChevronDownIcon, { className: "h-4 w-4" }) })] })] }) }), isExpanded && (_jsxs(_Fragment, { children: [_jsx(CustomPromptPanel, { prompts: parseCustomPrompts(info?.metadata) }), _jsx(BatchItemList, { batchId: b.id, batchProvider: info?.provider, jobs: jobs, isLoading: isLoadingThis, expandedItems: expandedItems, languages: sitecoreLanguages, itemFilter: filters.itemIdOrPath.trim(), itemIncludeSubitems: filters.itemIncludeSubitems, statusFilter: filters.status, batchIsTerminal: isTerminalBatchStatus(info?.status), batchStartedAtUtc: info?.startedAtUtc ?? info?.createdAtUtc, retryingKeys: retryingKeys, onToggleItem: toggleItem, onOpenItem: openItemInEditor, onRetryJobs: handleRetryJobs })] }))] }, b.id));
|
|
800
|
+
}) })] }, dateGroup.label))), hasMore && (_jsx("div", { className: "flex justify-center pt-6", children: _jsx(Button, { variant: "outline", size: "sm", onClick: loadMore, disabled: isLoadingMore, children: isLoadingMore ? (_jsxs(_Fragment, { children: [_jsx(LoaderIcon, { className: "h-4 w-4 animate-spin text-highlight-100" }), "Loading more..."] })) : (_jsxs(_Fragment, { children: [_jsx(ChevronDownIcon, { className: "h-4 w-4" }), "Load More Batches"] })) }) }))] })) })] }));
|
|
737
801
|
}
|
|
738
802
|
function isLikelyGuid(value) {
|
|
739
803
|
return /^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[)}]?$/i.test(value.trim());
|
|
@@ -785,7 +849,9 @@ function parseTranslationStatistics(metadata) {
|
|
|
785
849
|
const fieldCount = Number(stats.fieldCount);
|
|
786
850
|
const nonEmptyFieldCount = Number(stats.nonEmptyFieldCount ?? stats.translatedFieldCount ?? fieldCount);
|
|
787
851
|
const emptyFieldCount = Number(stats.emptyFieldCount ?? Math.max(0, fieldCount - nonEmptyFieldCount));
|
|
788
|
-
const sourceCharacterCount = Number(stats.sourceCharacterCount ??
|
|
852
|
+
const sourceCharacterCount = Number(stats.sourceCharacterCount ??
|
|
853
|
+
stats.textCharacterCount ??
|
|
854
|
+
stats.characterCount);
|
|
789
855
|
if (!Number.isFinite(fieldCount) ||
|
|
790
856
|
!Number.isFinite(nonEmptyFieldCount) ||
|
|
791
857
|
!Number.isFinite(emptyFieldCount) ||
|
|
@@ -815,19 +881,19 @@ function formatUsdCost(value) {
|
|
|
815
881
|
}).format(value);
|
|
816
882
|
}
|
|
817
883
|
function itemStatusDot(jobs) {
|
|
818
|
-
const hasError = jobs.some(j => j.status === "Error");
|
|
819
|
-
const hasInProgress = jobs.some(j => j.status === "In Progress");
|
|
820
|
-
const hasPending = jobs.some(j => j.status === "Pending");
|
|
821
|
-
const hasAborted = jobs.some(j => j.status === "Aborted");
|
|
884
|
+
const hasError = jobs.some((j) => j.status === "Error");
|
|
885
|
+
const hasInProgress = jobs.some((j) => j.status === "In Progress");
|
|
886
|
+
const hasPending = jobs.some((j) => j.status === "Pending");
|
|
887
|
+
const hasAborted = jobs.some((j) => j.status === "Aborted");
|
|
822
888
|
if (hasError)
|
|
823
|
-
return { cls: "bg-red
|
|
889
|
+
return { cls: "bg-feedback-red", label: "Has errors" };
|
|
824
890
|
if (hasInProgress)
|
|
825
|
-
return { cls: "bg-
|
|
891
|
+
return { cls: "bg-highlight-100", label: "In progress" };
|
|
826
892
|
if (hasPending)
|
|
827
|
-
return { cls: "bg-
|
|
893
|
+
return { cls: "bg-neutral-grey-15", label: "Queued" };
|
|
828
894
|
if (hasAborted)
|
|
829
|
-
return { cls: "bg-
|
|
830
|
-
return { cls: "bg-
|
|
895
|
+
return { cls: "bg-feedback-orange", label: "Aborted" };
|
|
896
|
+
return { cls: "bg-feedback-green/70", label: "Completed" };
|
|
831
897
|
}
|
|
832
898
|
function CustomPromptPanel({ prompts }) {
|
|
833
899
|
const [openProvider, setOpenProvider] = React.useState(null);
|
|
@@ -853,21 +919,21 @@ function CustomPromptPanel({ prompts }) {
|
|
|
853
919
|
toast.error("Failed to copy prompt to clipboard");
|
|
854
920
|
}
|
|
855
921
|
};
|
|
856
|
-
return (_jsx("div", { className: "bg-
|
|
922
|
+
return (_jsx("div", { className: "bg-neutral-grey-5/60 border-t border-border-default/50", children: prompts.map((p) => {
|
|
857
923
|
const isOpen = openProvider === p.provider;
|
|
858
924
|
const justCopied = copiedProvider === p.provider;
|
|
859
|
-
return (_jsxs("div", { children: [_jsxs("div", { role: "button", tabIndex: 0, style: { paddingLeft: "2rem" }, className: "group flex items-center gap-2 pr-3 md:pr-4 py-2 text-[12px] text-
|
|
925
|
+
return (_jsxs("div", { children: [_jsxs("div", { role: "button", tabIndex: 0, style: { paddingLeft: "2rem" }, className: "group flex items-center gap-2 pr-3 md:pr-4 py-2 text-[12px] text-neutral-grey-50 hover:bg-neutral-grey-5 transition-colors cursor-pointer", onClick: () => setOpenProvider(isOpen ? null : p.provider), onKeyDown: (e) => {
|
|
860
926
|
if (e.key === "Enter" || e.key === " ") {
|
|
861
927
|
e.preventDefault();
|
|
862
928
|
setOpenProvider(isOpen ? null : p.provider);
|
|
863
929
|
}
|
|
864
|
-
}, children: [_jsx(SparklesIcon, { className: "h-3 w-3 shrink-0 text-
|
|
930
|
+
}, children: [_jsx(SparklesIcon, { className: "h-3 w-3 shrink-0 text-highlight-100", strokeWidth: 1.75 }), _jsx("span", { className: "text-neutral-grey-100", children: "Custom prompt" }), _jsx("span", { className: "text-neutral-grey-15", children: "\u00B7" }), _jsx("span", { children: p.provider }), _jsxs("span", { className: "ml-auto flex items-center gap-2", children: [isOpen && (_jsx(SimpleIconButton, { onClick: (event) => {
|
|
865
931
|
event.stopPropagation();
|
|
866
932
|
void handleCopy(p.provider, p.prompt);
|
|
867
|
-
}, icon: justCopied ? (_jsx(CheckIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.5 })) : (_jsx(CopyIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.5 })), label: justCopied ? "Copied" : "Copy prompt", className: "p-0! text-
|
|
933
|
+
}, icon: justCopied ? (_jsx(CheckIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.5 })) : (_jsx(CopyIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.5 })), label: justCopied ? "Copied" : "Copy prompt", className: "p-0! text-neutral-grey-50 hover:text-neutral-grey-100" })), _jsx(ChevronDownIcon, { className: `h-3.5 w-3.5 text-neutral-grey-15 transition-transform group-hover:text-neutral-grey-50 ${isOpen ? "rotate-180" : ""}` })] })] }), isOpen && (_jsx("div", { style: { paddingLeft: "2.5rem" }, className: "pr-3 md:pr-4 pb-3", children: _jsx("pre", { className: "max-h-64 overflow-y-auto whitespace-pre-wrap break-words rounded border border-border-default/50 bg-white p-2 font-mono text-[12px] text-neutral-grey-100", children: p.prompt }) }))] }, p.provider));
|
|
868
934
|
}) }));
|
|
869
935
|
}
|
|
870
|
-
function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems, languages, itemFilter, itemIncludeSubitems, statusFilter, batchIsTerminal, batchStartedAtUtc, retryingKeys, onToggleItem, onOpenItem, onRetryJobs }) {
|
|
936
|
+
function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems, languages, itemFilter, itemIncludeSubitems, statusFilter, batchIsTerminal, batchStartedAtUtc, retryingKeys, onToggleItem, onOpenItem, onRetryJobs, }) {
|
|
871
937
|
const languageMap = React.useMemo(() => {
|
|
872
938
|
const map = new Map();
|
|
873
939
|
for (const l of languages) {
|
|
@@ -907,11 +973,11 @@ function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems,
|
|
|
907
973
|
if (isLikelyGuid(filter)) {
|
|
908
974
|
const normalized = normalizeGuid(filter);
|
|
909
975
|
if (itemIncludeSubitems) {
|
|
910
|
-
const rootJob = effectiveJobs.find(j => normalizeGuid((j.itemId || "").toString()) === normalized);
|
|
976
|
+
const rootJob = effectiveJobs.find((j) => normalizeGuid((j.itemId || "").toString()) === normalized);
|
|
911
977
|
pathPrefix = rootJob?.itemPath ? rootJob.itemPath.toLowerCase() : null;
|
|
912
978
|
}
|
|
913
979
|
if (!pathPrefix) {
|
|
914
|
-
return effectiveJobs.filter(j => normalizeGuid((j.itemId || "").toString()) === normalized);
|
|
980
|
+
return effectiveJobs.filter((j) => normalizeGuid((j.itemId || "").toString()) === normalized);
|
|
915
981
|
}
|
|
916
982
|
}
|
|
917
983
|
else if (filter.startsWith("/")) {
|
|
@@ -923,12 +989,12 @@ function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems,
|
|
|
923
989
|
if (itemIncludeSubitems) {
|
|
924
990
|
const prefix = pathPrefix;
|
|
925
991
|
const prefixSlash = prefix.endsWith("/") ? prefix : prefix + "/";
|
|
926
|
-
return effectiveJobs.filter(j => {
|
|
992
|
+
return effectiveJobs.filter((j) => {
|
|
927
993
|
const p = (j.itemPath || "").toLowerCase();
|
|
928
994
|
return p === prefix || p.startsWith(prefixSlash);
|
|
929
995
|
});
|
|
930
996
|
}
|
|
931
|
-
return effectiveJobs.filter(j => (j.itemPath || "").toLowerCase() === pathPrefix);
|
|
997
|
+
return effectiveJobs.filter((j) => (j.itemPath || "").toLowerCase() === pathPrefix);
|
|
932
998
|
}, [effectiveJobs, itemFilter, itemIncludeSubitems]);
|
|
933
999
|
// Keep all jobs of items that have at least one job matching the selected status —
|
|
934
1000
|
// this way the per-item language overview stays intact even when filtering.
|
|
@@ -946,15 +1012,15 @@ function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems,
|
|
|
946
1012
|
}
|
|
947
1013
|
if (matchingItemIds.size === 0)
|
|
948
1014
|
return [];
|
|
949
|
-
return filteredJobs.filter(j => matchingItemIds.has((j.itemId || "").toString().toLowerCase()));
|
|
1015
|
+
return filteredJobs.filter((j) => matchingItemIds.has((j.itemId || "").toString().toLowerCase()));
|
|
950
1016
|
}, [filteredJobs, statusFilter]);
|
|
951
1017
|
if (isLoading && !jobs) {
|
|
952
|
-
return (_jsxs("div", { className: "px-3 md:px-4 py-4 pl-8 text-[12px] text-
|
|
1018
|
+
return (_jsxs("div", { className: "px-3 md:px-4 py-4 pl-8 text-[12px] text-neutral-grey-50", children: [_jsx(LoaderIcon, { className: "inline h-3.5 w-3.5 mr-1.5 animate-spin text-highlight-100" }), "Loading items\u2026"] }));
|
|
953
1019
|
}
|
|
954
1020
|
if (!statusFilteredJobs || statusFilteredJobs.length === 0) {
|
|
955
1021
|
const hasItemFilter = itemFilter.trim() !== "";
|
|
956
1022
|
const hasStatusFilter = statusFilter !== "all";
|
|
957
|
-
return (_jsx("div", { className: "px-3 md:px-4 py-3 pl-8 text-[12px] text-
|
|
1023
|
+
return (_jsx("div", { className: "px-3 md:px-4 py-3 pl-8 text-[12px] text-neutral-grey-50", children: hasItemFilter || hasStatusFilter
|
|
958
1024
|
? "No items match the current filter."
|
|
959
1025
|
: "No items in this batch." }));
|
|
960
1026
|
}
|
|
@@ -965,10 +1031,10 @@ function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems,
|
|
|
965
1031
|
byItem.set(key, []);
|
|
966
1032
|
byItem.get(key).push(job);
|
|
967
1033
|
}
|
|
968
|
-
return (_jsx("div", { className: "bg-
|
|
1034
|
+
return (_jsx("div", { className: "bg-neutral-grey-5/60 divide-y divide-border-default/50 border-t border-border-default/50", children: Array.from(byItem.entries()).map(([itemId, itemJobs]) => {
|
|
969
1035
|
const isItemExpanded = expandedItems.has(itemId);
|
|
970
|
-
const itemAnyInProgress = itemJobs.some(j => j.status === "In Progress");
|
|
971
|
-
const itemAnyPending = itemJobs.some(j => j.status === "Pending");
|
|
1036
|
+
const itemAnyInProgress = itemJobs.some((j) => j.status === "In Progress");
|
|
1037
|
+
const itemAnyPending = itemJobs.some((j) => j.status === "Pending");
|
|
972
1038
|
const itemIsQueued = !itemAnyInProgress && itemAnyPending;
|
|
973
1039
|
const status = itemStatusDot(itemJobs);
|
|
974
1040
|
const firstJob = itemJobs[0];
|
|
@@ -976,28 +1042,28 @@ function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems,
|
|
|
976
1042
|
const serverName = firstJob?.itemName ?? null;
|
|
977
1043
|
const metadataName = parseItemName(firstJob?.metadata, "");
|
|
978
1044
|
const displayName = metadataName || serverName || itemId;
|
|
979
|
-
const langs = itemJobs.map(j => j.targetLanguage).filter(Boolean);
|
|
1045
|
+
const langs = itemJobs.map((j) => j.targetLanguage).filter(Boolean);
|
|
980
1046
|
const uniqueLangs = Array.from(new Set(langs));
|
|
981
1047
|
const titleAttr = `${itemPath ?? ""}\n${itemId}`.trim();
|
|
982
1048
|
const itemErrorJobs = itemJobs.filter(isRetriableJob);
|
|
983
1049
|
const itemRetryKey = `item:${batchId}:${itemId}`;
|
|
984
1050
|
const isRetryingItem = retryingKeys.has(itemRetryKey);
|
|
985
|
-
return (_jsxs("div", { children: [_jsx("div", { role: "button", tabIndex: 0, style: { paddingLeft: "2.5rem" }, className: "group w-full pr-3 md:pr-4 py-2 hover:bg-
|
|
1051
|
+
return (_jsxs("div", { children: [_jsx("div", { role: "button", tabIndex: 0, style: { paddingLeft: "2.5rem" }, className: "group w-full pr-3 md:pr-4 py-2 hover:bg-neutral-grey-5 transition-colors cursor-pointer text-left", onClick: () => onToggleItem(itemId), onKeyDown: (e) => {
|
|
986
1052
|
if (e.key === "Enter" || e.key === " ") {
|
|
987
1053
|
e.preventDefault();
|
|
988
1054
|
onToggleItem(itemId);
|
|
989
1055
|
}
|
|
990
|
-
}, children: _jsxs("div", { className: "flex items-start gap-2.5 min-w-0", children: [_jsx("span", { className: "mt-1 shrink-0", children: itemAnyInProgress ? (_jsx(LoaderIcon, { className: "h-3 w-3 animate-spin text-
|
|
1056
|
+
}, children: _jsxs("div", { className: "flex items-start gap-2.5 min-w-0", children: [_jsx("span", { className: "mt-1 shrink-0", children: itemAnyInProgress ? (_jsx(LoaderIcon, { className: "h-3 w-3 animate-spin text-highlight-100" })) : itemIsQueued ? (_jsx(QueuedIcon, { className: "h-3 w-3 text-neutral-grey-50", strokeWidth: 1.75, "aria-label": "Queued" })) : (_jsx("span", { className: `block h-1.5 w-1.5 rounded-full ${status.cls}`, "aria-hidden": "true" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("span", { className: "text-[13px] text-neutral-grey-100 truncate", title: titleAttr, children: displayName }), _jsxs("span", { className: "text-[11px] text-neutral-grey-50 shrink-0", children: ["\u00B7 ", uniqueLangs.length, " language", uniqueLangs.length !== 1 ? "s" : ""] })] }), itemPath && (_jsx("div", { className: "text-[11px] text-neutral-grey-50 truncate font-mono", title: titleAttr, children: itemPath }))] }), itemErrorJobs.length > 0 && (_jsx(SimpleIconButton, { onClick: (event) => {
|
|
991
1057
|
event.stopPropagation();
|
|
992
1058
|
void onRetryJobs(batchId, batchProvider, itemErrorJobs, itemRetryKey);
|
|
993
|
-
}, icon: _jsx(RetryIcon, { className: `h-3.5 w-3.5 ${isRetryingItem ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Retry failed translations for this item", disabled: isRetryingItem, className: "mt-0.5 p-0! text-red
|
|
1059
|
+
}, icon: _jsx(RetryIcon, { className: `h-3.5 w-3.5 ${isRetryingItem ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Retry failed translations for this item", disabled: isRetryingItem, className: "mt-0.5 p-0! text-feedback-red hover:text-feedback-red" })), _jsx("span", { className: "mt-1 shrink-0 text-neutral-grey-15 group-hover:text-neutral-grey-50", children: _jsx(ChevronDownIcon, { className: `h-3.5 w-3.5 transition-transform ${isItemExpanded ? "rotate-180" : ""}` }) })] }) }), isItemExpanded && (_jsx("div", { style: { paddingLeft: "4rem" }, className: "pr-3 md:pr-4 pb-3 pt-1 flex flex-wrap gap-1.5", children: itemJobs.map((job, idx) => {
|
|
994
1060
|
const lang = languageMap.get((job.targetLanguage || "").toLowerCase());
|
|
995
1061
|
const retryKey = `job:${job.id ?? `${job.itemId}:${job.sourceLanguage}:${job.targetLanguage}`}`;
|
|
996
1062
|
return (_jsx(LanguageJobChip, { job: job, language: lang, batchStartedAtUtc: batchStartedAtUtc, onOpen: () => onOpenItem(job.itemId, job.targetLanguage), isRetrying: retryingKeys.has(retryKey), onRetry: () => onRetryJobs(batchId, batchProvider, [job], retryKey) }, `${job.id ?? idx}-${job.targetLanguage}`));
|
|
997
1063
|
}) }))] }, itemId));
|
|
998
1064
|
}) }));
|
|
999
1065
|
}
|
|
1000
|
-
function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen, isRetrying = false, onRetry }) {
|
|
1066
|
+
function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen, isRetrying = false, onRetry, }) {
|
|
1001
1067
|
const [open, setOpen] = React.useState(false);
|
|
1002
1068
|
const jobStatus = job.status;
|
|
1003
1069
|
const jobInProgress = jobStatus === "In Progress";
|
|
@@ -1006,28 +1072,30 @@ function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen, isRetrying
|
|
|
1006
1072
|
const jobAborted = jobStatus === "Aborted";
|
|
1007
1073
|
const langName = language?.name || job.targetLanguage;
|
|
1008
1074
|
const chipCls = jobError
|
|
1009
|
-
? "border-red
|
|
1075
|
+
? "border-feedback-red bg-feedback-red-light text-feedback-red hover:bg-feedback-red-light"
|
|
1010
1076
|
: jobAborted
|
|
1011
|
-
? "border-
|
|
1077
|
+
? "border-feedback-orange bg-feedback-orange-light text-feedback-orange hover:bg-feedback-orange-light"
|
|
1012
1078
|
: jobInProgress
|
|
1013
|
-
? "border-
|
|
1079
|
+
? "border-highlight-100/30 bg-highlight-10 text-highlight-100 hover:brightness-95"
|
|
1014
1080
|
: jobPending
|
|
1015
|
-
? "border-
|
|
1016
|
-
: "border-
|
|
1081
|
+
? "border-border-default bg-neutral-grey-5 text-neutral-grey-50 hover:bg-neutral-grey-5"
|
|
1082
|
+
: "border-border-default bg-background text-neutral-grey-100 hover:bg-neutral-grey-5";
|
|
1017
1083
|
const statusBadgeCls = jobError
|
|
1018
|
-
? "bg-red-
|
|
1084
|
+
? "bg-feedback-red-light text-feedback-red ring-1 ring-inset ring-feedback-red"
|
|
1019
1085
|
: jobAborted
|
|
1020
|
-
? "bg-
|
|
1086
|
+
? "bg-feedback-orange-light text-feedback-orange ring-1 ring-inset ring-feedback-orange"
|
|
1021
1087
|
: jobInProgress
|
|
1022
|
-
? "bg-
|
|
1088
|
+
? "bg-highlight-10 text-highlight-100 ring-1 ring-inset ring-highlight-100/20"
|
|
1023
1089
|
: jobPending
|
|
1024
|
-
? "bg-
|
|
1025
|
-
: "bg-
|
|
1090
|
+
? "bg-neutral-grey-5 text-neutral-grey-50 ring-1 ring-inset ring-border-default"
|
|
1091
|
+
: "bg-feedback-green-light text-feedback-green ring-1 ring-inset ring-feedback-green";
|
|
1026
1092
|
const timestamp = job.timestamp ? new Date(job.timestamp) : null;
|
|
1027
1093
|
const timestampValid = timestamp && !isNaN(timestamp.getTime());
|
|
1028
1094
|
const claimedAt = job.claimedAtUtc ? new Date(job.claimedAtUtc) : null;
|
|
1029
1095
|
const claimedAtValid = !!(claimedAt && !isNaN(claimedAt.getTime()));
|
|
1030
|
-
const lastHeartbeat = job.lastHeartbeatUtc
|
|
1096
|
+
const lastHeartbeat = job.lastHeartbeatUtc
|
|
1097
|
+
? new Date(job.lastHeartbeatUtc)
|
|
1098
|
+
: null;
|
|
1031
1099
|
const lastHeartbeatValid = !!(lastHeartbeat && !isNaN(lastHeartbeat.getTime()));
|
|
1032
1100
|
// Fall back to the batch's start time when the job didn't record one of its
|
|
1033
1101
|
// own — older jobs (and provider-less paths) skip ClaimedAtUtc.
|
|
@@ -1046,7 +1114,9 @@ function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen, isRetrying
|
|
|
1046
1114
|
return null;
|
|
1047
1115
|
const end = jobInProgress || jobPending
|
|
1048
1116
|
? Date.now()
|
|
1049
|
-
:
|
|
1117
|
+
: timestampValid
|
|
1118
|
+
? timestamp.getTime()
|
|
1119
|
+
: null;
|
|
1050
1120
|
if (end == null)
|
|
1051
1121
|
return null;
|
|
1052
1122
|
const ms = end - startMs;
|
|
@@ -1076,7 +1146,9 @@ function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen, isRetrying
|
|
|
1076
1146
|
const emptyFieldText = statistics && statistics.emptyFieldCount > 0
|
|
1077
1147
|
? `${formatCount(statistics.emptyFieldCount)} empty`
|
|
1078
1148
|
: null;
|
|
1079
|
-
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: `inline-flex items-center gap-1.5 rounded-md border
|
|
1149
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: `inline-flex items-center gap-1.5 rounded-md border badge-pad-sm text-[12px] transition-colors cursor-pointer ${chipCls}`, children: [language?.icon ? (_jsx("img", { src: language.icon, alt: langName, className: "h-3.5 shrink-0" })) : null, _jsx("span", { className: "font-mono tracking-tight", children: job.targetLanguage }), jobInProgress && (_jsx(LoaderIcon, { className: "h-3 w-3 shrink-0 animate-spin" })), jobPending && (_jsx(QueuedIcon, { className: "h-3 w-3 shrink-0", strokeWidth: 1.75, "aria-label": "Queued" })), jobError && _jsx(AlertCircleIcon, { className: "h-3 w-3 shrink-0" }), !jobInProgress && !jobError && !jobPending && !jobAborted && (_jsx(CheckIcon, { className: "h-3 w-3 shrink-0 text-feedback-green" }))] }) }), _jsxs(PopoverContent, { align: "start", sideOffset: 6, className: "w-[22rem] max-w-[calc(100vw-2rem)] p-0", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-border-default px-3 py-2", children: [language?.icon ? (_jsx("img", { src: language.icon, alt: langName, className: "h-4 shrink-0" })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-[13px] font-medium text-neutral-grey-100 truncate", children: langName }), _jsxs("div", { className: "text-[11px] text-neutral-grey-50 font-mono truncate", children: [job.targetLanguage, job.sourceLanguage ? ` ← ${job.sourceLanguage}` : ""] })] }), _jsxs("span", { className: `inline-flex items-center gap-1 rounded-badge badge-pad-sm text-2xs font-semibold ${statusBadgeCls}`, children: [jobInProgress && (_jsx(LoaderIcon, { className: "h-2.5 w-2.5 animate-spin" })), jobStatus || "Unknown"] })] }), _jsxs("dl", { className: "divide-y divide-border-default/60", children: [claimedAtValid && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Claimed" }), _jsx("dd", { className: "text-[12px] text-neutral-grey-100", children: claimedAt.toLocaleString() })] })), timestampValid && (jobInProgress || jobPending) && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Updated" }), _jsx("dd", { className: "text-[12px] text-neutral-grey-100", children: timestamp.toLocaleString() })] })), durationMs != null && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: jobInProgress || jobPending ? "Running" : "Duration" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", title: durationApproximated
|
|
1150
|
+
? "Approximate — measured from batch start"
|
|
1151
|
+
: undefined, children: [durationApproximated ? "~" : "", formatDuration(durationMs)] })] })), statistics && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Fields" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: [fieldText, emptyFieldText ? (_jsxs("span", { className: "ml-1.5 text-neutral-grey-50", children: ["(", emptyFieldText, ")"] })) : null] })] })), statistics && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Text" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: [formatCount(statistics.sourceCharacterCount), " chars"] })] })), job.totalCost != null && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Cost" }), _jsx("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: formatUsdCost(job.totalCost) })] })), lastHeartbeatValid && (jobInProgress || jobPending) && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Heartbeat" }), _jsx("dd", { className: "text-[12px] text-neutral-grey-100", children: lastHeartbeat.toLocaleString() })] })), attemptCount > 1 && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Attempts" }), _jsx("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: attemptCount })] })), job.hash && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Hash" }), _jsx("dd", { className: "text-[11px] font-mono text-neutral-grey-100 break-all", children: job.hash })] })), job.message && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: jobError ? "Error" : jobAborted ? "Aborted" : "Message" }), _jsx("dd", { className: `text-[12px] break-words ${jobError ? "text-feedback-red" : jobAborted ? "text-feedback-orange" : "text-neutral-grey-100"}`, children: job.message })] })), job.batchId && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Batch" }), _jsx("dd", { className: "text-[11px] font-mono text-neutral-grey-50 break-all", children: job.batchId })] }))] }), _jsxs("div", { className: "flex justify-end gap-2 border-t border-border-default px-3 py-2", children: [jobError && onRetry && (_jsxs(Button, { size: "sm", variant: "outline", disabled: isRetrying, onClick: (e) => {
|
|
1080
1152
|
e.stopPropagation();
|
|
1081
1153
|
void onRetry();
|
|
1082
1154
|
setOpen(false);
|