@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,568 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { editorCodeFromAlertType, editorMessageFor } from '../../lib/error-messages.js';
|
|
5
|
+
import { docHref } from '../shared/docHref.js';
|
|
6
|
+
import { groupAlerts, splitVisibleGroups } from './alertGrouping.js';
|
|
7
|
+
const ALERT_LOOKBACK_MS = 24 * 60 * 60 * 1000; // 24h
|
|
8
|
+
// Active poll cadence — used while ≥1 undismissed alert is showing.
|
|
9
|
+
// 30s keeps the visible banner stack reasonably fresh as new alerts
|
|
10
|
+
// fire mid-translation without thrashing the endpoint.
|
|
11
|
+
const POLL_INTERVAL_MS = 30_000;
|
|
12
|
+
// Idle cadence — used when the latest poll returned zero alerts.
|
|
13
|
+
// Pre-1.2.8 the loop re-queued at POLL_INTERVAL_MS unconditionally
|
|
14
|
+
// (no visibility check, no idle back-off), so any admin tab on
|
|
15
|
+
// `/admin/translation` hit `/api/translation-alerts?where[...]=...`
|
|
16
|
+
// every 30s for the lifetime of the page — even after every alert
|
|
17
|
+
// was dismissed. Matches the same defect class fixed in
|
|
18
|
+
// `ActiveJobs.tsx` and `useBulkTranslateActive.ts`.
|
|
19
|
+
const IDLE_POLL_INTERVAL_MS = 120_000;
|
|
20
|
+
/** v1.2.6 HUB-1: bumped from 10 to 30 so the grouping has the data it
|
|
21
|
+
* needs to summarise larger bursts. The visible stack is capped at 3
|
|
22
|
+
* groups via `splitVisibleGroups`. */ const ALERT_PAGE_LIMIT = 30;
|
|
23
|
+
const MAX_VISIBLE_GROUPS = 3;
|
|
24
|
+
const ALERT_COLORS = {
|
|
25
|
+
'cost-guard-abort': {
|
|
26
|
+
bg: 'var(--theme-warning-100, #fef3c7)',
|
|
27
|
+
border: 'var(--theme-warning-500, #d97706)',
|
|
28
|
+
fg: 'var(--theme-warning-500, #d97706)'
|
|
29
|
+
},
|
|
30
|
+
'persistent-failure': {
|
|
31
|
+
bg: 'var(--theme-error-100, #fee2e2)',
|
|
32
|
+
border: 'var(--theme-error-500, #b91c1c)',
|
|
33
|
+
fg: 'var(--theme-error-500, #b91c1c)'
|
|
34
|
+
},
|
|
35
|
+
'provider-outage': {
|
|
36
|
+
bg: 'var(--theme-error-100, #fee2e2)',
|
|
37
|
+
border: 'var(--theme-error-500, #b91c1c)',
|
|
38
|
+
fg: 'var(--theme-error-500, #b91c1c)'
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Pull the editor-facing code + context off the persisted alert row.
|
|
43
|
+
* The catalogue's `editorMessageFor(code, context)` renders the
|
|
44
|
+
* friendly title + body. Falls back to the `alertType` mapping when
|
|
45
|
+
* `metadata.code` is absent (pre-1.2.5 rows).
|
|
46
|
+
*/ function readAlertMessage(alert) {
|
|
47
|
+
const meta = alert.metadata ?? {};
|
|
48
|
+
const code = typeof meta['code'] === 'string' ? meta['code'] : editorCodeFromAlertType(alert.alertType);
|
|
49
|
+
const context = typeof meta['context'] === 'object' && meta['context'] !== null ? meta['context'] : {};
|
|
50
|
+
return editorMessageFor(code, context);
|
|
51
|
+
}
|
|
52
|
+
function relTime(iso) {
|
|
53
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
54
|
+
const s = Math.floor(diff / 1000);
|
|
55
|
+
if (s < 60) {
|
|
56
|
+
return `${s}s ago`;
|
|
57
|
+
}
|
|
58
|
+
if (s < 3600) {
|
|
59
|
+
return `${Math.floor(s / 60)}m ago`;
|
|
60
|
+
}
|
|
61
|
+
if (s < 86_400) {
|
|
62
|
+
return `${Math.floor(s / 3600)}h ago`;
|
|
63
|
+
}
|
|
64
|
+
return `${Math.floor(s / 86_400)}d ago`;
|
|
65
|
+
}
|
|
66
|
+
export const AlertBanner = ({ basePath })=>{
|
|
67
|
+
const [alerts, setAlerts] = useState([]);
|
|
68
|
+
const [dismissingIds, setDismissingIds] = useState(new Set());
|
|
69
|
+
const [showAllGroups, setShowAllGroups] = useState(false);
|
|
70
|
+
useEffect(()=>{
|
|
71
|
+
let cancelled = false;
|
|
72
|
+
let timer = null;
|
|
73
|
+
// Tracks whether the last successful poll returned any alerts.
|
|
74
|
+
// Drives the next-tick cadence (active 30s vs idle 120s).
|
|
75
|
+
let hasActiveAlerts = false;
|
|
76
|
+
async function poll() {
|
|
77
|
+
try {
|
|
78
|
+
const since = new Date(Date.now() - ALERT_LOOKBACK_MS).toISOString();
|
|
79
|
+
const url = `${basePath}/api/translation-alerts` + '?where[dismissed][equals]=false' + `&where[createdAt][greater_than]=${encodeURIComponent(since)}` + `&sort=-createdAt&limit=${ALERT_PAGE_LIMIT}&depth=0`;
|
|
80
|
+
const r = await fetch(url, {
|
|
81
|
+
credentials: 'include'
|
|
82
|
+
});
|
|
83
|
+
if (!r.ok) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const d = await r.json();
|
|
87
|
+
if (!cancelled) {
|
|
88
|
+
const docs = d.docs ?? [];
|
|
89
|
+
setAlerts(docs);
|
|
90
|
+
hasActiveAlerts = docs.length > 0;
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// Best-effort polling — swallow network failures.
|
|
94
|
+
} finally{
|
|
95
|
+
// Visibility-aware + cadence-aware polling (matches the
|
|
96
|
+
// pattern used by `ActiveJobs` and `useBulkTranslateActive`):
|
|
97
|
+
// skip polls while the tab is hidden (the visibilitychange
|
|
98
|
+
// listener below re-fires when the tab returns), and back off
|
|
99
|
+
// to a 2-minute idle cadence when no alerts are showing.
|
|
100
|
+
if (!cancelled && document.visibilityState === 'visible') {
|
|
101
|
+
const nextDelay = hasActiveAlerts ? POLL_INTERVAL_MS : IDLE_POLL_INTERVAL_MS;
|
|
102
|
+
timer = setTimeout(poll, nextDelay);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function onVisibility() {
|
|
107
|
+
if (document.visibilityState === 'visible' && !cancelled) {
|
|
108
|
+
if (timer) {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
}
|
|
111
|
+
poll();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
document.addEventListener('visibilitychange', onVisibility);
|
|
115
|
+
poll();
|
|
116
|
+
return ()=>{
|
|
117
|
+
cancelled = true;
|
|
118
|
+
if (timer) {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
}
|
|
121
|
+
document.removeEventListener('visibilitychange', onVisibility);
|
|
122
|
+
};
|
|
123
|
+
}, [
|
|
124
|
+
basePath
|
|
125
|
+
]);
|
|
126
|
+
async function dismiss(id) {
|
|
127
|
+
setDismissingIds((prev)=>new Set(prev).add(id));
|
|
128
|
+
// Optimistic local removal — the next poll will re-confirm.
|
|
129
|
+
setAlerts((prev)=>prev.filter((a)=>a.id !== id));
|
|
130
|
+
try {
|
|
131
|
+
await fetch(`${basePath}/api/translation-alerts/${id}`, {
|
|
132
|
+
method: 'PATCH',
|
|
133
|
+
credentials: 'include',
|
|
134
|
+
headers: {
|
|
135
|
+
'content-type': 'application/json'
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
dismissed: true
|
|
139
|
+
})
|
|
140
|
+
});
|
|
141
|
+
} catch {
|
|
142
|
+
// If the PATCH fails the next poll will surface the alert again.
|
|
143
|
+
} finally{
|
|
144
|
+
setDismissingIds((prev)=>{
|
|
145
|
+
const next = new Set(prev);
|
|
146
|
+
next.delete(id);
|
|
147
|
+
return next;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* HUB-1: bulk-dismiss every currently-visible alert in one go.
|
|
153
|
+
* Optimistically clears local state; PATCH calls fan out in parallel.
|
|
154
|
+
* No bulk endpoint exists yet — per-id PATCH is the cheapest path
|
|
155
|
+
* without adding a new endpoint to the plugin surface. We bound by
|
|
156
|
+
* the page limit (30) so a runaway burst doesn't fan out unbounded.
|
|
157
|
+
*/ async function dismissAll(ids) {
|
|
158
|
+
if (ids.length === 0) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const idSet = new Set(ids);
|
|
162
|
+
setDismissingIds((prev)=>{
|
|
163
|
+
const next = new Set(prev);
|
|
164
|
+
for (const id of ids){
|
|
165
|
+
next.add(id);
|
|
166
|
+
}
|
|
167
|
+
return next;
|
|
168
|
+
});
|
|
169
|
+
setAlerts((prev)=>prev.filter((a)=>!idSet.has(a.id)));
|
|
170
|
+
await Promise.allSettled(ids.map((id)=>fetch(`${basePath}/api/translation-alerts/${id}`, {
|
|
171
|
+
method: 'PATCH',
|
|
172
|
+
credentials: 'include',
|
|
173
|
+
headers: {
|
|
174
|
+
'content-type': 'application/json'
|
|
175
|
+
},
|
|
176
|
+
body: JSON.stringify({
|
|
177
|
+
dismissed: true
|
|
178
|
+
})
|
|
179
|
+
})));
|
|
180
|
+
setDismissingIds((prev)=>{
|
|
181
|
+
const next = new Set(prev);
|
|
182
|
+
for (const id of ids){
|
|
183
|
+
next.delete(id);
|
|
184
|
+
}
|
|
185
|
+
return next;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
// Memo the grouping so re-renders driven by `dismissingIds` don't
|
|
189
|
+
// re-shuffle group order. `groupAlerts` is pure so referential
|
|
190
|
+
// stability on `alerts` is enough.
|
|
191
|
+
const groups = useMemo(()=>groupAlerts(alerts), [
|
|
192
|
+
alerts
|
|
193
|
+
]);
|
|
194
|
+
const split = useMemo(()=>splitVisibleGroups(groups, showAllGroups ? groups.length : MAX_VISIBLE_GROUPS), [
|
|
195
|
+
groups,
|
|
196
|
+
showAllGroups
|
|
197
|
+
]);
|
|
198
|
+
if (alerts.length === 0) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
const allIds = alerts.map((a)=>a.id);
|
|
202
|
+
const bulkBusy = allIds.every((id)=>dismissingIds.has(id));
|
|
203
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
204
|
+
style: {
|
|
205
|
+
display: 'flex',
|
|
206
|
+
flexDirection: 'column',
|
|
207
|
+
gap: '0.5rem'
|
|
208
|
+
},
|
|
209
|
+
children: [
|
|
210
|
+
alerts.length > 1 && /*#__PURE__*/ _jsxs("div", {
|
|
211
|
+
style: {
|
|
212
|
+
display: 'flex',
|
|
213
|
+
alignItems: 'center',
|
|
214
|
+
justifyContent: 'space-between',
|
|
215
|
+
gap: '0.75rem',
|
|
216
|
+
padding: '0.4rem 0.75rem',
|
|
217
|
+
background: 'var(--theme-elevation-50)',
|
|
218
|
+
border: '1px solid var(--theme-elevation-150)',
|
|
219
|
+
borderRadius: '4px',
|
|
220
|
+
fontSize: '0.75rem',
|
|
221
|
+
color: 'var(--theme-elevation-700)'
|
|
222
|
+
},
|
|
223
|
+
children: [
|
|
224
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
225
|
+
children: [
|
|
226
|
+
/*#__PURE__*/ _jsx("strong", {
|
|
227
|
+
style: {
|
|
228
|
+
color: 'var(--theme-elevation-900)'
|
|
229
|
+
},
|
|
230
|
+
children: alerts.length
|
|
231
|
+
}),
|
|
232
|
+
" alert",
|
|
233
|
+
alerts.length === 1 ? '' : 's',
|
|
234
|
+
" across",
|
|
235
|
+
' ',
|
|
236
|
+
/*#__PURE__*/ _jsx("strong", {
|
|
237
|
+
style: {
|
|
238
|
+
color: 'var(--theme-elevation-900)'
|
|
239
|
+
},
|
|
240
|
+
children: groups.length
|
|
241
|
+
}),
|
|
242
|
+
" group",
|
|
243
|
+
groups.length === 1 ? '' : 's',
|
|
244
|
+
split.hiddenGroupCount > 0 && !showAllGroups ? ` · ${split.hiddenAlertCount} hidden` : ''
|
|
245
|
+
]
|
|
246
|
+
}),
|
|
247
|
+
/*#__PURE__*/ _jsx("button", {
|
|
248
|
+
"data-testid": "translation-alerts-dismiss-all",
|
|
249
|
+
disabled: bulkBusy,
|
|
250
|
+
onClick: ()=>dismissAll(allIds),
|
|
251
|
+
style: {
|
|
252
|
+
padding: '0.25rem 0.6rem',
|
|
253
|
+
background: 'transparent',
|
|
254
|
+
color: 'var(--theme-elevation-900)',
|
|
255
|
+
border: '1px solid var(--theme-elevation-300)',
|
|
256
|
+
borderRadius: '4px',
|
|
257
|
+
fontSize: '0.75rem',
|
|
258
|
+
fontWeight: 600,
|
|
259
|
+
cursor: bulkBusy ? 'wait' : 'pointer'
|
|
260
|
+
},
|
|
261
|
+
title: "Dismiss every alert currently visible",
|
|
262
|
+
type: "button",
|
|
263
|
+
children: "Dismiss all"
|
|
264
|
+
})
|
|
265
|
+
]
|
|
266
|
+
}),
|
|
267
|
+
split.visible.map((group)=>/*#__PURE__*/ _jsx(AlertGroupBanner, {
|
|
268
|
+
basePath: basePath,
|
|
269
|
+
dismissingIds: dismissingIds,
|
|
270
|
+
group: group,
|
|
271
|
+
onDismiss: dismiss,
|
|
272
|
+
onDismissGroup: (ids)=>void dismissAll(ids)
|
|
273
|
+
}, group.key)),
|
|
274
|
+
split.hiddenGroupCount > 0 && /*#__PURE__*/ _jsx("button", {
|
|
275
|
+
"data-testid": "translation-alerts-show-more",
|
|
276
|
+
onClick: ()=>setShowAllGroups((prev)=>!prev),
|
|
277
|
+
style: {
|
|
278
|
+
alignSelf: 'flex-start',
|
|
279
|
+
padding: '0.3rem 0.6rem',
|
|
280
|
+
background: 'transparent',
|
|
281
|
+
border: '1px dashed var(--theme-elevation-300)',
|
|
282
|
+
borderRadius: '4px',
|
|
283
|
+
color: 'var(--theme-elevation-700)',
|
|
284
|
+
fontSize: '0.75rem',
|
|
285
|
+
cursor: 'pointer'
|
|
286
|
+
},
|
|
287
|
+
type: "button",
|
|
288
|
+
children: showAllGroups ? 'Collapse to top groups' : `+ ${split.hiddenGroupCount} more group${split.hiddenGroupCount === 1 ? '' : 's'} (${split.hiddenAlertCount} alert${split.hiddenAlertCount === 1 ? '' : 's'})`
|
|
289
|
+
})
|
|
290
|
+
]
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Render one group as a single banner. The newest alert in the group
|
|
295
|
+
* provides the title / body / link; the group's `alerts.length`
|
|
296
|
+
* surfaces as a `× N` count badge. Expanding the group reveals the
|
|
297
|
+
* per-alert dismiss controls so an editor can deal with specific docs
|
|
298
|
+
* inside a group.
|
|
299
|
+
*/ const AlertGroupBanner = ({ basePath, group, dismissingIds, onDismiss, onDismissGroup })=>{
|
|
300
|
+
const [expanded, setExpanded] = useState(false);
|
|
301
|
+
// Sorting newest-first inside the group so the headline alert is the
|
|
302
|
+
// most recent — matches the "top of stack = newest" convention.
|
|
303
|
+
const sorted = useMemo(()=>[
|
|
304
|
+
...group.alerts
|
|
305
|
+
].sort((a, b)=>a.createdAt < b.createdAt ? 1 : -1), [
|
|
306
|
+
group.alerts
|
|
307
|
+
]);
|
|
308
|
+
const headline = sorted[0];
|
|
309
|
+
const colors = ALERT_COLORS[group.alertType];
|
|
310
|
+
const friendly = readAlertMessage(headline);
|
|
311
|
+
const isMultiple = sorted.length > 1;
|
|
312
|
+
const groupIds = sorted.map((a)=>a.id);
|
|
313
|
+
const allBusy = groupIds.every((id)=>dismissingIds.has(id));
|
|
314
|
+
const docLink = headline.collection && headline.documentId ? docHref(basePath, headline.collection, headline.documentId, group.locale) : null;
|
|
315
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
316
|
+
style: {
|
|
317
|
+
display: 'flex',
|
|
318
|
+
flexDirection: 'column',
|
|
319
|
+
gap: '0.5rem',
|
|
320
|
+
padding: '0.75rem 1rem',
|
|
321
|
+
background: colors.bg,
|
|
322
|
+
border: `1px solid ${colors.border}`,
|
|
323
|
+
borderRadius: '6px'
|
|
324
|
+
},
|
|
325
|
+
children: [
|
|
326
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
327
|
+
style: {
|
|
328
|
+
display: 'flex',
|
|
329
|
+
gap: '0.75rem',
|
|
330
|
+
alignItems: 'center'
|
|
331
|
+
},
|
|
332
|
+
children: [
|
|
333
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
334
|
+
style: {
|
|
335
|
+
flex: 1,
|
|
336
|
+
minWidth: 0
|
|
337
|
+
},
|
|
338
|
+
children: [
|
|
339
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
340
|
+
style: {
|
|
341
|
+
fontSize: '0.875rem',
|
|
342
|
+
fontWeight: 600,
|
|
343
|
+
color: colors.fg,
|
|
344
|
+
marginBottom: '0.15rem',
|
|
345
|
+
display: 'flex',
|
|
346
|
+
alignItems: 'center',
|
|
347
|
+
gap: '0.4rem',
|
|
348
|
+
flexWrap: 'wrap'
|
|
349
|
+
},
|
|
350
|
+
children: [
|
|
351
|
+
/*#__PURE__*/ _jsx("span", {
|
|
352
|
+
children: friendly.title
|
|
353
|
+
}),
|
|
354
|
+
isMultiple && /*#__PURE__*/ _jsxs("span", {
|
|
355
|
+
style: {
|
|
356
|
+
padding: '0.05rem 0.4rem',
|
|
357
|
+
background: colors.border,
|
|
358
|
+
color: 'var(--theme-elevation-0)',
|
|
359
|
+
borderRadius: '999px',
|
|
360
|
+
fontSize: '0.7rem',
|
|
361
|
+
fontWeight: 600
|
|
362
|
+
},
|
|
363
|
+
title: `${sorted.length} alerts in the last ${group.bucket}`,
|
|
364
|
+
children: [
|
|
365
|
+
"× ",
|
|
366
|
+
sorted.length
|
|
367
|
+
]
|
|
368
|
+
}),
|
|
369
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
370
|
+
style: {
|
|
371
|
+
marginLeft: '0.25rem',
|
|
372
|
+
fontWeight: 400,
|
|
373
|
+
fontSize: '0.75rem',
|
|
374
|
+
color: 'var(--theme-elevation-700)'
|
|
375
|
+
},
|
|
376
|
+
children: [
|
|
377
|
+
"· ",
|
|
378
|
+
relTime(headline.createdAt),
|
|
379
|
+
isMultiple ? ` · ${group.bucket}` : ''
|
|
380
|
+
]
|
|
381
|
+
})
|
|
382
|
+
]
|
|
383
|
+
}),
|
|
384
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
385
|
+
style: {
|
|
386
|
+
fontSize: '0.8rem',
|
|
387
|
+
color: 'var(--theme-elevation-900)',
|
|
388
|
+
lineHeight: 1.45
|
|
389
|
+
},
|
|
390
|
+
title: headline.message,
|
|
391
|
+
children: [
|
|
392
|
+
friendly.body,
|
|
393
|
+
headline.collection && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
394
|
+
children: [
|
|
395
|
+
' — ',
|
|
396
|
+
docLink && !isMultiple ? /*#__PURE__*/ _jsx("a", {
|
|
397
|
+
href: docLink,
|
|
398
|
+
rel: "noopener noreferrer",
|
|
399
|
+
style: {
|
|
400
|
+
color: 'var(--theme-success-500)',
|
|
401
|
+
textDecoration: 'none'
|
|
402
|
+
},
|
|
403
|
+
target: "_blank",
|
|
404
|
+
children: /*#__PURE__*/ _jsxs("code", {
|
|
405
|
+
children: [
|
|
406
|
+
headline.collection,
|
|
407
|
+
headline.documentId ? ` #${headline.documentId}` : ''
|
|
408
|
+
]
|
|
409
|
+
})
|
|
410
|
+
}) : /*#__PURE__*/ _jsxs("code", {
|
|
411
|
+
style: {
|
|
412
|
+
color: 'var(--theme-elevation-700)'
|
|
413
|
+
},
|
|
414
|
+
children: [
|
|
415
|
+
headline.collection,
|
|
416
|
+
isMultiple ? ` · ${sorted.length} docs` : ''
|
|
417
|
+
]
|
|
418
|
+
})
|
|
419
|
+
]
|
|
420
|
+
})
|
|
421
|
+
]
|
|
422
|
+
}),
|
|
423
|
+
friendly.action && /*#__PURE__*/ _jsx("div", {
|
|
424
|
+
style: {
|
|
425
|
+
fontSize: '0.75rem',
|
|
426
|
+
color: 'var(--theme-elevation-700)',
|
|
427
|
+
fontStyle: 'italic',
|
|
428
|
+
marginTop: '0.25rem'
|
|
429
|
+
},
|
|
430
|
+
children: friendly.action
|
|
431
|
+
})
|
|
432
|
+
]
|
|
433
|
+
}),
|
|
434
|
+
!isMultiple && docLink && /*#__PURE__*/ _jsx("a", {
|
|
435
|
+
href: docLink,
|
|
436
|
+
rel: "noopener noreferrer",
|
|
437
|
+
style: {
|
|
438
|
+
padding: '0.25rem 0.6rem',
|
|
439
|
+
background: 'transparent',
|
|
440
|
+
color: colors.fg,
|
|
441
|
+
border: `1px solid ${colors.border}`,
|
|
442
|
+
borderRadius: '4px',
|
|
443
|
+
fontSize: '0.75rem',
|
|
444
|
+
textDecoration: 'none',
|
|
445
|
+
whiteSpace: 'nowrap',
|
|
446
|
+
display: 'inline-block'
|
|
447
|
+
},
|
|
448
|
+
target: "_blank",
|
|
449
|
+
title: "Open the document this alert refers to",
|
|
450
|
+
children: "View document →"
|
|
451
|
+
}),
|
|
452
|
+
isMultiple && /*#__PURE__*/ _jsx("button", {
|
|
453
|
+
"aria-expanded": expanded,
|
|
454
|
+
"data-testid": "translation-alert-group-expand",
|
|
455
|
+
onClick: ()=>setExpanded((prev)=>!prev),
|
|
456
|
+
style: {
|
|
457
|
+
padding: '0.25rem 0.6rem',
|
|
458
|
+
background: 'transparent',
|
|
459
|
+
color: colors.fg,
|
|
460
|
+
border: `1px solid ${colors.border}`,
|
|
461
|
+
borderRadius: '4px',
|
|
462
|
+
fontSize: '0.75rem',
|
|
463
|
+
cursor: 'pointer',
|
|
464
|
+
whiteSpace: 'nowrap'
|
|
465
|
+
},
|
|
466
|
+
type: "button",
|
|
467
|
+
children: expanded ? 'Hide details' : `Show ${sorted.length}`
|
|
468
|
+
}),
|
|
469
|
+
/*#__PURE__*/ _jsx("button", {
|
|
470
|
+
disabled: isMultiple ? allBusy : dismissingIds.has(headline.id),
|
|
471
|
+
onClick: ()=>isMultiple ? onDismissGroup(groupIds) : onDismiss(headline.id),
|
|
472
|
+
style: {
|
|
473
|
+
padding: '0.25rem 0.6rem',
|
|
474
|
+
background: 'transparent',
|
|
475
|
+
color: colors.fg,
|
|
476
|
+
border: `1px solid ${colors.border}`,
|
|
477
|
+
borderRadius: '4px',
|
|
478
|
+
fontSize: '0.75rem',
|
|
479
|
+
cursor: 'pointer',
|
|
480
|
+
whiteSpace: 'nowrap'
|
|
481
|
+
},
|
|
482
|
+
title: isMultiple ? 'Dismiss every alert in this group' : 'Dismiss',
|
|
483
|
+
type: "button",
|
|
484
|
+
children: isMultiple ? 'Dismiss group' : 'Dismiss'
|
|
485
|
+
})
|
|
486
|
+
]
|
|
487
|
+
}),
|
|
488
|
+
isMultiple && expanded && /*#__PURE__*/ _jsx("ul", {
|
|
489
|
+
style: {
|
|
490
|
+
margin: 0,
|
|
491
|
+
padding: '0.5rem 0 0.25rem',
|
|
492
|
+
listStyle: 'none',
|
|
493
|
+
display: 'flex',
|
|
494
|
+
flexDirection: 'column',
|
|
495
|
+
gap: '0.3rem',
|
|
496
|
+
borderTop: `1px dashed ${colors.border}`
|
|
497
|
+
},
|
|
498
|
+
children: sorted.map((alert)=>{
|
|
499
|
+
const alertDoc = alert.collection && alert.documentId ? docHref(basePath, alert.collection, alert.documentId, group.locale) : null;
|
|
500
|
+
const busy = dismissingIds.has(alert.id);
|
|
501
|
+
return /*#__PURE__*/ _jsxs("li", {
|
|
502
|
+
style: {
|
|
503
|
+
display: 'flex',
|
|
504
|
+
alignItems: 'center',
|
|
505
|
+
justifyContent: 'space-between',
|
|
506
|
+
gap: '0.5rem',
|
|
507
|
+
fontSize: '0.75rem',
|
|
508
|
+
color: 'var(--theme-elevation-900)'
|
|
509
|
+
},
|
|
510
|
+
children: [
|
|
511
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
512
|
+
style: {
|
|
513
|
+
minWidth: 0,
|
|
514
|
+
overflow: 'hidden',
|
|
515
|
+
textOverflow: 'ellipsis'
|
|
516
|
+
},
|
|
517
|
+
children: [
|
|
518
|
+
alertDoc ? /*#__PURE__*/ _jsx("a", {
|
|
519
|
+
href: alertDoc,
|
|
520
|
+
rel: "noopener noreferrer",
|
|
521
|
+
style: {
|
|
522
|
+
color: 'var(--theme-success-500)',
|
|
523
|
+
textDecoration: 'none'
|
|
524
|
+
},
|
|
525
|
+
target: "_blank",
|
|
526
|
+
children: /*#__PURE__*/ _jsxs("code", {
|
|
527
|
+
children: [
|
|
528
|
+
alert.collection,
|
|
529
|
+
alert.documentId ? ` #${alert.documentId}` : ''
|
|
530
|
+
]
|
|
531
|
+
})
|
|
532
|
+
}) : /*#__PURE__*/ _jsx("code", {
|
|
533
|
+
style: {
|
|
534
|
+
color: 'var(--theme-elevation-700)'
|
|
535
|
+
},
|
|
536
|
+
children: alert.collection ?? 'unknown'
|
|
537
|
+
}),
|
|
538
|
+
/*#__PURE__*/ _jsx("span", {
|
|
539
|
+
style: {
|
|
540
|
+
marginLeft: '0.5rem',
|
|
541
|
+
color: 'var(--theme-elevation-700)'
|
|
542
|
+
},
|
|
543
|
+
children: relTime(alert.createdAt)
|
|
544
|
+
})
|
|
545
|
+
]
|
|
546
|
+
}),
|
|
547
|
+
/*#__PURE__*/ _jsx("button", {
|
|
548
|
+
disabled: busy,
|
|
549
|
+
onClick: ()=>onDismiss(alert.id),
|
|
550
|
+
style: {
|
|
551
|
+
padding: '0.1rem 0.4rem',
|
|
552
|
+
background: 'transparent',
|
|
553
|
+
color: colors.fg,
|
|
554
|
+
border: `1px solid ${colors.border}`,
|
|
555
|
+
borderRadius: '4px',
|
|
556
|
+
fontSize: '0.7rem',
|
|
557
|
+
cursor: busy ? 'wait' : 'pointer'
|
|
558
|
+
},
|
|
559
|
+
type: "button",
|
|
560
|
+
children: "Dismiss"
|
|
561
|
+
})
|
|
562
|
+
]
|
|
563
|
+
}, alert.id);
|
|
564
|
+
})
|
|
565
|
+
})
|
|
566
|
+
]
|
|
567
|
+
});
|
|
568
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers extracted from `AuditPanel.tsx` so the reconciliation
|
|
3
|
+
* logic flagged in v1.2.6 BugIndex NEW-1 / NEW-2 can be unit-tested
|
|
4
|
+
* without dragging React or `fetch` into the test environment.
|
|
5
|
+
*
|
|
6
|
+
* - `isRealModelId` — decides whether a `model` string on a
|
|
7
|
+
* usage row counts as an actual model
|
|
8
|
+
* (provider/model slug) vs a provider-only
|
|
9
|
+
* fallback like `'openrouter'` that should
|
|
10
|
+
* collapse into "Failed before model
|
|
11
|
+
* selection". See NEW-2.
|
|
12
|
+
* - `summarizeFailedCounts` — reconciles the doc-level "failed" KPI
|
|
13
|
+
* with the per-locale-row "failed" total
|
|
14
|
+
* so the editor sees both numbers labeled.
|
|
15
|
+
* See NEW-1.
|
|
16
|
+
*/
|
|
17
|
+
export type AuditUsageRowLike = {
|
|
18
|
+
status: 'succeeded' | 'failed';
|
|
19
|
+
model?: string | null;
|
|
20
|
+
targetLocales?: Array<{
|
|
21
|
+
locale: string;
|
|
22
|
+
status?: 'succeeded' | 'failed' | null;
|
|
23
|
+
}> | null;
|
|
24
|
+
};
|
|
25
|
+
export declare function isRealModelId(model: string | null | undefined): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Two distinct units of "failed" need to be surfaced separately to
|
|
28
|
+
* reconcile the AuditPanel KPI card with the Target locales table:
|
|
29
|
+
*
|
|
30
|
+
* - `docFailed` — usage rows whose `status === 'failed'`. One row
|
|
31
|
+
* per (collection, doc, run); the unit the top KPI
|
|
32
|
+
* reports.
|
|
33
|
+
* - `localeFailed` — entries in `targetLocales[]` whose status is
|
|
34
|
+
* `'failed'`. A run targeting 2 locales contributes
|
|
35
|
+
* 2 entries; the unit the per-locale table reports.
|
|
36
|
+
*
|
|
37
|
+
* The two are NOT expected to match — a doc that partially fails counts
|
|
38
|
+
* once at doc level but multiple times at locale level.
|
|
39
|
+
*/
|
|
40
|
+
export declare function summarizeFailedCounts(rows: AuditUsageRowLike[]): {
|
|
41
|
+
docFailed: number;
|
|
42
|
+
localeFailed: number;
|
|
43
|
+
localeRows: number;
|
|
44
|
+
};
|