@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,1367 @@
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 { docHref as buildDocHref, globalHref } from '../shared/docHref.js';
5
+ import { filterPillColors } from '../shared/filterPillStyle.js';
6
+ import { formatCost } from '../shared/format.js';
7
+ import { ModelCell } from '../shared/ModelCell.js';
8
+ import { useTranslationHubUsageSummary } from './useTranslationHubUsageSummary.js';
9
+ const PRESET_LABEL = {
10
+ '7d': 'Last 7 days',
11
+ '30d': 'Last 30 days',
12
+ '90d': 'Last 90 days',
13
+ all: 'All time',
14
+ custom: 'Custom'
15
+ };
16
+ const SECTION_STYLE = {
17
+ background: 'var(--theme-elevation-50)',
18
+ border: '1px solid var(--theme-elevation-150)',
19
+ borderRadius: '6px',
20
+ padding: '1rem 1.25rem'
21
+ };
22
+ const TH_STYLE = {
23
+ textAlign: 'left',
24
+ padding: '0.4rem 0.5rem',
25
+ fontSize: '0.7rem',
26
+ fontWeight: 600,
27
+ textTransform: 'uppercase',
28
+ letterSpacing: '0.05em',
29
+ color: 'var(--theme-elevation-500)',
30
+ borderBottom: '1px solid var(--theme-elevation-150)',
31
+ whiteSpace: 'nowrap',
32
+ userSelect: 'none'
33
+ };
34
+ // ROUND5-1 (v1.2.6): sortable `th` gets the cursor + role hint. Kept
35
+ // separate from `TH_STYLE` so the non-sortable Recent failures
36
+ // headers stop advertising the same deceptive `cursor: pointer`.
37
+ const SORTABLE_TH_STYLE = {
38
+ ...TH_STYLE,
39
+ cursor: 'pointer'
40
+ };
41
+ function nextDir(currentCol, nextCol, currentDir) {
42
+ // Clicking the same column flips the direction. Clicking a new
43
+ // column defaults to desc — numeric columns are most useful "biggest
44
+ // first," and the editor can flip if they want ascending. The text
45
+ // columns (surface / model / locale) also default to desc → asc on
46
+ // second click, which feels natural enough.
47
+ if (currentCol !== nextCol) return 'desc';
48
+ return currentDir === 'desc' ? 'asc' : 'desc';
49
+ }
50
+ function ariaSortFor(activeCol, thisCol, dir) {
51
+ if (activeCol !== thisCol) return 'none';
52
+ return dir === 'asc' ? 'ascending' : 'descending';
53
+ }
54
+ function sortIndicator(activeCol, thisCol, dir) {
55
+ if (activeCol !== thisCol) return ' —';
56
+ return dir === 'asc' ? ' ▲' : ' ▼';
57
+ }
58
+ const TD_STYLE = {
59
+ padding: '0.4rem 0.5rem',
60
+ fontSize: '0.85rem',
61
+ color: 'var(--theme-elevation-800)',
62
+ borderTop: '1px solid var(--theme-elevation-100)'
63
+ };
64
+ // NEW-24 (v1.2.6): reserve minHeight so the Runs / Tokens cards
65
+ // (which sprout a second sub-line when summary lands) don't grow
66
+ // height-wise on first paint. The other three KPI cards only show
67
+ // one line; the min-height is sized to the tallest two so the strip
68
+ // stays uniform.
69
+ const CARD_STYLE = {
70
+ flex: 1,
71
+ padding: '0.75rem 1rem',
72
+ background: 'var(--theme-elevation-50)',
73
+ border: '1px solid var(--theme-elevation-150)',
74
+ borderRadius: '6px',
75
+ minWidth: '120px',
76
+ minHeight: '5.25rem'
77
+ };
78
+ function fmtNum(n) {
79
+ if (n >= 1_000_000) {
80
+ return `${(n / 1_000_000).toFixed(1)}M`;
81
+ }
82
+ if (n >= 1000) {
83
+ return `${(n / 1000).toFixed(1)}K`;
84
+ }
85
+ return n.toString();
86
+ }
87
+ // Cost rendering uses the shared `formatCost` helper from
88
+ // `views/shared/format.ts` (NEW-17 — one source of truth across
89
+ // Hub Overview, Audit, Runs, batch rows, drill-down).
90
+ // Shared comparator for the per-collection / per-model breakdown
91
+ // tables. ROUND5-1 (v1.2.6): extended with direction + text-column
92
+ // support so the per-column click handlers below can route through
93
+ // the same helper. Numeric columns sort by raw value; text columns
94
+ // use `localeCompare`.
95
+ function compareSurface(a, b, col, dir) {
96
+ const mult = dir === 'asc' ? 1 : -1;
97
+ if (col === 'surface') return a.slug.localeCompare(b.slug) * mult;
98
+ if (col === 'runs') return (a.runs - b.runs) * mult;
99
+ if (col === 'tokens') return (a.tokens - b.tokens) * mult;
100
+ if (col === 'cost') return (a.costUsd - b.costUsd) * mult;
101
+ // Avg / run — derived. Guard against runs=0 (division by zero).
102
+ const aAvg = a.runs > 0 ? a.costUsd / a.runs : 0;
103
+ const bAvg = b.runs > 0 ? b.costUsd / b.runs : 0;
104
+ return (aAvg - bAvg) * mult;
105
+ }
106
+ function compareModel(a, b, col, dir) {
107
+ const mult = dir === 'asc' ? 1 : -1;
108
+ if (col === 'model') return a.model.localeCompare(b.model) * mult;
109
+ if (col === 'runs') return (a.runs - b.runs) * mult;
110
+ if (col === 'tokens') return (a.tokens - b.tokens) * mult;
111
+ return (a.costUsd - b.costUsd) * mult;
112
+ }
113
+ function compareLocale(a, b, col, dir) {
114
+ const mult = dir === 'asc' ? 1 : -1;
115
+ if (col === 'locale') return a.locale.localeCompare(b.locale) * mult;
116
+ if (col === 'runs') return (a.runs - b.runs) * mult;
117
+ if (col === 'failed') return (a.failed - b.failed) * mult;
118
+ return (a.successRate - b.successRate) * mult;
119
+ }
120
+ function relTime(iso) {
121
+ const diff = Date.now() - new Date(iso).getTime();
122
+ const s = Math.floor(diff / 1000);
123
+ if (s < 60) {
124
+ return `${s}s ago`;
125
+ }
126
+ if (s < 3600) {
127
+ return `${Math.floor(s / 60)}m ago`;
128
+ }
129
+ if (s < 86_400) {
130
+ return `${Math.floor(s / 3600)}h ago`;
131
+ }
132
+ return `${Math.floor(s / 86_400)}d ago`;
133
+ }
134
+ function docHref(basePath, r, locale) {
135
+ if (r.kind === 'global') {
136
+ return globalHref(basePath, r.slug, locale);
137
+ }
138
+ if (r.documentId) {
139
+ return buildDocHref(basePath, r.slug, r.documentId, locale);
140
+ }
141
+ return null;
142
+ }
143
+ /**
144
+ * Recent-failures rows don't carry a single row-level locale — they carry
145
+ * `targetLocales[]` with individual statuses. The locale that actually
146
+ * failed (so the editor lands on the right target to verify) is the first
147
+ * one with `status === 'failed'`. Falls back to the first target locale if
148
+ * none are explicitly marked failed.
149
+ */ function failedLocaleFor(r) {
150
+ const targets = r.targetLocales ?? [];
151
+ const failed = targets.find((tl)=>tl.status === 'failed');
152
+ if (failed) {
153
+ return failed.locale;
154
+ }
155
+ return targets[0]?.locale ?? null;
156
+ }
157
+ // NEW-24 (v1.2.6): shimmer placeholder rendered while `rows === null`.
158
+ // Reserves visible vertical space inside each breakdown section so
159
+ // the page settles into its final layout on first paint instead of
160
+ // growing as data arrives. Aria-hidden — the surrounding section
161
+ // header carries the semantic meaning ("Collections & globals").
162
+ const SkeletonLines = ({ count = 3 })=>{
163
+ const lines = [];
164
+ for(let i = 0; i < count; i++){
165
+ lines.push(i);
166
+ }
167
+ return /*#__PURE__*/ _jsx("div", {
168
+ "aria-hidden": "true",
169
+ style: {
170
+ display: 'flex',
171
+ flexDirection: 'column',
172
+ gap: '0.5rem'
173
+ },
174
+ children: lines.map((i)=>/*#__PURE__*/ _jsx("div", {
175
+ style: {
176
+ height: '0.85rem',
177
+ background: 'var(--theme-elevation-100)',
178
+ borderRadius: '3px',
179
+ width: i === count - 1 ? '60%' : '100%'
180
+ }
181
+ }, i))
182
+ });
183
+ };
184
+ export const AuditPanel = ({ basePath })=>{
185
+ // ROUND2-2: default range was '30d' while Hub Overview defaults to
186
+ // '7d'. Switching tabs without re-clicking a pill silently changed
187
+ // the window so editors compared apples to oranges. Both tabs now
188
+ // default to the same range.
189
+ const [preset, setPreset] = useState('7d');
190
+ const [customFrom, setCustomFrom] = useState('');
191
+ const [customTo, setCustomTo] = useState('');
192
+ const [summaryData, setSummaryData] = useState(null);
193
+ const [error, setError] = useState(null);
194
+ // ROUND5-1 (v1.2.6): per-table sort state. The global pill row
195
+ // (`Sort breakdown tables by [cost / runs / tokens]`) is a
196
+ // convenience cross-table shortcut — clicking a pill routes the
197
+ // matching column into each table's state (preserves the existing
198
+ // affordance). Per-column header clicks update only that table's
199
+ // state.
200
+ const [surfaceSort, setSurfaceSort] = useState({
201
+ col: 'cost',
202
+ dir: 'desc'
203
+ });
204
+ const [modelSort, setModelSort] = useState({
205
+ col: 'cost',
206
+ dir: 'desc'
207
+ });
208
+ const [localeSort, setLocaleSort] = useState({
209
+ col: 'runs',
210
+ dir: 'desc'
211
+ });
212
+ // Total translatable surfaces (collections + globals registered in
213
+ // plugin config). Used to label the breakdown headers as
214
+ // "X of Y active" so editors don't misread the count as "we lost
215
+ // some surfaces." See NEW-23 — mirrors StatusStrip.
216
+ const [totalSurfaces, setTotalSurfaces] = useState(null);
217
+ useEffect(()=>{
218
+ let cancelled = false;
219
+ fetch(`${basePath}/api/ai-translate/client-config`, {
220
+ credentials: 'include'
221
+ }).then((r)=>r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))).then((d)=>{
222
+ if (cancelled) return;
223
+ setTotalSurfaces(Object.keys(d.translatableFieldsBySlug ?? {}).length);
224
+ }).catch(()=>{
225
+ // Non-fatal: the header still renders with just the active count.
226
+ });
227
+ return ()=>{
228
+ cancelled = true;
229
+ };
230
+ }, [
231
+ basePath
232
+ ]);
233
+ // ROUND2-4: switched to the singleton hook so Hub Overview's
234
+ // StatusStrip and this tab share the same in-flight request when
235
+ // they're both mounted under the same `(basePath, range)`. Mirrors
236
+ // NEW-5 / `useBulkTranslateActive`. Removes the StrictMode dev-
237
+ // double-fetch and the paired-burst pattern.
238
+ const rangeKey = useMemo(()=>{
239
+ if (preset === 'custom') {
240
+ return {
241
+ kind: 'custom',
242
+ ...customFrom ? {
243
+ from: customFrom
244
+ } : {},
245
+ ...customTo ? {
246
+ to: customTo
247
+ } : {}
248
+ };
249
+ }
250
+ return {
251
+ kind: preset
252
+ };
253
+ }, [
254
+ preset,
255
+ customFrom,
256
+ customTo
257
+ ]);
258
+ const { data: hookData, error: hookError, refetch: refetchSummary } = useTranslationHubUsageSummary(basePath, rangeKey);
259
+ useEffect(()=>{
260
+ if (hookError) {
261
+ setError(hookError);
262
+ return;
263
+ }
264
+ setError(null);
265
+ if (hookData) {
266
+ // The hook's shape matches `UsageSummary` exactly (same endpoint),
267
+ // so the assignment is a no-op rename — kept the local
268
+ // `summaryData` state for diff minimality with the rest of the
269
+ // file that reads from it.
270
+ setSummaryData(hookData);
271
+ }
272
+ }, [
273
+ hookData,
274
+ hookError
275
+ ]);
276
+ const summary = summaryData ? {
277
+ runs: summaryData.totals.runs,
278
+ succeeded: summaryData.totals.succeeded,
279
+ // ROUND2-2: surfaced alongside `succeeded`/`failed` so the
280
+ // editor sees the same numbers Overview reports.
281
+ preserved: summaryData.totals.preserved ?? 0,
282
+ failed: summaryData.totals.failed,
283
+ inputTokens: summaryData.totals.inputTokens,
284
+ outputTokens: summaryData.totals.outputTokens,
285
+ cost: summaryData.totals.costUsd,
286
+ avgCost: summaryData.totals.runs > 0 ? summaryData.totals.costUsd / summaryData.totals.runs : 0,
287
+ // ROUND2-2: success rate now treats preserved runs as
288
+ // not-failures (the LLM had nothing new to write — that's a
289
+ // healthy outcome, not a failure). Denominator stays at total
290
+ // runs so the percentage is comparable across windows.
291
+ successRate: summaryData.totals.runs > 0 ? (summaryData.totals.succeeded + (summaryData.totals.preserved ?? 0)) / summaryData.totals.runs * 100 : 0,
292
+ // Reconciliation of locale-rows vs doc-failed (NEW-1) — the
293
+ // endpoint reports both; the UI shows them side-by-side in the
294
+ // top KPI card.
295
+ localeRows: summaryData.byLocale.reduce((sum, l)=>sum + l.runs, 0),
296
+ localeFailed: summaryData.byLocale.reduce((sum, l)=>sum + l.failed, 0)
297
+ } : null;
298
+ const perCollection = summaryData ? [
299
+ ...summaryData.byCollection
300
+ ].sort((a, b)=>compareSurface(a, b, surfaceSort.col, surfaceSort.dir)) : [];
301
+ // Per-model breakdown. The endpoint already collapses provider-only
302
+ // and null-model rows into a single `__unresolved__` bucket (mirrors
303
+ // the old client-side `isRealModelId` rule — NEW-2). We split it
304
+ // back out so the existing "Failed before model selection" row
305
+ // renders unchanged.
306
+ const perModel = summaryData ? (()=>{
307
+ const real = [];
308
+ let unresolved = null;
309
+ for (const b of summaryData.byModel){
310
+ if (b.model === '__unresolved__') {
311
+ unresolved = {
312
+ runs: b.runs,
313
+ tokens: b.tokens,
314
+ costUsd: b.costUsd
315
+ };
316
+ } else {
317
+ real.push(b);
318
+ }
319
+ }
320
+ real.sort((a, b)=>compareModel(a, b, modelSort.col, modelSort.dir));
321
+ return {
322
+ real,
323
+ unresolved
324
+ };
325
+ })() : {
326
+ real: [],
327
+ unresolved: null
328
+ };
329
+ const perLocale = summaryData ? summaryData.byLocale.map((b)=>({
330
+ locale: b.locale,
331
+ runs: b.runs,
332
+ failed: b.failed,
333
+ successRate: b.runs > 0 ? (b.runs - b.failed) / b.runs * 100 : 0
334
+ })).sort((a, b)=>compareLocale(a, b, localeSort.col, localeSort.dir)) : [];
335
+ // Recent failures pulls from the endpoint's `samples` (most-recent 20
336
+ // rows in the window, regardless of status). Filter client-side to
337
+ // failed rows. The "Recent translations" table on Overview still
338
+ // uses `/translation-usage?limit=20` directly — that's a separate
339
+ // surface (UsageTable.tsx).
340
+ const recentFailures = summaryData ? summaryData.samples.filter((r)=>r.status === 'failed').slice(0, 10) : [];
341
+ // With server-side aggregation the totals are always complete; the
342
+ // truncation chip never fires unless the endpoint reports it back.
343
+ const truncated = summaryData?.totals.truncated ?? false;
344
+ const totalDocs = summaryData?.totals.totalMatching ?? 0;
345
+ // NEW-24 (v1.2.6): the four breakdown tables grow when usage data
346
+ // arrives, causing visible "popcorn" CLS on the initial paint. We
347
+ // reserve a min-height on each section so the page settles into its
348
+ // final layout immediately; the loading branches render a low-key
349
+ // shimmer line instead of the empty-state message so the eye knows
350
+ // data is en route.
351
+ const loading = summaryData === null;
352
+ const SECTION_MIN_HEIGHT = '8rem';
353
+ return /*#__PURE__*/ _jsxs("div", {
354
+ style: {
355
+ display: 'flex',
356
+ flexDirection: 'column',
357
+ gap: '1rem'
358
+ },
359
+ children: [
360
+ error && /*#__PURE__*/ _jsxs("div", {
361
+ style: {
362
+ ...SECTION_STYLE,
363
+ background: 'var(--theme-error-100, #fee2e2)',
364
+ color: 'var(--theme-error-500, #b91c1c)',
365
+ fontSize: '0.875rem',
366
+ display: 'flex',
367
+ alignItems: 'center',
368
+ justifyContent: 'space-between',
369
+ gap: '0.75rem'
370
+ },
371
+ children: [
372
+ /*#__PURE__*/ _jsxs("span", {
373
+ children: [
374
+ "Failed to load: ",
375
+ error
376
+ ]
377
+ }),
378
+ /*#__PURE__*/ _jsx("button", {
379
+ onClick: ()=>refetchSummary(),
380
+ style: {
381
+ padding: '0.3rem 0.75rem',
382
+ background: 'transparent',
383
+ border: '1px solid var(--theme-error-500, #b91c1c)',
384
+ borderRadius: '4px',
385
+ color: 'var(--theme-error-500, #b91c1c)',
386
+ fontSize: '0.75rem',
387
+ fontWeight: 500,
388
+ cursor: 'pointer'
389
+ },
390
+ type: "button",
391
+ children: "Retry"
392
+ })
393
+ ]
394
+ }),
395
+ /*#__PURE__*/ _jsx("section", {
396
+ style: SECTION_STYLE,
397
+ children: /*#__PURE__*/ _jsxs("div", {
398
+ style: {
399
+ display: 'flex',
400
+ gap: '0.5rem',
401
+ alignItems: 'center',
402
+ flexWrap: 'wrap'
403
+ },
404
+ children: [
405
+ Object.keys(PRESET_LABEL).map((p)=>{
406
+ const isActive = preset === p;
407
+ const colors = filterPillColors(isActive);
408
+ return /*#__PURE__*/ _jsx("button", {
409
+ "aria-pressed": isActive,
410
+ onClick: ()=>setPreset(p),
411
+ style: {
412
+ padding: '0.35rem 0.75rem',
413
+ fontSize: '0.8rem',
414
+ background: colors.background,
415
+ border: colors.border,
416
+ borderRadius: '4px',
417
+ color: colors.color,
418
+ cursor: 'pointer',
419
+ fontWeight: isActive ? 600 : 400
420
+ },
421
+ type: "button",
422
+ children: PRESET_LABEL[p]
423
+ }, p);
424
+ }),
425
+ preset === 'custom' && /*#__PURE__*/ _jsxs("div", {
426
+ style: {
427
+ display: 'flex',
428
+ gap: '0.5rem',
429
+ alignItems: 'center',
430
+ marginLeft: '0.5rem'
431
+ },
432
+ children: [
433
+ /*#__PURE__*/ _jsxs("label", {
434
+ style: {
435
+ fontSize: '0.8rem',
436
+ color: 'var(--theme-elevation-700)'
437
+ },
438
+ children: [
439
+ "From",
440
+ ' ',
441
+ /*#__PURE__*/ _jsx("input", {
442
+ onChange: (e)=>setCustomFrom(e.target.value),
443
+ style: {
444
+ padding: '0.25rem',
445
+ fontSize: '0.8rem'
446
+ },
447
+ type: "date",
448
+ value: customFrom
449
+ })
450
+ ]
451
+ }),
452
+ /*#__PURE__*/ _jsxs("label", {
453
+ style: {
454
+ fontSize: '0.8rem',
455
+ color: 'var(--theme-elevation-700)'
456
+ },
457
+ children: [
458
+ "To",
459
+ ' ',
460
+ /*#__PURE__*/ _jsx("input", {
461
+ onChange: (e)=>setCustomTo(e.target.value),
462
+ style: {
463
+ padding: '0.25rem',
464
+ fontSize: '0.8rem'
465
+ },
466
+ type: "date",
467
+ value: customTo
468
+ })
469
+ ]
470
+ })
471
+ ]
472
+ }),
473
+ truncated && /*#__PURE__*/ _jsxs("span", {
474
+ style: {
475
+ marginLeft: 'auto',
476
+ fontSize: '0.75rem',
477
+ color: 'var(--theme-warning-500, #d97706)'
478
+ },
479
+ title: `Showing ${summaryData?.totals.runs ?? 0} of ${totalDocs} rows. Narrow the date range for complete totals.`,
480
+ children: [
481
+ "⚠ Showing first ",
482
+ summaryData?.totals.runs ?? 0,
483
+ " of ",
484
+ totalDocs
485
+ ]
486
+ })
487
+ ]
488
+ })
489
+ }),
490
+ /*#__PURE__*/ _jsxs("div", {
491
+ style: {
492
+ display: 'flex',
493
+ gap: '0.75rem',
494
+ flexWrap: 'wrap'
495
+ },
496
+ children: [
497
+ /*#__PURE__*/ _jsxs("div", {
498
+ style: CARD_STYLE,
499
+ children: [
500
+ /*#__PURE__*/ _jsx("div", {
501
+ style: {
502
+ fontSize: '0.7rem',
503
+ color: 'var(--theme-elevation-500)',
504
+ textTransform: 'uppercase',
505
+ fontWeight: 600
506
+ },
507
+ children: "Total spend"
508
+ }),
509
+ /*#__PURE__*/ _jsx("div", {
510
+ style: {
511
+ fontSize: '1.5rem',
512
+ fontWeight: 600,
513
+ marginTop: '0.25rem'
514
+ },
515
+ children: summary ? formatCost(summary.cost) : '—'
516
+ })
517
+ ]
518
+ }),
519
+ /*#__PURE__*/ _jsxs("div", {
520
+ style: CARD_STYLE,
521
+ children: [
522
+ /*#__PURE__*/ _jsx("div", {
523
+ style: {
524
+ fontSize: '0.7rem',
525
+ color: 'var(--theme-elevation-500)',
526
+ textTransform: 'uppercase',
527
+ fontWeight: 600
528
+ },
529
+ children: "Runs"
530
+ }),
531
+ /*#__PURE__*/ _jsx("div", {
532
+ style: {
533
+ fontSize: '1.5rem',
534
+ fontWeight: 600,
535
+ marginTop: '0.25rem'
536
+ },
537
+ children: summary ? fmtNum(summary.runs) : '—'
538
+ }),
539
+ summary && /*#__PURE__*/ _jsxs("div", {
540
+ style: {
541
+ fontSize: '0.7rem',
542
+ color: 'var(--theme-elevation-500)',
543
+ marginTop: '0.15rem'
544
+ },
545
+ title: "Doc-level outcomes: a doc counts as one run regardless of how many target locales it covered. `docs ok` = wrote at least one field; `preserved` = no new fields needed translating; `docs failed` = the run errored. Per-locale failure counts live in the Target locales table below.",
546
+ children: [
547
+ summary.succeeded,
548
+ " docs ok · ",
549
+ summary.failed,
550
+ " docs failed",
551
+ summary.preserved > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
552
+ children: [
553
+ " · ",
554
+ summary.preserved,
555
+ " preserved"
556
+ ]
557
+ }),
558
+ summary.localeFailed > summary.failed && /*#__PURE__*/ _jsxs(_Fragment, {
559
+ children: [
560
+ ' ',
561
+ "·",
562
+ ' ',
563
+ /*#__PURE__*/ _jsxs("span", {
564
+ style: {
565
+ color: 'var(--theme-elevation-700)'
566
+ },
567
+ children: [
568
+ summary.localeFailed,
569
+ " locale-rows failed"
570
+ ]
571
+ })
572
+ ]
573
+ })
574
+ ]
575
+ })
576
+ ]
577
+ }),
578
+ /*#__PURE__*/ _jsxs("div", {
579
+ style: CARD_STYLE,
580
+ children: [
581
+ /*#__PURE__*/ _jsx("div", {
582
+ style: {
583
+ fontSize: '0.7rem',
584
+ color: 'var(--theme-elevation-500)',
585
+ textTransform: 'uppercase',
586
+ fontWeight: 600
587
+ },
588
+ children: "Avg cost / run"
589
+ }),
590
+ /*#__PURE__*/ _jsx("div", {
591
+ style: {
592
+ fontSize: '1.5rem',
593
+ fontWeight: 600,
594
+ marginTop: '0.25rem'
595
+ },
596
+ children: summary ? formatCost(summary.avgCost) : '—'
597
+ })
598
+ ]
599
+ }),
600
+ /*#__PURE__*/ _jsxs("div", {
601
+ style: CARD_STYLE,
602
+ children: [
603
+ /*#__PURE__*/ _jsx("div", {
604
+ style: {
605
+ fontSize: '0.7rem',
606
+ color: 'var(--theme-elevation-500)',
607
+ textTransform: 'uppercase',
608
+ fontWeight: 600
609
+ },
610
+ children: "Tokens"
611
+ }),
612
+ /*#__PURE__*/ _jsx("div", {
613
+ style: {
614
+ fontSize: '1.5rem',
615
+ fontWeight: 600,
616
+ marginTop: '0.25rem'
617
+ },
618
+ children: summary ? fmtNum(summary.inputTokens + summary.outputTokens) : '—'
619
+ }),
620
+ summary && /*#__PURE__*/ _jsxs("div", {
621
+ style: {
622
+ fontSize: '0.7rem',
623
+ color: 'var(--theme-elevation-500)',
624
+ marginTop: '0.15rem'
625
+ },
626
+ children: [
627
+ fmtNum(summary.inputTokens),
628
+ " in · ",
629
+ fmtNum(summary.outputTokens),
630
+ " out"
631
+ ]
632
+ })
633
+ ]
634
+ }),
635
+ /*#__PURE__*/ _jsxs("div", {
636
+ style: CARD_STYLE,
637
+ children: [
638
+ /*#__PURE__*/ _jsx("div", {
639
+ style: {
640
+ fontSize: '0.7rem',
641
+ color: 'var(--theme-elevation-500)',
642
+ textTransform: 'uppercase',
643
+ fontWeight: 600
644
+ },
645
+ children: "Success rate"
646
+ }),
647
+ /*#__PURE__*/ _jsx("div", {
648
+ style: {
649
+ fontSize: '1.5rem',
650
+ fontWeight: 600,
651
+ marginTop: '0.25rem'
652
+ },
653
+ children: summary ? `${summary.successRate.toFixed(1)}%` : '—'
654
+ })
655
+ ]
656
+ })
657
+ ]
658
+ }),
659
+ /*#__PURE__*/ _jsxs("div", {
660
+ style: {
661
+ display: 'flex',
662
+ gap: '0.5rem',
663
+ alignItems: 'center'
664
+ },
665
+ children: [
666
+ /*#__PURE__*/ _jsx("span", {
667
+ style: {
668
+ fontSize: '0.75rem',
669
+ color: 'var(--theme-elevation-500)'
670
+ },
671
+ children: "Sort breakdown tables by:"
672
+ }),
673
+ [
674
+ 'cost',
675
+ 'runs',
676
+ 'tokens'
677
+ ].map((k)=>{
678
+ // The pill is "active" only when all three tables agree on
679
+ // the equivalent column. For Locales, `cost` has no direct
680
+ // analogue so the pill goes inactive whenever Locales is on
681
+ // a non-mapped column.
682
+ const surfaceMatches = surfaceSort.col === k;
683
+ const modelMatches = modelSort.col === k;
684
+ const localeMatches = k === 'runs' && localeSort.col === 'runs' || k === 'cost' && localeSort.col === 'failed' || k === 'tokens' && localeSort.col === 'successRate';
685
+ const isActive = surfaceMatches && modelMatches && localeMatches;
686
+ const colors = filterPillColors(isActive);
687
+ return /*#__PURE__*/ _jsx("button", {
688
+ "aria-pressed": isActive,
689
+ onClick: ()=>{
690
+ setSurfaceSort({
691
+ col: k,
692
+ dir: 'desc'
693
+ });
694
+ setModelSort({
695
+ col: k,
696
+ dir: 'desc'
697
+ });
698
+ if (k === 'runs') setLocaleSort({
699
+ col: 'runs',
700
+ dir: 'desc'
701
+ });
702
+ else if (k === 'cost') setLocaleSort({
703
+ col: 'failed',
704
+ dir: 'desc'
705
+ });
706
+ else setLocaleSort({
707
+ col: 'successRate',
708
+ dir: 'desc'
709
+ });
710
+ },
711
+ style: {
712
+ padding: '0.25rem 0.55rem',
713
+ fontSize: '0.75rem',
714
+ background: colors.background,
715
+ border: colors.border,
716
+ borderRadius: '4px',
717
+ color: colors.color,
718
+ cursor: 'pointer',
719
+ fontWeight: isActive ? 600 : 400
720
+ },
721
+ type: "button",
722
+ children: k
723
+ }, k);
724
+ })
725
+ ]
726
+ }),
727
+ /*#__PURE__*/ _jsxs("section", {
728
+ style: {
729
+ ...SECTION_STYLE,
730
+ minHeight: SECTION_MIN_HEIGHT
731
+ },
732
+ children: [
733
+ /*#__PURE__*/ _jsxs("h3", {
734
+ style: {
735
+ margin: '0 0 0.5rem',
736
+ fontSize: '0.95rem',
737
+ color: 'var(--theme-elevation-1000)'
738
+ },
739
+ children: [
740
+ "Collections & globals (",
741
+ loading ? '…' : totalSurfaces !== null && totalSurfaces >= perCollection.length ? `${perCollection.length} of ${totalSurfaces} active` : perCollection.length,
742
+ ")"
743
+ ]
744
+ }),
745
+ loading ? /*#__PURE__*/ _jsx(SkeletonLines, {}) : perCollection.length === 0 ? /*#__PURE__*/ _jsx("p", {
746
+ style: {
747
+ margin: 0,
748
+ fontSize: '0.85rem',
749
+ color: 'var(--theme-elevation-500)'
750
+ },
751
+ children: "No translation activity in this range."
752
+ }) : /*#__PURE__*/ _jsx("div", {
753
+ style: {
754
+ overflowX: 'auto'
755
+ },
756
+ children: /*#__PURE__*/ _jsxs("table", {
757
+ style: {
758
+ width: '100%',
759
+ borderCollapse: 'collapse',
760
+ fontSize: '0.85rem'
761
+ },
762
+ children: [
763
+ /*#__PURE__*/ _jsx("thead", {
764
+ children: /*#__PURE__*/ _jsxs("tr", {
765
+ children: [
766
+ /*#__PURE__*/ _jsxs("th", {
767
+ "aria-sort": ariaSortFor(surfaceSort.col, 'surface', surfaceSort.dir),
768
+ onClick: ()=>setSurfaceSort((s)=>({
769
+ col: 'surface',
770
+ dir: nextDir(s.col, 'surface', s.dir)
771
+ })),
772
+ style: SORTABLE_TH_STYLE,
773
+ children: [
774
+ "Surface",
775
+ sortIndicator(surfaceSort.col, 'surface', surfaceSort.dir)
776
+ ]
777
+ }),
778
+ /*#__PURE__*/ _jsxs("th", {
779
+ "aria-sort": ariaSortFor(surfaceSort.col, 'runs', surfaceSort.dir),
780
+ onClick: ()=>setSurfaceSort((s)=>({
781
+ col: 'runs',
782
+ dir: nextDir(s.col, 'runs', s.dir)
783
+ })),
784
+ style: {
785
+ ...SORTABLE_TH_STYLE,
786
+ textAlign: 'right'
787
+ },
788
+ children: [
789
+ "Runs",
790
+ sortIndicator(surfaceSort.col, 'runs', surfaceSort.dir)
791
+ ]
792
+ }),
793
+ /*#__PURE__*/ _jsxs("th", {
794
+ "aria-sort": ariaSortFor(surfaceSort.col, 'tokens', surfaceSort.dir),
795
+ onClick: ()=>setSurfaceSort((s)=>({
796
+ col: 'tokens',
797
+ dir: nextDir(s.col, 'tokens', s.dir)
798
+ })),
799
+ style: {
800
+ ...SORTABLE_TH_STYLE,
801
+ textAlign: 'right'
802
+ },
803
+ children: [
804
+ "Tokens",
805
+ sortIndicator(surfaceSort.col, 'tokens', surfaceSort.dir)
806
+ ]
807
+ }),
808
+ /*#__PURE__*/ _jsxs("th", {
809
+ "aria-sort": ariaSortFor(surfaceSort.col, 'cost', surfaceSort.dir),
810
+ onClick: ()=>setSurfaceSort((s)=>({
811
+ col: 'cost',
812
+ dir: nextDir(s.col, 'cost', s.dir)
813
+ })),
814
+ style: {
815
+ ...SORTABLE_TH_STYLE,
816
+ textAlign: 'right'
817
+ },
818
+ children: [
819
+ "Total cost",
820
+ sortIndicator(surfaceSort.col, 'cost', surfaceSort.dir)
821
+ ]
822
+ }),
823
+ /*#__PURE__*/ _jsxs("th", {
824
+ "aria-sort": ariaSortFor(surfaceSort.col, 'avg', surfaceSort.dir),
825
+ onClick: ()=>setSurfaceSort((s)=>({
826
+ col: 'avg',
827
+ dir: nextDir(s.col, 'avg', s.dir)
828
+ })),
829
+ style: {
830
+ ...SORTABLE_TH_STYLE,
831
+ textAlign: 'right'
832
+ },
833
+ children: [
834
+ "Avg / run",
835
+ sortIndicator(surfaceSort.col, 'avg', surfaceSort.dir)
836
+ ]
837
+ })
838
+ ]
839
+ })
840
+ }),
841
+ /*#__PURE__*/ _jsx("tbody", {
842
+ children: perCollection.map((b)=>/*#__PURE__*/ _jsxs("tr", {
843
+ children: [
844
+ /*#__PURE__*/ _jsxs("td", {
845
+ style: TD_STYLE,
846
+ children: [
847
+ /*#__PURE__*/ _jsx("code", {
848
+ children: b.slug
849
+ }),
850
+ /*#__PURE__*/ _jsx("span", {
851
+ style: {
852
+ marginLeft: '0.4rem',
853
+ color: 'var(--theme-elevation-500)',
854
+ fontSize: '0.7rem'
855
+ },
856
+ children: b.kind
857
+ })
858
+ ]
859
+ }),
860
+ /*#__PURE__*/ _jsx("td", {
861
+ style: {
862
+ ...TD_STYLE,
863
+ textAlign: 'right'
864
+ },
865
+ children: fmtNum(b.runs)
866
+ }),
867
+ /*#__PURE__*/ _jsx("td", {
868
+ style: {
869
+ ...TD_STYLE,
870
+ textAlign: 'right'
871
+ },
872
+ children: fmtNum(b.tokens)
873
+ }),
874
+ /*#__PURE__*/ _jsx("td", {
875
+ style: {
876
+ ...TD_STYLE,
877
+ textAlign: 'right'
878
+ },
879
+ children: formatCost(b.costUsd)
880
+ }),
881
+ /*#__PURE__*/ _jsx("td", {
882
+ style: {
883
+ ...TD_STYLE,
884
+ textAlign: 'right',
885
+ color: 'var(--theme-elevation-700)'
886
+ },
887
+ children: formatCost(b.runs > 0 ? b.costUsd / b.runs : 0)
888
+ })
889
+ ]
890
+ }, `${b.kind}:${b.slug}`))
891
+ })
892
+ ]
893
+ })
894
+ })
895
+ ]
896
+ }),
897
+ /*#__PURE__*/ _jsxs("section", {
898
+ style: {
899
+ ...SECTION_STYLE,
900
+ minHeight: SECTION_MIN_HEIGHT
901
+ },
902
+ children: [
903
+ /*#__PURE__*/ _jsxs("h3", {
904
+ style: {
905
+ margin: '0 0 0.5rem',
906
+ fontSize: '0.95rem',
907
+ color: 'var(--theme-elevation-1000)'
908
+ },
909
+ children: [
910
+ "Models (",
911
+ loading ? '…' : perModel.real.length,
912
+ ")"
913
+ ]
914
+ }),
915
+ loading ? /*#__PURE__*/ _jsx(SkeletonLines, {}) : perModel.real.length === 0 && perModel.unresolved === null ? /*#__PURE__*/ _jsx("p", {
916
+ style: {
917
+ margin: 0,
918
+ fontSize: '0.85rem',
919
+ color: 'var(--theme-elevation-500)'
920
+ },
921
+ children: "No model usage in this range."
922
+ }) : /*#__PURE__*/ _jsx("div", {
923
+ style: {
924
+ overflowX: 'auto'
925
+ },
926
+ children: /*#__PURE__*/ _jsxs("table", {
927
+ style: {
928
+ width: '100%',
929
+ borderCollapse: 'collapse',
930
+ fontSize: '0.85rem'
931
+ },
932
+ children: [
933
+ /*#__PURE__*/ _jsx("thead", {
934
+ children: /*#__PURE__*/ _jsxs("tr", {
935
+ children: [
936
+ /*#__PURE__*/ _jsxs("th", {
937
+ "aria-sort": ariaSortFor(modelSort.col, 'model', modelSort.dir),
938
+ onClick: ()=>setModelSort((s)=>({
939
+ col: 'model',
940
+ dir: nextDir(s.col, 'model', s.dir)
941
+ })),
942
+ style: SORTABLE_TH_STYLE,
943
+ children: [
944
+ "Model",
945
+ sortIndicator(modelSort.col, 'model', modelSort.dir)
946
+ ]
947
+ }),
948
+ /*#__PURE__*/ _jsxs("th", {
949
+ "aria-sort": ariaSortFor(modelSort.col, 'runs', modelSort.dir),
950
+ onClick: ()=>setModelSort((s)=>({
951
+ col: 'runs',
952
+ dir: nextDir(s.col, 'runs', s.dir)
953
+ })),
954
+ style: {
955
+ ...SORTABLE_TH_STYLE,
956
+ textAlign: 'right'
957
+ },
958
+ children: [
959
+ "Runs",
960
+ sortIndicator(modelSort.col, 'runs', modelSort.dir)
961
+ ]
962
+ }),
963
+ /*#__PURE__*/ _jsxs("th", {
964
+ "aria-sort": ariaSortFor(modelSort.col, 'tokens', modelSort.dir),
965
+ onClick: ()=>setModelSort((s)=>({
966
+ col: 'tokens',
967
+ dir: nextDir(s.col, 'tokens', s.dir)
968
+ })),
969
+ style: {
970
+ ...SORTABLE_TH_STYLE,
971
+ textAlign: 'right'
972
+ },
973
+ children: [
974
+ "Tokens",
975
+ sortIndicator(modelSort.col, 'tokens', modelSort.dir)
976
+ ]
977
+ }),
978
+ /*#__PURE__*/ _jsxs("th", {
979
+ "aria-sort": ariaSortFor(modelSort.col, 'cost', modelSort.dir),
980
+ onClick: ()=>setModelSort((s)=>({
981
+ col: 'cost',
982
+ dir: nextDir(s.col, 'cost', s.dir)
983
+ })),
984
+ style: {
985
+ ...SORTABLE_TH_STYLE,
986
+ textAlign: 'right'
987
+ },
988
+ children: [
989
+ "Cost",
990
+ sortIndicator(modelSort.col, 'cost', modelSort.dir)
991
+ ]
992
+ })
993
+ ]
994
+ })
995
+ }),
996
+ /*#__PURE__*/ _jsxs("tbody", {
997
+ children: [
998
+ perModel.real.map((b)=>/*#__PURE__*/ _jsxs("tr", {
999
+ children: [
1000
+ /*#__PURE__*/ _jsx("td", {
1001
+ style: {
1002
+ ...TD_STYLE,
1003
+ fontFamily: 'monospace',
1004
+ fontSize: '0.75rem'
1005
+ },
1006
+ children: b.model
1007
+ }),
1008
+ /*#__PURE__*/ _jsx("td", {
1009
+ style: {
1010
+ ...TD_STYLE,
1011
+ textAlign: 'right'
1012
+ },
1013
+ children: fmtNum(b.runs)
1014
+ }),
1015
+ /*#__PURE__*/ _jsx("td", {
1016
+ style: {
1017
+ ...TD_STYLE,
1018
+ textAlign: 'right'
1019
+ },
1020
+ children: fmtNum(b.tokens)
1021
+ }),
1022
+ /*#__PURE__*/ _jsx("td", {
1023
+ style: {
1024
+ ...TD_STYLE,
1025
+ textAlign: 'right'
1026
+ },
1027
+ children: formatCost(b.costUsd)
1028
+ })
1029
+ ]
1030
+ }, b.model)),
1031
+ perModel.unresolved && /*#__PURE__*/ _jsxs("tr", {
1032
+ children: [
1033
+ /*#__PURE__*/ _jsx("td", {
1034
+ style: {
1035
+ ...TD_STYLE,
1036
+ fontSize: '0.75rem',
1037
+ color: 'var(--theme-elevation-600)',
1038
+ fontStyle: 'italic'
1039
+ },
1040
+ title: "These runs failed before a specific model handled them (auth error, missing config, provider rejected the request). They consumed no tokens.",
1041
+ children: "Failed before model selection"
1042
+ }),
1043
+ /*#__PURE__*/ _jsx("td", {
1044
+ style: {
1045
+ ...TD_STYLE,
1046
+ textAlign: 'right',
1047
+ color: 'var(--theme-elevation-600)'
1048
+ },
1049
+ children: fmtNum(perModel.unresolved.runs)
1050
+ }),
1051
+ /*#__PURE__*/ _jsx("td", {
1052
+ style: {
1053
+ ...TD_STYLE,
1054
+ textAlign: 'right',
1055
+ color: 'var(--theme-elevation-500)'
1056
+ },
1057
+ children: "—"
1058
+ }),
1059
+ /*#__PURE__*/ _jsx("td", {
1060
+ style: {
1061
+ ...TD_STYLE,
1062
+ textAlign: 'right',
1063
+ color: 'var(--theme-elevation-500)'
1064
+ },
1065
+ children: "—"
1066
+ })
1067
+ ]
1068
+ })
1069
+ ]
1070
+ })
1071
+ ]
1072
+ })
1073
+ })
1074
+ ]
1075
+ }),
1076
+ /*#__PURE__*/ _jsxs("section", {
1077
+ style: {
1078
+ ...SECTION_STYLE,
1079
+ minHeight: SECTION_MIN_HEIGHT
1080
+ },
1081
+ children: [
1082
+ /*#__PURE__*/ _jsxs("h3", {
1083
+ style: {
1084
+ margin: '0 0 0.5rem',
1085
+ fontSize: '0.95rem',
1086
+ color: 'var(--theme-elevation-1000)'
1087
+ },
1088
+ children: [
1089
+ "Target locales (",
1090
+ loading ? '…' : perLocale.length,
1091
+ ")"
1092
+ ]
1093
+ }),
1094
+ /*#__PURE__*/ _jsx("p", {
1095
+ style: {
1096
+ margin: '0 0 0.5rem',
1097
+ fontSize: '0.75rem',
1098
+ color: 'var(--theme-elevation-500)'
1099
+ },
1100
+ children: "Counts here are per locale-row (one row per target locale on a run). A run covering 2 locales contributes 2 rows; that's why locale-failed totals can exceed the doc-level “failed” count above."
1101
+ }),
1102
+ loading ? /*#__PURE__*/ _jsx(SkeletonLines, {}) : perLocale.length === 0 ? /*#__PURE__*/ _jsx("p", {
1103
+ style: {
1104
+ margin: 0,
1105
+ fontSize: '0.85rem',
1106
+ color: 'var(--theme-elevation-500)'
1107
+ },
1108
+ children: "No locale activity in this range."
1109
+ }) : /*#__PURE__*/ _jsx("div", {
1110
+ style: {
1111
+ overflowX: 'auto'
1112
+ },
1113
+ children: /*#__PURE__*/ _jsxs("table", {
1114
+ style: {
1115
+ width: '100%',
1116
+ borderCollapse: 'collapse',
1117
+ fontSize: '0.85rem'
1118
+ },
1119
+ children: [
1120
+ /*#__PURE__*/ _jsx("thead", {
1121
+ children: /*#__PURE__*/ _jsxs("tr", {
1122
+ children: [
1123
+ /*#__PURE__*/ _jsxs("th", {
1124
+ "aria-sort": ariaSortFor(localeSort.col, 'locale', localeSort.dir),
1125
+ onClick: ()=>setLocaleSort((s)=>({
1126
+ col: 'locale',
1127
+ dir: nextDir(s.col, 'locale', s.dir)
1128
+ })),
1129
+ style: SORTABLE_TH_STYLE,
1130
+ children: [
1131
+ "Locale",
1132
+ sortIndicator(localeSort.col, 'locale', localeSort.dir)
1133
+ ]
1134
+ }),
1135
+ /*#__PURE__*/ _jsxs("th", {
1136
+ "aria-sort": ariaSortFor(localeSort.col, 'runs', localeSort.dir),
1137
+ onClick: ()=>setLocaleSort((s)=>({
1138
+ col: 'runs',
1139
+ dir: nextDir(s.col, 'runs', s.dir)
1140
+ })),
1141
+ style: {
1142
+ ...SORTABLE_TH_STYLE,
1143
+ textAlign: 'right'
1144
+ },
1145
+ children: [
1146
+ "Runs",
1147
+ sortIndicator(localeSort.col, 'runs', localeSort.dir)
1148
+ ]
1149
+ }),
1150
+ /*#__PURE__*/ _jsxs("th", {
1151
+ "aria-sort": ariaSortFor(localeSort.col, 'failed', localeSort.dir),
1152
+ onClick: ()=>setLocaleSort((s)=>({
1153
+ col: 'failed',
1154
+ dir: nextDir(s.col, 'failed', s.dir)
1155
+ })),
1156
+ style: {
1157
+ ...SORTABLE_TH_STYLE,
1158
+ textAlign: 'right'
1159
+ },
1160
+ children: [
1161
+ "Failed",
1162
+ sortIndicator(localeSort.col, 'failed', localeSort.dir)
1163
+ ]
1164
+ }),
1165
+ /*#__PURE__*/ _jsxs("th", {
1166
+ "aria-sort": ariaSortFor(localeSort.col, 'successRate', localeSort.dir),
1167
+ onClick: ()=>setLocaleSort((s)=>({
1168
+ col: 'successRate',
1169
+ dir: nextDir(s.col, 'successRate', s.dir)
1170
+ })),
1171
+ style: {
1172
+ ...SORTABLE_TH_STYLE,
1173
+ textAlign: 'right'
1174
+ },
1175
+ children: [
1176
+ "Success rate",
1177
+ sortIndicator(localeSort.col, 'successRate', localeSort.dir)
1178
+ ]
1179
+ })
1180
+ ]
1181
+ })
1182
+ }),
1183
+ /*#__PURE__*/ _jsx("tbody", {
1184
+ children: perLocale.map((b)=>/*#__PURE__*/ _jsxs("tr", {
1185
+ children: [
1186
+ /*#__PURE__*/ _jsx("td", {
1187
+ style: {
1188
+ ...TD_STYLE,
1189
+ fontFamily: 'monospace'
1190
+ },
1191
+ children: b.locale
1192
+ }),
1193
+ /*#__PURE__*/ _jsx("td", {
1194
+ style: {
1195
+ ...TD_STYLE,
1196
+ textAlign: 'right'
1197
+ },
1198
+ children: fmtNum(b.runs)
1199
+ }),
1200
+ /*#__PURE__*/ _jsx("td", {
1201
+ style: {
1202
+ ...TD_STYLE,
1203
+ textAlign: 'right',
1204
+ color: b.failed > 0 ? 'var(--theme-error-500)' : 'var(--theme-elevation-700)'
1205
+ },
1206
+ children: b.failed
1207
+ }),
1208
+ /*#__PURE__*/ _jsxs("td", {
1209
+ style: {
1210
+ ...TD_STYLE,
1211
+ textAlign: 'right'
1212
+ },
1213
+ children: [
1214
+ b.successRate.toFixed(1),
1215
+ "%"
1216
+ ]
1217
+ })
1218
+ ]
1219
+ }, b.locale))
1220
+ })
1221
+ ]
1222
+ })
1223
+ })
1224
+ ]
1225
+ }),
1226
+ /*#__PURE__*/ _jsxs("section", {
1227
+ style: {
1228
+ ...SECTION_STYLE,
1229
+ minHeight: SECTION_MIN_HEIGHT
1230
+ },
1231
+ children: [
1232
+ /*#__PURE__*/ _jsxs("h3", {
1233
+ style: {
1234
+ margin: '0 0 0.5rem',
1235
+ fontSize: '0.95rem',
1236
+ color: 'var(--theme-elevation-1000)'
1237
+ },
1238
+ children: [
1239
+ "Recent failures (",
1240
+ loading ? '…' : recentFailures.length,
1241
+ ")"
1242
+ ]
1243
+ }),
1244
+ loading ? /*#__PURE__*/ _jsx(SkeletonLines, {}) : recentFailures.length === 0 ? /*#__PURE__*/ _jsx("p", {
1245
+ style: {
1246
+ margin: 0,
1247
+ fontSize: '0.85rem',
1248
+ color: 'var(--theme-elevation-500)'
1249
+ },
1250
+ children: "No failures in this range. Nice."
1251
+ }) : /*#__PURE__*/ _jsx("div", {
1252
+ style: {
1253
+ overflowX: 'auto'
1254
+ },
1255
+ children: /*#__PURE__*/ _jsxs("table", {
1256
+ style: {
1257
+ width: '100%',
1258
+ borderCollapse: 'collapse',
1259
+ fontSize: '0.85rem'
1260
+ },
1261
+ children: [
1262
+ /*#__PURE__*/ _jsx("thead", {
1263
+ children: /*#__PURE__*/ _jsxs("tr", {
1264
+ children: [
1265
+ /*#__PURE__*/ _jsx("th", {
1266
+ style: TH_STYLE,
1267
+ children: "Doc"
1268
+ }),
1269
+ /*#__PURE__*/ _jsx("th", {
1270
+ style: TH_STYLE,
1271
+ children: "Model"
1272
+ }),
1273
+ /*#__PURE__*/ _jsx("th", {
1274
+ style: TH_STYLE,
1275
+ children: "Error"
1276
+ }),
1277
+ /*#__PURE__*/ _jsx("th", {
1278
+ style: {
1279
+ ...TH_STYLE,
1280
+ textAlign: 'right'
1281
+ },
1282
+ children: "When"
1283
+ })
1284
+ ]
1285
+ })
1286
+ }),
1287
+ /*#__PURE__*/ _jsx("tbody", {
1288
+ children: recentFailures.map((r)=>{
1289
+ const failedLocale = failedLocaleFor(r);
1290
+ const href = docHref(basePath, r, failedLocale);
1291
+ return /*#__PURE__*/ _jsxs("tr", {
1292
+ children: [
1293
+ /*#__PURE__*/ _jsx("td", {
1294
+ style: TD_STYLE,
1295
+ children: href ? /*#__PURE__*/ _jsxs("a", {
1296
+ href: href,
1297
+ rel: "noopener noreferrer",
1298
+ style: {
1299
+ color: 'var(--theme-success-500)',
1300
+ textDecoration: 'none'
1301
+ },
1302
+ target: "_blank",
1303
+ children: [
1304
+ /*#__PURE__*/ _jsx("code", {
1305
+ children: r.slug
1306
+ }),
1307
+ r.documentId && r.kind === 'collection' && /*#__PURE__*/ _jsxs("span", {
1308
+ style: {
1309
+ color: 'var(--theme-elevation-500)'
1310
+ },
1311
+ children: [
1312
+ ' ',
1313
+ "#",
1314
+ r.documentId
1315
+ ]
1316
+ })
1317
+ ]
1318
+ }) : /*#__PURE__*/ _jsx("code", {
1319
+ children: r.slug
1320
+ })
1321
+ }),
1322
+ /*#__PURE__*/ _jsx("td", {
1323
+ style: {
1324
+ ...TD_STYLE,
1325
+ fontFamily: 'monospace',
1326
+ fontSize: '0.75rem',
1327
+ color: 'var(--theme-elevation-700)'
1328
+ },
1329
+ children: /*#__PURE__*/ _jsx(ModelCell, {
1330
+ model: r.model
1331
+ })
1332
+ }),
1333
+ /*#__PURE__*/ _jsx("td", {
1334
+ style: {
1335
+ ...TD_STYLE,
1336
+ color: 'var(--theme-error-500)',
1337
+ fontSize: '0.75rem',
1338
+ maxWidth: '400px',
1339
+ overflow: 'hidden',
1340
+ textOverflow: 'ellipsis',
1341
+ whiteSpace: 'nowrap'
1342
+ },
1343
+ title: r.error ?? undefined,
1344
+ children: r.error ?? '—'
1345
+ }),
1346
+ /*#__PURE__*/ _jsx("td", {
1347
+ style: {
1348
+ ...TD_STYLE,
1349
+ textAlign: 'right',
1350
+ color: 'var(--theme-elevation-500)',
1351
+ whiteSpace: 'nowrap'
1352
+ },
1353
+ children: relTime(r.createdAt)
1354
+ })
1355
+ ]
1356
+ }, r.id);
1357
+ })
1358
+ })
1359
+ ]
1360
+ })
1361
+ })
1362
+ ]
1363
+ })
1364
+ ]
1365
+ });
1366
+ };
1367
+ export default AuditPanel;