@parhelia/localization 0.1.12792 → 0.1.12798
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TranslationBatches.d.ts","sourceRoot":"","sources":["../../src/translation-center/TranslationBatches.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TranslationBatches.d.ts","sourceRoot":"","sources":["../../src/translation-center/TranslationBatches.tsx"],"names":[],"mappings":"AAgPA,wBAAgB,kBAAkB,4CA6gCjC"}
|
|
@@ -2,9 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useCallback, useEffect, useRef, useState, useMemo } from "react";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { Button, Select, Input, UserPicker, Popover, PopoverContent, PopoverTrigger, ContentTree, fetchItemStubs, searchUsers as searchBackendUsers, useEditContext, getLanguages, SimpleIconButton, } from "@parhelia/core";
|
|
5
|
-
import { listBatches, getTranslationProviders, listBatchTranslationJobs, abortBatch, deleteBatch, } from "../services/translationService";
|
|
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, Trash2, Hourglass, Copy, Sparkles, } from "lucide-react";
|
|
7
|
+
import { X, ChevronDown, Languages, Loader2, Calendar, CheckCircle2, User as UserIcon2, Cloud, Globe, FileText, FolderTree, ExternalLink, Check, AlertCircle, CircleStop, Trash2, 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);
|
|
@@ -26,6 +26,7 @@ const TrashIcon = (props) => React.createElement(Trash2, props);
|
|
|
26
26
|
const QueuedIcon = (props) => React.createElement(Hourglass, props);
|
|
27
27
|
const CopyIcon = (props) => React.createElement(Copy, props);
|
|
28
28
|
const SparklesIcon = (props) => React.createElement(Sparkles, props);
|
|
29
|
+
const RetryIcon = (props) => React.createElement(RotateCcw, props);
|
|
29
30
|
const DATE_RANGE_OPTIONS = [
|
|
30
31
|
{ value: "lastHour", label: "Last Hour" },
|
|
31
32
|
{ value: "last24hours", label: "Last 24 Hours" },
|
|
@@ -57,6 +58,20 @@ function isTerminalBatchStatus(status) {
|
|
|
57
58
|
function isActiveBatchStatus(status) {
|
|
58
59
|
return status === "Pending" || status === "In Progress";
|
|
59
60
|
}
|
|
61
|
+
function isRetriableJob(job) {
|
|
62
|
+
return job?.status === "Error";
|
|
63
|
+
}
|
|
64
|
+
function buildRetryJobRequest(job) {
|
|
65
|
+
if (!job.itemId || !job.sourceLanguage || !job.targetLanguage)
|
|
66
|
+
return null;
|
|
67
|
+
return {
|
|
68
|
+
sourceTranslationId: job.id,
|
|
69
|
+
itemId: job.itemId,
|
|
70
|
+
sourceLanguage: job.sourceLanguage,
|
|
71
|
+
targetLanguage: job.targetLanguage,
|
|
72
|
+
metadata: job.metadata,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
60
75
|
function normalizeGuid(value) {
|
|
61
76
|
return (value ?? "").toString().replace(/[{}()]/g, "").toLowerCase();
|
|
62
77
|
}
|
|
@@ -110,6 +125,7 @@ export function TranslationBatches() {
|
|
|
110
125
|
const [loadingJobs, setLoadingJobs] = useState(new Set());
|
|
111
126
|
const [abortingBatchIds, setAbortingBatchIds] = useState(new Set());
|
|
112
127
|
const [deletingBatchIds, setDeletingBatchIds] = useState(new Set());
|
|
128
|
+
const [retryingKeys, setRetryingKeys] = useState(new Set());
|
|
113
129
|
// Cache of userName → displayName so we can render full names on batch rows
|
|
114
130
|
// without re-querying the user service every render.
|
|
115
131
|
const [userDisplayNames, setUserDisplayNames] = useState({});
|
|
@@ -388,10 +404,13 @@ export function TranslationBatches() {
|
|
|
388
404
|
try {
|
|
389
405
|
const res = await listBatchTranslationJobs(batchId);
|
|
390
406
|
const data = (res?.data ?? res ?? []);
|
|
391
|
-
|
|
407
|
+
const jobs = Array.isArray(data) ? data : [];
|
|
408
|
+
setBatchJobs(prev => ({ ...prev, [batchId]: jobs }));
|
|
409
|
+
return jobs;
|
|
392
410
|
}
|
|
393
411
|
catch (error) {
|
|
394
412
|
console.error("Failed to load batch jobs:", error);
|
|
413
|
+
return [];
|
|
395
414
|
}
|
|
396
415
|
finally {
|
|
397
416
|
setLoadingJobs(prev => {
|
|
@@ -487,6 +506,64 @@ export function TranslationBatches() {
|
|
|
487
506
|
},
|
|
488
507
|
});
|
|
489
508
|
}, [editContext]);
|
|
509
|
+
const handleRetryJobs = useCallback(async (batchId, provider, jobs, retryKey) => {
|
|
510
|
+
const sessionId = editContext?.sessionId;
|
|
511
|
+
if (!sessionId) {
|
|
512
|
+
toast.error("Cannot retry translations without an active session.");
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const retryJobs = jobs
|
|
516
|
+
.filter(isRetriableJob)
|
|
517
|
+
.map(buildRetryJobRequest)
|
|
518
|
+
.filter((job) => job != null);
|
|
519
|
+
if (retryJobs.length === 0) {
|
|
520
|
+
toast.error("No failed translations are available to retry.");
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
setRetryingKeys(prev => new Set(prev).add(retryKey));
|
|
524
|
+
try {
|
|
525
|
+
const result = await retryBatchTranslation({
|
|
526
|
+
sessionId,
|
|
527
|
+
sourceBatchId: batchId,
|
|
528
|
+
provider,
|
|
529
|
+
jobs: retryJobs,
|
|
530
|
+
});
|
|
531
|
+
if (result.type !== "success") {
|
|
532
|
+
toast.error(result.details || result.summary || "Failed to retry translations.");
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const payload = (result?.data ?? result);
|
|
536
|
+
const startedCount = payload.started?.length ?? 0;
|
|
537
|
+
const skippedCount = payload.skipped?.length ?? 0;
|
|
538
|
+
if (startedCount === 0) {
|
|
539
|
+
toast.error(skippedCount > 0
|
|
540
|
+
? "No translations were retried. They may already have been retried."
|
|
541
|
+
: "No translations were retried.");
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
toast.success(`Retry queued for ${startedCount} translation${startedCount !== 1 ? "s" : ""}.`);
|
|
545
|
+
if (skippedCount > 0) {
|
|
546
|
+
toast.error(`${skippedCount} translation${skippedCount !== 1 ? "s were" : " was"} skipped.`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
await loadRecentBatches(0, false, effectiveFilters);
|
|
550
|
+
if (expandedBatchId === batchId || batchJobs[batchId]) {
|
|
551
|
+
await loadBatchJobs(batchId);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
finally {
|
|
555
|
+
setRetryingKeys(prev => {
|
|
556
|
+
const next = new Set(prev);
|
|
557
|
+
next.delete(retryKey);
|
|
558
|
+
return next;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}, [batchJobs, editContext?.sessionId, effectiveFilters, expandedBatchId, loadBatchJobs, loadRecentBatches]);
|
|
562
|
+
const handleRetryBatch = useCallback(async (batch) => {
|
|
563
|
+
const batchId = batch.id;
|
|
564
|
+
const jobs = batchJobs[batchId] ?? await loadBatchJobs(batchId);
|
|
565
|
+
await handleRetryJobs(batchId, batch.provider, jobs, `batch:${batchId}`);
|
|
566
|
+
}, [batchJobs, handleRetryJobs, loadBatchJobs]);
|
|
490
567
|
const openItemInEditor = useCallback(async (itemId, language) => {
|
|
491
568
|
const normalized = (itemId ?? "").toString().toLowerCase();
|
|
492
569
|
editContext?.setShowAgentsWorkspaceEditor(true);
|
|
@@ -609,8 +686,10 @@ export function TranslationBatches() {
|
|
|
609
686
|
const isExpanded = expandedBatchId === b.id;
|
|
610
687
|
const jobs = batchJobs[b.id];
|
|
611
688
|
const isLoadingThis = loadingJobs.has(b.id);
|
|
689
|
+
const canRetry = anyError;
|
|
612
690
|
const canAbort = isActiveBatchStatus(info?.status);
|
|
613
691
|
const canDelete = isTerminalBatchStatus(info?.status);
|
|
692
|
+
const isRetryingBatch = retryingKeys.has(`batch:${b.id}`);
|
|
614
693
|
const isAborting = abortingBatchIds.has(b.id);
|
|
615
694
|
const isDeleting = deletingBatchIds.has(b.id);
|
|
616
695
|
// Stable test-id status used by Playwright assertions.
|
|
@@ -644,13 +723,16 @@ export function TranslationBatches() {
|
|
|
644
723
|
}
|
|
645
724
|
}
|
|
646
725
|
return parsedName ?? `Translation Batch · ${timeDisplay}`;
|
|
647
|
-
})() }), showProgress && (_jsxs("span", { className: `inline-flex items-center gap-1 text-[11px] tabular-nums ${anyError ? 'text-red-600' : 'text-theme-secondary'}`, "data-testid": `batch-progress-${b.id}`, children: [completedJobs, "/", totalJobs, anyError && _jsxs("span", { className: "text-red-600", 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-gray-2 pl-4", children: [metaParts.map((part, i) => (_jsxs(React.Fragment, { children: [i > 0 && _jsx("span", { className: "text-gray-3", children: "\u00B7" }), part] }, i))), info?.lastUpdatedUtc && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-gray-3", children: "\u00B7" }), _jsx("span", { children: new Date(info.lastUpdatedUtc).toLocaleString() })] }))] })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
726
|
+
})() }), showProgress && (_jsxs("span", { className: `inline-flex items-center gap-1 text-[11px] tabular-nums ${anyError ? 'text-red-600' : 'text-theme-secondary'}`, "data-testid": `batch-progress-${b.id}`, children: [completedJobs, "/", totalJobs, anyError && _jsxs("span", { className: "text-red-600", 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-gray-2 pl-4", children: [metaParts.map((part, i) => (_jsxs(React.Fragment, { children: [i > 0 && _jsx("span", { className: "text-gray-3", children: "\u00B7" }), part] }, i))), info?.lastUpdatedUtc && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-gray-3", 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
|
+
event.stopPropagation();
|
|
728
|
+
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 || isLoadingThis || isAborting || isDeleting, className: "p-0! text-red-600 hover:text-red-700" })), canAbort && (_jsx(SimpleIconButton, { onClick: (event) => {
|
|
648
730
|
event.stopPropagation();
|
|
649
731
|
handleAbortBatch(b.id);
|
|
650
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-amber-600 hover:text-amber-700" })), canDelete && (_jsx(SimpleIconButton, { onClick: (event) => {
|
|
651
733
|
event.stopPropagation();
|
|
652
734
|
handleDeleteBatch(b.id);
|
|
653
|
-
}, icon: _jsx(TrashIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }), label: "Delete translation history", disabled: isDeleting || isAborting, className: "p-0! text-red-500 hover:text-red-600" })), _jsx("div", { className: `text-gray-3 transition-transform ${isExpanded ? 'rotate-180' : ''} group-hover:text-(--color-gray-2)`, children: _jsx(ChevronDownIcon, { className: "h-4 w-4" }) })] })] }) }), isExpanded && (_jsxs(_Fragment, { children: [_jsx(CustomPromptPanel, { prompts: parseCustomPrompts(info?.metadata) }), _jsx(BatchItemList, { 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, onToggleItem: toggleItem, onOpenItem: openItemInEditor })] }))] }, b.id));
|
|
735
|
+
}, icon: _jsx(TrashIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }), label: "Delete translation history", disabled: isDeleting || isAborting, className: "p-0! text-red-500 hover:text-red-600" })), _jsx("div", { className: `text-gray-3 transition-transform ${isExpanded ? 'rotate-180' : ''} group-hover:text-(--color-gray-2)`, 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));
|
|
654
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-theme-secondary" }), "Loading more..."] })) : (_jsxs(_Fragment, { children: [_jsx(ChevronDownIcon, { className: "h-4 w-4" }), "Load More Batches"] })) }) }))] })) })] }));
|
|
655
737
|
}
|
|
656
738
|
function isLikelyGuid(value) {
|
|
@@ -785,7 +867,7 @@ function CustomPromptPanel({ prompts }) {
|
|
|
785
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-gray-2 hover:text-(--color-dark-lighter)" })), _jsx(ChevronDownIcon, { className: `h-3.5 w-3.5 text-gray-3 transition-transform group-hover:text-(--color-gray-2) ${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-gray-3/50 bg-white p-2 font-mono text-[12px] text-(--color-dark-lighter)", children: p.prompt }) }))] }, p.provider));
|
|
786
868
|
}) }));
|
|
787
869
|
}
|
|
788
|
-
function BatchItemList({ jobs, isLoading, expandedItems, languages, itemFilter, itemIncludeSubitems, statusFilter, batchIsTerminal, batchStartedAtUtc, onToggleItem, onOpenItem }) {
|
|
870
|
+
function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems, languages, itemFilter, itemIncludeSubitems, statusFilter, batchIsTerminal, batchStartedAtUtc, retryingKeys, onToggleItem, onOpenItem, onRetryJobs }) {
|
|
789
871
|
const languageMap = React.useMemo(() => {
|
|
790
872
|
const map = new Map();
|
|
791
873
|
for (const l of languages) {
|
|
@@ -897,18 +979,25 @@ function BatchItemList({ jobs, isLoading, expandedItems, languages, itemFilter,
|
|
|
897
979
|
const langs = itemJobs.map(j => j.targetLanguage).filter(Boolean);
|
|
898
980
|
const uniqueLangs = Array.from(new Set(langs));
|
|
899
981
|
const titleAttr = `${itemPath ?? ""}\n${itemId}`.trim();
|
|
982
|
+
const itemErrorJobs = itemJobs.filter(isRetriableJob);
|
|
983
|
+
const itemRetryKey = `item:${batchId}:${itemId}`;
|
|
984
|
+
const isRetryingItem = retryingKeys.has(itemRetryKey);
|
|
900
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-gray-5 transition-colors cursor-pointer text-left", onClick: () => onToggleItem(itemId), onKeyDown: (e) => {
|
|
901
986
|
if (e.key === "Enter" || e.key === " ") {
|
|
902
987
|
e.preventDefault();
|
|
903
988
|
onToggleItem(itemId);
|
|
904
989
|
}
|
|
905
|
-
}, 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-theme-secondary" })) : itemIsQueued ? (_jsx(QueuedIcon, { className: "h-3 w-3 text-gray-2", 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-(--color-dark-lighter) truncate", title: titleAttr, children: displayName }), _jsxs("span", { className: "text-[11px] text-gray-2 shrink-0", children: ["\u00B7 ", uniqueLangs.length, " language", uniqueLangs.length !== 1 ? 's' : ''] })] }), itemPath && (_jsx("div", { className: "text-[11px] text-gray-2 truncate font-mono", title: titleAttr, children: itemPath }))] }),
|
|
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-theme-secondary" })) : itemIsQueued ? (_jsx(QueuedIcon, { className: "h-3 w-3 text-gray-2", 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-(--color-dark-lighter) truncate", title: titleAttr, children: displayName }), _jsxs("span", { className: "text-[11px] text-gray-2 shrink-0", children: ["\u00B7 ", uniqueLangs.length, " language", uniqueLangs.length !== 1 ? 's' : ''] })] }), itemPath && (_jsx("div", { className: "text-[11px] text-gray-2 truncate font-mono", title: titleAttr, children: itemPath }))] }), itemErrorJobs.length > 0 && (_jsx(SimpleIconButton, { onClick: (event) => {
|
|
991
|
+
event.stopPropagation();
|
|
992
|
+
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-600 hover:text-red-700" })), _jsx("span", { className: "mt-1 shrink-0 text-gray-3 group-hover:text-(--color-gray-2)", 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) => {
|
|
906
994
|
const lang = languageMap.get((job.targetLanguage || "").toLowerCase());
|
|
907
|
-
|
|
995
|
+
const retryKey = `job:${job.id ?? `${job.itemId}:${job.sourceLanguage}:${job.targetLanguage}`}`;
|
|
996
|
+
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}`));
|
|
908
997
|
}) }))] }, itemId));
|
|
909
998
|
}) }));
|
|
910
999
|
}
|
|
911
|
-
function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen }) {
|
|
1000
|
+
function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen, isRetrying = false, onRetry }) {
|
|
912
1001
|
const [open, setOpen] = React.useState(false);
|
|
913
1002
|
const jobStatus = job.status;
|
|
914
1003
|
const jobInProgress = jobStatus === "In Progress";
|
|
@@ -987,9 +1076,13 @@ function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen }) {
|
|
|
987
1076
|
const emptyFieldText = statistics && statistics.emptyFieldCount > 0
|
|
988
1077
|
? `${formatCount(statistics.emptyFieldCount)} empty`
|
|
989
1078
|
: null;
|
|
990
|
-
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 px-2 py-0.5 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-emerald-600" }))] }) }), _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-gray-3 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-(--color-dark-lighter) truncate", children: langName }), _jsxs("div", { className: "text-[11px] text-gray-2 font-mono truncate", children: [job.targetLanguage, job.sourceLanguage ? ` ← ${job.sourceLanguage}` : ""] })] }), _jsxs("span", { className: `inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${statusBadgeCls}`, children: [jobInProgress && _jsx(LoaderIcon, { className: "h-2.5 w-2.5 animate-spin" }), jobStatus || "Unknown"] })] }), _jsxs("dl", { className: "divide-y divide-gray-3/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] uppercase tracking-wide text-gray-2", children: "Claimed" }), _jsx("dd", { className: "text-[12px] text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: "Updated" }), _jsx("dd", { className: "text-[12px] text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: jobInProgress || jobPending ? "Running" : "Duration" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", title: durationApproximated ? "Approximate — measured from batch start" : 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] uppercase tracking-wide text-gray-2", children: "Fields" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", children: [fieldText, emptyFieldText ? _jsxs("span", { className: "ml-1.5 text-gray-2", 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] uppercase tracking-wide text-gray-2", children: "Text" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: "Cost" }), _jsx("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: "Heartbeat" }), _jsx("dd", { className: "text-[12px] text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: "Attempts" }), _jsx("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: "Hash" }), _jsx("dd", { className: "text-[11px] font-mono text-(--color-gray-1) 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] uppercase tracking-wide text-gray-2", children: jobError ? "Error" : jobAborted ? "Aborted" : "Message" }), _jsx("dd", { className: `text-[12px] break-words ${jobError ? "text-red-600" : jobAborted ? "text-amber-700" : "text-(--color-gray-1)"}`, 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] uppercase tracking-wide text-gray-2", children: "Batch" }), _jsx("dd", { className: "text-[11px] font-mono text-gray-2 break-all", children: job.batchId })] }))] }),
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
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 px-2 py-0.5 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-emerald-600" }))] }) }), _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-gray-3 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-(--color-dark-lighter) truncate", children: langName }), _jsxs("div", { className: "text-[11px] text-gray-2 font-mono truncate", children: [job.targetLanguage, job.sourceLanguage ? ` ← ${job.sourceLanguage}` : ""] })] }), _jsxs("span", { className: `inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${statusBadgeCls}`, children: [jobInProgress && _jsx(LoaderIcon, { className: "h-2.5 w-2.5 animate-spin" }), jobStatus || "Unknown"] })] }), _jsxs("dl", { className: "divide-y divide-gray-3/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] uppercase tracking-wide text-gray-2", children: "Claimed" }), _jsx("dd", { className: "text-[12px] text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: "Updated" }), _jsx("dd", { className: "text-[12px] text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: jobInProgress || jobPending ? "Running" : "Duration" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", title: durationApproximated ? "Approximate — measured from batch start" : 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] uppercase tracking-wide text-gray-2", children: "Fields" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", children: [fieldText, emptyFieldText ? _jsxs("span", { className: "ml-1.5 text-gray-2", 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] uppercase tracking-wide text-gray-2", children: "Text" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: "Cost" }), _jsx("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: "Heartbeat" }), _jsx("dd", { className: "text-[12px] text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: "Attempts" }), _jsx("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", 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] uppercase tracking-wide text-gray-2", children: "Hash" }), _jsx("dd", { className: "text-[11px] font-mono text-(--color-gray-1) 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] uppercase tracking-wide text-gray-2", children: jobError ? "Error" : jobAborted ? "Aborted" : "Message" }), _jsx("dd", { className: `text-[12px] break-words ${jobError ? "text-red-600" : jobAborted ? "text-amber-700" : "text-(--color-gray-1)"}`, 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] uppercase tracking-wide text-gray-2", children: "Batch" }), _jsx("dd", { className: "text-[11px] font-mono text-gray-2 break-all", children: job.batchId })] }))] }), _jsxs("div", { className: "flex justify-end gap-2 border-t border-gray-3 px-3 py-2", children: [jobError && onRetry && (_jsxs(Button, { size: "sm", variant: "outline", disabled: isRetrying, onClick: (e) => {
|
|
1080
|
+
e.stopPropagation();
|
|
1081
|
+
void onRetry();
|
|
1082
|
+
setOpen(false);
|
|
1083
|
+
}, className: "gap-1.5", children: [_jsx(RetryIcon, { className: `h-3.5 w-3.5 ${isRetrying ? "animate-spin" : ""}` }), "Retry"] })), _jsxs(Button, { size: "sm", variant: "default", onClick: (e) => {
|
|
1084
|
+
e.stopPropagation();
|
|
1085
|
+
void onOpen();
|
|
1086
|
+
setOpen(false);
|
|
1087
|
+
}, className: "gap-1.5", children: [_jsx(ExternalLinkIcon, { className: "h-3.5 w-3.5" }), "Open in editor"] })] })] })] }));
|
|
995
1088
|
}
|