@purposeinplay/payload-ai-translate 0.1.0
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/LICENSE +21 -0
- package/README.md +714 -0
- package/dist/alerts-collection.d.ts +21 -0
- package/dist/alerts-collection.js +159 -0
- package/dist/api.d.ts +4 -0
- package/dist/api.js +918 -0
- package/dist/bulk-translate-batches-collection.d.ts +29 -0
- package/dist/bulk-translate-batches-collection.js +404 -0
- package/dist/bulk-translate-units-collection.d.ts +35 -0
- package/dist/bulk-translate-units-collection.js +310 -0
- package/dist/client/estimated-cost-cell.d.ts +6 -0
- package/dist/client/estimated-cost-cell.js +12 -0
- package/dist/client/excluded-fields-field.d.ts +45 -0
- package/dist/client/excluded-fields-field.js +553 -0
- package/dist/client/field-translate-button.d.ts +6 -0
- package/dist/client/field-translate-button.js +199 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.js +6 -0
- package/dist/client/lib/use-global-kill-switches.d.ts +20 -0
- package/dist/client/lib/use-global-kill-switches.js +58 -0
- package/dist/client/translate-button.d.ts +2 -0
- package/dist/client/translate-button.js +228 -0
- package/dist/client/translate-modal.d.ts +16 -0
- package/dist/client/translate-modal.js +549 -0
- package/dist/client/translation-progress.d.ts +10 -0
- package/dist/client/translation-progress.js +297 -0
- package/dist/components/TranslationNavGroup.d.ts +45 -0
- package/dist/components/TranslationNavGroup.js +104 -0
- package/dist/defaults.d.ts +11 -0
- package/dist/defaults.js +16 -0
- package/dist/endpoints/client-config.d.ts +44 -0
- package/dist/endpoints/client-config.js +145 -0
- package/dist/endpoints/estimate.d.ts +5 -0
- package/dist/endpoints/estimate.js +237 -0
- package/dist/endpoints/progress.d.ts +2 -0
- package/dist/endpoints/progress.js +314 -0
- package/dist/endpoints/translate.d.ts +11 -0
- package/dist/endpoints/translate.js +376 -0
- package/dist/endpoints/translation-hub/_helpers.d.ts +140 -0
- package/dist/endpoints/translation-hub/_helpers.js +297 -0
- package/dist/endpoints/translation-hub/active.d.ts +21 -0
- package/dist/endpoints/translation-hub/active.js +220 -0
- package/dist/endpoints/translation-hub/cancel.d.ts +22 -0
- package/dist/endpoints/translation-hub/cancel.js +233 -0
- package/dist/endpoints/translation-hub/enqueue.d.ts +70 -0
- package/dist/endpoints/translation-hub/enqueue.js +529 -0
- package/dist/endpoints/translation-hub/failures.d.ts +12 -0
- package/dist/endpoints/translation-hub/failures.js +67 -0
- package/dist/endpoints/translation-hub/force-reset.d.ts +20 -0
- package/dist/endpoints/translation-hub/force-reset.js +144 -0
- package/dist/endpoints/translation-hub/index.d.ts +21 -0
- package/dist/endpoints/translation-hub/index.js +20 -0
- package/dist/endpoints/translation-hub/list.d.ts +40 -0
- package/dist/endpoints/translation-hub/list.js +182 -0
- package/dist/endpoints/translation-hub/preflight.d.ts +19 -0
- package/dist/endpoints/translation-hub/preflight.js +141 -0
- package/dist/endpoints/translation-hub/retry-failed.d.ts +38 -0
- package/dist/endpoints/translation-hub/retry-failed.js +235 -0
- package/dist/endpoints/translation-hub/revert.d.ts +88 -0
- package/dist/endpoints/translation-hub/revert.js +405 -0
- package/dist/endpoints/translation-hub/status.d.ts +45 -0
- package/dist/endpoints/translation-hub/status.js +391 -0
- package/dist/endpoints/translation-hub/usage-summary.d.ts +114 -0
- package/dist/endpoints/translation-hub/usage-summary.js +481 -0
- package/dist/exports/client.d.ts +6 -0
- package/dist/exports/client.js +6 -0
- package/dist/exports/components.d.ts +6 -0
- package/dist/exports/components.js +5 -0
- package/dist/exports/index.d.ts +8 -0
- package/dist/exports/index.js +7 -0
- package/dist/exports/providers.d.ts +9 -0
- package/dist/exports/providers.js +5 -0
- package/dist/exports/views-client.d.ts +23 -0
- package/dist/exports/views-client.js +22 -0
- package/dist/exports/views.d.ts +30 -0
- package/dist/exports/views.js +29 -0
- package/dist/hooks/after-change-global.d.ts +4 -0
- package/dist/hooks/after-change-global.js +109 -0
- package/dist/hooks/after-change.d.ts +16 -0
- package/dist/hooks/after-change.js +205 -0
- package/dist/hooks/after-delete.d.ts +30 -0
- package/dist/hooks/after-delete.js +95 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/jobs-collection.d.ts +17 -0
- package/dist/jobs-collection.js +139 -0
- package/dist/lexical/classifier.d.ts +3 -0
- package/dist/lexical/classifier.js +108 -0
- package/dist/lexical/deserializer.d.ts +4 -0
- package/dist/lexical/deserializer.js +263 -0
- package/dist/lexical/placeholder-integrity.d.ts +6 -0
- package/dist/lexical/placeholder-integrity.js +21 -0
- package/dist/lexical/placeholders.d.ts +21 -0
- package/dist/lexical/placeholders.js +117 -0
- package/dist/lexical/serializer.d.ts +21 -0
- package/dist/lexical/serializer.js +233 -0
- package/dist/lexical/types.d.ts +32 -0
- package/dist/lexical/types.js +1 -0
- package/dist/lib/auth-diagnostics.d.ts +14 -0
- package/dist/lib/auth-diagnostics.js +19 -0
- package/dist/lib/batch-counts.d.ts +58 -0
- package/dist/lib/batch-counts.js +105 -0
- package/dist/lib/bulk-translate-migrations.d.ts +92 -0
- package/dist/lib/bulk-translate-migrations.js +153 -0
- package/dist/lib/coalescing-queue.d.ts +38 -0
- package/dist/lib/coalescing-queue.js +69 -0
- package/dist/lib/content-extractor.d.ts +16 -0
- package/dist/lib/content-extractor.js +410 -0
- package/dist/lib/content-hash.d.ts +1 -0
- package/dist/lib/content-hash.js +19 -0
- package/dist/lib/content-patcher.d.ts +15 -0
- package/dist/lib/content-patcher.js +293 -0
- package/dist/lib/cost-guards.d.ts +2 -0
- package/dist/lib/cost-guards.js +18 -0
- package/dist/lib/daily-spend-cap.d.ts +58 -0
- package/dist/lib/daily-spend-cap.js +233 -0
- package/dist/lib/effective-locales.d.ts +181 -0
- package/dist/lib/effective-locales.js +302 -0
- package/dist/lib/error-messages.d.ts +245 -0
- package/dist/lib/error-messages.js +626 -0
- package/dist/lib/events.d.ts +39 -0
- package/dist/lib/events.js +146 -0
- package/dist/lib/exclude-fields.d.ts +3 -0
- package/dist/lib/exclude-fields.js +64 -0
- package/dist/lib/field-breadcrumb.d.ts +31 -0
- package/dist/lib/field-breadcrumb.js +227 -0
- package/dist/lib/field-diff.d.ts +1 -0
- package/dist/lib/field-diff.js +25 -0
- package/dist/lib/field-empty.d.ts +2 -0
- package/dist/lib/field-empty.js +68 -0
- package/dist/lib/field-resolver.d.ts +3 -0
- package/dist/lib/field-resolver.js +164 -0
- package/dist/lib/group-soft-skips.d.ts +39 -0
- package/dist/lib/group-soft-skips.js +45 -0
- package/dist/lib/locale-merge.d.ts +44 -0
- package/dist/lib/locale-merge.js +357 -0
- package/dist/lib/locale-row-check.d.ts +30 -0
- package/dist/lib/locale-row-check.js +64 -0
- package/dist/lib/logger.d.ts +74 -0
- package/dist/lib/logger.js +97 -0
- package/dist/lib/manual-edit-guard.d.ts +128 -0
- package/dist/lib/manual-edit-guard.js +393 -0
- package/dist/lib/output-validation.d.ts +48 -0
- package/dist/lib/output-validation.js +148 -0
- package/dist/lib/payload-read.d.ts +16 -0
- package/dist/lib/payload-read.js +51 -0
- package/dist/lib/per-doc-claim.d.ts +90 -0
- package/dist/lib/per-doc-claim.js +140 -0
- package/dist/lib/per-doc-lock.d.ts +94 -0
- package/dist/lib/per-doc-lock.js +119 -0
- package/dist/lib/persist-usage.d.ts +91 -0
- package/dist/lib/persist-usage.js +116 -0
- package/dist/lib/progress-store.d.ts +103 -0
- package/dist/lib/progress-store.js +314 -0
- package/dist/lib/rate-limiter.d.ts +3 -0
- package/dist/lib/rate-limiter.js +53 -0
- package/dist/lib/snapshot-select.d.ts +43 -0
- package/dist/lib/snapshot-select.js +108 -0
- package/dist/lib/translate-prompt.d.ts +31 -0
- package/dist/lib/translate-prompt.js +66 -0
- package/dist/lib/translation-token-bucket.d.ts +57 -0
- package/dist/lib/translation-token-bucket.js +365 -0
- package/dist/lib/truncate-source-value.d.ts +1 -0
- package/dist/lib/truncate-source-value.js +27 -0
- package/dist/manual-edit-collection.d.ts +22 -0
- package/dist/manual-edit-collection.js +124 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +934 -0
- package/dist/providers/ai-sdk-adapter.d.ts +35 -0
- package/dist/providers/ai-sdk-adapter.js +100 -0
- package/dist/providers/anthropic.d.ts +31 -0
- package/dist/providers/anthropic.js +66 -0
- package/dist/providers/custom.d.ts +36 -0
- package/dist/providers/custom.js +24 -0
- package/dist/providers/gemini.d.ts +20 -0
- package/dist/providers/gemini.js +48 -0
- package/dist/providers/mock.d.ts +2 -0
- package/dist/providers/mock.js +29 -0
- package/dist/providers/openai.d.ts +28 -0
- package/dist/providers/openai.js +69 -0
- package/dist/settings-global.d.ts +74 -0
- package/dist/settings-global.js +216 -0
- package/dist/tasks/bulk-translate-coordinator.d.ts +115 -0
- package/dist/tasks/bulk-translate-coordinator.js +708 -0
- package/dist/tasks/bulk-translate-doc-task.d.ts +142 -0
- package/dist/tasks/bulk-translate-doc-task.js +1000 -0
- package/dist/tasks/bulk-translate-janitor.d.ts +87 -0
- package/dist/tasks/bulk-translate-janitor.js +311 -0
- package/dist/tasks/translate-job-task.d.ts +51 -0
- package/dist/tasks/translate-job-task.js +154 -0
- package/dist/translate.d.ts +113 -0
- package/dist/translate.js +911 -0
- package/dist/translation-daily-spend-collection.d.ts +24 -0
- package/dist/translation-daily-spend-collection.js +133 -0
- package/dist/translation-rate-limits-collection.d.ts +30 -0
- package/dist/translation-rate-limits-collection.js +144 -0
- package/dist/types.d.ts +672 -0
- package/dist/types.js +1 -0
- package/dist/usage-collection.d.ts +14 -0
- package/dist/usage-collection.js +377 -0
- package/dist/views/BulkRunsHub/BatchRow.d.ts +32 -0
- package/dist/views/BulkRunsHub/BatchRow.js +1222 -0
- package/dist/views/BulkRunsHub/BucketRow.d.ts +62 -0
- package/dist/views/BulkRunsHub/BucketRow.js +982 -0
- package/dist/views/BulkRunsHub/BulkRunsHub.client.d.ts +18 -0
- package/dist/views/BulkRunsHub/BulkRunsHub.client.js +331 -0
- package/dist/views/BulkRunsHub/EmptyState.d.ts +6 -0
- package/dist/views/BulkRunsHub/EmptyState.js +64 -0
- package/dist/views/BulkRunsHub/FilterBar.d.ts +16 -0
- package/dist/views/BulkRunsHub/FilterBar.js +284 -0
- package/dist/views/BulkRunsHub/InFlightBanner.d.ts +14 -0
- package/dist/views/BulkRunsHub/InFlightBanner.js +59 -0
- package/dist/views/BulkRunsHub/StatusBadge.d.ts +64 -0
- package/dist/views/BulkRunsHub/StatusBadge.js +248 -0
- package/dist/views/BulkRunsHub/SummaryStrip.d.ts +22 -0
- package/dist/views/BulkRunsHub/SummaryStrip.js +249 -0
- package/dist/views/BulkRunsHub/bucket-grouping.d.ts +200 -0
- package/dist/views/BulkRunsHub/bucket-grouping.js +344 -0
- package/dist/views/BulkRunsHub/bucketFailureSummary.d.ts +9 -0
- package/dist/views/BulkRunsHub/bucketFailureSummary.js +36 -0
- package/dist/views/BulkRunsHub/dedupedStatusFetch.d.ts +5 -0
- package/dist/views/BulkRunsHub/dedupedStatusFetch.js +45 -0
- package/dist/views/BulkRunsHub/index.d.ts +17 -0
- package/dist/views/BulkRunsHub/index.js +80 -0
- package/dist/views/BulkRunsHub/urlFilters.d.ts +14 -0
- package/dist/views/BulkRunsHub/urlFilters.js +50 -0
- package/dist/views/BulkRunsHub/useBulkRunsList.d.ts +26 -0
- package/dist/views/BulkRunsHub/useBulkRunsList.js +204 -0
- package/dist/views/BulkRunsHub/useUrlFilters.d.ts +10 -0
- package/dist/views/BulkRunsHub/useUrlFilters.js +88 -0
- package/dist/views/TranslationHub/ActiveJobs.d.ts +6 -0
- package/dist/views/TranslationHub/ActiveJobs.js +320 -0
- package/dist/views/TranslationHub/AdvancedPanel.d.ts +17 -0
- package/dist/views/TranslationHub/AdvancedPanel.js +996 -0
- package/dist/views/TranslationHub/AlertBanner.d.ts +6 -0
- package/dist/views/TranslationHub/AlertBanner.js +568 -0
- package/dist/views/TranslationHub/AuditPanel.d.ts +6 -0
- package/dist/views/TranslationHub/AuditPanel.helpers.d.ts +44 -0
- package/dist/views/TranslationHub/AuditPanel.helpers.js +71 -0
- package/dist/views/TranslationHub/AuditPanel.js +1367 -0
- package/dist/views/TranslationHub/BulkTranslate.types.d.ts +242 -0
- package/dist/views/TranslationHub/BulkTranslate.types.js +36 -0
- package/dist/views/TranslationHub/BulkTranslateFailureDrawer.d.ts +19 -0
- package/dist/views/TranslationHub/BulkTranslateFailureDrawer.js +332 -0
- package/dist/views/TranslationHub/BulkTranslateMonitor.d.ts +28 -0
- package/dist/views/TranslationHub/BulkTranslateMonitor.js +305 -0
- package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.d.ts +3 -0
- package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.js +42 -0
- package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.d.ts +26 -0
- package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.js +95 -0
- package/dist/views/TranslationHub/BulkTranslatePreflightModal.d.ts +22 -0
- package/dist/views/TranslationHub/BulkTranslatePreflightModal.js +879 -0
- package/dist/views/TranslationHub/BulkTranslateTerminalCard.d.ts +29 -0
- package/dist/views/TranslationHub/BulkTranslateTerminalCard.js +445 -0
- package/dist/views/TranslationHub/BulkTranslateTrigger.d.ts +66 -0
- package/dist/views/TranslationHub/BulkTranslateTrigger.js +161 -0
- package/dist/views/TranslationHub/EditorRecentRunsPanel.d.ts +33 -0
- package/dist/views/TranslationHub/EditorRecentRunsPanel.js +290 -0
- package/dist/views/TranslationHub/Hub.client.d.ts +74 -0
- package/dist/views/TranslationHub/Hub.client.js +357 -0
- package/dist/views/TranslationHub/ModelCombobox.d.ts +14 -0
- package/dist/views/TranslationHub/ModelCombobox.js +415 -0
- package/dist/views/TranslationHub/PerCollectionConfig.d.ts +10 -0
- package/dist/views/TranslationHub/PerCollectionConfig.helpers.d.ts +16 -0
- package/dist/views/TranslationHub/PerCollectionConfig.helpers.js +19 -0
- package/dist/views/TranslationHub/PerCollectionConfig.js +759 -0
- package/dist/views/TranslationHub/SettingsRail.d.ts +11 -0
- package/dist/views/TranslationHub/SettingsRail.js +382 -0
- package/dist/views/TranslationHub/StatusStrip.d.ts +6 -0
- package/dist/views/TranslationHub/StatusStrip.js +451 -0
- package/dist/views/TranslationHub/UsageTable.d.ts +6 -0
- package/dist/views/TranslationHub/UsageTable.helpers.d.ts +69 -0
- package/dist/views/TranslationHub/UsageTable.helpers.js +49 -0
- package/dist/views/TranslationHub/UsageTable.js +1240 -0
- package/dist/views/TranslationHub/alertGrouping.d.ts +70 -0
- package/dist/views/TranslationHub/alertGrouping.js +99 -0
- package/dist/views/TranslationHub/index.d.ts +20 -0
- package/dist/views/TranslationHub/index.js +109 -0
- package/dist/views/TranslationHub/tabNavigation.d.ts +53 -0
- package/dist/views/TranslationHub/tabNavigation.js +74 -0
- package/dist/views/TranslationHub/terminalBannerVisibility.d.ts +33 -0
- package/dist/views/TranslationHub/terminalBannerVisibility.js +124 -0
- package/dist/views/TranslationHub/useBulkTranslateActive.d.ts +49 -0
- package/dist/views/TranslationHub/useBulkTranslateActive.js +251 -0
- package/dist/views/TranslationHub/useFocusTrap.d.ts +6 -0
- package/dist/views/TranslationHub/useFocusTrap.js +81 -0
- package/dist/views/TranslationHub/useTranslationHubUsageSummary.d.ts +77 -0
- package/dist/views/TranslationHub/useTranslationHubUsageSummary.js +267 -0
- package/dist/views/shared/EditorError.d.ts +97 -0
- package/dist/views/shared/EditorError.js +205 -0
- package/dist/views/shared/ModelCell.d.ts +18 -0
- package/dist/views/shared/ModelCell.js +31 -0
- package/dist/views/shared/docHref.d.ts +16 -0
- package/dist/views/shared/docHref.js +26 -0
- package/dist/views/shared/fetch-error-body.d.ts +25 -0
- package/dist/views/shared/fetch-error-body.js +42 -0
- package/dist/views/shared/filterPillStyle.d.ts +35 -0
- package/dist/views/shared/filterPillStyle.js +40 -0
- package/dist/views/shared/format.d.ts +75 -0
- package/dist/views/shared/format.js +131 -0
- package/package.json +141 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG } from '../../bulk-translate-batches-collection.js';
|
|
2
|
+
import { DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG } from '../../bulk-translate-units-collection.js';
|
|
3
|
+
import { checkAndIncrementDailySpend } from '../../lib/daily-spend-cap.js';
|
|
4
|
+
import { createScopedLogger } from '../../lib/logger.js';
|
|
5
|
+
import { BULK_TRANSLATE_DOC_TASK_SLUG } from '../../tasks/bulk-translate-coordinator.js';
|
|
6
|
+
import { errorResponse, forbiddenOwnershipResponse, getAiTranslateConfig, isEditorOrAdmin, ownsBatch, readJsonBody, unauthorizedResponse } from './_helpers.js';
|
|
7
|
+
import { extractBatchId } from './status.js';
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Handler
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const TERMINAL_STATUSES_FOR_RETRY = new Set([
|
|
12
|
+
'success',
|
|
13
|
+
'partial',
|
|
14
|
+
'failed'
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* `POST /api/translation-hub/bulk-translate/:id/retry-failed`
|
|
18
|
+
*
|
|
19
|
+
* Re-enqueues all `failed` units for the batch back to `pending`.
|
|
20
|
+
* Refuses when the batch is non-terminal — there's no semantic way
|
|
21
|
+
* to "retry failed" while units are still running.
|
|
22
|
+
*
|
|
23
|
+
* Daily-cap is re-checked (F-SEC-TOTP-BYPASS — the cap applies to all
|
|
24
|
+
* translate paths, not just first-enqueue).
|
|
25
|
+
*/ export const getBulkTranslateRetryFailedHandler = (options = {})=>async (req)=>{
|
|
26
|
+
const batchesSlug = options.batchesCollectionSlug ?? DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG;
|
|
27
|
+
const unitsSlug = options.unitsCollectionSlug ?? DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG;
|
|
28
|
+
const workerSlug = options.workerTaskSlug ?? BULK_TRANSLATE_DOC_TASK_SLUG;
|
|
29
|
+
if (!req.user) {
|
|
30
|
+
return unauthorizedResponse(req);
|
|
31
|
+
}
|
|
32
|
+
if (!isEditorOrAdmin(req.user)) {
|
|
33
|
+
return errorResponse('forbidden', "You don't have permission to retry failed units. Contact an admin.", 403);
|
|
34
|
+
}
|
|
35
|
+
const config = getAiTranslateConfig(req.payload);
|
|
36
|
+
const bulkConfig = config?.bulk ?? {
|
|
37
|
+
enabled: false
|
|
38
|
+
};
|
|
39
|
+
const batchId = extractBatchId(req.url ?? '');
|
|
40
|
+
if (!batchId) {
|
|
41
|
+
return errorResponse('invalid_batch_id', 'Batch ID could not be parsed from the request URL.', 400);
|
|
42
|
+
}
|
|
43
|
+
const parsed = await readJsonBody(req);
|
|
44
|
+
if (!parsed.ok) return parsed.res;
|
|
45
|
+
const collectionFilter = typeof parsed.body.collection === 'string' && parsed.body.collection.length > 0 ? parsed.body.collection : null;
|
|
46
|
+
const documentIdFilter = typeof parsed.body.documentId === 'string' && parsed.body.documentId.length > 0 ? parsed.body.documentId : null;
|
|
47
|
+
const skipErrorCodes = Array.isArray(parsed.body.skipErrorCodes) ? new Set(parsed.body.skipErrorCodes.filter((s)=>typeof s === 'string')) : new Set();
|
|
48
|
+
let batch;
|
|
49
|
+
try {
|
|
50
|
+
batch = await req.payload.findByID({
|
|
51
|
+
collection: batchesSlug,
|
|
52
|
+
id: batchId,
|
|
53
|
+
overrideAccess: true,
|
|
54
|
+
depth: 0
|
|
55
|
+
});
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const log = createScopedLogger(req.payload, {
|
|
58
|
+
component: 'hub.retry-failed',
|
|
59
|
+
batchId
|
|
60
|
+
});
|
|
61
|
+
log.event('warn', 'hub.retry-failed.batch-read.failed', {
|
|
62
|
+
err,
|
|
63
|
+
endpoint: 'hub.retry-failed',
|
|
64
|
+
batchId
|
|
65
|
+
});
|
|
66
|
+
batch = undefined;
|
|
67
|
+
}
|
|
68
|
+
if (!batch) {
|
|
69
|
+
return errorResponse('not_found', 'This translation run no longer exists. Refresh the page.', 404);
|
|
70
|
+
}
|
|
71
|
+
// 1.2.8: editor can only retry a run they triggered. Admins pass.
|
|
72
|
+
// See ownership-leak rationale in cancel.ts.
|
|
73
|
+
if (!ownsBatch(req.user, batch)) {
|
|
74
|
+
return forbiddenOwnershipResponse();
|
|
75
|
+
}
|
|
76
|
+
if (!TERMINAL_STATUSES_FOR_RETRY.has(batch.status)) {
|
|
77
|
+
return errorResponse('invalid_state', 'The run is still in progress. Wait for it to finish before retrying failed documents.', 409, {
|
|
78
|
+
status: batch.status
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// ----- Fetch failed units, apply skip + collection + doc filters -----
|
|
82
|
+
const failedUnits = await loadAllFailedUnits(req.payload, unitsSlug, batchId);
|
|
83
|
+
const skipped = [];
|
|
84
|
+
const toRetry = [];
|
|
85
|
+
for (const u of failedUnits){
|
|
86
|
+
// Per-bucket retry: when the caller pinned a collection, exclude
|
|
87
|
+
// failed units in other collections / globals from this retry.
|
|
88
|
+
if (collectionFilter && u.collection !== collectionFilter) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
// Per-doc retry: when the caller pinned a documentId, exclude
|
|
92
|
+
// failed units for other docs in the same collection.
|
|
93
|
+
if (documentIdFilter && u.documentId !== documentIdFilter) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (u.failureCode && skipErrorCodes.has(u.failureCode)) {
|
|
97
|
+
skipped.push(String(u.id));
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
toRetry.push(u);
|
|
101
|
+
}
|
|
102
|
+
if (toRetry.length === 0) {
|
|
103
|
+
return Response.json({
|
|
104
|
+
data: {
|
|
105
|
+
batchId,
|
|
106
|
+
retriedJobs: 0,
|
|
107
|
+
skippedJobs: skipped.length
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// ----- Daily cap re-check -----
|
|
112
|
+
// Sum the per-unit cost estimate. Units carry `costUsd` populated
|
|
113
|
+
// on the previous attempt (0 for ones that failed before billing
|
|
114
|
+
// — those re-run for free per cap arithmetic). When NO unit has
|
|
115
|
+
// a known cost we still call the cap helper with 0; that keeps the
|
|
116
|
+
// accounting honest without forcing a fresh batch-level estimate
|
|
117
|
+
// (Decision #29 doesn't apply to retries — we already have observed
|
|
118
|
+
// history).
|
|
119
|
+
const estimatedRetryCost = toRetry.reduce((sum, u)=>sum + (typeof u.costUsd === 'number' ? u.costUsd : 0), 0);
|
|
120
|
+
const capResult = await checkAndIncrementDailySpend(req.payload, estimatedRetryCost, {
|
|
121
|
+
capUsd: bulkConfig.dailyUsdCap
|
|
122
|
+
});
|
|
123
|
+
if (!capResult.allowed) {
|
|
124
|
+
try {
|
|
125
|
+
await bulkConfig.onCapExceeded?.({
|
|
126
|
+
todaySpentUsd: capResult.todaySpentUsd,
|
|
127
|
+
capUsd: capResult.capUsd,
|
|
128
|
+
rejectedEstimateUsd: estimatedRetryCost,
|
|
129
|
+
requestPath: 'bulk-endpoint'
|
|
130
|
+
});
|
|
131
|
+
} catch {
|
|
132
|
+
// Best-effort hook — never block the response on callback.
|
|
133
|
+
}
|
|
134
|
+
return errorResponse(capResult.reason === 'cap_exceeded' ? 'daily_cap_exceeded' : 'invalid_estimate', capResult.message, 402, {
|
|
135
|
+
todaySpentUsd: String(capResult.todaySpentUsd),
|
|
136
|
+
remainingUsd: String(capResult.remainingUsd),
|
|
137
|
+
capUsd: String(capResult.capUsd)
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// ----- Re-enqueue -----
|
|
141
|
+
let retriedJobs = 0;
|
|
142
|
+
for (const u of toRetry){
|
|
143
|
+
try {
|
|
144
|
+
await req.payload.update({
|
|
145
|
+
collection: unitsSlug,
|
|
146
|
+
id: u.id,
|
|
147
|
+
data: {
|
|
148
|
+
status: 'pending',
|
|
149
|
+
attempts: (u.attempts ?? 0) + 0,
|
|
150
|
+
failureCode: null,
|
|
151
|
+
failureMessage: null,
|
|
152
|
+
completedAt: null
|
|
153
|
+
},
|
|
154
|
+
overrideAccess: true
|
|
155
|
+
});
|
|
156
|
+
await req.payload.jobs.queue({
|
|
157
|
+
task: workerSlug,
|
|
158
|
+
input: {
|
|
159
|
+
unitId: String(u.id)
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
retriedJobs += 1;
|
|
163
|
+
} catch (err) {
|
|
164
|
+
req.payload.logger?.warn?.(`[ai-translate] retry-failed: failed to requeue unit ${u.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// ----- Reset batch to running so transitions resume -----
|
|
168
|
+
if (retriedJobs > 0) {
|
|
169
|
+
try {
|
|
170
|
+
await req.payload.update({
|
|
171
|
+
collection: batchesSlug,
|
|
172
|
+
id: batchId,
|
|
173
|
+
data: {
|
|
174
|
+
status: 'running',
|
|
175
|
+
completedAt: null
|
|
176
|
+
},
|
|
177
|
+
overrideAccess: true
|
|
178
|
+
});
|
|
179
|
+
} catch (err) {
|
|
180
|
+
req.payload.logger?.warn?.(`[ai-translate] retry-failed: failed to reset batch ${batchId} status: ${err instanceof Error ? err.message : String(err)}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return Response.json({
|
|
184
|
+
data: {
|
|
185
|
+
batchId,
|
|
186
|
+
retriedJobs,
|
|
187
|
+
skippedJobs: skipped.length
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
async function loadAllFailedUnits(payload, unitsSlug, batchId) {
|
|
192
|
+
// Paginate to bound memory. 1000 per page is well below Payload's
|
|
193
|
+
// default heap budget and works for realistic batch sizes
|
|
194
|
+
// (~10k units would page 10 times).
|
|
195
|
+
const out = [];
|
|
196
|
+
const limit = 1000;
|
|
197
|
+
let page = 1;
|
|
198
|
+
// eslint-disable-next-line no-constant-condition
|
|
199
|
+
while(true){
|
|
200
|
+
const result = await payload.find({
|
|
201
|
+
collection: unitsSlug,
|
|
202
|
+
where: {
|
|
203
|
+
and: [
|
|
204
|
+
{
|
|
205
|
+
batchId: {
|
|
206
|
+
equals: batchId
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
status: {
|
|
211
|
+
equals: 'failed'
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
},
|
|
216
|
+
page,
|
|
217
|
+
limit,
|
|
218
|
+
depth: 0,
|
|
219
|
+
overrideAccess: true
|
|
220
|
+
});
|
|
221
|
+
for (const doc of result.docs){
|
|
222
|
+
out.push({
|
|
223
|
+
id: doc.id,
|
|
224
|
+
attempts: typeof doc.attempts === 'number' ? doc.attempts : 0,
|
|
225
|
+
failureCode: typeof doc.failureCode === 'string' ? doc.failureCode : null,
|
|
226
|
+
costUsd: typeof doc.costUsd === 'number' ? doc.costUsd : 0,
|
|
227
|
+
collection: typeof doc.collection === 'string' ? doc.collection : undefined,
|
|
228
|
+
documentId: typeof doc.documentId === 'string' ? doc.documentId : undefined
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (!result.hasNextPage) break;
|
|
232
|
+
page += 1;
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { PayloadHandler } from 'payload';
|
|
2
|
+
export interface BulkRevertBody {
|
|
3
|
+
/** Required — TOTP is mandatory regardless of plugin config (Decision #33). */
|
|
4
|
+
totpCode?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Bypass the schema-drift refusal when set to `true`. Decision #34
|
|
7
|
+
* + F-DA-REVERT-SCHEMA: revert refuses on schema mismatch unless
|
|
8
|
+
* the admin explicitly opts in. Use with caution — restoring data
|
|
9
|
+
* against a renamed/removed field can corrupt the doc.
|
|
10
|
+
*/
|
|
11
|
+
force?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface BulkRevertWarning {
|
|
14
|
+
unitId: string;
|
|
15
|
+
collection: string;
|
|
16
|
+
documentId: string;
|
|
17
|
+
locale: string;
|
|
18
|
+
reason: 'schema_drift' | 'no_snapshot' | 'snapshot_locale_mismatch' | 'restore_failed' | 'doc_deleted';
|
|
19
|
+
message?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface BulkRevertHandlerOptions {
|
|
22
|
+
batchesCollectionSlug?: string;
|
|
23
|
+
unitsCollectionSlug?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Wall-clock window after `completedAt` during which revert is
|
|
26
|
+
* permitted (Decision #33). Defaults to 24h.
|
|
27
|
+
*/
|
|
28
|
+
windowMs?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Page size for the unit iteration. Default 100 — bounded so the
|
|
31
|
+
* handler stays within serverless memory budgets per F-DA-REVERT-OOM.
|
|
32
|
+
*/
|
|
33
|
+
pageSize?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `POST /api/translation-hub/bulk-translate/:id/revert`
|
|
37
|
+
*
|
|
38
|
+
* Restores the pre-run snapshot of every `success` unit in the batch.
|
|
39
|
+
* Decision #22 v2 / F-DA-REVERT: the snapshot is a nested-object
|
|
40
|
+
* payload directly passable to `payload.update({ data: snapshot })`.
|
|
41
|
+
*
|
|
42
|
+
* Refusal cases:
|
|
43
|
+
* - non-revertable batch status (must be `completed | partial`).
|
|
44
|
+
* - batch is currently in-flight (`queued | running | cancelling`).
|
|
45
|
+
* - batch's `completedAt` is more than `windowMs` ago.
|
|
46
|
+
* - TOTP required regardless of plugin config (Decision #33).
|
|
47
|
+
*
|
|
48
|
+
* Per-unit warnings (don't abort the run):
|
|
49
|
+
* - `schema_drift` — schema hash differs and `force !== true`.
|
|
50
|
+
* - `no_snapshot` — unit has no `preRunSnapshot` (older row).
|
|
51
|
+
* - `restore_failed` — `payload.update` threw.
|
|
52
|
+
* - `doc_deleted` — the source doc was deleted post-bulk; nothing
|
|
53
|
+
* to revert against.
|
|
54
|
+
*
|
|
55
|
+
* Writes are flagged via `context: { aiTranslateInternal: true,
|
|
56
|
+
* disableRevalidate: true }` so:
|
|
57
|
+
* - audit-log marks them `source: 'ai-translate'` (avoids confusing
|
|
58
|
+
* editor with 1000 "user updated" entries for a revert).
|
|
59
|
+
* - the plugin's allowlist-spread at translate.ts:483 + the
|
|
60
|
+
* consumer's revalidate hooks honor the disable flag — we'll
|
|
61
|
+
* fire a terminal revalidate sweep at the end.
|
|
62
|
+
*/
|
|
63
|
+
export declare const getBulkTranslateRevertHandler: (options?: BulkRevertHandlerOptions) => PayloadHandler;
|
|
64
|
+
export type RevertUnitRow = {
|
|
65
|
+
id: string | number;
|
|
66
|
+
collection: string;
|
|
67
|
+
documentId: string;
|
|
68
|
+
locale: string;
|
|
69
|
+
preRunSnapshot: Record<string, unknown> | null;
|
|
70
|
+
/** Locale whose values `preRunSnapshot` holds (v1.2.12+; null on legacy rows). */
|
|
71
|
+
snapshotLocale: string | null;
|
|
72
|
+
schemaHash: string | null;
|
|
73
|
+
};
|
|
74
|
+
export type RevertOutcome = {
|
|
75
|
+
reverted: true;
|
|
76
|
+
} | {
|
|
77
|
+
reverted: false;
|
|
78
|
+
warning?: BulkRevertWarning;
|
|
79
|
+
};
|
|
80
|
+
export type RevertSingleParams = {
|
|
81
|
+
payload: import('payload').Payload;
|
|
82
|
+
unit: RevertUnitRow;
|
|
83
|
+
unitsSlug: string;
|
|
84
|
+
currentSchemaHashByCollection: Map<string, string>;
|
|
85
|
+
force: boolean;
|
|
86
|
+
pluginGlobals: Set<string>;
|
|
87
|
+
};
|
|
88
|
+
export declare function revertSingleUnit(params: RevertSingleParams): Promise<RevertOutcome>;
|