@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.
Files changed (301) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +714 -0
  3. package/dist/alerts-collection.d.ts +21 -0
  4. package/dist/alerts-collection.js +159 -0
  5. package/dist/api.d.ts +4 -0
  6. package/dist/api.js +918 -0
  7. package/dist/bulk-translate-batches-collection.d.ts +29 -0
  8. package/dist/bulk-translate-batches-collection.js +404 -0
  9. package/dist/bulk-translate-units-collection.d.ts +35 -0
  10. package/dist/bulk-translate-units-collection.js +310 -0
  11. package/dist/client/estimated-cost-cell.d.ts +6 -0
  12. package/dist/client/estimated-cost-cell.js +12 -0
  13. package/dist/client/excluded-fields-field.d.ts +45 -0
  14. package/dist/client/excluded-fields-field.js +553 -0
  15. package/dist/client/field-translate-button.d.ts +6 -0
  16. package/dist/client/field-translate-button.js +199 -0
  17. package/dist/client/index.d.ts +6 -0
  18. package/dist/client/index.js +6 -0
  19. package/dist/client/lib/use-global-kill-switches.d.ts +20 -0
  20. package/dist/client/lib/use-global-kill-switches.js +58 -0
  21. package/dist/client/translate-button.d.ts +2 -0
  22. package/dist/client/translate-button.js +228 -0
  23. package/dist/client/translate-modal.d.ts +16 -0
  24. package/dist/client/translate-modal.js +549 -0
  25. package/dist/client/translation-progress.d.ts +10 -0
  26. package/dist/client/translation-progress.js +297 -0
  27. package/dist/components/TranslationNavGroup.d.ts +45 -0
  28. package/dist/components/TranslationNavGroup.js +104 -0
  29. package/dist/defaults.d.ts +11 -0
  30. package/dist/defaults.js +16 -0
  31. package/dist/endpoints/client-config.d.ts +44 -0
  32. package/dist/endpoints/client-config.js +145 -0
  33. package/dist/endpoints/estimate.d.ts +5 -0
  34. package/dist/endpoints/estimate.js +237 -0
  35. package/dist/endpoints/progress.d.ts +2 -0
  36. package/dist/endpoints/progress.js +314 -0
  37. package/dist/endpoints/translate.d.ts +11 -0
  38. package/dist/endpoints/translate.js +376 -0
  39. package/dist/endpoints/translation-hub/_helpers.d.ts +140 -0
  40. package/dist/endpoints/translation-hub/_helpers.js +297 -0
  41. package/dist/endpoints/translation-hub/active.d.ts +21 -0
  42. package/dist/endpoints/translation-hub/active.js +220 -0
  43. package/dist/endpoints/translation-hub/cancel.d.ts +22 -0
  44. package/dist/endpoints/translation-hub/cancel.js +233 -0
  45. package/dist/endpoints/translation-hub/enqueue.d.ts +70 -0
  46. package/dist/endpoints/translation-hub/enqueue.js +529 -0
  47. package/dist/endpoints/translation-hub/failures.d.ts +12 -0
  48. package/dist/endpoints/translation-hub/failures.js +67 -0
  49. package/dist/endpoints/translation-hub/force-reset.d.ts +20 -0
  50. package/dist/endpoints/translation-hub/force-reset.js +144 -0
  51. package/dist/endpoints/translation-hub/index.d.ts +21 -0
  52. package/dist/endpoints/translation-hub/index.js +20 -0
  53. package/dist/endpoints/translation-hub/list.d.ts +40 -0
  54. package/dist/endpoints/translation-hub/list.js +182 -0
  55. package/dist/endpoints/translation-hub/preflight.d.ts +19 -0
  56. package/dist/endpoints/translation-hub/preflight.js +141 -0
  57. package/dist/endpoints/translation-hub/retry-failed.d.ts +38 -0
  58. package/dist/endpoints/translation-hub/retry-failed.js +235 -0
  59. package/dist/endpoints/translation-hub/revert.d.ts +88 -0
  60. package/dist/endpoints/translation-hub/revert.js +405 -0
  61. package/dist/endpoints/translation-hub/status.d.ts +45 -0
  62. package/dist/endpoints/translation-hub/status.js +391 -0
  63. package/dist/endpoints/translation-hub/usage-summary.d.ts +114 -0
  64. package/dist/endpoints/translation-hub/usage-summary.js +481 -0
  65. package/dist/exports/client.d.ts +6 -0
  66. package/dist/exports/client.js +6 -0
  67. package/dist/exports/components.d.ts +6 -0
  68. package/dist/exports/components.js +5 -0
  69. package/dist/exports/index.d.ts +8 -0
  70. package/dist/exports/index.js +7 -0
  71. package/dist/exports/providers.d.ts +9 -0
  72. package/dist/exports/providers.js +5 -0
  73. package/dist/exports/views-client.d.ts +23 -0
  74. package/dist/exports/views-client.js +22 -0
  75. package/dist/exports/views.d.ts +30 -0
  76. package/dist/exports/views.js +29 -0
  77. package/dist/hooks/after-change-global.d.ts +4 -0
  78. package/dist/hooks/after-change-global.js +109 -0
  79. package/dist/hooks/after-change.d.ts +16 -0
  80. package/dist/hooks/after-change.js +205 -0
  81. package/dist/hooks/after-delete.d.ts +30 -0
  82. package/dist/hooks/after-delete.js +95 -0
  83. package/dist/index.d.ts +5 -0
  84. package/dist/index.js +5 -0
  85. package/dist/jobs-collection.d.ts +17 -0
  86. package/dist/jobs-collection.js +139 -0
  87. package/dist/lexical/classifier.d.ts +3 -0
  88. package/dist/lexical/classifier.js +108 -0
  89. package/dist/lexical/deserializer.d.ts +4 -0
  90. package/dist/lexical/deserializer.js +263 -0
  91. package/dist/lexical/placeholder-integrity.d.ts +6 -0
  92. package/dist/lexical/placeholder-integrity.js +21 -0
  93. package/dist/lexical/placeholders.d.ts +21 -0
  94. package/dist/lexical/placeholders.js +117 -0
  95. package/dist/lexical/serializer.d.ts +21 -0
  96. package/dist/lexical/serializer.js +233 -0
  97. package/dist/lexical/types.d.ts +32 -0
  98. package/dist/lexical/types.js +1 -0
  99. package/dist/lib/auth-diagnostics.d.ts +14 -0
  100. package/dist/lib/auth-diagnostics.js +19 -0
  101. package/dist/lib/batch-counts.d.ts +58 -0
  102. package/dist/lib/batch-counts.js +105 -0
  103. package/dist/lib/bulk-translate-migrations.d.ts +92 -0
  104. package/dist/lib/bulk-translate-migrations.js +153 -0
  105. package/dist/lib/coalescing-queue.d.ts +38 -0
  106. package/dist/lib/coalescing-queue.js +69 -0
  107. package/dist/lib/content-extractor.d.ts +16 -0
  108. package/dist/lib/content-extractor.js +410 -0
  109. package/dist/lib/content-hash.d.ts +1 -0
  110. package/dist/lib/content-hash.js +19 -0
  111. package/dist/lib/content-patcher.d.ts +15 -0
  112. package/dist/lib/content-patcher.js +293 -0
  113. package/dist/lib/cost-guards.d.ts +2 -0
  114. package/dist/lib/cost-guards.js +18 -0
  115. package/dist/lib/daily-spend-cap.d.ts +58 -0
  116. package/dist/lib/daily-spend-cap.js +233 -0
  117. package/dist/lib/effective-locales.d.ts +181 -0
  118. package/dist/lib/effective-locales.js +302 -0
  119. package/dist/lib/error-messages.d.ts +245 -0
  120. package/dist/lib/error-messages.js +626 -0
  121. package/dist/lib/events.d.ts +39 -0
  122. package/dist/lib/events.js +146 -0
  123. package/dist/lib/exclude-fields.d.ts +3 -0
  124. package/dist/lib/exclude-fields.js +64 -0
  125. package/dist/lib/field-breadcrumb.d.ts +31 -0
  126. package/dist/lib/field-breadcrumb.js +227 -0
  127. package/dist/lib/field-diff.d.ts +1 -0
  128. package/dist/lib/field-diff.js +25 -0
  129. package/dist/lib/field-empty.d.ts +2 -0
  130. package/dist/lib/field-empty.js +68 -0
  131. package/dist/lib/field-resolver.d.ts +3 -0
  132. package/dist/lib/field-resolver.js +164 -0
  133. package/dist/lib/group-soft-skips.d.ts +39 -0
  134. package/dist/lib/group-soft-skips.js +45 -0
  135. package/dist/lib/locale-merge.d.ts +44 -0
  136. package/dist/lib/locale-merge.js +357 -0
  137. package/dist/lib/locale-row-check.d.ts +30 -0
  138. package/dist/lib/locale-row-check.js +64 -0
  139. package/dist/lib/logger.d.ts +74 -0
  140. package/dist/lib/logger.js +97 -0
  141. package/dist/lib/manual-edit-guard.d.ts +128 -0
  142. package/dist/lib/manual-edit-guard.js +393 -0
  143. package/dist/lib/output-validation.d.ts +48 -0
  144. package/dist/lib/output-validation.js +148 -0
  145. package/dist/lib/payload-read.d.ts +16 -0
  146. package/dist/lib/payload-read.js +51 -0
  147. package/dist/lib/per-doc-claim.d.ts +90 -0
  148. package/dist/lib/per-doc-claim.js +140 -0
  149. package/dist/lib/per-doc-lock.d.ts +94 -0
  150. package/dist/lib/per-doc-lock.js +119 -0
  151. package/dist/lib/persist-usage.d.ts +91 -0
  152. package/dist/lib/persist-usage.js +116 -0
  153. package/dist/lib/progress-store.d.ts +103 -0
  154. package/dist/lib/progress-store.js +314 -0
  155. package/dist/lib/rate-limiter.d.ts +3 -0
  156. package/dist/lib/rate-limiter.js +53 -0
  157. package/dist/lib/snapshot-select.d.ts +43 -0
  158. package/dist/lib/snapshot-select.js +108 -0
  159. package/dist/lib/translate-prompt.d.ts +31 -0
  160. package/dist/lib/translate-prompt.js +66 -0
  161. package/dist/lib/translation-token-bucket.d.ts +57 -0
  162. package/dist/lib/translation-token-bucket.js +365 -0
  163. package/dist/lib/truncate-source-value.d.ts +1 -0
  164. package/dist/lib/truncate-source-value.js +27 -0
  165. package/dist/manual-edit-collection.d.ts +22 -0
  166. package/dist/manual-edit-collection.js +124 -0
  167. package/dist/plugin.d.ts +3 -0
  168. package/dist/plugin.js +934 -0
  169. package/dist/providers/ai-sdk-adapter.d.ts +35 -0
  170. package/dist/providers/ai-sdk-adapter.js +100 -0
  171. package/dist/providers/anthropic.d.ts +31 -0
  172. package/dist/providers/anthropic.js +66 -0
  173. package/dist/providers/custom.d.ts +36 -0
  174. package/dist/providers/custom.js +24 -0
  175. package/dist/providers/gemini.d.ts +20 -0
  176. package/dist/providers/gemini.js +48 -0
  177. package/dist/providers/mock.d.ts +2 -0
  178. package/dist/providers/mock.js +29 -0
  179. package/dist/providers/openai.d.ts +28 -0
  180. package/dist/providers/openai.js +69 -0
  181. package/dist/settings-global.d.ts +74 -0
  182. package/dist/settings-global.js +216 -0
  183. package/dist/tasks/bulk-translate-coordinator.d.ts +115 -0
  184. package/dist/tasks/bulk-translate-coordinator.js +708 -0
  185. package/dist/tasks/bulk-translate-doc-task.d.ts +142 -0
  186. package/dist/tasks/bulk-translate-doc-task.js +1000 -0
  187. package/dist/tasks/bulk-translate-janitor.d.ts +87 -0
  188. package/dist/tasks/bulk-translate-janitor.js +311 -0
  189. package/dist/tasks/translate-job-task.d.ts +51 -0
  190. package/dist/tasks/translate-job-task.js +154 -0
  191. package/dist/translate.d.ts +113 -0
  192. package/dist/translate.js +911 -0
  193. package/dist/translation-daily-spend-collection.d.ts +24 -0
  194. package/dist/translation-daily-spend-collection.js +133 -0
  195. package/dist/translation-rate-limits-collection.d.ts +30 -0
  196. package/dist/translation-rate-limits-collection.js +144 -0
  197. package/dist/types.d.ts +672 -0
  198. package/dist/types.js +1 -0
  199. package/dist/usage-collection.d.ts +14 -0
  200. package/dist/usage-collection.js +377 -0
  201. package/dist/views/BulkRunsHub/BatchRow.d.ts +32 -0
  202. package/dist/views/BulkRunsHub/BatchRow.js +1222 -0
  203. package/dist/views/BulkRunsHub/BucketRow.d.ts +62 -0
  204. package/dist/views/BulkRunsHub/BucketRow.js +982 -0
  205. package/dist/views/BulkRunsHub/BulkRunsHub.client.d.ts +18 -0
  206. package/dist/views/BulkRunsHub/BulkRunsHub.client.js +331 -0
  207. package/dist/views/BulkRunsHub/EmptyState.d.ts +6 -0
  208. package/dist/views/BulkRunsHub/EmptyState.js +64 -0
  209. package/dist/views/BulkRunsHub/FilterBar.d.ts +16 -0
  210. package/dist/views/BulkRunsHub/FilterBar.js +284 -0
  211. package/dist/views/BulkRunsHub/InFlightBanner.d.ts +14 -0
  212. package/dist/views/BulkRunsHub/InFlightBanner.js +59 -0
  213. package/dist/views/BulkRunsHub/StatusBadge.d.ts +64 -0
  214. package/dist/views/BulkRunsHub/StatusBadge.js +248 -0
  215. package/dist/views/BulkRunsHub/SummaryStrip.d.ts +22 -0
  216. package/dist/views/BulkRunsHub/SummaryStrip.js +249 -0
  217. package/dist/views/BulkRunsHub/bucket-grouping.d.ts +200 -0
  218. package/dist/views/BulkRunsHub/bucket-grouping.js +344 -0
  219. package/dist/views/BulkRunsHub/bucketFailureSummary.d.ts +9 -0
  220. package/dist/views/BulkRunsHub/bucketFailureSummary.js +36 -0
  221. package/dist/views/BulkRunsHub/dedupedStatusFetch.d.ts +5 -0
  222. package/dist/views/BulkRunsHub/dedupedStatusFetch.js +45 -0
  223. package/dist/views/BulkRunsHub/index.d.ts +17 -0
  224. package/dist/views/BulkRunsHub/index.js +80 -0
  225. package/dist/views/BulkRunsHub/urlFilters.d.ts +14 -0
  226. package/dist/views/BulkRunsHub/urlFilters.js +50 -0
  227. package/dist/views/BulkRunsHub/useBulkRunsList.d.ts +26 -0
  228. package/dist/views/BulkRunsHub/useBulkRunsList.js +204 -0
  229. package/dist/views/BulkRunsHub/useUrlFilters.d.ts +10 -0
  230. package/dist/views/BulkRunsHub/useUrlFilters.js +88 -0
  231. package/dist/views/TranslationHub/ActiveJobs.d.ts +6 -0
  232. package/dist/views/TranslationHub/ActiveJobs.js +320 -0
  233. package/dist/views/TranslationHub/AdvancedPanel.d.ts +17 -0
  234. package/dist/views/TranslationHub/AdvancedPanel.js +996 -0
  235. package/dist/views/TranslationHub/AlertBanner.d.ts +6 -0
  236. package/dist/views/TranslationHub/AlertBanner.js +568 -0
  237. package/dist/views/TranslationHub/AuditPanel.d.ts +6 -0
  238. package/dist/views/TranslationHub/AuditPanel.helpers.d.ts +44 -0
  239. package/dist/views/TranslationHub/AuditPanel.helpers.js +71 -0
  240. package/dist/views/TranslationHub/AuditPanel.js +1367 -0
  241. package/dist/views/TranslationHub/BulkTranslate.types.d.ts +242 -0
  242. package/dist/views/TranslationHub/BulkTranslate.types.js +36 -0
  243. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.d.ts +19 -0
  244. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.js +332 -0
  245. package/dist/views/TranslationHub/BulkTranslateMonitor.d.ts +28 -0
  246. package/dist/views/TranslationHub/BulkTranslateMonitor.js +305 -0
  247. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.d.ts +3 -0
  248. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.js +42 -0
  249. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.d.ts +26 -0
  250. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.js +95 -0
  251. package/dist/views/TranslationHub/BulkTranslatePreflightModal.d.ts +22 -0
  252. package/dist/views/TranslationHub/BulkTranslatePreflightModal.js +879 -0
  253. package/dist/views/TranslationHub/BulkTranslateTerminalCard.d.ts +29 -0
  254. package/dist/views/TranslationHub/BulkTranslateTerminalCard.js +445 -0
  255. package/dist/views/TranslationHub/BulkTranslateTrigger.d.ts +66 -0
  256. package/dist/views/TranslationHub/BulkTranslateTrigger.js +161 -0
  257. package/dist/views/TranslationHub/EditorRecentRunsPanel.d.ts +33 -0
  258. package/dist/views/TranslationHub/EditorRecentRunsPanel.js +290 -0
  259. package/dist/views/TranslationHub/Hub.client.d.ts +74 -0
  260. package/dist/views/TranslationHub/Hub.client.js +357 -0
  261. package/dist/views/TranslationHub/ModelCombobox.d.ts +14 -0
  262. package/dist/views/TranslationHub/ModelCombobox.js +415 -0
  263. package/dist/views/TranslationHub/PerCollectionConfig.d.ts +10 -0
  264. package/dist/views/TranslationHub/PerCollectionConfig.helpers.d.ts +16 -0
  265. package/dist/views/TranslationHub/PerCollectionConfig.helpers.js +19 -0
  266. package/dist/views/TranslationHub/PerCollectionConfig.js +759 -0
  267. package/dist/views/TranslationHub/SettingsRail.d.ts +11 -0
  268. package/dist/views/TranslationHub/SettingsRail.js +382 -0
  269. package/dist/views/TranslationHub/StatusStrip.d.ts +6 -0
  270. package/dist/views/TranslationHub/StatusStrip.js +451 -0
  271. package/dist/views/TranslationHub/UsageTable.d.ts +6 -0
  272. package/dist/views/TranslationHub/UsageTable.helpers.d.ts +69 -0
  273. package/dist/views/TranslationHub/UsageTable.helpers.js +49 -0
  274. package/dist/views/TranslationHub/UsageTable.js +1240 -0
  275. package/dist/views/TranslationHub/alertGrouping.d.ts +70 -0
  276. package/dist/views/TranslationHub/alertGrouping.js +99 -0
  277. package/dist/views/TranslationHub/index.d.ts +20 -0
  278. package/dist/views/TranslationHub/index.js +109 -0
  279. package/dist/views/TranslationHub/tabNavigation.d.ts +53 -0
  280. package/dist/views/TranslationHub/tabNavigation.js +74 -0
  281. package/dist/views/TranslationHub/terminalBannerVisibility.d.ts +33 -0
  282. package/dist/views/TranslationHub/terminalBannerVisibility.js +124 -0
  283. package/dist/views/TranslationHub/useBulkTranslateActive.d.ts +49 -0
  284. package/dist/views/TranslationHub/useBulkTranslateActive.js +251 -0
  285. package/dist/views/TranslationHub/useFocusTrap.d.ts +6 -0
  286. package/dist/views/TranslationHub/useFocusTrap.js +81 -0
  287. package/dist/views/TranslationHub/useTranslationHubUsageSummary.d.ts +77 -0
  288. package/dist/views/TranslationHub/useTranslationHubUsageSummary.js +267 -0
  289. package/dist/views/shared/EditorError.d.ts +97 -0
  290. package/dist/views/shared/EditorError.js +205 -0
  291. package/dist/views/shared/ModelCell.d.ts +18 -0
  292. package/dist/views/shared/ModelCell.js +31 -0
  293. package/dist/views/shared/docHref.d.ts +16 -0
  294. package/dist/views/shared/docHref.js +26 -0
  295. package/dist/views/shared/fetch-error-body.d.ts +25 -0
  296. package/dist/views/shared/fetch-error-body.js +42 -0
  297. package/dist/views/shared/filterPillStyle.d.ts +35 -0
  298. package/dist/views/shared/filterPillStyle.js +40 -0
  299. package/dist/views/shared/format.d.ts +75 -0
  300. package/dist/views/shared/format.js +131 -0
  301. package/package.json +141 -0
