@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,233 @@
|
|
|
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 { getDrizzle, slugToTable } from '../../lib/bulk-translate-migrations.js';
|
|
4
|
+
import { createScopedLogger } from '../../lib/logger.js';
|
|
5
|
+
import { errorResponse, forbiddenOwnershipResponse, isEditorOrAdmin, ownsBatch, unauthorizedResponse } from './_helpers.js';
|
|
6
|
+
import { extractBatchId } from './status.js';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Handler
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const TERMINAL_STATUSES = new Set([
|
|
11
|
+
'success',
|
|
12
|
+
'partial',
|
|
13
|
+
'failed',
|
|
14
|
+
'cancelled',
|
|
15
|
+
'reverted'
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* `POST /api/translation-hub/bulk-translate/:id/cancel`
|
|
19
|
+
*
|
|
20
|
+
* Transitions a queued/running batch to `cancelling`. The coordinator
|
|
21
|
+
* and worker tasks honor the new status at their next checkpoint
|
|
22
|
+
* (coordinator reads `batch.status` at the top of every tick; worker
|
|
23
|
+
* reads at terminal write). In-flight LLM calls finish naturally —
|
|
24
|
+
* we don't try to interrupt them mid-stream.
|
|
25
|
+
*
|
|
26
|
+
* Queued payload-jobs rows for this batch are best-effort cancelled
|
|
27
|
+
* here (we update them to a terminal state so the runner skips them).
|
|
28
|
+
* Some Payload versions expose a jobs API for this; we fall back to a
|
|
29
|
+
* direct update on the queued rows when the API isn't available.
|
|
30
|
+
*/ export const getBulkTranslateCancelHandler = (options = {})=>async (req)=>{
|
|
31
|
+
const batchesSlug = options.batchesCollectionSlug ?? DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG;
|
|
32
|
+
const unitsSlug = options.unitsCollectionSlug ?? DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG;
|
|
33
|
+
const jobsSlug = options.jobsCollectionSlug ?? 'payload-jobs';
|
|
34
|
+
if (!req.user) {
|
|
35
|
+
return unauthorizedResponse(req);
|
|
36
|
+
}
|
|
37
|
+
if (!isEditorOrAdmin(req.user)) {
|
|
38
|
+
return errorResponse('forbidden', "You don't have permission to cancel a bulk-translation batch. Contact an admin.", 403);
|
|
39
|
+
}
|
|
40
|
+
const batchId = extractBatchId(req.url ?? '');
|
|
41
|
+
if (!batchId) {
|
|
42
|
+
return errorResponse('invalid_batch_id', 'Batch ID could not be parsed from the request URL.', 400);
|
|
43
|
+
}
|
|
44
|
+
let batch;
|
|
45
|
+
try {
|
|
46
|
+
batch = await req.payload.findByID({
|
|
47
|
+
collection: batchesSlug,
|
|
48
|
+
id: batchId,
|
|
49
|
+
overrideAccess: true,
|
|
50
|
+
depth: 0
|
|
51
|
+
});
|
|
52
|
+
} catch (err) {
|
|
53
|
+
const log = createScopedLogger(req.payload, {
|
|
54
|
+
component: 'hub.cancel',
|
|
55
|
+
batchId
|
|
56
|
+
});
|
|
57
|
+
log.event('warn', 'hub.cancel.batch-read.failed', {
|
|
58
|
+
err,
|
|
59
|
+
endpoint: 'hub.cancel',
|
|
60
|
+
batchId
|
|
61
|
+
});
|
|
62
|
+
batch = undefined;
|
|
63
|
+
}
|
|
64
|
+
if (!batch) {
|
|
65
|
+
return errorResponse('not_found', 'This translation run no longer exists. Refresh the page.', 404);
|
|
66
|
+
}
|
|
67
|
+
// 1.2.8: editor can only cancel a run they triggered. Admins always
|
|
68
|
+
// pass (see `ownsBatch`). The check is intentionally AFTER the
|
|
69
|
+
// not_found case so editors can't probe for the existence of other
|
|
70
|
+
// editors' batches via 403-vs-404 timing.
|
|
71
|
+
if (!ownsBatch(req.user, batch)) {
|
|
72
|
+
return forbiddenOwnershipResponse();
|
|
73
|
+
}
|
|
74
|
+
if (TERMINAL_STATUSES.has(batch.status) || batch.status === 'cancelling') {
|
|
75
|
+
const isCancelling = batch.status === 'cancelling';
|
|
76
|
+
return errorResponse('invalid_state', isCancelling ? 'This run is already being cancelled — no action needed.' : "This run has already ended — there's nothing to cancel.", 409, {
|
|
77
|
+
status: batch.status
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// ----- Flip status to cancelling -----
|
|
81
|
+
try {
|
|
82
|
+
await req.payload.update({
|
|
83
|
+
collection: batchesSlug,
|
|
84
|
+
id: batchId,
|
|
85
|
+
data: {
|
|
86
|
+
status: 'cancelling',
|
|
87
|
+
cancelledByUserId: String(req.user.id),
|
|
88
|
+
cancelledAt: new Date().toISOString()
|
|
89
|
+
},
|
|
90
|
+
overrideAccess: true
|
|
91
|
+
});
|
|
92
|
+
} catch (err) {
|
|
93
|
+
req.payload.logger?.warn?.(`[ai-translate] cancel: failed to mark batch cancelling for ${batchId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
94
|
+
return errorResponse('cancel_failed', "The run couldn't be cancelled right now. Refresh the page and try again, or contact engineering.", 500);
|
|
95
|
+
}
|
|
96
|
+
// ----- Cancel queued payload-jobs rows for this batch -----
|
|
97
|
+
// We can't be surgical without a jsonb path query (see Spike 2);
|
|
98
|
+
// instead, we look up the units that are still `pending` (and have
|
|
99
|
+
// not yet been dequeued) and mark them as `skipped`. The worker
|
|
100
|
+
// task's first-action terminal-state guard makes any race here
|
|
101
|
+
// safe — if a worker did dequeue between our read + write, it
|
|
102
|
+
// sees the unit is no longer `pending` and exits.
|
|
103
|
+
let cancelledJobs = 0;
|
|
104
|
+
let inFlightJobs = 0;
|
|
105
|
+
try {
|
|
106
|
+
// Fast path: one set-based UPDATE on Postgres. The previous
|
|
107
|
+
// per-row loop held the HTTP request open for ~15ms × pending
|
|
108
|
+
// units — on a 10k-unit batch that's ~2.5 minutes, past every
|
|
109
|
+
// LB timeout, while units claimed mid-loop kept translating.
|
|
110
|
+
// The single statement is atomic w.r.t. the worker's claim
|
|
111
|
+
// (claim flips pending→running; whichever lands first wins and
|
|
112
|
+
// the other side sees a terminal/claimed row and backs off).
|
|
113
|
+
let sweptInBulk = false;
|
|
114
|
+
if (/^\d+$/.test(batchId)) {
|
|
115
|
+
const db = getDrizzle(req.payload);
|
|
116
|
+
if (db) {
|
|
117
|
+
try {
|
|
118
|
+
const res = await db.execute(`UPDATE ${slugToTable(unitsSlug)}
|
|
119
|
+
SET status = 'skipped',
|
|
120
|
+
failure_message = 'cancelled_by_admin',
|
|
121
|
+
completed_at = now(),
|
|
122
|
+
updated_at = now()
|
|
123
|
+
WHERE batch_id_id = ${batchId}
|
|
124
|
+
AND status = 'pending'`);
|
|
125
|
+
cancelledJobs = Number(res?.rowCount ?? 0);
|
|
126
|
+
sweptInBulk = true;
|
|
127
|
+
} catch (err) {
|
|
128
|
+
req.payload.logger?.warn?.(`[ai-translate] cancel: set-based pending sweep failed for batch ${batchId}, falling back to per-row: ${err instanceof Error ? err.message : String(err)}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!sweptInBulk) {
|
|
133
|
+
// Non-Postgres adapters / non-numeric ids: per-row fallback.
|
|
134
|
+
const pending = await req.payload.find({
|
|
135
|
+
collection: unitsSlug,
|
|
136
|
+
where: {
|
|
137
|
+
and: [
|
|
138
|
+
{
|
|
139
|
+
batchId: {
|
|
140
|
+
equals: batchId
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
status: {
|
|
145
|
+
equals: 'pending'
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
limit: 10_000,
|
|
151
|
+
depth: 0,
|
|
152
|
+
overrideAccess: true
|
|
153
|
+
});
|
|
154
|
+
for (const unit of pending.docs){
|
|
155
|
+
try {
|
|
156
|
+
await req.payload.update({
|
|
157
|
+
collection: unitsSlug,
|
|
158
|
+
id: unit.id,
|
|
159
|
+
data: {
|
|
160
|
+
status: 'skipped',
|
|
161
|
+
failureMessage: 'cancelled_by_admin',
|
|
162
|
+
completedAt: new Date().toISOString()
|
|
163
|
+
},
|
|
164
|
+
overrideAccess: true
|
|
165
|
+
});
|
|
166
|
+
cancelledJobs += 1;
|
|
167
|
+
} catch {
|
|
168
|
+
// Per-unit failure — best-effort cancel.
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const running = await req.payload.count({
|
|
173
|
+
collection: unitsSlug,
|
|
174
|
+
where: {
|
|
175
|
+
and: [
|
|
176
|
+
{
|
|
177
|
+
batchId: {
|
|
178
|
+
equals: batchId
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
status: {
|
|
183
|
+
equals: 'running'
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
overrideAccess: true
|
|
189
|
+
});
|
|
190
|
+
inFlightJobs = running.totalDocs;
|
|
191
|
+
} catch (err) {
|
|
192
|
+
req.payload.logger?.warn?.(`[ai-translate] cancel: best-effort unit cleanup failed for batch ${batchId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
193
|
+
}
|
|
194
|
+
// v1.2.5: short-circuit batch transition when there are no
|
|
195
|
+
// in-flight workers. Pre-1.2.5 the batch stayed in `cancelling`
|
|
196
|
+
// forever in this case — `maybeTransitionBatch` is fired by
|
|
197
|
+
// workers as they finish, but if 0 workers are running there's
|
|
198
|
+
// nothing to fire it. The result was a "stuck cancelling" state
|
|
199
|
+
// that only `/force-reset` could clear. Now we flip directly to
|
|
200
|
+
// `cancelled` here. When `inFlightJobs > 0` we leave the batch in
|
|
201
|
+
// `cancelling` and rely on the (now-fixed) `maybeTransitionBatch`
|
|
202
|
+
// path to flip it once the last in-flight unit lands.
|
|
203
|
+
if (inFlightJobs === 0) {
|
|
204
|
+
try {
|
|
205
|
+
await req.payload.update({
|
|
206
|
+
collection: batchesSlug,
|
|
207
|
+
id: batchId,
|
|
208
|
+
data: {
|
|
209
|
+
status: 'cancelled',
|
|
210
|
+
completedAt: new Date().toISOString()
|
|
211
|
+
},
|
|
212
|
+
overrideAccess: true
|
|
213
|
+
});
|
|
214
|
+
} catch (err) {
|
|
215
|
+
// Best-effort. If this fails, the batch stays in `cancelling`
|
|
216
|
+
// and force-reset is still the manual escape hatch.
|
|
217
|
+
req.payload.logger?.warn?.(`[ai-translate] cancel: short-circuit batch transition failed for ${batchId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Try (best-effort) to drop queued payload-jobs rows for the
|
|
221
|
+
// coordinator/worker for this batch. The exact column layout
|
|
222
|
+
// varies across Payload versions, so we wrap the optional path
|
|
223
|
+
// in a try.
|
|
224
|
+
void jobsSlug; // reserved for future use; see comment above.
|
|
225
|
+
return Response.json({
|
|
226
|
+
data: {
|
|
227
|
+
batchId,
|
|
228
|
+
cancelledJobs,
|
|
229
|
+
inFlightJobs,
|
|
230
|
+
status: inFlightJobs === 0 ? 'cancelled' : 'cancelling'
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { PayloadHandler } from 'payload';
|
|
2
|
+
import type { AITranslatePluginConfig, BulkTranslateConfig } from '../../types.js';
|
|
3
|
+
export interface BulkEnqueueScope {
|
|
4
|
+
collections?: string[];
|
|
5
|
+
globals?: string[];
|
|
6
|
+
locales?: string[];
|
|
7
|
+
excludeCollections?: string[];
|
|
8
|
+
documentIds?: Record<string, string[]>;
|
|
9
|
+
}
|
|
10
|
+
export interface BulkEnqueueBody {
|
|
11
|
+
scope: BulkEnqueueScope;
|
|
12
|
+
mode: 'changed' | 'force' | 'canary';
|
|
13
|
+
canaryLimit?: number;
|
|
14
|
+
totpCode?: string;
|
|
15
|
+
triggerReason?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface BulkEnqueueHandlerOptions {
|
|
18
|
+
/** Slug override for the batches collection. */
|
|
19
|
+
batchesCollectionSlug?: string;
|
|
20
|
+
/** Coordinator task slug to enqueue. */
|
|
21
|
+
coordinatorTaskSlug?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `POST /api/translation-hub/bulk-translate` — enqueue a bulk run.
|
|
25
|
+
*
|
|
26
|
+
* Flow (see Design-2026-05-27-bulk-translate.md §4 PR2):
|
|
27
|
+
* 1. Auth — admin role (Decision #31).
|
|
28
|
+
* 2. TOTP friction — gated by `bulk.requireTotp` (Decision #13 v2).
|
|
29
|
+
* Friction-not-boundary: the daily USD cap is the real guard.
|
|
30
|
+
* 3. Body validation — discriminated by `mode`.
|
|
31
|
+
* 4. Concurrency refusal — one bulk run at a time per Decision #2.
|
|
32
|
+
* 5. Cost estimate — sum provider's `estimate()` across the resolved
|
|
33
|
+
* doc list. Hard-reject if `undefined` (Decision #29). Canary
|
|
34
|
+
* mode skips estimation — too cheap to matter.
|
|
35
|
+
* 6. Daily cap — `checkAndIncrementDailySpend` (Decision #14).
|
|
36
|
+
* 7. Snapshot pinned config — `{ providerKey, modelId, sourceLocale }`
|
|
37
|
+
* captured at enqueue (Decision #5).
|
|
38
|
+
* 8. Create batch row → queue coordinator task → 202.
|
|
39
|
+
*/
|
|
40
|
+
export declare const getBulkTranslateEnqueueHandler: (options?: BulkEnqueueHandlerOptions) => PayloadHandler;
|
|
41
|
+
type ResolvedScope = {
|
|
42
|
+
collections: string[];
|
|
43
|
+
globals: string[];
|
|
44
|
+
locales: string[];
|
|
45
|
+
excludeCollections: string[];
|
|
46
|
+
/**
|
|
47
|
+
* The coordinator's `BulkBatchScope.documentIds` is a flat array
|
|
48
|
+
* (it only enumerates a single collection in `documents-explicit`
|
|
49
|
+
* mode). When the caller supplies a per-collection map, we flatten
|
|
50
|
+
* here AFTER honoring `collections` membership.
|
|
51
|
+
*/
|
|
52
|
+
documentIdsFlat: string[];
|
|
53
|
+
};
|
|
54
|
+
export declare function resolveScope(scope: BulkEnqueueScope, config: AITranslatePluginConfig, bulk: BulkTranslateConfig): ResolvedScope;
|
|
55
|
+
export type BatchCostEstimate = {
|
|
56
|
+
estimatedCostUsd: number | undefined;
|
|
57
|
+
documentCount: number;
|
|
58
|
+
};
|
|
59
|
+
export type BatchEstimateScope = ResolvedScope;
|
|
60
|
+
/**
|
|
61
|
+
* Enumerate the scope's docs, run the provider's `estimate()` once
|
|
62
|
+
* with the aggregated items, multiply by locale count. Mirrors the
|
|
63
|
+
* pattern in `translate.ts:estimateRequestCost` but at batch scale.
|
|
64
|
+
*
|
|
65
|
+
* Returns `estimatedCostUsd: undefined` only when the provider has no
|
|
66
|
+
* `estimate()` method — the enqueue handler then hard-rejects per
|
|
67
|
+
* Decision #29.
|
|
68
|
+
*/
|
|
69
|
+
export declare function estimateBatchCost(payload: import('payload').Payload, config: AITranslatePluginConfig, scope: ResolvedScope): Promise<BatchCostEstimate>;
|
|
70
|
+
export {};
|