@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,481 @@
|
|
|
1
|
+
import { DEFAULT_USAGE_COLLECTION_SLUG } from '../../defaults.js';
|
|
2
|
+
import { errorResponse, isAdminUser, unauthorizedResponse } from './_helpers.js';
|
|
3
|
+
/**
|
|
4
|
+
* `GET /api/translation-hub/usage-summary`
|
|
5
|
+
*
|
|
6
|
+
* Server-side aggregation of the `translation-usage` collection for
|
|
7
|
+
* Hub Overview + Audit & Cost KPIs. Replaces the prior client-side
|
|
8
|
+
* pattern where both panels fetched `?limit=1000` and reduced in JS;
|
|
9
|
+
* past 1000 rows the totals silently truncated and the network panel
|
|
10
|
+
* showed two parallel megabyte responses per Hub mount. See NEW-15.
|
|
11
|
+
*
|
|
12
|
+
* Query params:
|
|
13
|
+
* - `range` — one of `7d` | `30d` | `90d` | `all` | `custom`.
|
|
14
|
+
* Defaults to `7d`.
|
|
15
|
+
* - `from` — ISO timestamp; required when `range=custom`.
|
|
16
|
+
* - `to` — ISO timestamp; optional ceiling when `range=custom`.
|
|
17
|
+
*
|
|
18
|
+
* Response shape (`UsageSummaryResponse`):
|
|
19
|
+
* {
|
|
20
|
+
* range: { kind, from?, to?, since?, until? },
|
|
21
|
+
* totals: {
|
|
22
|
+
* runs, succeeded, failed, inputTokens, outputTokens, costUsd,
|
|
23
|
+
* totalMatching, truncated
|
|
24
|
+
* },
|
|
25
|
+
* byCollection: [{ slug, kind, runs, tokens, costUsd }],
|
|
26
|
+
* byModel: [{ model, runs, tokens, costUsd }],
|
|
27
|
+
* byLocale: [{ locale, runs, failed }],
|
|
28
|
+
* samples: [UsageRow] // 20 most recent rows for the "Recent
|
|
29
|
+
* // translations" / "Recent failures" tables
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* Implementation note: when `payload.db.drizzle` is available (Postgres
|
|
33
|
+
* adapter), aggregates run as `SELECT … SUM(...) GROUP BY ...` against
|
|
34
|
+
* the underlying tables. When it isn't (mock / SQLite / other adapter
|
|
35
|
+
* stacks in tests), we fall back to a paginated `payload.find()` walk
|
|
36
|
+
* that still avoids loading the entire result set into memory at once.
|
|
37
|
+
* Both paths return the same shape — consumers cannot tell the
|
|
38
|
+
* difference. The drizzle path is what makes >1000-row windows
|
|
39
|
+
* correct, which is the NEW-15 contract.
|
|
40
|
+
*
|
|
41
|
+
* Auth: admin-only, identical posture to every other translation-hub
|
|
42
|
+
* endpoint (see `active.ts`, `list.ts`). Editors aren't allowed to
|
|
43
|
+
* read cost data.
|
|
44
|
+
*/ const PRESET_MS = {
|
|
45
|
+
'7d': 7 * 24 * 60 * 60 * 1000,
|
|
46
|
+
'30d': 30 * 24 * 60 * 60 * 1000,
|
|
47
|
+
'90d': 90 * 24 * 60 * 60 * 1000
|
|
48
|
+
};
|
|
49
|
+
const SAMPLES_LIMIT = 20;
|
|
50
|
+
/**
|
|
51
|
+
* Parse the `range`/`from`/`to` query params. Exported for testing —
|
|
52
|
+
* mirrors the StatusStrip/AuditPanel resolution rules so the UI and
|
|
53
|
+
* the endpoint agree on what "Last 7 days" means.
|
|
54
|
+
*/ export function parseRangeParams(params) {
|
|
55
|
+
const rangeRaw = (params.get('range') ?? '7d').toLowerCase();
|
|
56
|
+
if (rangeRaw === 'all') {
|
|
57
|
+
return {
|
|
58
|
+
range: {
|
|
59
|
+
kind: 'all'
|
|
60
|
+
},
|
|
61
|
+
since: null,
|
|
62
|
+
until: null
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (rangeRaw === 'custom') {
|
|
66
|
+
const fromRaw = params.get('from');
|
|
67
|
+
const toRaw = params.get('to');
|
|
68
|
+
let since = null;
|
|
69
|
+
let until = null;
|
|
70
|
+
if (fromRaw) {
|
|
71
|
+
const d = new Date(fromRaw);
|
|
72
|
+
if (!Number.isNaN(d.getTime())) {
|
|
73
|
+
since = d.toISOString();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (toRaw) {
|
|
77
|
+
const d = new Date(toRaw);
|
|
78
|
+
if (!Number.isNaN(d.getTime())) {
|
|
79
|
+
// Include end-of-day so a "to 2026-01-15" filter matches rows
|
|
80
|
+
// logged that day, not just rows before midnight UTC. Mirrors
|
|
81
|
+
// the StatusStrip + AuditPanel client behaviour.
|
|
82
|
+
d.setUTCHours(23, 59, 59, 999);
|
|
83
|
+
until = d.toISOString();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
range: {
|
|
88
|
+
kind: 'custom',
|
|
89
|
+
...since ? {
|
|
90
|
+
from: since
|
|
91
|
+
} : {},
|
|
92
|
+
...until ? {
|
|
93
|
+
to: until
|
|
94
|
+
} : {}
|
|
95
|
+
},
|
|
96
|
+
since,
|
|
97
|
+
until
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (rangeRaw === '7d' || rangeRaw === '30d' || rangeRaw === '90d') {
|
|
101
|
+
const since = new Date(Date.now() - PRESET_MS[rangeRaw]).toISOString();
|
|
102
|
+
return {
|
|
103
|
+
range: {
|
|
104
|
+
kind: rangeRaw,
|
|
105
|
+
since
|
|
106
|
+
},
|
|
107
|
+
since,
|
|
108
|
+
until: null
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// Unknown preset — fall back to 7d so a client typo doesn't surface a
|
|
112
|
+
// 400. Same shape every other consumer expects.
|
|
113
|
+
const since = new Date(Date.now() - PRESET_MS['7d']).toISOString();
|
|
114
|
+
return {
|
|
115
|
+
range: {
|
|
116
|
+
kind: '7d',
|
|
117
|
+
since
|
|
118
|
+
},
|
|
119
|
+
since,
|
|
120
|
+
until: null
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export const getTranslationHubUsageSummaryHandler = (options = {})=>async (req)=>{
|
|
124
|
+
if (!req.user) {
|
|
125
|
+
return unauthorizedResponse(req);
|
|
126
|
+
}
|
|
127
|
+
if (!isAdminUser(req.user)) {
|
|
128
|
+
return errorResponse('forbidden', 'Admin role required to read translation usage.', 403);
|
|
129
|
+
}
|
|
130
|
+
const usageSlug = options.usageCollectionSlug ?? DEFAULT_USAGE_COLLECTION_SLUG;
|
|
131
|
+
const url = new URL(req.url ?? 'http://localhost');
|
|
132
|
+
const { range, since, until } = parseRangeParams(url.searchParams);
|
|
133
|
+
try {
|
|
134
|
+
const drizzle = getDrizzleHandle(req.payload);
|
|
135
|
+
const aggregates = drizzle ? await aggregateWithDrizzle(drizzle, usageSlug, since, until) : await aggregateWithPayload(req.payload, usageSlug, since, until);
|
|
136
|
+
const samples = await fetchRecentSamples(req.payload, usageSlug, since, until);
|
|
137
|
+
const body = {
|
|
138
|
+
range,
|
|
139
|
+
totals: aggregates.totals,
|
|
140
|
+
byCollection: aggregates.byCollection,
|
|
141
|
+
byModel: aggregates.byModel,
|
|
142
|
+
byLocale: aggregates.byLocale,
|
|
143
|
+
samples
|
|
144
|
+
};
|
|
145
|
+
return Response.json(body);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
req.payload.logger?.error?.(`[ai-translate] usage-summary: aggregation failed — ${err instanceof Error ? err.message : String(err)}`);
|
|
148
|
+
return errorResponse('aggregation_failed', 'Could not compute usage summary. Try again or narrow the time range.', 500);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
function getDrizzleHandle(payload) {
|
|
152
|
+
const db = payload.db.drizzle;
|
|
153
|
+
if (!db || typeof db.execute !== 'function') return null;
|
|
154
|
+
return db;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Slug → snake_case table identifier. Identical rule to the one in
|
|
158
|
+
* `lib/bulk-translate-migrations.ts`; duplicated locally to keep this
|
|
159
|
+
* endpoint independent of bulk-translate internals.
|
|
160
|
+
*/ function slugToTable(slug) {
|
|
161
|
+
if (!/^[a-z][a-z0-9_-]{0,62}$/.test(slug)) {
|
|
162
|
+
throw new Error(`[ai-translate] usage-summary: invalid usage collection slug "${slug}" (must match /^[a-z][a-z0-9_-]{0,62}$/)`);
|
|
163
|
+
}
|
|
164
|
+
return slug.replace(/-/g, '_');
|
|
165
|
+
}
|
|
166
|
+
async function loadSqlTag() {
|
|
167
|
+
try {
|
|
168
|
+
// String-indirected so the bundler/tsc don't try to resolve
|
|
169
|
+
// `drizzle-orm` at build time — consumers without the dep still
|
|
170
|
+
// ship the plugin, the fallback aggregation path catches them.
|
|
171
|
+
const modulePath = 'drizzle-orm';
|
|
172
|
+
const mod = await import(modulePath);
|
|
173
|
+
return mod.sql ?? null;
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function aggregateWithDrizzle(drizzle, usageSlug, since, until) {
|
|
179
|
+
const sql = await loadSqlTag();
|
|
180
|
+
if (!sql) {
|
|
181
|
+
throw new Error('drizzle-orm sql tag unavailable');
|
|
182
|
+
}
|
|
183
|
+
const usageTable = slugToTable(usageSlug);
|
|
184
|
+
const targetLocalesTable = `${usageTable}_target_locales`;
|
|
185
|
+
// We splice the table identifiers via sql.raw — they come from the
|
|
186
|
+
// slug allowlist above (`/^[a-z][a-z0-9_-]{0,62}$/`), not from user
|
|
187
|
+
// input, so there's no injection surface.
|
|
188
|
+
const usageRef = sql.raw(`"${usageTable}"`);
|
|
189
|
+
const targetLocalesRef = sql.raw(`"${targetLocalesTable}"`);
|
|
190
|
+
// Build the WHERE filter as a single fragment so each query reuses it.
|
|
191
|
+
// Bound params are interpolated by drizzle.
|
|
192
|
+
const sinceParam = since ? sql`AND "created_at" >= ${since}` : sql``;
|
|
193
|
+
const untilParam = until ? sql`AND "created_at" <= ${until}` : sql``;
|
|
194
|
+
const whereClause = sql`WHERE 1 = 1 ${sinceParam} ${untilParam}`;
|
|
195
|
+
// ----- totals + truncation count (single row) -----
|
|
196
|
+
// ROUND2-2 canonical definitions:
|
|
197
|
+
// - succeeded = status = 'succeeded' AND fields_translated > 0
|
|
198
|
+
// - preserved = status = 'succeeded' AND COALESCE(fields_translated, 0) <= 0
|
|
199
|
+
// - failed = status = 'failed'
|
|
200
|
+
// Invariant: runs = succeeded + preserved + failed. The Hub Overview
|
|
201
|
+
// and Audit tabs both consume this endpoint and must report the same
|
|
202
|
+
// numbers for the same time window.
|
|
203
|
+
const totalsResult = await drizzle.execute(sql`
|
|
204
|
+
SELECT
|
|
205
|
+
COUNT(*)::bigint AS runs,
|
|
206
|
+
COALESCE(SUM(CASE WHEN "status" = 'succeeded' AND COALESCE("fields_translated", 0) > 0 THEN 1 ELSE 0 END), 0)::bigint AS succeeded,
|
|
207
|
+
COALESCE(SUM(CASE WHEN "status" = 'succeeded' AND COALESCE("fields_translated", 0) <= 0 THEN 1 ELSE 0 END), 0)::bigint AS preserved,
|
|
208
|
+
COALESCE(SUM(CASE WHEN "status" = 'failed' THEN 1 ELSE 0 END), 0)::bigint AS failed,
|
|
209
|
+
COALESCE(SUM("input_tokens"), 0)::numeric AS input_tokens,
|
|
210
|
+
COALESCE(SUM("output_tokens"), 0)::numeric AS output_tokens,
|
|
211
|
+
COALESCE(SUM("estimated_cost_usd"), 0)::numeric AS cost_usd
|
|
212
|
+
FROM ${usageRef}
|
|
213
|
+
${whereClause}
|
|
214
|
+
`);
|
|
215
|
+
const totalsRow = totalsResult.rows?.[0] ?? {};
|
|
216
|
+
const runs = toFiniteNumber(totalsRow.runs);
|
|
217
|
+
const totals = {
|
|
218
|
+
runs,
|
|
219
|
+
succeeded: toFiniteNumber(totalsRow.succeeded),
|
|
220
|
+
preserved: toFiniteNumber(totalsRow.preserved),
|
|
221
|
+
failed: toFiniteNumber(totalsRow.failed),
|
|
222
|
+
inputTokens: toFiniteNumber(totalsRow.input_tokens),
|
|
223
|
+
outputTokens: toFiniteNumber(totalsRow.output_tokens),
|
|
224
|
+
costUsd: toFiniteNumber(totalsRow.cost_usd),
|
|
225
|
+
totalMatching: runs,
|
|
226
|
+
truncated: false
|
|
227
|
+
};
|
|
228
|
+
// ----- per-collection -----
|
|
229
|
+
const byCollectionResult = await drizzle.execute(sql`
|
|
230
|
+
SELECT
|
|
231
|
+
"slug",
|
|
232
|
+
"kind",
|
|
233
|
+
COUNT(*)::bigint AS runs,
|
|
234
|
+
COALESCE(SUM("input_tokens" + "output_tokens"), 0)::numeric AS tokens,
|
|
235
|
+
COALESCE(SUM("estimated_cost_usd"), 0)::numeric AS cost_usd
|
|
236
|
+
FROM ${usageRef}
|
|
237
|
+
${whereClause}
|
|
238
|
+
GROUP BY "slug", "kind"
|
|
239
|
+
ORDER BY cost_usd DESC, runs DESC
|
|
240
|
+
`);
|
|
241
|
+
const byCollection = (byCollectionResult.rows ?? []).map((row)=>({
|
|
242
|
+
slug: typeof row.slug === 'string' ? row.slug : '—',
|
|
243
|
+
kind: row.kind === 'global' ? 'global' : 'collection',
|
|
244
|
+
runs: toFiniteNumber(row.runs),
|
|
245
|
+
tokens: toFiniteNumber(row.tokens),
|
|
246
|
+
costUsd: toFiniteNumber(row.cost_usd)
|
|
247
|
+
}));
|
|
248
|
+
// ----- per-model. Rows whose model is null/empty/provider-only
|
|
249
|
+
// (no slash) collapse into a single "__unresolved__" bucket so the
|
|
250
|
+
// UI's "Failed before model selection" pseudo-row stays accurate
|
|
251
|
+
// even when the dataset crosses the 1000-row boundary. Mirrors
|
|
252
|
+
// `isRealModelId` in AuditPanel.helpers.
|
|
253
|
+
const byModelResult = await drizzle.execute(sql`
|
|
254
|
+
SELECT
|
|
255
|
+
CASE
|
|
256
|
+
WHEN "model" IS NULL OR "model" = '' OR POSITION('/' IN "model") = 0
|
|
257
|
+
THEN '__unresolved__'
|
|
258
|
+
ELSE "model"
|
|
259
|
+
END AS model,
|
|
260
|
+
COUNT(*)::bigint AS runs,
|
|
261
|
+
COALESCE(SUM("input_tokens" + "output_tokens"), 0)::numeric AS tokens,
|
|
262
|
+
COALESCE(SUM("estimated_cost_usd"), 0)::numeric AS cost_usd
|
|
263
|
+
FROM ${usageRef}
|
|
264
|
+
${whereClause}
|
|
265
|
+
GROUP BY model
|
|
266
|
+
ORDER BY cost_usd DESC, runs DESC
|
|
267
|
+
`);
|
|
268
|
+
const byModel = (byModelResult.rows ?? []).map((row)=>({
|
|
269
|
+
model: typeof row.model === 'string' ? row.model : '__unresolved__',
|
|
270
|
+
runs: toFiniteNumber(row.runs),
|
|
271
|
+
tokens: toFiniteNumber(row.tokens),
|
|
272
|
+
costUsd: toFiniteNumber(row.cost_usd)
|
|
273
|
+
}));
|
|
274
|
+
// ----- per-locale (join on target_locales) -----
|
|
275
|
+
// Locale rows are joined to the parent usage row so the same time
|
|
276
|
+
// filter applies. Counts both total target-locale entries and the
|
|
277
|
+
// ones marked `status = 'failed'`.
|
|
278
|
+
const byLocaleResult = await drizzle.execute(sql`
|
|
279
|
+
SELECT
|
|
280
|
+
tl."locale" AS locale,
|
|
281
|
+
COUNT(*)::bigint AS runs,
|
|
282
|
+
COALESCE(SUM(CASE WHEN tl."status" = 'failed' THEN 1 ELSE 0 END), 0)::bigint AS failed
|
|
283
|
+
FROM ${targetLocalesRef} AS tl
|
|
284
|
+
JOIN ${usageRef} AS u ON u."id" = tl."_parent_id"
|
|
285
|
+
WHERE 1 = 1
|
|
286
|
+
${since ? sql`AND u."created_at" >= ${since}` : sql``}
|
|
287
|
+
${until ? sql`AND u."created_at" <= ${until}` : sql``}
|
|
288
|
+
GROUP BY tl."locale"
|
|
289
|
+
ORDER BY runs DESC
|
|
290
|
+
`);
|
|
291
|
+
const byLocale = (byLocaleResult.rows ?? []).map((row)=>({
|
|
292
|
+
locale: typeof row.locale === 'string' ? row.locale : '—',
|
|
293
|
+
runs: toFiniteNumber(row.runs),
|
|
294
|
+
failed: toFiniteNumber(row.failed)
|
|
295
|
+
}));
|
|
296
|
+
return {
|
|
297
|
+
totals,
|
|
298
|
+
byCollection,
|
|
299
|
+
byModel,
|
|
300
|
+
byLocale
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
// Fallback aggregation path (page-by-page payload.find)
|
|
305
|
+
// ---------------------------------------------------------------------------
|
|
306
|
+
// `targetLocales` join-table arrives as a nested array on each doc when
|
|
307
|
+
// read via `payload.find`. The Postgres adapter only inflates it when
|
|
308
|
+
// `depth > 0`, but reading `depth=0` returns the raw IDs. We use
|
|
309
|
+
// `depth=1` for the fallback so per-locale counts are correct.
|
|
310
|
+
const PAGINATION_LIMIT = 500;
|
|
311
|
+
async function aggregateWithPayload(payload, usageSlug, since, until) {
|
|
312
|
+
const whereClauses = [];
|
|
313
|
+
if (since) whereClauses.push({
|
|
314
|
+
createdAt: {
|
|
315
|
+
greater_than_equal: since
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
if (until) whereClauses.push({
|
|
319
|
+
createdAt: {
|
|
320
|
+
less_than_equal: until
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
const where = whereClauses.length > 0 ? {
|
|
324
|
+
and: whereClauses
|
|
325
|
+
} : undefined;
|
|
326
|
+
let runs = 0;
|
|
327
|
+
let succeeded = 0;
|
|
328
|
+
let preserved = 0;
|
|
329
|
+
let failed = 0;
|
|
330
|
+
let inputTokens = 0;
|
|
331
|
+
let outputTokens = 0;
|
|
332
|
+
let costUsd = 0;
|
|
333
|
+
const perCollection = new Map();
|
|
334
|
+
const perModel = new Map();
|
|
335
|
+
const perLocale = new Map();
|
|
336
|
+
let page = 1;
|
|
337
|
+
// eslint-disable-next-line no-constant-condition
|
|
338
|
+
while(true){
|
|
339
|
+
const result = await payload.find({
|
|
340
|
+
collection: usageSlug,
|
|
341
|
+
where,
|
|
342
|
+
page,
|
|
343
|
+
limit: PAGINATION_LIMIT,
|
|
344
|
+
depth: 0,
|
|
345
|
+
overrideAccess: true
|
|
346
|
+
});
|
|
347
|
+
const docs = result.docs;
|
|
348
|
+
for (const r of docs){
|
|
349
|
+
runs++;
|
|
350
|
+
// ROUND2-2 canonical definitions — mirrors the drizzle path's
|
|
351
|
+
// CASE expressions. `fieldsTranslated > 0` separates LLM-write
|
|
352
|
+
// successes from preserve-only no-ops.
|
|
353
|
+
const fieldsTranslated = toFiniteNumber(r.fieldsTranslated);
|
|
354
|
+
if (r.status === 'succeeded' && fieldsTranslated > 0) succeeded++;
|
|
355
|
+
else if (r.status === 'succeeded') preserved++;
|
|
356
|
+
if (r.status === 'failed') failed++;
|
|
357
|
+
const inT = toFiniteNumber(r.inputTokens);
|
|
358
|
+
const outT = toFiniteNumber(r.outputTokens);
|
|
359
|
+
inputTokens += inT;
|
|
360
|
+
outputTokens += outT;
|
|
361
|
+
const cost = toFiniteNumber(r.estimatedCostUsd);
|
|
362
|
+
costUsd += cost;
|
|
363
|
+
const slug = typeof r.slug === 'string' ? r.slug : '—';
|
|
364
|
+
const kind = r.kind === 'global' ? 'global' : 'collection';
|
|
365
|
+
const collectionKey = `${kind}:${slug}`;
|
|
366
|
+
const cBucket = perCollection.get(collectionKey) ?? {
|
|
367
|
+
slug,
|
|
368
|
+
kind,
|
|
369
|
+
runs: 0,
|
|
370
|
+
tokens: 0,
|
|
371
|
+
costUsd: 0
|
|
372
|
+
};
|
|
373
|
+
cBucket.runs++;
|
|
374
|
+
cBucket.tokens += inT + outT;
|
|
375
|
+
cBucket.costUsd += cost;
|
|
376
|
+
perCollection.set(collectionKey, cBucket);
|
|
377
|
+
const modelRaw = typeof r.model === 'string' ? r.model : '';
|
|
378
|
+
const modelKey = modelRaw.length === 0 || !modelRaw.includes('/') ? '__unresolved__' : modelRaw;
|
|
379
|
+
const mBucket = perModel.get(modelKey) ?? {
|
|
380
|
+
model: modelKey,
|
|
381
|
+
runs: 0,
|
|
382
|
+
tokens: 0,
|
|
383
|
+
costUsd: 0
|
|
384
|
+
};
|
|
385
|
+
mBucket.runs++;
|
|
386
|
+
mBucket.tokens += inT + outT;
|
|
387
|
+
mBucket.costUsd += cost;
|
|
388
|
+
perModel.set(modelKey, mBucket);
|
|
389
|
+
for (const tl of r.targetLocales ?? []){
|
|
390
|
+
const locale = typeof tl?.locale === 'string' ? tl.locale : '—';
|
|
391
|
+
const lBucket = perLocale.get(locale) ?? {
|
|
392
|
+
locale,
|
|
393
|
+
runs: 0,
|
|
394
|
+
failed: 0
|
|
395
|
+
};
|
|
396
|
+
lBucket.runs++;
|
|
397
|
+
if (tl?.status === 'failed') lBucket.failed++;
|
|
398
|
+
perLocale.set(locale, lBucket);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (!result.hasNextPage) break;
|
|
402
|
+
page++;
|
|
403
|
+
// Safety cap — don't walk forever if pagination misreports.
|
|
404
|
+
if (page > 200) break;
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
totals: {
|
|
408
|
+
runs,
|
|
409
|
+
succeeded,
|
|
410
|
+
preserved,
|
|
411
|
+
failed,
|
|
412
|
+
inputTokens,
|
|
413
|
+
outputTokens,
|
|
414
|
+
costUsd,
|
|
415
|
+
totalMatching: runs,
|
|
416
|
+
truncated: false
|
|
417
|
+
},
|
|
418
|
+
byCollection: Array.from(perCollection.values()).sort((a, b)=>b.costUsd - a.costUsd || b.runs - a.runs),
|
|
419
|
+
byModel: Array.from(perModel.values()).sort((a, b)=>b.costUsd - a.costUsd || b.runs - a.runs),
|
|
420
|
+
byLocale: Array.from(perLocale.values()).sort((a, b)=>b.runs - a.runs)
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
async function fetchRecentSamples(payload, usageSlug, since, until) {
|
|
424
|
+
const whereClauses = [];
|
|
425
|
+
if (since) whereClauses.push({
|
|
426
|
+
createdAt: {
|
|
427
|
+
greater_than_equal: since
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
if (until) whereClauses.push({
|
|
431
|
+
createdAt: {
|
|
432
|
+
less_than_equal: until
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
const where = whereClauses.length > 0 ? {
|
|
436
|
+
and: whereClauses
|
|
437
|
+
} : undefined;
|
|
438
|
+
const result = await payload.find({
|
|
439
|
+
collection: usageSlug,
|
|
440
|
+
where,
|
|
441
|
+
sort: '-createdAt',
|
|
442
|
+
limit: SAMPLES_LIMIT,
|
|
443
|
+
depth: 0,
|
|
444
|
+
overrideAccess: true
|
|
445
|
+
});
|
|
446
|
+
const docs = result.docs;
|
|
447
|
+
return docs.map((r)=>({
|
|
448
|
+
id: r.id ?? 0,
|
|
449
|
+
createdAt: typeof r.createdAt === 'string' ? r.createdAt : new Date(0).toISOString(),
|
|
450
|
+
slug: typeof r.slug === 'string' ? r.slug : '—',
|
|
451
|
+
kind: r.kind === 'global' ? 'global' : 'collection',
|
|
452
|
+
documentId: typeof r.documentId === 'string' ? r.documentId : null,
|
|
453
|
+
status: r.status === 'failed' ? 'failed' : 'succeeded',
|
|
454
|
+
model: typeof r.model === 'string' ? r.model : null,
|
|
455
|
+
inputTokens: toFiniteNumber(r.inputTokens),
|
|
456
|
+
outputTokens: toFiniteNumber(r.outputTokens),
|
|
457
|
+
estimatedCostUsd: r.estimatedCostUsd == null ? null : toFiniteNumber(r.estimatedCostUsd),
|
|
458
|
+
durationMs: r.durationMs == null ? null : toFiniteNumber(r.durationMs),
|
|
459
|
+
error: typeof r.error === 'string' ? r.error : null,
|
|
460
|
+
failedCount: toFiniteNumber(r.failedCount),
|
|
461
|
+
succeededCount: toFiniteNumber(r.succeededCount),
|
|
462
|
+
targetLocales: Array.isArray(r.targetLocales) ? r.targetLocales.map((tl)=>({
|
|
463
|
+
locale: typeof tl?.locale === 'string' ? tl.locale : '',
|
|
464
|
+
status: tl?.status ?? null
|
|
465
|
+
})) : null
|
|
466
|
+
}));
|
|
467
|
+
}
|
|
468
|
+
function toFiniteNumber(v) {
|
|
469
|
+
if (typeof v === 'number') {
|
|
470
|
+
return Number.isFinite(v) ? v : 0;
|
|
471
|
+
}
|
|
472
|
+
if (typeof v === 'string') {
|
|
473
|
+
// Postgres returns `numeric`/`bigint` as strings — coerce.
|
|
474
|
+
const n = Number.parseFloat(v);
|
|
475
|
+
return Number.isFinite(n) ? n : 0;
|
|
476
|
+
}
|
|
477
|
+
if (typeof v === 'bigint') {
|
|
478
|
+
return Number(v);
|
|
479
|
+
}
|
|
480
|
+
return 0;
|
|
481
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { EstimatedCostCell } from '../client/estimated-cost-cell.js';
|
|
2
|
+
export { ExcludedFieldsField } from '../client/excluded-fields-field.js';
|
|
3
|
+
export { FieldTranslateButton } from '../client/field-translate-button.js';
|
|
4
|
+
export { TranslateButton } from '../client/translate-button.js';
|
|
5
|
+
export { TranslateModal } from '../client/translate-modal.js';
|
|
6
|
+
export { TranslationProgress } from '../client/translation-progress.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { EstimatedCostCell } from '../client/estimated-cost-cell.js';
|
|
2
|
+
export { ExcludedFieldsField } from '../client/excluded-fields-field.js';
|
|
3
|
+
export { FieldTranslateButton } from '../client/field-translate-button.js';
|
|
4
|
+
export { TranslateButton } from '../client/translate-button.js';
|
|
5
|
+
export { TranslateModal } from '../client/translate-modal.js';
|
|
6
|
+
export { TranslationProgress } from '../client/translation-progress.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports for admin-sidebar components shipped by the ai-translate
|
|
3
|
+
* plugin. Consumers wire these via `admin.components.beforeNavLinks`
|
|
4
|
+
* or `afterNavLinks` in their payload.config.ts.
|
|
5
|
+
*/
|
|
6
|
+
export { default, TranslationNavGroup } from '../components/TranslationNavGroup.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports for admin-sidebar components shipped by the ai-translate
|
|
3
|
+
* plugin. Consumers wire these via `admin.components.beforeNavLinks`
|
|
4
|
+
* or `afterNavLinks` in their payload.config.ts.
|
|
5
|
+
*/ export { default, TranslationNavGroup } from '../components/TranslationNavGroup.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { translateDocument, translateGlobal } from '../api.js';
|
|
2
|
+
export { ensureBulkTranslateSchema, migrateBatchStatusVocabulary, migrateHashesToV1Prefix, } from '../lib/bulk-translate-migrations.js';
|
|
3
|
+
export { diffFields } from '../lib/field-diff.js';
|
|
4
|
+
export { isFieldEmpty } from '../lib/field-empty.js';
|
|
5
|
+
export { createScopedLogger, type LogContext, type LogEventFields, type LogLevel, type ScopedLogger, type SerializedError, serializeErr, } from '../lib/logger.js';
|
|
6
|
+
export { type AcquireTokenResult, acquireToken, getBucketStatus, type TokenBucketOptions, } from '../lib/translation-token-bucket.js';
|
|
7
|
+
export { aiTranslatePlugin } from '../plugin.js';
|
|
8
|
+
export type { AITranslatePluginConfig, AutomationConfig, ConcurrencyLimits, CostGuardError, CostLimits, FieldLocaleResult, LexicalNodeRegistration, QualityConfig, RetryConfig, SamplingConfig, TargetPolicy, TranslateDocumentOptions, TranslateDocumentResult, TranslatedItem, TranslateGlobalOptions, TranslateRequest, TranslateResponse, TranslationAlert, TranslationAlertType, TranslationContext, TranslationEstimate, TranslationEvent, TranslationEventType, TranslationItem, TranslationItemKind, TranslationProvider, TranslationSample, TranslationUsage, ValidationConfig, } from '../types.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { translateDocument, translateGlobal } from '../api.js';
|
|
2
|
+
export { ensureBulkTranslateSchema, migrateBatchStatusVocabulary, migrateHashesToV1Prefix } from '../lib/bulk-translate-migrations.js';
|
|
3
|
+
export { diffFields } from '../lib/field-diff.js';
|
|
4
|
+
export { isFieldEmpty } from '../lib/field-empty.js';
|
|
5
|
+
export { createScopedLogger, serializeErr } from '../lib/logger.js';
|
|
6
|
+
export { acquireToken, getBucketStatus } from '../lib/translation-token-bucket.js';
|
|
7
|
+
export { aiTranslatePlugin } from '../plugin.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { AnthropicProviderOptions } from '../providers/anthropic.js';
|
|
2
|
+
export { createAnthropicProvider } from '../providers/anthropic.js';
|
|
3
|
+
export type { CustomProviderOptions, CustomProviderPricing } from '../providers/custom.js';
|
|
4
|
+
export { createCustomProvider } from '../providers/custom.js';
|
|
5
|
+
export type { GeminiProviderOptions } from '../providers/gemini.js';
|
|
6
|
+
export { createGeminiProvider } from '../providers/gemini.js';
|
|
7
|
+
export { createMockProvider } from '../providers/mock.js';
|
|
8
|
+
export type { OpenAIProviderOptions } from '../providers/openai.js';
|
|
9
|
+
export { createOpenAIProvider } from '../providers/openai.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createAnthropicProvider } from '../providers/anthropic.js';
|
|
2
|
+
export { createCustomProvider } from '../providers/custom.js';
|
|
3
|
+
export { createGeminiProvider } from '../providers/gemini.js';
|
|
4
|
+
export { createMockProvider } from '../providers/mock.js';
|
|
5
|
+
export { createOpenAIProvider } from '../providers/openai.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side surface for the Translation Hub view.
|
|
3
|
+
*
|
|
4
|
+
* Separated from `./views.ts` (the server entry) because tsup's single-
|
|
5
|
+
* bundle output strips `"use client"` directives from any sub-files
|
|
6
|
+
* (Hub.client.tsx, ActiveJobs.tsx, etc) — so the entire bundle would
|
|
7
|
+
* compile as a server component and React useState/useEffect would
|
|
8
|
+
* throw "useState only works in Client Components" at runtime.
|
|
9
|
+
*
|
|
10
|
+
* Two-entry split:
|
|
11
|
+
* - `ai-translate-views` (server, no banner): exports `TranslationHubView`
|
|
12
|
+
* which uses `DefaultTemplate` from `@payloadcms/next/templates` (server).
|
|
13
|
+
* - `ai-translate-views-client` (this file, banner: '"use client";'): all
|
|
14
|
+
* client sub-components.
|
|
15
|
+
*
|
|
16
|
+
* The server `index.tsx` imports `TranslationHubClient` via the package's
|
|
17
|
+
* own subpath `@purposeinplay/payload-ai-translate/views-client`, which
|
|
18
|
+
* is marked `external` in the server tsup config so it stays as an external
|
|
19
|
+
* import (resolved at runtime via package.json `exports`) instead of being
|
|
20
|
+
* inlined into the server bundle.
|
|
21
|
+
*/
|
|
22
|
+
export { BulkRunsHubClient } from '../views/BulkRunsHub/BulkRunsHub.client.js';
|
|
23
|
+
export { TranslationHubClient } from '../views/TranslationHub/Hub.client.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side surface for the Translation Hub view.
|
|
3
|
+
*
|
|
4
|
+
* Separated from `./views.ts` (the server entry) because tsup's single-
|
|
5
|
+
* bundle output strips `"use client"` directives from any sub-files
|
|
6
|
+
* (Hub.client.tsx, ActiveJobs.tsx, etc) — so the entire bundle would
|
|
7
|
+
* compile as a server component and React useState/useEffect would
|
|
8
|
+
* throw "useState only works in Client Components" at runtime.
|
|
9
|
+
*
|
|
10
|
+
* Two-entry split:
|
|
11
|
+
* - `ai-translate-views` (server, no banner): exports `TranslationHubView`
|
|
12
|
+
* which uses `DefaultTemplate` from `@payloadcms/next/templates` (server).
|
|
13
|
+
* - `ai-translate-views-client` (this file, banner: '"use client";'): all
|
|
14
|
+
* client sub-components.
|
|
15
|
+
*
|
|
16
|
+
* The server `index.tsx` imports `TranslationHubClient` via the package's
|
|
17
|
+
* own subpath `@purposeinplay/payload-ai-translate/views-client`, which
|
|
18
|
+
* is marked `external` in the server tsup config so it stays as an external
|
|
19
|
+
* import (resolved at runtime via package.json `exports`) instead of being
|
|
20
|
+
* inlined into the server bundle.
|
|
21
|
+
*/ export { BulkRunsHubClient } from '../views/BulkRunsHub/BulkRunsHub.client.js';
|
|
22
|
+
export { TranslationHubClient } from '../views/TranslationHub/Hub.client.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-rendered admin views provided by the ai-translate plugin.
|
|
3
|
+
*
|
|
4
|
+
* Consumers wire these into Payload's `admin.components.views` config so the
|
|
5
|
+
* plugin owns the canonical UI surface — there's no copy-paste between
|
|
6
|
+
* consumer repos.
|
|
7
|
+
*
|
|
8
|
+
* Example (payload.config.ts):
|
|
9
|
+
*
|
|
10
|
+
* import { TranslationHubView } from '@purposeinplay/payload-ai-translate/views'
|
|
11
|
+
*
|
|
12
|
+
* admin: {
|
|
13
|
+
* components: {
|
|
14
|
+
* views: {
|
|
15
|
+
* TranslationHub: {
|
|
16
|
+
* Component: TranslationHubView,
|
|
17
|
+
* path: '/translation',
|
|
18
|
+
* // ...
|
|
19
|
+
* },
|
|
20
|
+
* },
|
|
21
|
+
* },
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* NOTE: This entry must NOT carry a "use client" banner — `TranslationHubView`
|
|
25
|
+
* is a Server Component that wraps the client `<TranslationHubClient />` in
|
|
26
|
+
* Payload's `<DefaultTemplate>`. The client subtree lives behind a
|
|
27
|
+
* `"use client"` directive in `Hub.client.tsx` and its children.
|
|
28
|
+
*/
|
|
29
|
+
export { BulkRunsHubView, default as BulkRunsHub } from '../views/BulkRunsHub/index.js';
|
|
30
|
+
export { default as TranslationHub, TranslationHubView } from '../views/TranslationHub/index.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-rendered admin views provided by the ai-translate plugin.
|
|
3
|
+
*
|
|
4
|
+
* Consumers wire these into Payload's `admin.components.views` config so the
|
|
5
|
+
* plugin owns the canonical UI surface — there's no copy-paste between
|
|
6
|
+
* consumer repos.
|
|
7
|
+
*
|
|
8
|
+
* Example (payload.config.ts):
|
|
9
|
+
*
|
|
10
|
+
* import { TranslationHubView } from '@purposeinplay/payload-ai-translate/views'
|
|
11
|
+
*
|
|
12
|
+
* admin: {
|
|
13
|
+
* components: {
|
|
14
|
+
* views: {
|
|
15
|
+
* TranslationHub: {
|
|
16
|
+
* Component: TranslationHubView,
|
|
17
|
+
* path: '/translation',
|
|
18
|
+
* // ...
|
|
19
|
+
* },
|
|
20
|
+
* },
|
|
21
|
+
* },
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* NOTE: This entry must NOT carry a "use client" banner — `TranslationHubView`
|
|
25
|
+
* is a Server Component that wraps the client `<TranslationHubClient />` in
|
|
26
|
+
* Payload's `<DefaultTemplate>`. The client subtree lives behind a
|
|
27
|
+
* `"use client"` directive in `Hub.client.tsx` and its children.
|
|
28
|
+
*/ export { BulkRunsHubView, default as BulkRunsHub } from '../views/BulkRunsHub/index.js';
|
|
29
|
+
export { default as TranslationHub, TranslationHubView } from '../views/TranslationHub/index.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { GlobalAfterChangeHook } from 'payload';
|
|
2
|
+
import type { CoalescingQueue } from '../lib/coalescing-queue.js';
|
|
3
|
+
import type { AITranslatePluginConfig } from '../types.js';
|
|
4
|
+
export declare function createTranslateGlobalAfterChangeHook(globalSlug: string, pluginOptions: AITranslatePluginConfig, queue?: CoalescingQueue | null): GlobalAfterChangeHook;
|