@@ -0,0 +1,996 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useState } from 'react';
4
+ const SECTION_STYLE = {
5
+ background: 'var(--theme-elevation-50)',
6
+ border: '1px solid var(--theme-elevation-150)',
7
+ borderRadius: '6px',
8
+ padding: '1rem 1.25rem'
9
+ };
10
+ const CARD_STYLE = {
11
+ flex: 1,
12
+ minWidth: '140px',
13
+ padding: '0.75rem 1rem',
14
+ background: 'var(--theme-elevation-50)',
15
+ border: '1px solid var(--theme-elevation-150)',
16
+ borderRadius: '6px'
17
+ };
18
+ const LABEL_STYLE = {
19
+ fontSize: '0.7rem',
20
+ fontWeight: 600,
21
+ textTransform: 'uppercase',
22
+ letterSpacing: '0.05em',
23
+ color: 'var(--theme-elevation-500)'
24
+ };
25
+ const VALUE_STYLE = {
26
+ fontSize: '1.5rem',
27
+ fontWeight: 600,
28
+ marginTop: '0.25rem',
29
+ color: 'var(--theme-elevation-1000)'
30
+ };
31
+ const SUBVALUE_STYLE = {
32
+ fontSize: '0.7rem',
33
+ color: 'var(--theme-elevation-500)',
34
+ marginTop: '0.15rem'
35
+ };
36
+ function fmtNum(n) {
37
+ if (n == null) {
38
+ return '—';
39
+ }
40
+ if (n >= 1_000_000) {
41
+ return `${(n / 1_000_000).toFixed(1)}M`;
42
+ }
43
+ if (n >= 1000) {
44
+ return `${(n / 1000).toFixed(1)}K`;
45
+ }
46
+ return n.toString();
47
+ }
48
+ const FlagBadge = ({ value, onLabel, offLabel })=>{
49
+ if (value === null) {
50
+ return /*#__PURE__*/ _jsx(_Fragment, {
51
+ children: "—"
52
+ });
53
+ }
54
+ if (value) {
55
+ return /*#__PURE__*/ _jsx("span", {
56
+ style: {
57
+ color: 'var(--theme-success-500)'
58
+ },
59
+ children: onLabel
60
+ });
61
+ }
62
+ return /*#__PURE__*/ _jsx("span", {
63
+ style: {
64
+ color: 'var(--theme-elevation-500)'
65
+ },
66
+ children: offLabel
67
+ });
68
+ };
69
+ export const AdvancedPanel = ({ basePath })=>{
70
+ const [counts, setCounts] = useState({
71
+ usageRows: null,
72
+ alertsTotal: null,
73
+ alertsOpen: null,
74
+ jobsRunning: null,
75
+ jobsStaleCandidate: null,
76
+ metaRows: null
77
+ });
78
+ const [config, setConfig] = useState(null);
79
+ const [flags, setFlags] = useState({
80
+ usageTracking: null,
81
+ preserveManualEdits: null,
82
+ persistJobs: null,
83
+ alertsPersistence: null
84
+ });
85
+ const [error, setError] = useState(null);
86
+ const [refreshTick, setRefreshTick] = useState(0);
87
+ // HUB-7 (v1.2.6): inline force-reset for stale ai-translate-jobs rows.
88
+ // The "View stale jobs →" link was the only affordance — admins had
89
+ // to navigate, multi-select, and bulk-delete. This button calls
90
+ // DELETE /api/ai-translate-jobs with a `where` filter matching the
91
+ // same `status=running AND createdAt < now-5min` predicate used to
92
+ // compute `jobsStaleCandidate`. Two-step confirm because the action
93
+ // is destructive (deletes rows that COULD still be in flight on a
94
+ // healthy worker, just over the 5-min threshold).
95
+ const [resetState, setResetState] = useState({
96
+ kind: 'idle'
97
+ });
98
+ async function forceResetStaleJobs() {
99
+ setResetState({
100
+ kind: 'resetting'
101
+ });
102
+ const fiveMinAgo = new Date(Date.now() - 5 * 60 * 1000).toISOString();
103
+ const query = new URLSearchParams();
104
+ query.set('where[status][equals]', 'running');
105
+ query.set('where[createdAt][less_than]', fiveMinAgo);
106
+ try {
107
+ const res = await fetch(`${basePath}/api/ai-translate-jobs?${query.toString()}`, {
108
+ method: 'DELETE',
109
+ credentials: 'include'
110
+ });
111
+ if (!res.ok) {
112
+ const text = await res.text();
113
+ throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);
114
+ }
115
+ const body = await res.json().catch(()=>null);
116
+ const deleted = Array.isArray(body?.docs) ? body.docs.length : 0;
117
+ setResetState({
118
+ kind: 'done',
119
+ deleted
120
+ });
121
+ // Refresh counters so the warning banner clears.
122
+ setRefreshTick((n)=>n + 1);
123
+ } catch (e) {
124
+ setResetState({
125
+ kind: 'error',
126
+ message: e instanceof Error ? e.message : String(e)
127
+ });
128
+ }
129
+ }
130
+ useEffect(()=>{
131
+ let cancelled = false;
132
+ setError(null);
133
+ // Use limit=0 + depth=0 to fetch only `totalDocs` — Payload returns
134
+ // it on every list response and we don't actually need the rows.
135
+ const fifteenMinAgo = new Date(Date.now() - 15 * 60 * 1000).toISOString();
136
+ const fiveMinAgo = new Date(Date.now() - 5 * 60 * 1000).toISOString();
137
+ const queries = [
138
+ [
139
+ 'usageRows',
140
+ `${basePath}/api/translation-usage?limit=0&depth=0`
141
+ ],
142
+ [
143
+ 'alertsTotal',
144
+ `${basePath}/api/translation-alerts?limit=0&depth=0`
145
+ ],
146
+ [
147
+ 'alertsOpen',
148
+ `${basePath}/api/translation-alerts?limit=0&depth=0&where[dismissed][equals]=false`
149
+ ],
150
+ [
151
+ 'jobsRunning',
152
+ `${basePath}/api/ai-translate-jobs?limit=0&depth=0&where[status][equals]=running&where[createdAt][greater_than]=${encodeURIComponent(fifteenMinAgo)}`
153
+ ],
154
+ [
155
+ 'jobsStaleCandidate',
156
+ `${basePath}/api/ai-translate-jobs?limit=0&depth=0&where[status][equals]=running&where[createdAt][less_than]=${encodeURIComponent(fiveMinAgo)}`
157
+ ],
158
+ [
159
+ 'metaRows',
160
+ `${basePath}/api/ai-translate-meta?limit=0&depth=0`
161
+ ]
162
+ ];
163
+ Promise.all(queries.map(([key, url])=>fetch(url, {
164
+ credentials: 'include'
165
+ }).then(async (r)=>{
166
+ if (!r.ok) {
167
+ throw new Error(`${key}: HTTP ${r.status}`);
168
+ }
169
+ const d = await r.json();
170
+ return [
171
+ key,
172
+ d.totalDocs ?? null
173
+ ];
174
+ }).catch((e)=>{
175
+ if (!cancelled) {
176
+ const msg = e instanceof Error ? e.message : String(e);
177
+ setError(`Failed to load diagnostics — ${msg}`);
178
+ }
179
+ return [
180
+ key,
181
+ null
182
+ ];
183
+ }))).then((entries)=>{
184
+ if (cancelled) {
185
+ return;
186
+ }
187
+ setCounts((prev)=>{
188
+ const next = {
189
+ ...prev
190
+ };
191
+ for (const [key, value] of entries){
192
+ next[key] = value;
193
+ }
194
+ return next;
195
+ });
196
+ });
197
+ // Client-config endpoint returns locale + translatable field map.
198
+ fetch(`${basePath}/api/ai-translate/client-config`, {
199
+ credentials: 'include'
200
+ }).then(async (r)=>{
201
+ if (!r.ok) {
202
+ return null;
203
+ }
204
+ return await r.json();
205
+ }).then((cfg)=>{
206
+ if (cancelled) {
207
+ return;
208
+ }
209
+ if (cfg) {
210
+ setConfig(cfg);
211
+ }
212
+ }).catch(()=>{
213
+ // Endpoint optional — render falls back to "—".
214
+ });
215
+ // Feature-flag probe: each plugin-owned collection only registers
216
+ // when the matching feature is enabled. A 404 = feature off; a 200
217
+ // or 403 (access-denied for non-admin) = collection exists =
218
+ // feature on. Cheaper than parsing server config and survives the
219
+ // plugin not exposing flags via client-config.
220
+ const probe = (path)=>fetch(`${basePath}${path}?limit=0&depth=0`, {
221
+ credentials: 'include'
222
+ }).then((r)=>r.status !== 404).catch(()=>null);
223
+ Promise.all([
224
+ probe('/api/translation-usage'),
225
+ probe('/api/ai-translate-meta'),
226
+ probe('/api/ai-translate-jobs'),
227
+ probe('/api/translation-alerts')
228
+ ]).then(([usage, meta, jobs, alerts])=>{
229
+ if (cancelled) {
230
+ return;
231
+ }
232
+ setFlags({
233
+ usageTracking: usage,
234
+ preserveManualEdits: meta,
235
+ persistJobs: jobs,
236
+ alertsPersistence: alerts
237
+ });
238
+ });
239
+ return ()=>{
240
+ cancelled = true;
241
+ };
242
+ }, [
243
+ basePath,
244
+ refreshTick
245
+ ]);
246
+ return /*#__PURE__*/ _jsxs("div", {
247
+ style: {
248
+ display: 'flex',
249
+ flexDirection: 'column',
250
+ gap: '1rem'
251
+ },
252
+ children: [
253
+ error && /*#__PURE__*/ _jsx("div", {
254
+ style: {
255
+ ...SECTION_STYLE,
256
+ background: 'var(--theme-error-100, #fee2e2)',
257
+ color: 'var(--theme-error-500, #b91c1c)',
258
+ fontSize: '0.875rem'
259
+ },
260
+ children: error
261
+ }),
262
+ /*#__PURE__*/ _jsxs("section", {
263
+ style: SECTION_STYLE,
264
+ children: [
265
+ /*#__PURE__*/ _jsxs("div", {
266
+ style: {
267
+ display: 'flex',
268
+ alignItems: 'center',
269
+ justifyContent: 'space-between',
270
+ marginBottom: '0.5rem'
271
+ },
272
+ children: [
273
+ /*#__PURE__*/ _jsx("h3", {
274
+ style: {
275
+ margin: 0,
276
+ fontSize: '0.95rem',
277
+ color: 'var(--theme-elevation-1000)'
278
+ },
279
+ children: "Plugin configuration"
280
+ }),
281
+ /*#__PURE__*/ _jsx("button", {
282
+ onClick: ()=>setRefreshTick((n)=>n + 1),
283
+ style: {
284
+ padding: '0.25rem 0.7rem',
285
+ background: 'transparent',
286
+ border: '1px solid var(--theme-elevation-150)',
287
+ borderRadius: '4px',
288
+ fontSize: '0.75rem',
289
+ cursor: 'pointer',
290
+ color: 'var(--theme-elevation-700)'
291
+ },
292
+ type: "button",
293
+ children: "↻ Refresh"
294
+ })
295
+ ]
296
+ }),
297
+ /*#__PURE__*/ _jsxs("div", {
298
+ style: {
299
+ display: 'flex',
300
+ flexWrap: 'wrap',
301
+ gap: '1.5rem',
302
+ fontSize: '0.85rem'
303
+ },
304
+ children: [
305
+ /*#__PURE__*/ _jsxs("div", {
306
+ children: [
307
+ /*#__PURE__*/ _jsx("span", {
308
+ style: LABEL_STYLE,
309
+ children: "Source locale"
310
+ }),
311
+ /*#__PURE__*/ _jsx("div", {
312
+ style: {
313
+ marginTop: '0.15rem',
314
+ fontFamily: 'monospace'
315
+ },
316
+ children: config?.sourceLocale ?? '—'
317
+ })
318
+ ]
319
+ }),
320
+ /*#__PURE__*/ _jsxs("div", {
321
+ children: [
322
+ /*#__PURE__*/ _jsx("span", {
323
+ style: LABEL_STYLE,
324
+ children: "Target locales"
325
+ }),
326
+ /*#__PURE__*/ _jsx("div", {
327
+ style: {
328
+ marginTop: '0.15rem',
329
+ fontFamily: 'monospace'
330
+ },
331
+ children: config?.targetLocales?.length ? config.targetLocales.join(', ') : '—'
332
+ })
333
+ ]
334
+ }),
335
+ /*#__PURE__*/ _jsxs("div", {
336
+ children: [
337
+ /*#__PURE__*/ _jsx("span", {
338
+ style: LABEL_STYLE,
339
+ children: "Translatable surfaces"
340
+ }),
341
+ /*#__PURE__*/ _jsx("div", {
342
+ style: {
343
+ marginTop: '0.15rem'
344
+ },
345
+ children: config?.translatableFieldsBySlug ? `${Object.keys(config.translatableFieldsBySlug).length} collections / globals` : '—'
346
+ })
347
+ ]
348
+ }),
349
+ /*#__PURE__*/ _jsxs("div", {
350
+ children: [
351
+ /*#__PURE__*/ _jsx("span", {
352
+ style: LABEL_STYLE,
353
+ children: "Per-field button"
354
+ }),
355
+ /*#__PURE__*/ _jsxs("div", {
356
+ style: {
357
+ marginTop: '0.15rem'
358
+ },
359
+ children: [
360
+ config && /*#__PURE__*/ _jsx(FlagBadge, {
361
+ offLabel: "off",
362
+ onLabel: "enabled",
363
+ value: config.perFieldButton ?? false
364
+ }),
365
+ !config && '—'
366
+ ]
367
+ })
368
+ ]
369
+ })
370
+ ]
371
+ }),
372
+ /*#__PURE__*/ _jsxs("div", {
373
+ style: {
374
+ display: 'flex',
375
+ flexWrap: 'wrap',
376
+ gap: '1.5rem',
377
+ fontSize: '0.85rem',
378
+ marginTop: '1rem',
379
+ paddingTop: '0.75rem',
380
+ borderTop: '1px solid var(--theme-elevation-150)'
381
+ },
382
+ children: [
383
+ /*#__PURE__*/ _jsxs("div", {
384
+ children: [
385
+ /*#__PURE__*/ _jsx("span", {
386
+ style: LABEL_STYLE,
387
+ children: "Usage tracking"
388
+ }),
389
+ /*#__PURE__*/ _jsx("div", {
390
+ style: {
391
+ marginTop: '0.15rem'
392
+ },
393
+ children: /*#__PURE__*/ _jsx(FlagBadge, {
394
+ offLabel: "disabled",
395
+ onLabel: "enabled",
396
+ value: flags.usageTracking
397
+ })
398
+ })
399
+ ]
400
+ }),
401
+ /*#__PURE__*/ _jsxs("div", {
402
+ children: [
403
+ /*#__PURE__*/ _jsx("span", {
404
+ style: LABEL_STYLE,
405
+ children: "Manual-edit guard"
406
+ }),
407
+ /*#__PURE__*/ _jsx("div", {
408
+ style: {
409
+ marginTop: '0.15rem'
410
+ },
411
+ children: /*#__PURE__*/ _jsx(FlagBadge, {
412
+ offLabel: "off",
413
+ onLabel: "active",
414
+ value: flags.preserveManualEdits
415
+ })
416
+ })
417
+ ]
418
+ }),
419
+ /*#__PURE__*/ _jsxs("div", {
420
+ children: [
421
+ /*#__PURE__*/ _jsx("span", {
422
+ style: LABEL_STYLE,
423
+ children: "Persist jobs"
424
+ }),
425
+ /*#__PURE__*/ _jsx("div", {
426
+ style: {
427
+ marginTop: '0.15rem'
428
+ },
429
+ children: /*#__PURE__*/ _jsx(FlagBadge, {
430
+ offLabel: "disabled",
431
+ onLabel: "enabled",
432
+ value: flags.persistJobs
433
+ })
434
+ })
435
+ ]
436
+ }),
437
+ /*#__PURE__*/ _jsxs("div", {
438
+ children: [
439
+ /*#__PURE__*/ _jsx("span", {
440
+ style: LABEL_STYLE,
441
+ children: "Alerts persistence"
442
+ }),
443
+ /*#__PURE__*/ _jsx("div", {
444
+ style: {
445
+ marginTop: '0.15rem'
446
+ },
447
+ children: /*#__PURE__*/ _jsx(FlagBadge, {
448
+ offLabel: "disabled",
449
+ onLabel: "enabled",
450
+ value: flags.alertsPersistence
451
+ })
452
+ })
453
+ ]
454
+ })
455
+ ]
456
+ })
457
+ ]
458
+ }),
459
+ config?.translatableFieldsBySlug && Object.keys(config.translatableFieldsBySlug).length > 0 && /*#__PURE__*/ _jsxs("section", {
460
+ style: SECTION_STYLE,
461
+ children: [
462
+ /*#__PURE__*/ _jsx("h3", {
463
+ style: {
464
+ margin: '0 0 0.5rem',
465
+ fontSize: '0.95rem',
466
+ color: 'var(--theme-elevation-1000)'
467
+ },
468
+ children: "Translatable fields per surface"
469
+ }),
470
+ /*#__PURE__*/ _jsx("p", {
471
+ style: {
472
+ margin: '0 0 0.75rem',
473
+ fontSize: '0.75rem',
474
+ color: 'var(--theme-elevation-500)'
475
+ },
476
+ children: 'Field paths the plugin recognises as translatable for each collection / global. Excluded paths from per-collection overrides are NOT shown here — this is the pre-exclusion set. Surfaces flagged "Excluded from bulk translate" are still translatable per-document via the Translate button but skipped by Bulk Translate runs.'
477
+ }),
478
+ /*#__PURE__*/ _jsx("div", {
479
+ style: {
480
+ overflowX: 'auto'
481
+ },
482
+ children: /*#__PURE__*/ _jsxs("table", {
483
+ style: {
484
+ width: '100%',
485
+ borderCollapse: 'collapse',
486
+ fontSize: '0.85rem'
487
+ },
488
+ children: [
489
+ /*#__PURE__*/ _jsx("thead", {
490
+ children: /*#__PURE__*/ _jsxs("tr", {
491
+ children: [
492
+ /*#__PURE__*/ _jsx("th", {
493
+ style: {
494
+ textAlign: 'left',
495
+ padding: '0.4rem 0.5rem',
496
+ fontSize: '0.7rem',
497
+ fontWeight: 600,
498
+ textTransform: 'uppercase',
499
+ letterSpacing: '0.05em',
500
+ color: 'var(--theme-elevation-500)',
501
+ borderBottom: '1px solid var(--theme-elevation-150)'
502
+ },
503
+ children: "Surface"
504
+ }),
505
+ /*#__PURE__*/ _jsx("th", {
506
+ style: {
507
+ textAlign: 'right',
508
+ padding: '0.4rem 0.5rem',
509
+ fontSize: '0.7rem',
510
+ fontWeight: 600,
511
+ textTransform: 'uppercase',
512
+ letterSpacing: '0.05em',
513
+ color: 'var(--theme-elevation-500)',
514
+ borderBottom: '1px solid var(--theme-elevation-150)'
515
+ },
516
+ children: "Field count"
517
+ }),
518
+ /*#__PURE__*/ _jsx("th", {
519
+ style: {
520
+ textAlign: 'left',
521
+ padding: '0.4rem 0.5rem',
522
+ fontSize: '0.7rem',
523
+ fontWeight: 600,
524
+ textTransform: 'uppercase',
525
+ letterSpacing: '0.05em',
526
+ color: 'var(--theme-elevation-500)',
527
+ borderBottom: '1px solid var(--theme-elevation-150)'
528
+ },
529
+ children: "Paths"
530
+ })
531
+ ]
532
+ })
533
+ }),
534
+ /*#__PURE__*/ _jsx("tbody", {
535
+ children: Object.entries(config.translatableFieldsBySlug).sort(([, a], [, b])=>b.length - a.length).map(([slug, fields])=>{
536
+ const bulkExcluded = config.bulkExcludedCollections?.includes(slug) ?? false;
537
+ return /*#__PURE__*/ _jsxs("tr", {
538
+ children: [
539
+ /*#__PURE__*/ _jsxs("td", {
540
+ style: {
541
+ padding: '0.4rem 0.5rem',
542
+ fontFamily: 'monospace',
543
+ fontSize: '0.8rem',
544
+ borderTop: '1px solid var(--theme-elevation-100)',
545
+ whiteSpace: 'nowrap',
546
+ verticalAlign: 'top'
547
+ },
548
+ children: [
549
+ slug,
550
+ bulkExcluded && // NEW-9 (v1.2.6): badge for surfaces the
551
+ // consumer has excluded from bulk-translate
552
+ // runs (e.g. `users` for PII). Editors
553
+ // see them in the per-field button +
554
+ // override picker but not in the Bulk
555
+ // Translate modal — this clarifies why.
556
+ /*#__PURE__*/ _jsx("span", {
557
+ "data-testid": `bulk-excluded-${slug}`,
558
+ style: {
559
+ marginLeft: '0.4rem',
560
+ padding: '0.05rem 0.4rem',
561
+ borderRadius: '999px',
562
+ fontSize: '0.65rem',
563
+ fontFamily: 'inherit',
564
+ fontWeight: 600,
565
+ background: 'var(--theme-warning-100, #fef3c7)',
566
+ color: 'var(--theme-warning-800, #92400e)',
567
+ border: '1px solid var(--theme-warning-300, #fde68a)',
568
+ whiteSpace: 'nowrap'
569
+ },
570
+ title: "This surface is translated per-document via the Translate button but is excluded from Bulk Translate runs (consumer config: bulk.excludeCollections).",
571
+ children: "Excluded from bulk translate"
572
+ })
573
+ ]
574
+ }),
575
+ /*#__PURE__*/ _jsx("td", {
576
+ style: {
577
+ padding: '0.4rem 0.5rem',
578
+ textAlign: 'right',
579
+ borderTop: '1px solid var(--theme-elevation-100)',
580
+ color: 'var(--theme-elevation-800)',
581
+ whiteSpace: 'nowrap',
582
+ verticalAlign: 'top'
583
+ },
584
+ children: fields.length
585
+ }),
586
+ /*#__PURE__*/ _jsxs("td", {
587
+ style: {
588
+ padding: '0.4rem 0.5rem',
589
+ borderTop: '1px solid var(--theme-elevation-100)',
590
+ color: 'var(--theme-elevation-700)',
591
+ fontSize: '0.75rem',
592
+ fontFamily: 'monospace'
593
+ },
594
+ children: [
595
+ fields.slice(0, 4).join(', '),
596
+ fields.length > 4 && /*#__PURE__*/ _jsxs("span", {
597
+ style: {
598
+ color: 'var(--theme-elevation-500)'
599
+ },
600
+ children: [
601
+ ' · ',
602
+ "+",
603
+ fields.length - 4,
604
+ " more"
605
+ ]
606
+ })
607
+ ]
608
+ })
609
+ ]
610
+ }, slug);
611
+ })
612
+ })
613
+ ]
614
+ })
615
+ })
616
+ ]
617
+ }),
618
+ /*#__PURE__*/ _jsxs("div", {
619
+ children: [
620
+ /*#__PURE__*/ _jsx("div", {
621
+ style: {
622
+ ...LABEL_STYLE,
623
+ marginBottom: '0.5rem'
624
+ },
625
+ children: "Diagnostic counters"
626
+ }),
627
+ /*#__PURE__*/ _jsxs("div", {
628
+ style: {
629
+ display: 'flex',
630
+ flexWrap: 'wrap',
631
+ gap: '0.75rem'
632
+ },
633
+ children: [
634
+ /*#__PURE__*/ _jsxs("div", {
635
+ style: CARD_STYLE,
636
+ children: [
637
+ /*#__PURE__*/ _jsx("div", {
638
+ style: LABEL_STYLE,
639
+ children: "Usage rows total"
640
+ }),
641
+ /*#__PURE__*/ _jsx("div", {
642
+ style: VALUE_STYLE,
643
+ children: fmtNum(counts.usageRows)
644
+ }),
645
+ /*#__PURE__*/ _jsx("div", {
646
+ style: SUBVALUE_STYLE,
647
+ children: "Every translate run, ever."
648
+ })
649
+ ]
650
+ }),
651
+ /*#__PURE__*/ _jsxs("div", {
652
+ style: CARD_STYLE,
653
+ children: [
654
+ /*#__PURE__*/ _jsx("div", {
655
+ style: LABEL_STYLE,
656
+ children: "Alerts"
657
+ }),
658
+ /*#__PURE__*/ _jsx("div", {
659
+ style: VALUE_STYLE,
660
+ children: fmtNum(counts.alertsTotal)
661
+ }),
662
+ /*#__PURE__*/ _jsx("div", {
663
+ style: SUBVALUE_STYLE,
664
+ children: counts.alertsOpen != null && counts.alertsTotal != null ? `${counts.alertsOpen} open · ${counts.alertsTotal - counts.alertsOpen} dismissed` : '—'
665
+ })
666
+ ]
667
+ }),
668
+ /*#__PURE__*/ _jsxs("div", {
669
+ style: CARD_STYLE,
670
+ children: [
671
+ /*#__PURE__*/ _jsx("div", {
672
+ style: LABEL_STYLE,
673
+ children: "Running jobs (15m)"
674
+ }),
675
+ /*#__PURE__*/ _jsx("div", {
676
+ style: {
677
+ ...VALUE_STYLE,
678
+ color: (counts.jobsRunning ?? 0) > 0 ? 'var(--theme-success-500)' : 'var(--theme-elevation-1000)'
679
+ },
680
+ children: fmtNum(counts.jobsRunning)
681
+ }),
682
+ /*#__PURE__*/ _jsx("div", {
683
+ style: SUBVALUE_STYLE,
684
+ children: "Jobs flagged `running` in last 15 minutes."
685
+ })
686
+ ]
687
+ }),
688
+ /*#__PURE__*/ _jsxs("div", {
689
+ style: CARD_STYLE,
690
+ children: [
691
+ /*#__PURE__*/ _jsx("div", {
692
+ style: LABEL_STYLE,
693
+ children: "Stale candidates"
694
+ }),
695
+ /*#__PURE__*/ _jsx("div", {
696
+ style: {
697
+ ...VALUE_STYLE,
698
+ color: (counts.jobsStaleCandidate ?? 0) > 0 ? 'var(--theme-warning-500, #d97706)' : 'var(--theme-elevation-1000)'
699
+ },
700
+ children: fmtNum(counts.jobsStaleCandidate)
701
+ }),
702
+ /*#__PURE__*/ _jsx("div", {
703
+ style: SUBVALUE_STYLE,
704
+ children: "Jobs `running` for > 5min. Server probably died mid-run."
705
+ })
706
+ ]
707
+ }),
708
+ /*#__PURE__*/ _jsxs("div", {
709
+ style: CARD_STYLE,
710
+ children: [
711
+ /*#__PURE__*/ _jsx("div", {
712
+ style: LABEL_STYLE,
713
+ children: "Meta rows"
714
+ }),
715
+ /*#__PURE__*/ _jsx("div", {
716
+ style: VALUE_STYLE,
717
+ children: fmtNum(counts.metaRows)
718
+ }),
719
+ /*#__PURE__*/ _jsx("div", {
720
+ style: SUBVALUE_STYLE,
721
+ children: "Per-(collection, doc, locale, field) hashes powering skip and manual-edit guard."
722
+ })
723
+ ]
724
+ })
725
+ ]
726
+ })
727
+ ]
728
+ }),
729
+ counts.jobsStaleCandidate != null && counts.jobsStaleCandidate > 0 && /*#__PURE__*/ _jsxs("section", {
730
+ style: {
731
+ ...SECTION_STYLE,
732
+ background: 'var(--theme-warning-100, #fef3c7)',
733
+ border: '1px solid var(--theme-warning-500, #d97706)'
734
+ },
735
+ children: [
736
+ /*#__PURE__*/ _jsxs("strong", {
737
+ style: {
738
+ color: 'var(--theme-warning-500, #d97706)'
739
+ },
740
+ children: [
741
+ "⚠ ",
742
+ counts.jobsStaleCandidate,
743
+ " stale running job",
744
+ counts.jobsStaleCandidate === 1 ? '' : 's',
745
+ " detected."
746
+ ]
747
+ }),
748
+ /*#__PURE__*/ _jsxs("p", {
749
+ style: {
750
+ margin: '0.4rem 0 0',
751
+ fontSize: '0.85rem',
752
+ color: 'var(--theme-elevation-900)'
753
+ },
754
+ children: [
755
+ "These rows were marked `running` more than 5 minutes ago and haven't been touched since — usually a sign the server process died mid-translation. They don't block new translations but pollute the in-flight panel.",
756
+ ' ',
757
+ /*#__PURE__*/ _jsx("a", {
758
+ href: `${basePath}/admin/collections/ai-translate-jobs?where[status][equals]=running&sort=createdAt`,
759
+ style: {
760
+ color: 'var(--theme-success-500)'
761
+ },
762
+ children: "View stale jobs →"
763
+ })
764
+ ]
765
+ }),
766
+ /*#__PURE__*/ _jsxs("div", {
767
+ style: {
768
+ marginTop: '0.6rem',
769
+ display: 'flex',
770
+ alignItems: 'center',
771
+ gap: '0.5rem',
772
+ flexWrap: 'wrap'
773
+ },
774
+ children: [
775
+ resetState.kind === 'idle' && /*#__PURE__*/ _jsx("button", {
776
+ onClick: ()=>setResetState({
777
+ kind: 'confirming'
778
+ }),
779
+ style: {
780
+ padding: '0.35rem 0.85rem',
781
+ background: 'var(--theme-warning-500, #d97706)',
782
+ border: 'none',
783
+ borderRadius: '4px',
784
+ color: '#fff',
785
+ fontWeight: 600,
786
+ fontSize: '0.8rem',
787
+ cursor: 'pointer'
788
+ },
789
+ type: "button",
790
+ children: "Force-reset stale jobs"
791
+ }),
792
+ resetState.kind === 'confirming' && /*#__PURE__*/ _jsxs(_Fragment, {
793
+ children: [
794
+ /*#__PURE__*/ _jsxs("span", {
795
+ style: {
796
+ fontSize: '0.8rem',
797
+ color: 'var(--theme-elevation-900)'
798
+ },
799
+ children: [
800
+ "Delete ",
801
+ counts.jobsStaleCandidate,
802
+ " stale job row",
803
+ counts.jobsStaleCandidate === 1 ? '' : 's',
804
+ "?"
805
+ ]
806
+ }),
807
+ /*#__PURE__*/ _jsx("button", {
808
+ onClick: ()=>setResetState({
809
+ kind: 'idle'
810
+ }),
811
+ style: {
812
+ padding: '0.35rem 0.85rem',
813
+ background: 'transparent',
814
+ border: '1px solid var(--theme-elevation-200)',
815
+ borderRadius: '4px',
816
+ color: 'var(--theme-elevation-700)',
817
+ fontSize: '0.8rem',
818
+ cursor: 'pointer'
819
+ },
820
+ type: "button",
821
+ children: "Cancel"
822
+ }),
823
+ /*#__PURE__*/ _jsx("button", {
824
+ onClick: ()=>{
825
+ void forceResetStaleJobs();
826
+ },
827
+ style: {
828
+ padding: '0.35rem 0.85rem',
829
+ background: 'var(--theme-error-500, #b91c1c)',
830
+ border: 'none',
831
+ borderRadius: '4px',
832
+ color: '#fff',
833
+ fontWeight: 600,
834
+ fontSize: '0.8rem',
835
+ cursor: 'pointer'
836
+ },
837
+ type: "button",
838
+ children: "Confirm reset"
839
+ })
840
+ ]
841
+ }),
842
+ resetState.kind === 'resetting' && /*#__PURE__*/ _jsx("span", {
843
+ style: {
844
+ fontSize: '0.8rem',
845
+ color: 'var(--theme-elevation-700)'
846
+ },
847
+ children: "Resetting…"
848
+ }),
849
+ resetState.kind === 'done' && /*#__PURE__*/ _jsxs("span", {
850
+ role: "status",
851
+ style: {
852
+ fontSize: '0.8rem',
853
+ color: 'var(--theme-success-500, #15803d)'
854
+ },
855
+ children: [
856
+ "Deleted ",
857
+ resetState.deleted,
858
+ " stale job row",
859
+ resetState.deleted === 1 ? '' : 's',
860
+ "."
861
+ ]
862
+ }),
863
+ resetState.kind === 'error' && /*#__PURE__*/ _jsxs("span", {
864
+ role: "alert",
865
+ style: {
866
+ fontSize: '0.8rem',
867
+ color: 'var(--theme-error-500, #b91c1c)'
868
+ },
869
+ children: [
870
+ "Reset failed — ",
871
+ resetState.message
872
+ ]
873
+ })
874
+ ]
875
+ })
876
+ ]
877
+ }),
878
+ /*#__PURE__*/ _jsxs("section", {
879
+ style: SECTION_STYLE,
880
+ children: [
881
+ /*#__PURE__*/ _jsx("h3", {
882
+ style: {
883
+ margin: '0 0 0.5rem',
884
+ fontSize: '0.95rem',
885
+ color: 'var(--theme-elevation-1000)'
886
+ },
887
+ children: "Raw collection access"
888
+ }),
889
+ /*#__PURE__*/ _jsxs("div", {
890
+ style: {
891
+ display: 'grid',
892
+ gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
893
+ gap: '0.5rem',
894
+ fontSize: '0.85rem'
895
+ },
896
+ children: [
897
+ /*#__PURE__*/ _jsxs("a", {
898
+ href: `${basePath}/admin/collections/translation-usage`,
899
+ style: {
900
+ padding: '0.6rem 0.75rem',
901
+ background: 'var(--theme-elevation-100)',
902
+ borderRadius: '4px',
903
+ color: 'var(--theme-elevation-1000)',
904
+ textDecoration: 'none',
905
+ display: 'block'
906
+ },
907
+ children: [
908
+ /*#__PURE__*/ _jsx("strong", {
909
+ children: "Translation Usage"
910
+ }),
911
+ /*#__PURE__*/ _jsx("div", {
912
+ style: {
913
+ color: 'var(--theme-elevation-500)',
914
+ fontSize: '0.75rem'
915
+ },
916
+ children: "Per-run rows with full filter / sort controls."
917
+ })
918
+ ]
919
+ }),
920
+ /*#__PURE__*/ _jsxs("a", {
921
+ href: `${basePath}/admin/collections/translation-alerts`,
922
+ style: {
923
+ padding: '0.6rem 0.75rem',
924
+ background: 'var(--theme-elevation-100)',
925
+ borderRadius: '4px',
926
+ color: 'var(--theme-elevation-1000)',
927
+ textDecoration: 'none',
928
+ display: 'block'
929
+ },
930
+ children: [
931
+ /*#__PURE__*/ _jsx("strong", {
932
+ children: "Translation Alerts"
933
+ }),
934
+ /*#__PURE__*/ _jsx("div", {
935
+ style: {
936
+ color: 'var(--theme-elevation-500)',
937
+ fontSize: '0.75rem'
938
+ },
939
+ children: "Cost-guard, persistent-failure, provider-outage events."
940
+ })
941
+ ]
942
+ }),
943
+ /*#__PURE__*/ _jsxs("a", {
944
+ href: `${basePath}/admin/collections/ai-translate-jobs`,
945
+ style: {
946
+ padding: '0.6rem 0.75rem',
947
+ background: 'var(--theme-elevation-100)',
948
+ borderRadius: '4px',
949
+ color: 'var(--theme-elevation-1000)',
950
+ textDecoration: 'none',
951
+ display: 'block'
952
+ },
953
+ children: [
954
+ /*#__PURE__*/ _jsx("strong", {
955
+ children: "Translate Jobs"
956
+ }),
957
+ /*#__PURE__*/ _jsx("div", {
958
+ style: {
959
+ color: 'var(--theme-elevation-500)',
960
+ fontSize: '0.75rem'
961
+ },
962
+ children: "Job-level state including in-flight + historical."
963
+ })
964
+ ]
965
+ }),
966
+ /*#__PURE__*/ _jsxs("a", {
967
+ href: `${basePath}/admin/collections/ai-translate-meta`,
968
+ style: {
969
+ padding: '0.6rem 0.75rem',
970
+ background: 'var(--theme-elevation-100)',
971
+ borderRadius: '4px',
972
+ color: 'var(--theme-elevation-1000)',
973
+ textDecoration: 'none',
974
+ display: 'block'
975
+ },
976
+ children: [
977
+ /*#__PURE__*/ _jsx("strong", {
978
+ children: "AI Translate Meta"
979
+ }),
980
+ /*#__PURE__*/ _jsx("div", {
981
+ style: {
982
+ color: 'var(--theme-elevation-500)',
983
+ fontSize: '0.75rem'
984
+ },
985
+ children: "Per-field hashes (source + last-written)."
986
+ })
987
+ ]
988
+ })
989
+ ]
990
+ })
991
+ ]
992
+ })
993
+ ]
994
+ });
995
+ };
996
+ export default AdvancedPanel;