@proofhound/web-ui 0.1.6

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 (483) hide show
  1. package/LICENSE +190 -0
  2. package/dist/components/annotations/annotation-claim-dialog.d.ts +13 -0
  3. package/dist/components/annotations/annotation-claim-dialog.d.ts.map +1 -0
  4. package/dist/components/annotations/annotation-claim-dialog.js +60 -0
  5. package/dist/components/annotations/annotation-claim-dialog.js.map +1 -0
  6. package/dist/components/charts/source-legend.d.ts +10 -0
  7. package/dist/components/charts/source-legend.d.ts.map +1 -0
  8. package/dist/components/charts/source-legend.js +15 -0
  9. package/dist/components/charts/source-legend.js.map +1 -0
  10. package/dist/components/charts/source-stacked-bar.d.ts +22 -0
  11. package/dist/components/charts/source-stacked-bar.d.ts.map +1 -0
  12. package/dist/components/charts/source-stacked-bar.js +43 -0
  13. package/dist/components/charts/source-stacked-bar.js.map +1 -0
  14. package/dist/components/index.d.ts +15 -0
  15. package/dist/components/index.d.ts.map +1 -0
  16. package/dist/components/index.js +17 -0
  17. package/dist/components/index.js.map +1 -0
  18. package/dist/components/json-object-textarea.d.ts +11 -0
  19. package/dist/components/json-object-textarea.d.ts.map +1 -0
  20. package/dist/components/json-object-textarea.js +82 -0
  21. package/dist/components/json-object-textarea.js.map +1 -0
  22. package/dist/components/model-context-window-input.d.ts +15 -0
  23. package/dist/components/model-context-window-input.d.ts.map +1 -0
  24. package/dist/components/model-context-window-input.js +57 -0
  25. package/dist/components/model-context-window-input.js.map +1 -0
  26. package/dist/components/model-probe-status.d.ts +11 -0
  27. package/dist/components/model-probe-status.d.ts.map +1 -0
  28. package/dist/components/model-probe-status.js +34 -0
  29. package/dist/components/model-probe-status.js.map +1 -0
  30. package/dist/components/prompt-diff/prompt-diff-split-view.d.ts +28 -0
  31. package/dist/components/prompt-diff/prompt-diff-split-view.d.ts.map +1 -0
  32. package/dist/components/prompt-diff/prompt-diff-split-view.js +101 -0
  33. package/dist/components/prompt-diff/prompt-diff-split-view.js.map +1 -0
  34. package/dist/components/prompt-language-select.d.ts +13 -0
  35. package/dist/components/prompt-language-select.d.ts.map +1 -0
  36. package/dist/components/prompt-language-select.js +14 -0
  37. package/dist/components/prompt-language-select.js.map +1 -0
  38. package/dist/components/prompt-version-picker-row.d.ts +27 -0
  39. package/dist/components/prompt-version-picker-row.d.ts.map +1 -0
  40. package/dist/components/prompt-version-picker-row.js +50 -0
  41. package/dist/components/prompt-version-picker-row.js.map +1 -0
  42. package/dist/components/prompt-version-status-badge.d.ts +8 -0
  43. package/dist/components/prompt-version-status-badge.d.ts.map +1 -0
  44. package/dist/components/prompt-version-status-badge.js +31 -0
  45. package/dist/components/prompt-version-status-badge.js.map +1 -0
  46. package/dist/components/quick-fill/quick-fill-picker.d.ts +42 -0
  47. package/dist/components/quick-fill/quick-fill-picker.d.ts.map +1 -0
  48. package/dist/components/quick-fill/quick-fill-picker.js +46 -0
  49. package/dist/components/quick-fill/quick-fill-picker.js.map +1 -0
  50. package/dist/components/time-zone-preference-menu.d.ts +10 -0
  51. package/dist/components/time-zone-preference-menu.d.ts.map +1 -0
  52. package/dist/components/time-zone-preference-menu.js +33 -0
  53. package/dist/components/time-zone-preference-menu.js.map +1 -0
  54. package/dist/contracts/index.d.ts +13 -0
  55. package/dist/contracts/index.d.ts.map +1 -0
  56. package/dist/contracts/index.js +7 -0
  57. package/dist/contracts/index.js.map +1 -0
  58. package/dist/features/index.d.ts +3 -0
  59. package/dist/features/index.d.ts.map +1 -0
  60. package/dist/features/index.js +4 -0
  61. package/dist/features/index.js.map +1 -0
  62. package/dist/features/model-quick-fill/model-preset-draft.d.ts +19 -0
  63. package/dist/features/model-quick-fill/model-preset-draft.d.ts.map +1 -0
  64. package/dist/features/model-quick-fill/model-preset-draft.js +22 -0
  65. package/dist/features/model-quick-fill/model-preset-draft.js.map +1 -0
  66. package/dist/features/model-quick-fill/model-preset-quick-fill.d.ts +7 -0
  67. package/dist/features/model-quick-fill/model-preset-quick-fill.d.ts.map +1 -0
  68. package/dist/features/model-quick-fill/model-preset-quick-fill.js +73 -0
  69. package/dist/features/model-quick-fill/model-preset-quick-fill.js.map +1 -0
  70. package/dist/hooks/annotation.d.ts +253 -0
  71. package/dist/hooks/annotation.d.ts.map +1 -0
  72. package/dist/hooks/annotation.js +79 -0
  73. package/dist/hooks/annotation.js.map +1 -0
  74. package/dist/hooks/canary-release.d.ts +674 -0
  75. package/dist/hooks/canary-release.d.ts.map +1 -0
  76. package/dist/hooks/canary-release.js +142 -0
  77. package/dist/hooks/canary-release.js.map +1 -0
  78. package/dist/hooks/connector.d.ts +585 -0
  79. package/dist/hooks/connector.d.ts.map +1 -0
  80. package/dist/hooks/connector.js +157 -0
  81. package/dist/hooks/connector.js.map +1 -0
  82. package/dist/hooks/dataset.d.ts +138 -0
  83. package/dist/hooks/dataset.d.ts.map +1 -0
  84. package/dist/hooks/dataset.js +84 -0
  85. package/dist/hooks/dataset.js.map +1 -0
  86. package/dist/hooks/experiment.d.ts +376 -0
  87. package/dist/hooks/experiment.d.ts.map +1 -0
  88. package/dist/hooks/experiment.js +58 -0
  89. package/dist/hooks/experiment.js.map +1 -0
  90. package/dist/hooks/index.d.ts +18 -0
  91. package/dist/hooks/index.d.ts.map +1 -0
  92. package/dist/hooks/index.js +18 -0
  93. package/dist/hooks/index.js.map +1 -0
  94. package/dist/hooks/model.d.ts +327 -0
  95. package/dist/hooks/model.d.ts.map +1 -0
  96. package/dist/hooks/model.js +103 -0
  97. package/dist/hooks/model.js.map +1 -0
  98. package/dist/hooks/optimization.d.ts +557 -0
  99. package/dist/hooks/optimization.d.ts.map +1 -0
  100. package/dist/hooks/optimization.js +94 -0
  101. package/dist/hooks/optimization.js.map +1 -0
  102. package/dist/hooks/production-release.d.ts +141 -0
  103. package/dist/hooks/production-release.d.ts.map +1 -0
  104. package/dist/hooks/production-release.js +51 -0
  105. package/dist/hooks/production-release.js.map +1 -0
  106. package/dist/hooks/project-monitoring.d.ts +199 -0
  107. package/dist/hooks/project-monitoring.d.ts.map +1 -0
  108. package/dist/hooks/project-monitoring.js +55 -0
  109. package/dist/hooks/project-monitoring.js.map +1 -0
  110. package/dist/hooks/prompt.d.ts +509 -0
  111. package/dist/hooks/prompt.d.ts.map +1 -0
  112. package/dist/hooks/prompt.js +127 -0
  113. package/dist/hooks/prompt.js.map +1 -0
  114. package/dist/hooks/quick-start.d.ts +219 -0
  115. package/dist/hooks/quick-start.d.ts.map +1 -0
  116. package/dist/hooks/quick-start.js +35 -0
  117. package/dist/hooks/quick-start.js.map +1 -0
  118. package/dist/hooks/release-line.d.ts +819 -0
  119. package/dist/hooks/release-line.d.ts.map +1 -0
  120. package/dist/hooks/release-line.js +52 -0
  121. package/dist/hooks/release-line.js.map +1 -0
  122. package/dist/hooks/run-result.d.ts +118 -0
  123. package/dist/hooks/run-result.d.ts.map +1 -0
  124. package/dist/hooks/run-result.js +56 -0
  125. package/dist/hooks/run-result.js.map +1 -0
  126. package/dist/hooks/token.d.ts +58 -0
  127. package/dist/hooks/token.d.ts.map +1 -0
  128. package/dist/hooks/token.js +39 -0
  129. package/dist/hooks/token.js.map +1 -0
  130. package/dist/hooks/use-auto-refresh.d.ts +30 -0
  131. package/dist/hooks/use-auto-refresh.d.ts.map +1 -0
  132. package/dist/hooks/use-auto-refresh.js +175 -0
  133. package/dist/hooks/use-auto-refresh.js.map +1 -0
  134. package/dist/hooks/use-date-time-formatter.d.ts +11 -0
  135. package/dist/hooks/use-date-time-formatter.d.ts.map +1 -0
  136. package/dist/hooks/use-date-time-formatter.js +24 -0
  137. package/dist/hooks/use-date-time-formatter.js.map +1 -0
  138. package/dist/hooks/use-delayed-loading.d.ts +25 -0
  139. package/dist/hooks/use-delayed-loading.d.ts.map +1 -0
  140. package/dist/hooks/use-delayed-loading.js +95 -0
  141. package/dist/hooks/use-delayed-loading.js.map +1 -0
  142. package/dist/i18n/index.d.ts +5678 -0
  143. package/dist/i18n/index.d.ts.map +1 -0
  144. package/dist/i18n/index.js +5712 -0
  145. package/dist/i18n/index.js.map +1 -0
  146. package/dist/i18n/language.d.ts +17 -0
  147. package/dist/i18n/language.d.ts.map +1 -0
  148. package/dist/i18n/language.js +43 -0
  149. package/dist/i18n/language.js.map +1 -0
  150. package/dist/index.d.ts +2 -0
  151. package/dist/index.d.ts.map +1 -0
  152. package/dist/index.js +2 -0
  153. package/dist/index.js.map +1 -0
  154. package/dist/lib/api-error.d.ts +2 -0
  155. package/dist/lib/api-error.d.ts.map +1 -0
  156. package/dist/lib/api-error.js +38 -0
  157. package/dist/lib/api-error.js.map +1 -0
  158. package/dist/lib/format.d.ts +30 -0
  159. package/dist/lib/format.d.ts.map +1 -0
  160. package/dist/lib/format.js +157 -0
  161. package/dist/lib/format.js.map +1 -0
  162. package/dist/lib/index.d.ts +8 -0
  163. package/dist/lib/index.d.ts.map +1 -0
  164. package/dist/lib/index.js +8 -0
  165. package/dist/lib/index.js.map +1 -0
  166. package/dist/lib/model-number.d.ts +2 -0
  167. package/dist/lib/model-number.d.ts.map +1 -0
  168. package/dist/lib/model-number.js +18 -0
  169. package/dist/lib/model-number.js.map +1 -0
  170. package/dist/lib/model-provider-type.d.ts +7 -0
  171. package/dist/lib/model-provider-type.d.ts.map +1 -0
  172. package/dist/lib/model-provider-type.js +28 -0
  173. package/dist/lib/model-provider-type.js.map +1 -0
  174. package/dist/lib/project-name.d.ts +7 -0
  175. package/dist/lib/project-name.d.ts.map +1 -0
  176. package/dist/lib/project-name.js +14 -0
  177. package/dist/lib/project-name.js.map +1 -0
  178. package/dist/lib/releases/release-line-model.d.ts +55 -0
  179. package/dist/lib/releases/release-line-model.d.ts.map +1 -0
  180. package/dist/lib/releases/release-line-model.js +476 -0
  181. package/dist/lib/releases/release-line-model.js.map +1 -0
  182. package/dist/lib/time-zone.d.ts +14 -0
  183. package/dist/lib/time-zone.d.ts.map +1 -0
  184. package/dist/lib/time-zone.js +118 -0
  185. package/dist/lib/time-zone.js.map +1 -0
  186. package/dist/lib/uuid.d.ts +2 -0
  187. package/dist/lib/uuid.d.ts.map +1 -0
  188. package/dist/lib/uuid.js +5 -0
  189. package/dist/lib/uuid.js.map +1 -0
  190. package/dist/providers/display-preferences-provider.d.ts +18 -0
  191. package/dist/providers/display-preferences-provider.d.ts.map +1 -0
  192. package/dist/providers/display-preferences-provider.js +46 -0
  193. package/dist/providers/display-preferences-provider.js.map +1 -0
  194. package/dist/providers/index.d.ts +5 -0
  195. package/dist/providers/index.d.ts.map +1 -0
  196. package/dist/providers/index.js +5 -0
  197. package/dist/providers/index.js.map +1 -0
  198. package/dist/providers/project-context-provider.d.ts +8 -0
  199. package/dist/providers/project-context-provider.d.ts.map +1 -0
  200. package/dist/providers/project-context-provider.js +15 -0
  201. package/dist/providers/project-context-provider.js.map +1 -0
  202. package/dist/providers/proofhound-web-provider.d.ts +9 -0
  203. package/dist/providers/proofhound-web-provider.d.ts.map +1 -0
  204. package/dist/providers/proofhound-web-provider.js +44 -0
  205. package/dist/providers/proofhound-web-provider.js.map +1 -0
  206. package/dist/providers/refine-provider.d.ts +5 -0
  207. package/dist/providers/refine-provider.d.ts.map +1 -0
  208. package/dist/providers/refine-provider.js +23 -0
  209. package/dist/providers/refine-provider.js.map +1 -0
  210. package/dist/screens/annotations/annotation-detail-page.d.ts +5 -0
  211. package/dist/screens/annotations/annotation-detail-page.d.ts.map +1 -0
  212. package/dist/screens/annotations/annotation-detail-page.js +379 -0
  213. package/dist/screens/annotations/annotation-detail-page.js.map +1 -0
  214. package/dist/screens/annotations/annotation-new-page.d.ts +4 -0
  215. package/dist/screens/annotations/annotation-new-page.d.ts.map +1 -0
  216. package/dist/screens/annotations/annotation-new-page.js +125 -0
  217. package/dist/screens/annotations/annotation-new-page.js.map +1 -0
  218. package/dist/screens/annotations/annotation-task-model.d.ts +39 -0
  219. package/dist/screens/annotations/annotation-task-model.d.ts.map +1 -0
  220. package/dist/screens/annotations/annotation-task-model.js +88 -0
  221. package/dist/screens/annotations/annotation-task-model.js.map +1 -0
  222. package/dist/screens/annotations/annotation-ui.d.ts +33 -0
  223. package/dist/screens/annotations/annotation-ui.d.ts.map +1 -0
  224. package/dist/screens/annotations/annotation-ui.js +92 -0
  225. package/dist/screens/annotations/annotation-ui.js.map +1 -0
  226. package/dist/screens/annotations/annotations-list-page.d.ts +4 -0
  227. package/dist/screens/annotations/annotations-list-page.d.ts.map +1 -0
  228. package/dist/screens/annotations/annotations-list-page.js +82 -0
  229. package/dist/screens/annotations/annotations-list-page.js.map +1 -0
  230. package/dist/screens/connectors/connector-detail-page.d.ts +5 -0
  231. package/dist/screens/connectors/connector-detail-page.d.ts.map +1 -0
  232. package/dist/screens/connectors/connector-detail-page.js +838 -0
  233. package/dist/screens/connectors/connector-detail-page.js.map +1 -0
  234. package/dist/screens/connectors/connector-form-page.d.ts +8 -0
  235. package/dist/screens/connectors/connector-form-page.d.ts.map +1 -0
  236. package/dist/screens/connectors/connector-form-page.js +328 -0
  237. package/dist/screens/connectors/connector-form-page.js.map +1 -0
  238. package/dist/screens/connectors/connector-peek-dialog.d.ts +8 -0
  239. package/dist/screens/connectors/connector-peek-dialog.d.ts.map +1 -0
  240. package/dist/screens/connectors/connector-peek-dialog.js +24 -0
  241. package/dist/screens/connectors/connector-peek-dialog.js.map +1 -0
  242. package/dist/screens/connectors/connector-types.d.ts +36 -0
  243. package/dist/screens/connectors/connector-types.d.ts.map +1 -0
  244. package/dist/screens/connectors/connector-types.js +48 -0
  245. package/dist/screens/connectors/connector-types.js.map +1 -0
  246. package/dist/screens/connectors/connector-ui.d.ts +14 -0
  247. package/dist/screens/connectors/connector-ui.d.ts.map +1 -0
  248. package/dist/screens/connectors/connector-ui.js +40 -0
  249. package/dist/screens/connectors/connector-ui.js.map +1 -0
  250. package/dist/screens/connectors/connectors-list-page.d.ts +4 -0
  251. package/dist/screens/connectors/connectors-list-page.d.ts.map +1 -0
  252. package/dist/screens/connectors/connectors-list-page.js +220 -0
  253. package/dist/screens/connectors/connectors-list-page.js.map +1 -0
  254. package/dist/screens/dashboard/dashboard-screen.d.ts +5 -0
  255. package/dist/screens/dashboard/dashboard-screen.d.ts.map +1 -0
  256. package/dist/screens/dashboard/dashboard-screen.js +524 -0
  257. package/dist/screens/dashboard/dashboard-screen.js.map +1 -0
  258. package/dist/screens/datasets/dataset-detail-helpers.d.ts +10 -0
  259. package/dist/screens/datasets/dataset-detail-helpers.d.ts.map +1 -0
  260. package/dist/screens/datasets/dataset-detail-helpers.js +84 -0
  261. package/dist/screens/datasets/dataset-detail-helpers.js.map +1 -0
  262. package/dist/screens/datasets/dataset-detail-page.d.ts +6 -0
  263. package/dist/screens/datasets/dataset-detail-page.d.ts.map +1 -0
  264. package/dist/screens/datasets/dataset-detail-page.js +379 -0
  265. package/dist/screens/datasets/dataset-detail-page.js.map +1 -0
  266. package/dist/screens/datasets/dataset-import-runner.d.ts +17 -0
  267. package/dist/screens/datasets/dataset-import-runner.d.ts.map +1 -0
  268. package/dist/screens/datasets/dataset-import-runner.js +33 -0
  269. package/dist/screens/datasets/dataset-import-runner.js.map +1 -0
  270. package/dist/screens/datasets/dataset-mappers.d.ts +5 -0
  271. package/dist/screens/datasets/dataset-mappers.d.ts.map +1 -0
  272. package/dist/screens/datasets/dataset-mappers.js +79 -0
  273. package/dist/screens/datasets/dataset-mappers.js.map +1 -0
  274. package/dist/screens/datasets/dataset-samples-table.d.ts +21 -0
  275. package/dist/screens/datasets/dataset-samples-table.d.ts.map +1 -0
  276. package/dist/screens/datasets/dataset-samples-table.js +59 -0
  277. package/dist/screens/datasets/dataset-samples-table.js.map +1 -0
  278. package/dist/screens/datasets/dataset-transfer-progress.d.ts +30 -0
  279. package/dist/screens/datasets/dataset-transfer-progress.d.ts.map +1 -0
  280. package/dist/screens/datasets/dataset-transfer-progress.js +157 -0
  281. package/dist/screens/datasets/dataset-transfer-progress.js.map +1 -0
  282. package/dist/screens/datasets/dataset-types.d.ts +59 -0
  283. package/dist/screens/datasets/dataset-types.d.ts.map +1 -0
  284. package/dist/screens/datasets/dataset-types.js +15 -0
  285. package/dist/screens/datasets/dataset-types.js.map +1 -0
  286. package/dist/screens/datasets/dataset-ui.d.ts +49 -0
  287. package/dist/screens/datasets/dataset-ui.d.ts.map +1 -0
  288. package/dist/screens/datasets/dataset-ui.js +163 -0
  289. package/dist/screens/datasets/dataset-ui.js.map +1 -0
  290. package/dist/screens/datasets/dataset-upload-page.d.ts +4 -0
  291. package/dist/screens/datasets/dataset-upload-page.d.ts.map +1 -0
  292. package/dist/screens/datasets/dataset-upload-page.js +425 -0
  293. package/dist/screens/datasets/dataset-upload-page.js.map +1 -0
  294. package/dist/screens/datasets/dataset-upload-parser.d.ts +21 -0
  295. package/dist/screens/datasets/dataset-upload-parser.d.ts.map +1 -0
  296. package/dist/screens/datasets/dataset-upload-parser.js +536 -0
  297. package/dist/screens/datasets/dataset-upload-parser.js.map +1 -0
  298. package/dist/screens/datasets/datasets-list-page.d.ts +4 -0
  299. package/dist/screens/datasets/datasets-list-page.d.ts.map +1 -0
  300. package/dist/screens/datasets/datasets-list-page.js +249 -0
  301. package/dist/screens/datasets/datasets-list-page.js.map +1 -0
  302. package/dist/screens/experiments/experiment-detail-page.d.ts +5 -0
  303. package/dist/screens/experiments/experiment-detail-page.d.ts.map +1 -0
  304. package/dist/screens/experiments/experiment-detail-page.js +460 -0
  305. package/dist/screens/experiments/experiment-detail-page.js.map +1 -0
  306. package/dist/screens/experiments/experiment-new-page.d.ts +21 -0
  307. package/dist/screens/experiments/experiment-new-page.d.ts.map +1 -0
  308. package/dist/screens/experiments/experiment-new-page.js +427 -0
  309. package/dist/screens/experiments/experiment-new-page.js.map +1 -0
  310. package/dist/screens/experiments/experiment-option-adapter.d.ts +68 -0
  311. package/dist/screens/experiments/experiment-option-adapter.d.ts.map +1 -0
  312. package/dist/screens/experiments/experiment-option-adapter.js +225 -0
  313. package/dist/screens/experiments/experiment-option-adapter.js.map +1 -0
  314. package/dist/screens/experiments/experiment-progress.d.ts +5 -0
  315. package/dist/screens/experiments/experiment-progress.d.ts.map +1 -0
  316. package/dist/screens/experiments/experiment-progress.js +15 -0
  317. package/dist/screens/experiments/experiment-progress.js.map +1 -0
  318. package/dist/screens/experiments/experiment-repeat-href.d.ts +17 -0
  319. package/dist/screens/experiments/experiment-repeat-href.d.ts.map +1 -0
  320. package/dist/screens/experiments/experiment-repeat-href.js +32 -0
  321. package/dist/screens/experiments/experiment-repeat-href.js.map +1 -0
  322. package/dist/screens/experiments/experiment-theme.d.ts +43 -0
  323. package/dist/screens/experiments/experiment-theme.d.ts.map +1 -0
  324. package/dist/screens/experiments/experiment-theme.js +43 -0
  325. package/dist/screens/experiments/experiment-theme.js.map +1 -0
  326. package/dist/screens/experiments/experiment-ui.d.ts +36 -0
  327. package/dist/screens/experiments/experiment-ui.d.ts.map +1 -0
  328. package/dist/screens/experiments/experiment-ui.js +46 -0
  329. package/dist/screens/experiments/experiment-ui.js.map +1 -0
  330. package/dist/screens/experiments/experiment-view-model.d.ts +118 -0
  331. package/dist/screens/experiments/experiment-view-model.d.ts.map +1 -0
  332. package/dist/screens/experiments/experiment-view-model.js +114 -0
  333. package/dist/screens/experiments/experiment-view-model.js.map +1 -0
  334. package/dist/screens/experiments/experiments-comparison-view.d.ts +10 -0
  335. package/dist/screens/experiments/experiments-comparison-view.d.ts.map +1 -0
  336. package/dist/screens/experiments/experiments-comparison-view.js +215 -0
  337. package/dist/screens/experiments/experiments-comparison-view.js.map +1 -0
  338. package/dist/screens/experiments/experiments-list-page.d.ts +4 -0
  339. package/dist/screens/experiments/experiments-list-page.d.ts.map +1 -0
  340. package/dist/screens/experiments/experiments-list-page.js +364 -0
  341. package/dist/screens/experiments/experiments-list-page.js.map +1 -0
  342. package/dist/screens/experiments/experiments-table.d.ts +18 -0
  343. package/dist/screens/experiments/experiments-table.d.ts.map +1 -0
  344. package/dist/screens/experiments/experiments-table.js +97 -0
  345. package/dist/screens/experiments/experiments-table.js.map +1 -0
  346. package/dist/screens/experiments/run-result-detail-sheet.d.ts +9 -0
  347. package/dist/screens/experiments/run-result-detail-sheet.d.ts.map +1 -0
  348. package/dist/screens/experiments/run-result-detail-sheet.js +89 -0
  349. package/dist/screens/experiments/run-result-detail-sheet.js.map +1 -0
  350. package/dist/screens/experiments/run-result-display.d.ts +28 -0
  351. package/dist/screens/experiments/run-result-display.d.ts.map +1 -0
  352. package/dist/screens/experiments/run-result-display.js +124 -0
  353. package/dist/screens/experiments/run-result-display.js.map +1 -0
  354. package/dist/screens/experiments/run-result-labels.d.ts +16 -0
  355. package/dist/screens/experiments/run-result-labels.d.ts.map +1 -0
  356. package/dist/screens/experiments/run-result-labels.js +52 -0
  357. package/dist/screens/experiments/run-result-labels.js.map +1 -0
  358. package/dist/screens/index.d.ts +29 -0
  359. package/dist/screens/index.d.ts.map +1 -0
  360. package/dist/screens/index.js +43 -0
  361. package/dist/screens/index.js.map +1 -0
  362. package/dist/screens/models/model-form-page.d.ts +9 -0
  363. package/dist/screens/models/model-form-page.d.ts.map +1 -0
  364. package/dist/screens/models/model-form-page.js +876 -0
  365. package/dist/screens/models/model-form-page.js.map +1 -0
  366. package/dist/screens/models/model-view-model.d.ts +66 -0
  367. package/dist/screens/models/model-view-model.d.ts.map +1 -0
  368. package/dist/screens/models/model-view-model.js +36 -0
  369. package/dist/screens/models/model-view-model.js.map +1 -0
  370. package/dist/screens/models/models-list-page.d.ts +4 -0
  371. package/dist/screens/models/models-list-page.d.ts.map +1 -0
  372. package/dist/screens/models/models-list-page.js +352 -0
  373. package/dist/screens/models/models-list-page.js.map +1 -0
  374. package/dist/screens/models/project-model-adapter.d.ts +5 -0
  375. package/dist/screens/models/project-model-adapter.d.ts.map +1 -0
  376. package/dist/screens/models/project-model-adapter.js +63 -0
  377. package/dist/screens/models/project-model-adapter.js.map +1 -0
  378. package/dist/screens/monitoring/big-chart-card.d.ts +36 -0
  379. package/dist/screens/monitoring/big-chart-card.d.ts.map +1 -0
  380. package/dist/screens/monitoring/big-chart-card.js +32 -0
  381. package/dist/screens/monitoring/big-chart-card.js.map +1 -0
  382. package/dist/screens/monitoring/monitoring-filter-strip.d.ts +14 -0
  383. package/dist/screens/monitoring/monitoring-filter-strip.d.ts.map +1 -0
  384. package/dist/screens/monitoring/monitoring-filter-strip.js +36 -0
  385. package/dist/screens/monitoring/monitoring-filter-strip.js.map +1 -0
  386. package/dist/screens/monitoring/project-monitoring-page.d.ts +9 -0
  387. package/dist/screens/monitoring/project-monitoring-page.d.ts.map +1 -0
  388. package/dist/screens/monitoring/project-monitoring-page.js +240 -0
  389. package/dist/screens/monitoring/project-monitoring-page.js.map +1 -0
  390. package/dist/screens/monitoring/ranking-cards.d.ts +23 -0
  391. package/dist/screens/monitoring/ranking-cards.d.ts.map +1 -0
  392. package/dist/screens/monitoring/ranking-cards.js +78 -0
  393. package/dist/screens/monitoring/ranking-cards.js.map +1 -0
  394. package/dist/screens/optimizations/optimization-detail-page.d.ts +5 -0
  395. package/dist/screens/optimizations/optimization-detail-page.d.ts.map +1 -0
  396. package/dist/screens/optimizations/optimization-detail-page.js +934 -0
  397. package/dist/screens/optimizations/optimization-detail-page.js.map +1 -0
  398. package/dist/screens/optimizations/optimization-mappers.d.ts +56 -0
  399. package/dist/screens/optimizations/optimization-mappers.d.ts.map +1 -0
  400. package/dist/screens/optimizations/optimization-mappers.js +142 -0
  401. package/dist/screens/optimizations/optimization-mappers.js.map +1 -0
  402. package/dist/screens/optimizations/optimization-new-page.d.ts +8 -0
  403. package/dist/screens/optimizations/optimization-new-page.d.ts.map +1 -0
  404. package/dist/screens/optimizations/optimization-new-page.js +732 -0
  405. package/dist/screens/optimizations/optimization-new-page.js.map +1 -0
  406. package/dist/screens/optimizations/optimization-theme.d.ts +43 -0
  407. package/dist/screens/optimizations/optimization-theme.d.ts.map +1 -0
  408. package/dist/screens/optimizations/optimization-theme.js +46 -0
  409. package/dist/screens/optimizations/optimization-theme.js.map +1 -0
  410. package/dist/screens/optimizations/optimization-ui.d.ts +60 -0
  411. package/dist/screens/optimizations/optimization-ui.d.ts.map +1 -0
  412. package/dist/screens/optimizations/optimization-ui.js +164 -0
  413. package/dist/screens/optimizations/optimization-ui.js.map +1 -0
  414. package/dist/screens/optimizations/optimizations-list-page.d.ts +4 -0
  415. package/dist/screens/optimizations/optimizations-list-page.d.ts.map +1 -0
  416. package/dist/screens/optimizations/optimizations-list-page.js +358 -0
  417. package/dist/screens/optimizations/optimizations-list-page.js.map +1 -0
  418. package/dist/screens/prompts/prompt-body-editor.d.ts +15 -0
  419. package/dist/screens/prompts/prompt-body-editor.d.ts.map +1 -0
  420. package/dist/screens/prompts/prompt-body-editor.js +165 -0
  421. package/dist/screens/prompts/prompt-body-editor.js.map +1 -0
  422. package/dist/screens/prompts/prompt-dataset-variables.d.ts +4 -0
  423. package/dist/screens/prompts/prompt-dataset-variables.d.ts.map +1 -0
  424. package/dist/screens/prompts/prompt-dataset-variables.js +27 -0
  425. package/dist/screens/prompts/prompt-dataset-variables.js.map +1 -0
  426. package/dist/screens/prompts/prompt-detail-page.d.ts +5 -0
  427. package/dist/screens/prompts/prompt-detail-page.d.ts.map +1 -0
  428. package/dist/screens/prompts/prompt-detail-page.js +1013 -0
  429. package/dist/screens/prompts/prompt-detail-page.js.map +1 -0
  430. package/dist/screens/prompts/prompt-model.d.ts +77 -0
  431. package/dist/screens/prompts/prompt-model.d.ts.map +1 -0
  432. package/dist/screens/prompts/prompt-model.js +152 -0
  433. package/dist/screens/prompts/prompt-model.js.map +1 -0
  434. package/dist/screens/prompts/prompt-preview-parts.d.ts +12 -0
  435. package/dist/screens/prompts/prompt-preview-parts.d.ts.map +1 -0
  436. package/dist/screens/prompts/prompt-preview-parts.js +32 -0
  437. package/dist/screens/prompts/prompt-preview-parts.js.map +1 -0
  438. package/dist/screens/prompts/prompt-preview.d.ts +10 -0
  439. package/dist/screens/prompts/prompt-preview.d.ts.map +1 -0
  440. package/dist/screens/prompts/prompt-preview.js +16 -0
  441. package/dist/screens/prompts/prompt-preview.js.map +1 -0
  442. package/dist/screens/prompts/prompt-ui.d.ts +30 -0
  443. package/dist/screens/prompts/prompt-ui.d.ts.map +1 -0
  444. package/dist/screens/prompts/prompt-ui.js +51 -0
  445. package/dist/screens/prompts/prompt-ui.js.map +1 -0
  446. package/dist/screens/prompts/prompts-list-page.d.ts +5 -0
  447. package/dist/screens/prompts/prompts-list-page.d.ts.map +1 -0
  448. package/dist/screens/prompts/prompts-list-page.js +208 -0
  449. package/dist/screens/prompts/prompts-list-page.js.map +1 -0
  450. package/dist/screens/quick-start/quick-start-screen.d.ts +2 -0
  451. package/dist/screens/quick-start/quick-start-screen.d.ts.map +1 -0
  452. package/dist/screens/quick-start/quick-start-screen.js +486 -0
  453. package/dist/screens/quick-start/quick-start-screen.js.map +1 -0
  454. package/dist/screens/releases/release-line-detail-page.d.ts +5 -0
  455. package/dist/screens/releases/release-line-detail-page.d.ts.map +1 -0
  456. package/dist/screens/releases/release-line-detail-page.js +973 -0
  457. package/dist/screens/releases/release-line-detail-page.js.map +1 -0
  458. package/dist/screens/releases/release-line-ui.d.ts +30 -0
  459. package/dist/screens/releases/release-line-ui.d.ts.map +1 -0
  460. package/dist/screens/releases/release-line-ui.js +197 -0
  461. package/dist/screens/releases/release-line-ui.js.map +1 -0
  462. package/dist/screens/releases/release-new-model.d.ts +5 -0
  463. package/dist/screens/releases/release-new-model.d.ts.map +1 -0
  464. package/dist/screens/releases/release-new-model.js +18 -0
  465. package/dist/screens/releases/release-new-model.js.map +1 -0
  466. package/dist/screens/releases/release-new-page.d.ts +6 -0
  467. package/dist/screens/releases/release-new-page.d.ts.map +1 -0
  468. package/dist/screens/releases/release-new-page.js +816 -0
  469. package/dist/screens/releases/release-new-page.js.map +1 -0
  470. package/dist/screens/releases/release-topology-canvas.d.ts +13 -0
  471. package/dist/screens/releases/release-topology-canvas.d.ts.map +1 -0
  472. package/dist/screens/releases/release-topology-canvas.js +856 -0
  473. package/dist/screens/releases/release-topology-canvas.js.map +1 -0
  474. package/dist/screens/releases/releases-list-page.d.ts +4 -0
  475. package/dist/screens/releases/releases-list-page.d.ts.map +1 -0
  476. package/dist/screens/releases/releases-list-page.js +86 -0
  477. package/dist/screens/releases/releases-list-page.js.map +1 -0
  478. package/dist/screens/settings/settings-page.d.ts +2 -0
  479. package/dist/screens/settings/settings-page.d.ts.map +1 -0
  480. package/dist/screens/settings/settings-page.js +250 -0
  481. package/dist/screens/settings/settings-page.js.map +1 -0
  482. package/dist/styles/globals.css +961 -0
  483. package/package.json +84 -0
@@ -0,0 +1,973 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useMemo, useState } from 'react';
4
+ import Link from 'next/link';
5
+ import { usePathname, useRouter, useSearchParams } from 'next/navigation';
6
+ import { useQueryClient } from '@tanstack/react-query';
7
+ import { Activity, AlertTriangle, ClipboardCheck, CircleDollarSign, Copy, Gauge, Plus, Square, Timer, } from 'lucide-react';
8
+ import { CartesianGrid, Line, LineChart as RechartsLineChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
9
+ import { Main } from '@proofhound/ui/layout';
10
+ import { Button, DateRangeSegmented, resolveDateRangePreset, resolveRollingDateRangeValue, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, PlatformLoader, DetailPageSkeleton, ResourcePaginationFooter, Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow, cn, } from '@proofhound/ui';
11
+ import { useAnnotationTaskList } from '../../hooks';
12
+ import { useDelayedLoading } from '../../hooks';
13
+ import { useProjectModels } from '../../hooks';
14
+ import { useProductionReleaseHistory, useStopProductionRelease } from '../../hooks';
15
+ import { useProjectMonitoringStats, useProjectMonitoringTimeseries } from '../../hooks';
16
+ import { useReleaseLineEvents, useReleaseLineList, useUpdateReleaseLineRunConfig, useUpdateReleaseLineTrafficRatio, } from '../../hooks';
17
+ import { useReleaseRunResults } from '../../hooks';
18
+ import { AUTO_REFRESH_INTERVAL_MS, useAutoRefresh, useDateTimeFormatter } from '../../hooks';
19
+ import { useI18n } from '../../i18n';
20
+ import { getReleaseLineId, getReleaseStopConfirmationName } from '../../lib';
21
+ import { BigChartCard } from '../monitoring/big-chart-card';
22
+ import { ReleaseEventPill, ReleaseMetricCard, formatCount, formatPercent, } from './release-line-ui';
23
+ import { ReleaseTopologyCanvas } from './release-topology-canvas';
24
+ const DETAIL_TABS = [
25
+ { value: 'monitoring', key: 'releases.detail.tab.monitoring' },
26
+ { value: 'variants', key: 'releases.detail.tab.variants' },
27
+ { value: 'results', key: 'releases.detail.tab.results' },
28
+ { value: 'quality', key: 'releases.detail.tab.quality' },
29
+ { value: 'history', key: 'releases.detail.tab.history' },
30
+ ];
31
+ const RESULT_COLUMNS = [
32
+ { key: 'externalId', width: 'normal' },
33
+ { key: 'input', width: 'wide' },
34
+ { key: 'output', width: 'wide' },
35
+ { key: 'source', width: 'compact' },
36
+ { key: 'variant', width: 'normal' },
37
+ { key: 'latency', width: 'compact' },
38
+ { key: 'tokens', width: 'compact' },
39
+ { key: 'createdAt', width: 'normal' },
40
+ ];
41
+ const RESULT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
42
+ const EMPTY_BY_SOURCE = { prod: 0, canary: 0, iter: 0, exp: 0 };
43
+ const EMPTY_TIMESERIES_POINTS = [];
44
+ const RELEASE_MONITORING_SOURCE_KEYS = ['prod', 'canary'];
45
+ function useDateTimeOrDash() {
46
+ const { formatDateTime } = useDateTimeFormatter();
47
+ return useCallback((value) => (value ? formatDateTime(value, { fallback: '—' }) : '—'), [formatDateTime]);
48
+ }
49
+ const COMPACT_METRIC_DOT_CLASS = {
50
+ default: 'bg-muted-foreground',
51
+ production: 'bg-[var(--src-prod-fg)]',
52
+ canary: 'bg-[var(--src-canary-fg)]',
53
+ success: 'bg-[var(--status-running-fg)]',
54
+ danger: 'bg-destructive',
55
+ };
56
+ const QUALITY_LINE_COLORS = {
57
+ score: 'var(--src-canary)',
58
+ };
59
+ function normalizeLineId(value) {
60
+ try {
61
+ return decodeURIComponent(value);
62
+ }
63
+ catch {
64
+ return value;
65
+ }
66
+ }
67
+ function resolveTab(value) {
68
+ if (value === 'annotation')
69
+ return 'quality';
70
+ if (value === 'monitoring' ||
71
+ value === 'variants' ||
72
+ value === 'results' ||
73
+ value === 'quality' ||
74
+ value === 'history') {
75
+ return value;
76
+ }
77
+ return 'monitoring';
78
+ }
79
+ function createDefaultMonitoringRange() {
80
+ const preset = resolveDateRangePreset('h1');
81
+ if (preset)
82
+ return { preset: 'h1', ...preset };
83
+ const now = new Date();
84
+ return {
85
+ preset: 'h1',
86
+ from: new Date(now.getTime() - 60 * 60_000).toISOString(),
87
+ to: now.toISOString(),
88
+ };
89
+ }
90
+ function getMonitoringRefreshInterval(preset) {
91
+ if (preset === 'h1')
92
+ return AUTO_REFRESH_INTERVAL_MS;
93
+ if (preset === 'h24')
94
+ return 30_000;
95
+ if (preset === 'd7')
96
+ return 60_000;
97
+ return false;
98
+ }
99
+ function hasRunningRelease(line) {
100
+ return line?.production?.currentEvent?.status === 'running' || line?.canary?.status === 'running';
101
+ }
102
+ function formatBigNumber(value) {
103
+ if (!Number.isFinite(value))
104
+ return '0';
105
+ const abs = Math.abs(value);
106
+ if (abs >= 1_000_000_000)
107
+ return `${(value / 1_000_000_000).toFixed(2)}B`;
108
+ if (abs >= 1_000_000)
109
+ return `${(value / 1_000_000).toFixed(2)}M`;
110
+ if (abs >= 1_000)
111
+ return `${(value / 1_000).toFixed(1)}k`;
112
+ return Math.round(value).toString();
113
+ }
114
+ function formatRateValue(value) {
115
+ if (!Number.isFinite(value) || value <= 0)
116
+ return '0';
117
+ if (value >= 1000)
118
+ return `${(value / 1000).toFixed(1)}k`;
119
+ return value.toFixed(0);
120
+ }
121
+ function formatPercentValue(value) {
122
+ if (!Number.isFinite(value) || value <= 0)
123
+ return '0%';
124
+ return `${value.toFixed(value >= 10 ? 1 : 2)}%`;
125
+ }
126
+ function formatLatencyMs(value) {
127
+ if (!Number.isFinite(value) || value <= 0)
128
+ return '—';
129
+ if (value < 1000)
130
+ return `${Math.round(value)}ms`;
131
+ const seconds = value / 1000;
132
+ return `${seconds >= 10 ? seconds.toFixed(1) : seconds.toFixed(2)}s`;
133
+ }
134
+ function formatCostValue(value) {
135
+ if (!Number.isFinite(value) || value <= 0)
136
+ return '$0';
137
+ if (Math.abs(value) >= 10000)
138
+ return `$${(value / 1000).toFixed(1)}k`;
139
+ if (Math.abs(value) >= 1000)
140
+ return `$${(value / 1000).toFixed(2)}k`;
141
+ return `$${value.toFixed(4)}`;
142
+ }
143
+ function toPercentPoint(value) {
144
+ if (!Number.isFinite(value))
145
+ return 0;
146
+ return Math.max(0, Math.min(100, value * 100));
147
+ }
148
+ function formatQualityPercent(value, fractionDigits = 1) {
149
+ if (value === null || value === undefined || !Number.isFinite(value))
150
+ return '—';
151
+ return `${value.toFixed(fractionDigits)}%`;
152
+ }
153
+ function timeValue(value) {
154
+ if (!value)
155
+ return 0;
156
+ const parsed = Date.parse(value);
157
+ return Number.isFinite(parsed) ? parsed : 0;
158
+ }
159
+ function buildAnnotationQualityPoints(tasks) {
160
+ const points = [];
161
+ for (const task of tasks) {
162
+ if (task.quality) {
163
+ points.push({
164
+ id: task.id,
165
+ x: '',
166
+ name: task.name,
167
+ promptVersionLabel: task.promptVersionLabel ?? '—',
168
+ modelName: task.modelName ?? '—',
169
+ releaseVariantLabel: task.releaseVariantLabel,
170
+ submitted: task.progress.submitted,
171
+ total: task.progress.total,
172
+ matched: task.quality.matched,
173
+ mismatched: task.quality.mismatched,
174
+ updatedAt: task.updatedAt,
175
+ score: toPercentPoint(task.quality.score),
176
+ });
177
+ }
178
+ }
179
+ return points
180
+ .sort((left, right) => timeValue(left.updatedAt) - timeValue(right.updatedAt))
181
+ .map((point, index) => ({ ...point, x: `#${index + 1}` }));
182
+ }
183
+ function comparisonFromDelta(current, previous, formatter, label, unit) {
184
+ const delta = current - previous;
185
+ const sign = delta > 0 ? '+' : delta < 0 ? '-' : '';
186
+ return {
187
+ value: `${sign}${formatter(Math.abs(delta))}`,
188
+ unit,
189
+ label,
190
+ tone: toneFromDelta(delta),
191
+ };
192
+ }
193
+ function toneFromDelta(delta) {
194
+ if (delta > 0)
195
+ return 'up';
196
+ if (delta < 0)
197
+ return 'down';
198
+ return 'neutral';
199
+ }
200
+ function failureRatePercent(stats, period) {
201
+ const requestCount = stats?.requests[period] ?? 0;
202
+ if (requestCount <= 0)
203
+ return 0;
204
+ return ((stats?.errors[period] ?? 0) / requestCount) * 100;
205
+ }
206
+ function sourceBucketTotal(values) {
207
+ return values.prod + values.canary + values.iter + values.exp;
208
+ }
209
+ function maxTimeseriesBucketTotal(points, metric) {
210
+ return points.reduce((max, point) => Math.max(max, sourceBucketTotal(point[metric])), 0);
211
+ }
212
+ function maxFailureRatePercent(points) {
213
+ return points.reduce((max, point) => {
214
+ const requests = sourceBucketTotal(point.requests);
215
+ if (requests <= 0)
216
+ return max;
217
+ const failureRate = (sourceBucketTotal(point.errors) / requests) * 100;
218
+ return Math.max(max, failureRate);
219
+ }, 0);
220
+ }
221
+ function pickReleaseTimeseries(points, granularity, metric, formatMonitoringTick) {
222
+ return points.map((point) => ({
223
+ x: formatMonitoringTick(point.bucketAt, granularity),
224
+ prod: point[metric].prod,
225
+ canary: point[metric].canary,
226
+ iter: point[metric].iter,
227
+ exp: point[metric].exp,
228
+ }));
229
+ }
230
+ function pickReleaseFailureRateTimeseries(points, granularity, formatMonitoringTick) {
231
+ return points.map((point) => {
232
+ const requestCount = sourceBucketTotal(point.requests);
233
+ return {
234
+ x: formatMonitoringTick(point.bucketAt, granularity),
235
+ prod: failureRateContributionPercent(point.errors.prod, requestCount),
236
+ canary: failureRateContributionPercent(point.errors.canary, requestCount),
237
+ iter: failureRateContributionPercent(point.errors.iter, requestCount),
238
+ exp: failureRateContributionPercent(point.errors.exp, requestCount),
239
+ };
240
+ });
241
+ }
242
+ function failureRateBySourcePercent(stats) {
243
+ const requestCount = stats?.requests.total ?? 0;
244
+ if (requestCount <= 0)
245
+ return EMPTY_BY_SOURCE;
246
+ return {
247
+ prod: failureRateContributionPercent(stats?.errors.bySource.prod ?? 0, requestCount),
248
+ canary: failureRateContributionPercent(stats?.errors.bySource.canary ?? 0, requestCount),
249
+ iter: failureRateContributionPercent(stats?.errors.bySource.iter ?? 0, requestCount),
250
+ exp: failureRateContributionPercent(stats?.errors.bySource.exp ?? 0, requestCount),
251
+ };
252
+ }
253
+ function failureRateContributionPercent(errors, totalRequests) {
254
+ return totalRequests > 0 ? (errors / totalRequests) * 100 : 0;
255
+ }
256
+ function readMetricNumber(metrics, keys) {
257
+ if (!metrics || typeof metrics !== 'object' || Array.isArray(metrics))
258
+ return 0;
259
+ const record = metrics;
260
+ for (const key of keys) {
261
+ const value = record[key];
262
+ const parsed = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN;
263
+ if (Number.isFinite(parsed) && parsed >= 0)
264
+ return parsed;
265
+ }
266
+ return 0;
267
+ }
268
+ function getDownstreamDeliveryStats(line) {
269
+ const metrics = line.canary?.metrics ?? line.production?.currentEvent?.sourceMetricsSnapshot ?? null;
270
+ const success = readMetricNumber(metrics, ['downstreamDeliverySuccess', 'outputDeliverySuccess']);
271
+ const failed = readMetricNumber(metrics, ['downstreamDeliveryFailed', 'outputDeliveryFailed']);
272
+ const total = success + failed;
273
+ return {
274
+ success,
275
+ failed,
276
+ failureRate: total > 0 ? failed / total : null,
277
+ };
278
+ }
279
+ function CompactMetricGroup({ title, items, className, }) {
280
+ return (_jsxs("div", { className: cn('min-w-0 space-y-3', className), children: [_jsx("div", { className: "text-[13px] font-medium text-muted-foreground", children: title }), _jsx("dl", { className: "grid grid-cols-2 gap-x-5 gap-y-3 sm:grid-cols-3", children: items.map((item) => {
281
+ const tone = item.tone ?? 'default';
282
+ return (_jsxs("div", { className: "min-w-0", children: [_jsxs("dt", { className: "flex items-center gap-1.5 text-[12px] text-muted-foreground", children: [_jsx("span", { className: cn('size-1.5 shrink-0 rounded-full', COMPACT_METRIC_DOT_CLASS[tone]) }), _jsx("span", { className: "truncate", children: item.label })] }), _jsx("dd", { className: cn('mt-1 truncate text-[20px] font-semibold leading-none text-foreground', tone === 'danger' && 'text-destructive'), children: item.value })] }, item.label));
283
+ }) })] }));
284
+ }
285
+ export function ReleaseLineDetailPage({ projectId, releaseLineId }) {
286
+ const router = useRouter();
287
+ const pathname = usePathname();
288
+ const searchParams = useSearchParams();
289
+ const { t } = useI18n();
290
+ const queryClient = useQueryClient();
291
+ const listQuery = useReleaseLineList(projectId);
292
+ const lineId = normalizeLineId(releaseLineId);
293
+ const line = useMemo(() => listQuery.data.find((item) => item.id === lineId || getReleaseLineId(item.promptId, item.inputConnectorId) === lineId) ?? null, [lineId, listQuery.data]);
294
+ const historyQuery = useProductionReleaseHistory(projectId, line?.promptId ?? '');
295
+ const releaseLineEventsQuery = useReleaseLineEvents(projectId, line?.id ?? '');
296
+ const stopProductionMutation = useStopProductionRelease(projectId);
297
+ const updateTrafficRatioMutation = useUpdateReleaseLineTrafficRatio(projectId);
298
+ const updateRunConfigMutation = useUpdateReleaseLineRunConfig(projectId);
299
+ const modelQuery = useProjectModels(projectId);
300
+ const tab = resolveTab(searchParams.get('tab'));
301
+ const selectedReleaseVariantId = searchParams.get('variant') ?? undefined;
302
+ const [stopDialogOpen, setStopDialogOpen] = useState(false);
303
+ const [stopConfirmationText, setStopConfirmationText] = useState('');
304
+ const productionReleaseName = useMemo(() => getReleaseStopConfirmationName(line), [line]);
305
+ const canConfirmStopProduction = stopConfirmationText === productionReleaseName && productionReleaseName.length > 0;
306
+ const canAddCanary = Boolean(line && line.production?.currentEvent?.status === 'running' && !line.canary);
307
+ const isLive = hasRunningRelease(line);
308
+ const onAutoRefreshTick = useCallback(async () => {
309
+ await Promise.all([
310
+ queryClient.invalidateQueries({ queryKey: ['release-lines', projectId] }),
311
+ queryClient.invalidateQueries({ queryKey: ['production-releases', projectId] }),
312
+ queryClient.invalidateQueries({ queryKey: ['canary-releases', projectId] }),
313
+ ]);
314
+ }, [projectId, queryClient]);
315
+ useAutoRefresh({
316
+ intervalMs: AUTO_REFRESH_INTERVAL_MS,
317
+ enabled: isLive,
318
+ onTick: onAutoRefreshTick,
319
+ });
320
+ useEffect(() => {
321
+ if (searchParams.get('tab') !== 'annotation')
322
+ return;
323
+ const params = new URLSearchParams(searchParams.toString());
324
+ params.set('tab', 'quality');
325
+ router.replace(`${pathname}?${params.toString()}`, { scroll: false });
326
+ }, [pathname, router, searchParams]);
327
+ const selectTab = useCallback((next) => {
328
+ const params = new URLSearchParams(searchParams.toString());
329
+ if (next === 'monitoring')
330
+ params.delete('tab');
331
+ else
332
+ params.set('tab', next);
333
+ if (next !== 'results')
334
+ params.delete('variant');
335
+ const query = params.toString();
336
+ router.replace(query ? `${pathname}?${query}` : pathname, { scroll: false });
337
+ }, [pathname, router, searchParams]);
338
+ const detailLoading = useDelayedLoading(listQuery.isLoading);
339
+ if (detailLoading) {
340
+ return (_jsx(Main, { fixed: true, className: "bg-muted/35", children: _jsx("div", { className: "mx-auto w-full max-w-[1760px] px-4 py-6 sm:px-6 lg:px-8", children: _jsx(DetailPageSkeleton, {}) }) }));
341
+ }
342
+ if (!line) {
343
+ return (_jsx(Main, { fixed: true, className: "bg-muted/35", children: _jsx("div", { className: "rounded-lg border bg-card p-10 text-center text-sm text-muted-foreground", children: t('releases.detail.notFound') }) }));
344
+ }
345
+ function openStopProductionDialog() {
346
+ if (!line?.production?.currentEvent)
347
+ return;
348
+ setStopConfirmationText('');
349
+ setStopDialogOpen(true);
350
+ }
351
+ function closeStopProductionDialog() {
352
+ if (stopProductionMutation.isPending)
353
+ return;
354
+ setStopDialogOpen(false);
355
+ setStopConfirmationText('');
356
+ }
357
+ function confirmStopProduction() {
358
+ if (!line?.production?.currentEvent || !canConfirmStopProduction)
359
+ return;
360
+ stopProductionMutation.mutate({
361
+ eventId: line.production.currentEvent.id,
362
+ body: { reason: t('releases.detail.stopReason') },
363
+ }, {
364
+ onSuccess: () => {
365
+ setStopDialogOpen(false);
366
+ setStopConfirmationText('');
367
+ },
368
+ });
369
+ }
370
+ function openAddCanaryPage() {
371
+ if (!line || !canAddCanary)
372
+ return;
373
+ router.push(`/releases/new?mode=canary&line=${encodeURIComponent(line.id)}`);
374
+ }
375
+ return (_jsxs(Main, { fixed: true, className: "gap-5 overflow-auto bg-muted/35 pb-8", children: [_jsxs("div", { className: "mx-auto flex w-full max-w-[1760px] flex-col gap-5", "data-testid": "release-line-detail-page", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsx("div", { className: "min-w-0", children: _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [_jsx("h1", { className: "truncate text-[22px] font-semibold leading-tight", children: line.promptName }), _jsx("span", { "data-testid": "release-line-detail-status", className: "sr-only", children: line.production?.currentEvent?.status ?? line.canary?.status ?? line.status })] }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [line.production?.currentEvent?.status === 'running' ? (_jsxs(Button, { variant: "outline", onClick: openStopProductionDialog, disabled: stopProductionMutation.isPending, className: "text-destructive hover:text-destructive", "data-testid": "release-line-detail-stop", children: [_jsx(Square, { className: "size-4" }), t('releases.detail.action.stopProduction')] })) : null, canAddCanary ? (_jsxs(Button, { onClick: openAddCanaryPage, children: [_jsx(Plus, { className: "size-4" }), t('releases.detail.action.addCanary')] })) : null] })] }), _jsx(ReleaseTopologyCanvas, { line: line, models: modelQuery.data?.data ?? [], modelsLoading: modelQuery.isLoading, onUpdateTrafficRatio: (_canary, trafficRatio) => updateTrafficRatioMutation.mutateAsync({
376
+ releaseLineId: line.id,
377
+ body: { trafficRatio },
378
+ }), trafficRatioPending: updateTrafficRatioMutation.isPending, onUpdateRunConfig: (body) => updateRunConfigMutation.mutateAsync({
379
+ releaseLineId: line.id,
380
+ body,
381
+ }), runConfigPending: updateRunConfigMutation.isPending, onAddCanary: canAddCanary ? openAddCanaryPage : undefined }), _jsx("div", { className: "inline-flex w-fit flex-wrap gap-0.5 rounded-lg border bg-card p-1", children: DETAIL_TABS.map((item) => (_jsx("button", { type: "button", onClick: () => selectTab(item.value), className: cn('rounded-md px-3.5 py-1.5 text-[13px] font-medium transition-colors', tab === item.value
382
+ ? 'bg-muted font-semibold text-foreground'
383
+ : 'text-muted-foreground hover:text-foreground'), children: t(item.key) }, item.value))) }), tab === 'monitoring' ? (_jsx(MonitoringPane, { projectId: projectId, line: line, releaseEvents: releaseLineEventsQuery.data?.data ?? [] })) : null, tab === 'variants' ? (_jsx(VariantsPane, { line: line, releaseEvents: releaseLineEventsQuery.data?.data ?? [], loading: releaseLineEventsQuery.isLoading })) : null, tab === 'results' ? (_jsx(ResultsPane, { projectId: projectId, line: line, releaseEvents: releaseLineEventsQuery.data?.data ?? [], initialReleaseVariantId: selectedReleaseVariantId })) : null, tab === 'quality' ? _jsx(QualityMetricsPane, { projectId: projectId, line: line }) : null, tab === 'history' ? (_jsx(HistoryPane, { line: line, productionHistory: historyQuery.data?.data ?? [], releaseEvents: releaseLineEventsQuery.data?.data ?? [], loading: historyQuery.isLoading || releaseLineEventsQuery.isLoading })) : null] }), _jsx(Dialog, { open: stopDialogOpen, onOpenChange: (open) => (open ? setStopDialogOpen(true) : closeStopProductionDialog()), children: _jsxs(DialogContent, { "data-testid": "release-stop-production-dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: t('releases.detail.stopDialog.title') }), _jsx(DialogDescription, { children: t('releases.detail.stopDialog.description') })] }), _jsxs("div", { className: "rounded-lg border bg-muted/40 p-3", children: [_jsx("div", { className: "text-[11.5px] font-medium text-muted-foreground", children: t('releases.detail.stopDialog.releaseName') }), _jsx("div", { className: "mt-1 break-all font-mono text-[13px] font-semibold", children: productionReleaseName || '—' })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { htmlFor: "release-stop-production-name", className: "text-[12.5px] font-medium", children: t('releases.detail.stopDialog.inputLabel') }), _jsx(Input, { id: "release-stop-production-name", value: stopConfirmationText, onChange: (event) => setStopConfirmationText(event.target.value), placeholder: t('releases.detail.stopDialog.inputPlaceholder').replace('{name}', productionReleaseName), autoComplete: "off" }), stopConfirmationText.length > 0 && !canConfirmStopProduction ? (_jsx("p", { className: "text-[12px] text-destructive", children: t('releases.detail.stopDialog.mismatch') })) : null] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", onClick: closeStopProductionDialog, disabled: stopProductionMutation.isPending, children: t('common.cancel') }), _jsxs(Button, { type: "button", variant: "destructive", onClick: confirmStopProduction, disabled: !canConfirmStopProduction || stopProductionMutation.isPending, children: [_jsx(Square, { className: "size-4" }), stopProductionMutation.isPending
384
+ ? t('releases.detail.stopDialog.stopping')
385
+ : t('releases.detail.stopDialog.confirm')] })] })] }) })] }));
386
+ }
387
+ function MonitoringPane({ projectId, line, releaseEvents, }) {
388
+ const { t, language } = useI18n();
389
+ const { formatMonitoringTick } = useDateTimeFormatter();
390
+ const queryClient = useQueryClient();
391
+ const [dateRange, setDateRange] = useState(() => createDefaultMonitoringRange());
392
+ const sourceIds = useMemo(() => getReleaseLineEventSourceIds(line, releaseEvents), [line, releaseEvents]);
393
+ const sources = useMemo(() => getReleaseLineEventSources(line, releaseEvents), [line, releaseEvents]);
394
+ const filter = useMemo(() => ({
395
+ from: dateRange.from,
396
+ to: dateRange.to,
397
+ sourceIds: sourceIds.length > 0 ? sourceIds : undefined,
398
+ sources,
399
+ granularity: 'auto',
400
+ }), [dateRange.from, dateRange.to, sourceIds, sources]);
401
+ const statsQuery = useProjectMonitoringStats(projectId, filter, sourceIds.length > 0);
402
+ const timeseriesQuery = useProjectMonitoringTimeseries(projectId, filter, sourceIds.length > 0);
403
+ const monitoringRefreshInterval = getMonitoringRefreshInterval(dateRange.preset);
404
+ const refreshMonitoring = useCallback(async () => {
405
+ const nextDateRange = resolveRollingDateRangeValue(dateRange);
406
+ if (nextDateRange.preset !== dateRange.preset ||
407
+ nextDateRange.from !== dateRange.from ||
408
+ nextDateRange.to !== dateRange.to) {
409
+ setDateRange(nextDateRange);
410
+ return;
411
+ }
412
+ await queryClient.invalidateQueries({ queryKey: ['project-monitoring', projectId] });
413
+ }, [dateRange, projectId, queryClient]);
414
+ useAutoRefresh({
415
+ intervalMs: monitoringRefreshInterval,
416
+ enabled: hasRunningRelease(line) && sourceIds.length > 0 && monitoringRefreshInterval !== false,
417
+ onTick: refreshMonitoring,
418
+ });
419
+ const stats = statsQuery.data;
420
+ const timeseriesPoints = timeseriesQuery.data?.points ?? EMPTY_TIMESERIES_POINTS;
421
+ const timeseriesGranularity = timeseriesQuery.data?.granularity ?? 'hour';
422
+ const processed = Math.trunc(stats?.requests.total ?? 0);
423
+ const filtered = line.canary?.totalFiltered ?? 0;
424
+ const failed = Math.trunc(stats?.errors.total ?? 0);
425
+ const total = processed + filtered;
426
+ const downstreamDelivery = getDownstreamDeliveryStats(line);
427
+ const vsPreviousPeriodLabel = t('monitoring.delta.vsPreviousPeriod');
428
+ const sourceLabels = useMemo(() => ({
429
+ prod: t('monitoring.source.prod'),
430
+ canary: t('monitoring.source.canary'),
431
+ iter: t('monitoring.source.iter'),
432
+ exp: t('monitoring.source.exp'),
433
+ }), [t]);
434
+ const chartLabels = useMemo(() => ({
435
+ sourceDistributionLabel: t('monitoring.chart.sourceDistribution'),
436
+ totalLabel: t('monitoring.chart.total'),
437
+ failureRateTotalLabel: t('monitoring.chart.failureRateTotal'),
438
+ }), [t]);
439
+ const timeseriesMax = useMemo(() => ({
440
+ rpm: maxTimeseriesBucketTotal(timeseriesPoints, 'rpm'),
441
+ tpm: maxTimeseriesBucketTotal(timeseriesPoints, 'tpm'),
442
+ latencyAverageMs: maxTimeseriesBucketTotal(timeseriesPoints, 'latencyAverageMs'),
443
+ latencyP50Ms: maxTimeseriesBucketTotal(timeseriesPoints, 'latencyP50Ms'),
444
+ latencyP95Ms: maxTimeseriesBucketTotal(timeseriesPoints, 'latencyP95Ms'),
445
+ latencyP99Ms: maxTimeseriesBucketTotal(timeseriesPoints, 'latencyP99Ms'),
446
+ cost: maxTimeseriesBucketTotal(timeseriesPoints, 'cost'),
447
+ failureRatePercent: maxFailureRatePercent(timeseriesPoints),
448
+ }), [timeseriesPoints]);
449
+ function pickTimeseries(metric) {
450
+ return pickReleaseTimeseries(timeseriesPoints, timeseriesGranularity, metric, formatMonitoringTick);
451
+ }
452
+ function pickFailureRateTimeseries() {
453
+ return pickReleaseFailureRateTimeseries(timeseriesPoints, timeseriesGranularity, formatMonitoringTick);
454
+ }
455
+ const dateRangePresetLabels = useMemo(() => [
456
+ { value: 'h1', label: t('monitoring.timeRange.preset.h1') },
457
+ { value: 'h24', label: t('monitoring.timeRange.preset.h24') },
458
+ { value: 'd7', label: t('monitoring.timeRange.preset.d7') },
459
+ { value: 'd30', label: t('monitoring.timeRange.preset.d30') },
460
+ { value: 'custom', label: t('monitoring.timeRange.preset.custom') },
461
+ ], [t]);
462
+ const dateRangeLabels = useMemo(() => ({
463
+ ariaLabel: t('monitoring.timeRange.ariaLabel'),
464
+ customRangeAriaLabel: t('monitoring.timeRange.customRangeAriaLabel'),
465
+ fromLabel: t('monitoring.timeRange.from'),
466
+ toLabel: t('monitoring.timeRange.to'),
467
+ dateLabel: t('monitoring.timeRange.date'),
468
+ timeLabel: t('monitoring.timeRange.time'),
469
+ previousMonth: t('monitoring.timeRange.previousMonth'),
470
+ nextMonth: t('monitoring.timeRange.nextMonth'),
471
+ cancel: t('common.cancel'),
472
+ apply: t('common.apply'),
473
+ invalidRange: t('monitoring.timeRange.invalidRange'),
474
+ }), [t]);
475
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between", children: [_jsx("h2", { className: "text-[14px] font-semibold", children: t('releases.detail.metric.realtime') }), _jsx("div", { className: "flex flex-wrap items-center gap-2", children: _jsx(DateRangeSegmented, { value: dateRange, onChange: setDateRange, presetLabels: dateRangePresetLabels, labels: dateRangeLabels, locale: language }) })] }), statsQuery.isError || timeseriesQuery.isError ? (_jsx("div", { className: "rounded-lg border bg-card px-4 py-3 text-sm text-destructive", children: t('monitoring.error.title') })) : null, _jsx("div", { className: "rounded-lg border bg-card p-4", children: _jsxs("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,2fr)_minmax(280px,1fr)]", children: [_jsx(CompactMetricGroup, { title: t('releases.detail.metric.runtimeSummary'), items: [
476
+ { label: t('releases.detail.metric.total'), value: formatCount(total) },
477
+ { label: t('releases.detail.metric.processed'), value: formatCount(processed), tone: 'success' },
478
+ { label: t('releases.detail.metric.filtered'), value: formatCount(filtered) },
479
+ { label: t('releases.detail.metric.failed'), value: formatCount(failed), tone: 'danger' },
480
+ {
481
+ label: t('releases.detail.metric.productionCount'),
482
+ value: formatCount(Math.trunc(stats?.requests.bySource.prod ?? 0)),
483
+ tone: 'production',
484
+ },
485
+ {
486
+ label: t('releases.detail.metric.canaryCount'),
487
+ value: formatCount(Math.trunc(stats?.requests.bySource.canary ?? 0)),
488
+ tone: 'canary',
489
+ },
490
+ ] }), _jsx(CompactMetricGroup, { title: t('releases.detail.metric.downstreamDelivery'), className: "border-t pt-4 lg:border-l lg:border-t-0 lg:pl-4 lg:pt-0", items: [
491
+ {
492
+ label: t('releases.detail.metric.deliverySuccess'),
493
+ value: formatCount(Math.trunc(downstreamDelivery.success)),
494
+ tone: 'success',
495
+ },
496
+ {
497
+ label: t('releases.detail.metric.deliveryFailed'),
498
+ value: formatCount(Math.trunc(downstreamDelivery.failed)),
499
+ tone: 'danger',
500
+ },
501
+ {
502
+ label: t('releases.detail.metric.deliveryFailureRate'),
503
+ value: formatPercent(downstreamDelivery.failureRate),
504
+ tone: downstreamDelivery.failed > 0 ? 'danger' : 'default',
505
+ },
506
+ ] })] }) }), _jsxs("section", { className: "space-y-3", "aria-label": t('releases.detail.metric.engineering'), children: [_jsx("h3", { className: "text-[14px] font-semibold", children: t('releases.detail.metric.engineering') }), _jsxs("div", { className: "grid grid-cols-1 gap-4 xl:grid-cols-2 2xl:grid-cols-4", children: [_jsx(BigChartCard, { title: t('releases.detail.metric.rpm'), icon: _jsx(Gauge, { className: "size-4", strokeWidth: 2.2 }), iconBg: "var(--status-pending-bg)", iconFg: "var(--status-pending-fg)", total: formatRateValue(timeseriesMax.rpm), comparison: comparisonFromDelta(timeseriesMax.rpm, stats?.rpmPeak.previous ?? 0, formatRateValue, vsPreviousPeriodLabel), subtitle: t('monitoring.delta.rpmSubtitle'), data: pickTimeseries('rpm'), yTickFormatter: formatRateValue, legendFormatter: formatRateValue, bySource: stats?.rpmPeak.bySource ?? EMPTY_BY_SOURCE, sourceLabels: sourceLabels, sourceKeys: RELEASE_MONITORING_SOURCE_KEYS, ...chartLabels }), _jsx(BigChartCard, { title: t('releases.detail.metric.tpm'), icon: _jsx(Activity, { className: "size-4", strokeWidth: 2.2 }), iconBg: "var(--src-iter-soft)", iconFg: "var(--src-iter-fg)", total: formatBigNumber(timeseriesMax.tpm), comparison: comparisonFromDelta(timeseriesMax.tpm, stats?.tpmPeak.previous ?? 0, formatBigNumber, vsPreviousPeriodLabel), subtitle: t('monitoring.delta.tpmSubtitle'), data: pickTimeseries('tpm'), yTickFormatter: formatBigNumber, legendFormatter: formatBigNumber, bySource: stats?.tpmPeak.bySource ?? EMPTY_BY_SOURCE, sourceLabels: sourceLabels, sourceKeys: RELEASE_MONITORING_SOURCE_KEYS, ...chartLabels }), _jsx(BigChartCard, { title: t('releases.detail.metric.averageLatency'), icon: _jsx(Timer, { className: "size-4", strokeWidth: 2.2 }), iconBg: "var(--src-canary-soft)", iconFg: "var(--src-canary-fg)", total: formatLatencyMs(timeseriesMax.latencyAverageMs), comparison: comparisonFromDelta(timeseriesMax.latencyAverageMs, stats?.latencyAverageMs.previous ?? 0, formatLatencyMs, vsPreviousPeriodLabel), data: pickTimeseries('latencyAverageMs'), yTickFormatter: formatLatencyMs, legendFormatter: formatLatencyMs, bySource: stats?.latencyAverageMs.bySource ?? EMPTY_BY_SOURCE, sourceLabels: sourceLabels, sourceKeys: RELEASE_MONITORING_SOURCE_KEYS, ...chartLabels }), _jsx(BigChartCard, { title: t('releases.detail.metric.p50Latency'), icon: _jsx(Timer, { className: "size-4", strokeWidth: 2.2 }), iconBg: "var(--src-prod-soft)", iconFg: "var(--src-prod-fg)", total: formatLatencyMs(timeseriesMax.latencyP50Ms), comparison: comparisonFromDelta(timeseriesMax.latencyP50Ms, stats?.latencyP50Ms.previous ?? 0, formatLatencyMs, vsPreviousPeriodLabel), data: pickTimeseries('latencyP50Ms'), yTickFormatter: formatLatencyMs, legendFormatter: formatLatencyMs, bySource: stats?.latencyP50Ms.bySource ?? EMPTY_BY_SOURCE, sourceLabels: sourceLabels, sourceKeys: RELEASE_MONITORING_SOURCE_KEYS, ...chartLabels }), _jsx(BigChartCard, { title: t('releases.detail.metric.p95Latency'), icon: _jsx(Timer, { className: "size-4", strokeWidth: 2.2 }), iconBg: "var(--status-running-bg)", iconFg: "var(--status-running-fg)", total: formatLatencyMs(timeseriesMax.latencyP95Ms), comparison: comparisonFromDelta(timeseriesMax.latencyP95Ms, stats?.latencyP95Ms.previous ?? 0, formatLatencyMs, vsPreviousPeriodLabel), data: pickTimeseries('latencyP95Ms'), yTickFormatter: formatLatencyMs, legendFormatter: formatLatencyMs, bySource: stats?.latencyP95Ms.bySource ?? EMPTY_BY_SOURCE, sourceLabels: sourceLabels, sourceKeys: RELEASE_MONITORING_SOURCE_KEYS, ...chartLabels }), _jsx(BigChartCard, { title: t('releases.detail.metric.p99Latency'), icon: _jsx(Timer, { className: "size-4", strokeWidth: 2.2 }), iconBg: "var(--status-pending-bg)", iconFg: "var(--status-pending-fg)", total: formatLatencyMs(timeseriesMax.latencyP99Ms), comparison: comparisonFromDelta(timeseriesMax.latencyP99Ms, stats?.latencyP99Ms.previous ?? 0, formatLatencyMs, vsPreviousPeriodLabel), data: pickTimeseries('latencyP99Ms'), yTickFormatter: formatLatencyMs, legendFormatter: formatLatencyMs, bySource: stats?.latencyP99Ms.bySource ?? EMPTY_BY_SOURCE, sourceLabels: sourceLabels, sourceKeys: RELEASE_MONITORING_SOURCE_KEYS, ...chartLabels }), _jsx(BigChartCard, { title: t('releases.detail.metric.cost'), icon: _jsx(CircleDollarSign, { className: "size-4", strokeWidth: 2.2 }), iconBg: "var(--status-running-bg)", iconFg: "var(--status-running-fg)", total: formatCostValue(timeseriesMax.cost), comparison: comparisonFromDelta(timeseriesMax.cost, stats?.cost.previous ?? 0, formatCostValue, vsPreviousPeriodLabel), subtitle: t('monitoring.delta.costSubtitle'), data: pickTimeseries('cost'), yTickFormatter: formatCostValue, legendFormatter: formatCostValue, bySource: stats?.cost.bySource ?? EMPTY_BY_SOURCE, sourceLabels: sourceLabels, sourceKeys: RELEASE_MONITORING_SOURCE_KEYS, ...chartLabels }), _jsx(BigChartCard, { title: t('releases.detail.metric.failureRate'), icon: _jsx(AlertTriangle, { className: "size-4", strokeWidth: 2.2 }), iconBg: "var(--status-pending-bg)", iconFg: "var(--status-pending-fg)", total: timeseriesMax.failureRatePercent.toFixed(2), unit: "%", comparison: comparisonFromDelta(timeseriesMax.failureRatePercent, failureRatePercent(stats, 'previous'), (value) => value.toFixed(2), vsPreviousPeriodLabel, '%'), subtitle: t('monitoring.delta.failureRateSubtitle'), data: pickFailureRateTimeseries(), yTickFormatter: formatPercentValue, legendFormatter: formatPercentValue, bySource: failureRateBySourcePercent(stats), sourceLabels: sourceLabels, sourceKeys: RELEASE_MONITORING_SOURCE_KEYS, sourceDistributionLabel: chartLabels.sourceDistributionLabel, totalLabel: chartLabels.failureRateTotalLabel })] })] })] }));
507
+ }
508
+ function VariantsPane({ line, releaseEvents, loading, }) {
509
+ const { t } = useI18n();
510
+ const formatDateTimeOrDash = useDateTimeOrDash();
511
+ const details = useMemo(() => buildReleaseVariantDetails(line, releaseEvents), [line, releaseEvents]);
512
+ const showLoader = useDelayedLoading(loading);
513
+ if (loading && details.length === 0) {
514
+ return showLoader ? _jsx(PlatformLoader, { className: "py-8", size: "sm" }) : null;
515
+ }
516
+ if (details.length === 0) {
517
+ return (_jsx("div", { className: "rounded-lg border bg-card p-10 text-center text-sm text-muted-foreground", children: t('releases.detail.variants.empty') }));
518
+ }
519
+ return (_jsx("section", { className: "space-y-3", "data-testid": "release-variants-pane", children: _jsx("div", { className: "grid grid-cols-1 gap-3 xl:grid-cols-2", children: details.map((detail) => (_jsxs("article", { className: "rounded-lg border bg-card p-4", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-mono text-[17px] font-semibold", children: detail.label }), _jsx(ReleaseVariantStageBadge, { stage: detail.stage })] }), _jsxs("div", { className: "mt-1 max-w-full truncate text-[12px] text-muted-foreground", children: [detail.promptName, " \u00B7 ", detail.promptVersionLabel ?? formatShortId(detail.promptVersionId), " \u00B7", ' ', detail.modelName ?? formatShortId(detail.modelId)] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void navigator.clipboard?.writeText(detail.id), children: [_jsx(Copy, { className: "size-3.5" }), t('releases.detail.variants.copyId')] }), _jsx(Button, { type: "button", variant: "outline", size: "sm", asChild: true, children: _jsxs(Link, { href: `/releases/${encodeURIComponent(line.id)}?tab=results&variant=${encodeURIComponent(detail.id)}`, children: [_jsx(Activity, { className: "size-3.5" }), t('releases.detail.variants.viewResults')] }) }), _jsx(Button, { type: "button", size: "sm", asChild: true, children: _jsxs(Link, { href: `/annotations/new?line=${encodeURIComponent(line.id)}&variant=${encodeURIComponent(detail.id)}`, children: [_jsx(ClipboardCheck, { className: "size-3.5" }), t('releases.detail.variants.newAnnotation')] }) })] })] }), _jsxs("dl", { className: "mt-4 grid grid-cols-2 gap-3 border-t pt-4 md:grid-cols-4", children: [_jsx(VariantMeta, { label: t('releases.detail.variants.promptVersion'), value: detail.promptVersionLabel ?? formatShortId(detail.promptVersionId) }), _jsx(VariantMeta, { label: t('releases.detail.variants.model'), value: detail.modelName ?? formatShortId(detail.modelId) }), _jsx(VariantMeta, { label: t('releases.detail.variants.provider'), value: detail.modelProvider ?? '—' }), _jsx(VariantMeta, { label: t('releases.detail.variants.updatedAt'), value: formatDateTimeOrDash(detail.updatedAt) }), _jsx(VariantMeta, { label: t('releases.detail.variants.productionEvents'), value: formatCount(detail.productionEventCount) }), _jsx(VariantMeta, { label: t('releases.detail.variants.canaryEvents'), value: formatCount(detail.canaryEventCount) }), _jsx(VariantMeta, { label: t('releases.detail.variants.processed'), value: formatCount(detail.totalProcessed) }), _jsx(VariantMeta, { label: t('releases.detail.variants.errors'), value: formatCount(detail.totalErrors) })] }), _jsxs("div", { className: "mt-4 border-t pt-4", children: [_jsx("div", { className: "mb-2 text-[12px] font-medium text-muted-foreground", children: t('releases.detail.variants.events') }), detail.events.length === 0 ? (_jsx("div", { className: "text-[12px] text-muted-foreground", children: t('releases.detail.variants.noEvents') })) : (_jsx("div", { className: "space-y-2", children: detail.events.slice(0, 5).map((event) => (_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2 text-[12px]", children: [_jsx(ReleaseEventPill, { event: event.operation }), _jsx("span", { className: "font-mono text-muted-foreground", children: t(event.laneType === 'production'
520
+ ? 'releases.detail.history.productionLane'
521
+ : 'releases.detail.history.canaryLane') }), _jsx("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: event.submitReason || event.status }), _jsx("span", { className: "font-mono text-[11.5px] text-muted-foreground", children: formatDateTimeOrDash(event.createdAt) })] }, event.id))) }))] }), _jsx("div", { className: "mt-4 border-t pt-3", children: _jsx("div", { className: "font-mono text-[11px] text-muted-foreground", children: detail.id }) })] }, detail.id))) }) }));
522
+ }
523
+ function VariantMeta({ label, value }) {
524
+ return (_jsxs("div", { className: "min-w-0", children: [_jsx("dt", { className: "truncate text-[11.5px] text-muted-foreground", children: label }), _jsx("dd", { className: "mt-1 truncate font-mono text-[12.5px] font-medium text-foreground", children: value })] }));
525
+ }
526
+ function ReleaseVariantStageBadge({ stage }) {
527
+ const { t } = useI18n();
528
+ const isProduction = stage === 'production' || stage === 'production_canary';
529
+ const isCanary = stage === 'canary' || stage === 'production_canary';
530
+ return (_jsx("span", { className: "inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium", style: {
531
+ background: isProduction ? 'var(--src-prod-soft)' : isCanary ? 'var(--src-canary-soft)' : 'var(--muted)',
532
+ color: isProduction ? 'var(--src-prod-fg)' : isCanary ? 'var(--src-canary-fg)' : 'var(--muted-foreground)',
533
+ borderColor: isProduction
534
+ ? 'color-mix(in srgb, var(--src-prod) 30%, transparent)'
535
+ : isCanary
536
+ ? 'color-mix(in srgb, var(--src-canary) 30%, transparent)'
537
+ : 'var(--border)',
538
+ }, children: t(`releases.detail.variants.stage.${stage}`) }));
539
+ }
540
+ function ResultsPane({ projectId, line, releaseEvents, initialReleaseVariantId, }) {
541
+ const { t } = useI18n();
542
+ const formatDateTimeOrDash = useDateTimeOrDash();
543
+ const [sourceFilter, setSourceFilter] = useState('all');
544
+ const [releaseVariantFilter, setReleaseVariantFilter] = useState(initialReleaseVariantId ?? 'all');
545
+ const [promptVersionFilter, setPromptVersionFilter] = useState('all');
546
+ const [pageIndex, setPageIndex] = useState(0);
547
+ const [pageSize, setPageSize] = useState(20);
548
+ const sourceIds = useMemo(() => getReleaseResultSourceIds(line, releaseEvents), [line, releaseEvents]);
549
+ const releaseVariantOptions = useMemo(() => getReleaseResultVariantOptions(line, releaseEvents), [line, releaseEvents]);
550
+ const promptVersionOptions = useMemo(() => getReleaseResultPromptVersionOptions(line, releaseEvents), [line, releaseEvents]);
551
+ const activeReleaseVariantFilter = releaseVariantFilter === 'all' || releaseVariantOptions.some((option) => option.id === releaseVariantFilter)
552
+ ? releaseVariantFilter
553
+ : 'all';
554
+ const activePromptVersionFilter = promptVersionFilter === 'all' || promptVersionOptions.some((option) => option.id === promptVersionFilter)
555
+ ? promptVersionFilter
556
+ : 'all';
557
+ const laneFilter = sourceFilter === 'all' ? undefined : [sourceFilter];
558
+ const releaseVariantIds = activeReleaseVariantFilter === 'all' ? undefined : [activeReleaseVariantFilter];
559
+ const promptVersionIds = activePromptVersionFilter === 'all' ? undefined : [activePromptVersionFilter];
560
+ const resultsQuery = useReleaseRunResults(projectId, {
561
+ page: pageIndex + 1,
562
+ pageSize,
563
+ sort: 'created_desc',
564
+ status: undefined,
565
+ judgmentStatus: undefined,
566
+ isCorrect: undefined,
567
+ sourceIds,
568
+ releaseVariantIds,
569
+ promptVersionIds,
570
+ lane: laneFilter,
571
+ }, sourceIds.length > 0);
572
+ const rows = resultsQuery.data?.data ?? [];
573
+ const resultsLoading = useDelayedLoading(resultsQuery.isLoading);
574
+ const total = resultsQuery.data?.total ?? 0;
575
+ const pageCount = Math.max(1, Math.ceil(total / pageSize));
576
+ const from = total === 0 ? 0 : pageIndex * pageSize + 1;
577
+ const to = Math.min((pageIndex + 1) * pageSize, total);
578
+ return (_jsxs("div", { className: "overflow-hidden rounded-lg border bg-card", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b px-4 py-3", children: [_jsx("div", { children: _jsx("h2", { className: "text-[14px] font-semibold", children: t('releases.detail.tab.results') }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("label", { className: "sr-only", htmlFor: "release-result-variant-filter", children: t('releases.detail.results.variant') }), _jsxs("select", { id: "release-result-variant-filter", name: "releaseVariantFilter", value: activeReleaseVariantFilter, onChange: (event) => {
579
+ setReleaseVariantFilter(event.currentTarget.value);
580
+ setPageIndex(0);
581
+ }, className: "h-9 rounded-md border bg-background px-3 text-[12px] font-medium text-foreground shadow-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", disabled: releaseVariantOptions.length === 0, children: [_jsx("option", { value: "all", children: t('releases.detail.results.variantFilter.all') }), releaseVariantOptions.map((option) => (_jsxs("option", { value: option.id, children: [option.label, " \u00B7 ", option.detail] }, option.id)))] }), _jsx("label", { className: "sr-only", htmlFor: "release-result-prompt-version-filter", children: t('releases.detail.results.promptVersion') }), _jsxs("select", { id: "release-result-prompt-version-filter", name: "promptVersionFilter", value: activePromptVersionFilter, onChange: (event) => {
582
+ setPromptVersionFilter(event.currentTarget.value);
583
+ setPageIndex(0);
584
+ }, className: "h-9 rounded-md border bg-background px-3 text-[12px] font-medium text-foreground shadow-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", disabled: promptVersionOptions.length === 0, children: [_jsx("option", { value: "all", children: t('releases.detail.results.promptVersionFilter.all') }), promptVersionOptions.map((option) => (_jsx("option", { value: option.id, children: option.label }, option.id)))] }), _jsx("div", { className: "inline-flex rounded-lg border bg-background p-1", children: ['all', 'production', 'canary'].map((value) => (_jsx("button", { type: "button", onClick: () => {
585
+ setSourceFilter(value);
586
+ setPageIndex(0);
587
+ }, className: cn('h-7 rounded-md px-3 text-[12px] font-medium transition-colors', sourceFilter === value ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground'), children: t(`releases.detail.results.sourceFilter.${value}`) }, value))) })] })] }), _jsxs(Table, { columns: RESULT_COLUMNS, children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { column: "externalId", children: t('releases.detail.results.externalId') }), _jsx(TableHead, { column: "input", children: t('releases.detail.results.input') }), _jsx(TableHead, { column: "output", children: t('releases.detail.results.output') }), _jsx(TableHead, { column: "source", children: t('releases.detail.results.source') }), _jsx(TableHead, { column: "variant", children: t('releases.detail.results.variant') }), _jsx(TableHead, { column: "latency", children: t('releases.detail.results.latency') }), _jsx(TableHead, { column: "tokens", children: t('releases.detail.results.tokens') }), _jsx(TableHead, { column: "createdAt", children: t('releases.detail.results.createdAt') })] }) }), _jsxs(TableBody, { children: [resultsLoading && rows.length === 0 ? (_jsx(TableEmpty, { children: _jsx(PlatformLoader, { className: "py-1", size: "sm" }) })) : null, resultsQuery.isError ? _jsx(TableEmpty, { children: t('releases.detail.results.loadFailed') }) : null, !resultsQuery.isLoading && !resultsQuery.isError && rows.length === 0 ? (_jsx(TableEmpty, { children: t('releases.detail.results.empty') })) : null, rows.map((row) => (_jsxs(TableRow, { children: [_jsx(TableCell, { column: "externalId", truncate: true, className: "font-mono text-[11.5px] text-muted-foreground", children: _jsx("span", { title: row.externalId ?? undefined, children: row.externalId ?? '—' }) }), _jsx(TableCell, { column: "input", truncate: 2, className: "text-[12px]", children: _jsx("span", { title: formatReleaseRunResultInput(row, 1000), children: formatReleaseRunResultInput(row, 220) }) }), _jsx(TableCell, { column: "output", truncate: 2, className: "text-[12px]", children: _jsx("span", { title: formatReleaseRunResultOutput(row, 1000), children: formatReleaseRunResultOutput(row, 220) }) }), _jsx(TableCell, { column: "source", children: _jsx(ReleaseRunResultLaneBadge, { lane: row.lane }) }), _jsx(TableCell, { column: "variant", className: "text-[12px]", children: _jsx(ReleaseRunResultVariant, { value: row }) }), _jsx(TableCell, { column: "latency", className: "font-mono text-[11.5px] text-muted-foreground", children: formatResultLatency(row.latencyMs) }), _jsx(TableCell, { column: "tokens", className: "font-mono text-[11.5px] text-muted-foreground", children: formatResultTokens(row) }), _jsx(TableCell, { column: "createdAt", className: "font-mono text-[11.5px] text-muted-foreground", children: formatDateTimeOrDash(row.createdAt) })] }, `${row.id}:${row.createdAt}`)))] })] }), _jsx(ResourcePaginationFooter, { summary: _jsx("span", { children: t('releases.detail.results.summary')
588
+ .replace('{from}', String(from))
589
+ .replace('{to}', String(to))
590
+ .replace('{total}', formatCount(total)) }), pageIndex: pageIndex, pageCount: pageCount, pageSize: pageSize, pageSizeOptions: RESULT_PAGE_SIZE_OPTIONS, previousPageLabel: t('common.previousPage'), nextPageLabel: t('common.nextPage'), onPageChange: setPageIndex, onPageSizeChange: (nextPageSize) => {
591
+ setPageSize(nextPageSize);
592
+ setPageIndex(0);
593
+ } })] }));
594
+ }
595
+ function getReleaseLineEventSourceIds(line, releaseEvents) {
596
+ const ids = [
597
+ ...releaseEvents.flatMap((event) => [
598
+ event.id,
599
+ event.sourceEventId,
600
+ event.supersedesEventId,
601
+ event.rollbackTargetEventId,
602
+ ]),
603
+ line.production?.currentEvent?.id,
604
+ line.production?.currentEvent?.sourceCanaryId,
605
+ line.canary?.id,
606
+ ...line.canaryHistory.map((canary) => canary.id),
607
+ ].filter((value) => Boolean(value));
608
+ return [...new Set(ids)];
609
+ }
610
+ function getReleaseLineEventSources(line, releaseEvents) {
611
+ const sources = new Set();
612
+ for (const event of releaseEvents) {
613
+ if (event.laneType === 'production')
614
+ sources.add('prod');
615
+ if (event.laneType === 'canary')
616
+ sources.add('canary');
617
+ }
618
+ if (line.production?.currentEvent)
619
+ sources.add('prod');
620
+ if (line.canary)
621
+ sources.add('canary');
622
+ if (sources.size === 0) {
623
+ sources.add('prod');
624
+ sources.add('canary');
625
+ }
626
+ return [...sources];
627
+ }
628
+ function getReleaseResultSourceIds(line, releaseEvents) {
629
+ return getReleaseLineEventSourceIds(line, releaseEvents);
630
+ }
631
+ function buildReleaseVariantDetails(line, releaseEvents) {
632
+ const baseById = new Map();
633
+ const eventsByVariant = new Map();
634
+ const addVariant = (variant) => {
635
+ baseById.set(variant.id, {
636
+ id: variant.id,
637
+ variantNumber: variant.variantNumber,
638
+ label: variant.label,
639
+ promptName: variant.promptName,
640
+ promptVersionId: variant.promptVersionId,
641
+ promptVersionLabel: variant.promptVersionLabel,
642
+ modelId: variant.modelId,
643
+ modelName: variant.modelName,
644
+ modelProvider: variant.modelProvider,
645
+ createdAt: variant.createdAt,
646
+ updatedAt: variant.updatedAt,
647
+ });
648
+ };
649
+ for (const variant of line.variants)
650
+ addVariant(variant);
651
+ for (const event of releaseEvents) {
652
+ if (!event.releaseVariantId)
653
+ continue;
654
+ const events = eventsByVariant.get(event.releaseVariantId) ?? [];
655
+ events.push(event);
656
+ eventsByVariant.set(event.releaseVariantId, events);
657
+ if (!baseById.has(event.releaseVariantId)) {
658
+ baseById.set(event.releaseVariantId, {
659
+ id: event.releaseVariantId,
660
+ variantNumber: event.releaseVariantNumber,
661
+ label: event.releaseVariantLabel ?? formatShortId(event.releaseVariantId),
662
+ promptName: event.promptName,
663
+ promptVersionId: event.promptVersionId,
664
+ promptVersionLabel: event.promptVersionLabel,
665
+ modelId: event.modelId,
666
+ modelName: event.modelName,
667
+ modelProvider: event.modelProvider,
668
+ createdAt: event.createdAt,
669
+ updatedAt: event.updatedAt,
670
+ });
671
+ }
672
+ }
673
+ const currentProductionVariantId = releaseEvents.find((event) => event.id === line.production?.currentEvent?.id)?.releaseVariantId ?? null;
674
+ const activeCanaryVariantId = line.canary?.releaseVariantId ??
675
+ releaseEvents.find((event) => event.id === line.canary?.id)?.releaseVariantId ??
676
+ null;
677
+ return [...baseById.values()]
678
+ .map((base) => {
679
+ const events = (eventsByVariant.get(base.id) ?? []).sort((left, right) => timeValue(right.createdAt) - timeValue(left.createdAt));
680
+ return {
681
+ ...base,
682
+ createdAt: minDateString([base.createdAt, ...events.map((event) => event.createdAt)]),
683
+ updatedAt: maxDateString([base.updatedAt, ...events.map((event) => event.updatedAt ?? event.createdAt)]),
684
+ stage: resolveReleaseVariantStage(base.id, currentProductionVariantId, activeCanaryVariantId, events),
685
+ events,
686
+ productionEventCount: events.filter((event) => event.laneType === 'production').length,
687
+ canaryEventCount: events.filter((event) => event.laneType === 'canary').length,
688
+ totalProcessed: events.reduce((sum, event) => sum + event.totalProcessed, 0),
689
+ totalErrors: events.reduce((sum, event) => sum + event.totalErrors, 0),
690
+ };
691
+ })
692
+ .sort((left, right) => {
693
+ if (left.variantNumber !== null && right.variantNumber !== null)
694
+ return left.variantNumber - right.variantNumber;
695
+ if (left.variantNumber !== null)
696
+ return -1;
697
+ if (right.variantNumber !== null)
698
+ return 1;
699
+ return left.label.localeCompare(right.label, undefined, { numeric: true });
700
+ });
701
+ }
702
+ function resolveReleaseVariantStage(releaseVariantId, currentProductionVariantId, activeCanaryVariantId, events) {
703
+ const isProduction = currentProductionVariantId === releaseVariantId ||
704
+ events.some((event) => event.laneType === 'production' && event.status === 'running');
705
+ const isCanary = activeCanaryVariantId === releaseVariantId ||
706
+ events.some((event) => event.laneType === 'canary' && (event.status === 'running' || event.status === 'stopped'));
707
+ if (isProduction && isCanary)
708
+ return 'production_canary';
709
+ if (isProduction)
710
+ return 'production';
711
+ if (isCanary)
712
+ return 'canary';
713
+ return 'history';
714
+ }
715
+ function minDateString(values) {
716
+ const dates = values.filter((value) => Boolean(value));
717
+ if (dates.length === 0)
718
+ return null;
719
+ return dates.reduce((min, value) => (timeValue(value) < timeValue(min) ? value : min));
720
+ }
721
+ function maxDateString(values) {
722
+ const dates = values.filter((value) => Boolean(value));
723
+ if (dates.length === 0)
724
+ return null;
725
+ return dates.reduce((max, value) => (timeValue(value) > timeValue(max) ? value : max));
726
+ }
727
+ function getReleaseResultVariantOptions(line, releaseEvents) {
728
+ const options = new Map();
729
+ const add = (input) => {
730
+ if (!input.id)
731
+ return;
732
+ const promptVersion = input.promptVersionLabel?.trim() || formatShortId(input.promptVersionId);
733
+ const model = input.modelName?.trim() || formatShortId(input.modelId);
734
+ options.set(input.id, {
735
+ id: input.id,
736
+ label: input.label?.trim() || formatShortId(input.id),
737
+ detail: `${promptVersion} · ${model}`,
738
+ });
739
+ };
740
+ for (const variant of line.variants) {
741
+ add({
742
+ id: variant.id,
743
+ label: variant.label,
744
+ promptVersionLabel: variant.promptVersionLabel,
745
+ promptVersionId: variant.promptVersionId,
746
+ modelName: variant.modelName,
747
+ modelId: variant.modelId,
748
+ });
749
+ }
750
+ for (const event of releaseEvents) {
751
+ add({
752
+ id: event.releaseVariantId,
753
+ label: event.releaseVariantLabel,
754
+ promptVersionLabel: event.promptVersionLabel,
755
+ promptVersionId: event.promptVersionId,
756
+ modelName: event.modelName,
757
+ modelId: event.modelId,
758
+ });
759
+ }
760
+ return [...options.values()].sort((left, right) => left.label.localeCompare(right.label, undefined, { numeric: true }));
761
+ }
762
+ function getReleaseResultPromptVersionOptions(line, releaseEvents) {
763
+ const options = new Map();
764
+ const add = (id, label) => {
765
+ if (!id)
766
+ return;
767
+ options.set(id, label?.trim() || formatShortId(id));
768
+ };
769
+ add(line.production?.currentEvent?.promptVersionId, line.productionVersionLabel);
770
+ add(line.canary?.promptVersionId, line.canaryVersionLabel);
771
+ for (const event of releaseEvents) {
772
+ add(event.promptVersionId, event.promptVersionLabel);
773
+ }
774
+ return [...options.entries()].map(([id, label]) => ({ id, label }));
775
+ }
776
+ function formatReleaseRunResultInput(row, maxLength) {
777
+ return compactReleaseRunResultValue(row.inputVariables, maxLength);
778
+ }
779
+ function formatReleaseRunResultOutput(row, maxLength) {
780
+ return compactReleaseRunResultValue(row.parsedOutput ?? parseMaybeJson(row.rawResponse) ?? row.rawResponse ?? row.decisionOutput ?? row.errorMessage, maxLength);
781
+ }
782
+ function formatReleaseRunResultPromptVersion(row) {
783
+ return row.promptVersionNumber ? `v${row.promptVersionNumber}` : formatShortId(row.promptVersionId);
784
+ }
785
+ function compactReleaseRunResultValue(value, maxLength) {
786
+ const formatted = formatReleaseRunResultValue(value).replace(/\s+/g, ' ').trim();
787
+ if (formatted.length <= maxLength)
788
+ return formatted;
789
+ return `${formatted.slice(0, Math.max(0, maxLength - 1))}…`;
790
+ }
791
+ function formatReleaseRunResultValue(value) {
792
+ if (value === null || value === undefined)
793
+ return '—';
794
+ if (typeof value === 'string')
795
+ return value.trim() || '—';
796
+ if (typeof value === 'number' || typeof value === 'boolean')
797
+ return String(value);
798
+ if (Array.isArray(value)) {
799
+ if (value.length === 0)
800
+ return '—';
801
+ return value.map(formatReleaseRunResultValue).join(', ');
802
+ }
803
+ if (typeof value === 'object') {
804
+ const entries = Object.entries(value);
805
+ if (entries.length === 0)
806
+ return '—';
807
+ return entries.map(([key, item]) => `${key}: ${formatReleaseRunResultValue(item)}`).join(' · ');
808
+ }
809
+ return String(value);
810
+ }
811
+ function parseMaybeJson(value) {
812
+ if (!value)
813
+ return null;
814
+ const trimmed = value.trim();
815
+ if (!trimmed.startsWith('{') && !trimmed.startsWith('['))
816
+ return null;
817
+ try {
818
+ return JSON.parse(trimmed);
819
+ }
820
+ catch {
821
+ return null;
822
+ }
823
+ }
824
+ function formatResultLatency(value) {
825
+ if (value === null || !Number.isFinite(value))
826
+ return '—';
827
+ if (value < 1000)
828
+ return `${Math.round(value)}ms`;
829
+ const seconds = value / 1000;
830
+ return `${seconds >= 10 ? seconds.toFixed(1) : seconds.toFixed(2)}s`;
831
+ }
832
+ function formatResultTokens(row) {
833
+ const input = row.inputTokens ?? 0;
834
+ const output = row.outputTokens ?? 0;
835
+ const total = input + output;
836
+ return total > 0 ? formatCount(total) : '—';
837
+ }
838
+ function formatShortId(value) {
839
+ return value ? value.slice(0, 8) : '—';
840
+ }
841
+ function ReleaseRunResultLaneBadge({ lane }) {
842
+ const { t } = useI18n();
843
+ const isProduction = lane === 'production';
844
+ return (_jsx("span", { className: "inline-flex items-center rounded-full border px-2 py-0.5 font-mono text-[10.5px] font-semibold leading-4", style: {
845
+ background: isProduction ? 'var(--src-prod-soft)' : 'var(--src-canary-soft)',
846
+ color: isProduction ? 'var(--src-prod-fg)' : 'var(--src-canary-fg)',
847
+ borderColor: isProduction
848
+ ? 'color-mix(in srgb, var(--src-prod) 30%, transparent)'
849
+ : 'color-mix(in srgb, var(--src-canary) 30%, transparent)',
850
+ }, children: t(isProduction ? 'releases.detail.results.lane.production' : 'releases.detail.results.lane.canary') }));
851
+ }
852
+ function ReleaseRunResultVariant({ value }) {
853
+ const label = value.releaseVariantLabel ?? formatShortId(value.releaseVariantId);
854
+ const promptVersion = formatReleaseRunResultPromptVersion(value);
855
+ const model = value.modelName ?? formatShortId(value.modelId);
856
+ return (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-mono text-[11.5px] font-semibold", children: label }), _jsxs("div", { className: "mt-0.5 truncate text-[11.5px] text-muted-foreground", children: [promptVersion, " \u00B7 ", model] })] }));
857
+ }
858
+ function QualityMetricsPane({ projectId, line }) {
859
+ const { t } = useI18n();
860
+ const annotationTasksQuery = useAnnotationTaskList(projectId);
861
+ const annotationTasksLoading = useDelayedLoading(annotationTasksQuery.isLoading);
862
+ const lineTasks = useMemo(() => (annotationTasksQuery.data?.data ?? []).filter((task) => task.releaseLineId === line.id), [annotationTasksQuery.data, line.id]);
863
+ const points = useMemo(() => buildAnnotationQualityPoints(lineTasks), [lineTasks]);
864
+ const latest = points[points.length - 1] ?? null;
865
+ const submitted = lineTasks.reduce((sum, task) => sum + task.progress.submitted, 0);
866
+ const matched = lineTasks.reduce((sum, task) => sum + (task.quality?.matched ?? 0), 0);
867
+ const mismatched = lineTasks.reduce((sum, task) => sum + (task.quality?.mismatched ?? 0), 0);
868
+ const judged = matched + mismatched;
869
+ const aggregateScore = judged > 0 ? toPercentPoint(matched / judged) : null;
870
+ const annotationHref = `/annotations/new?line=${encodeURIComponent(line.id)}`;
871
+ return (_jsxs("div", { className: "space-y-4", "data-testid": "release-quality-metrics-pane", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("h2", { className: "text-[14px] font-semibold", children: t('releases.detail.quality.title') }), _jsx(Button, { asChild: true, children: _jsxs(Link, { href: annotationHref, children: [_jsx(ClipboardCheck, { className: "size-4" }), t('releases.detail.action.newAnnotation')] }) })] }), annotationTasksLoading && lineTasks.length === 0 ? (_jsx(PlatformLoader, { className: "rounded-lg border bg-card py-10", size: "sm" })) : null, annotationTasksQuery.isError ? (_jsx("div", { className: "rounded-lg border bg-card px-4 py-3 text-sm text-destructive", children: t('releases.detail.quality.loadFailed') })) : null, !annotationTasksQuery.isLoading && !annotationTasksQuery.isError && lineTasks.length === 0 ? (_jsxs("div", { className: "rounded-lg border bg-card p-10 text-center", "data-testid": "release-quality-empty", children: [_jsx("div", { className: "text-[15px] font-semibold", children: t('releases.detail.quality.empty') }), _jsx(Button, { className: "mt-5", asChild: true, children: _jsxs(Link, { href: annotationHref, children: [_jsx(ClipboardCheck, { className: "size-4" }), t('releases.detail.action.newAnnotation')] }) })] })) : null, lineTasks.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-5", children: [_jsx(ReleaseMetricCard, { label: t('releases.detail.quality.matchRate'), value: formatQualityPercent(aggregateScore), detail: t('releases.detail.quality.tasksCount').replace('{count}', formatCount(lineTasks.length)), tone: "canary" }), _jsx(ReleaseMetricCard, { label: t('releases.detail.quality.latestMatchRate'), value: formatQualityPercent(latest?.score), detail: latest?.name ?? t('common.noData') }), _jsx(ReleaseMetricCard, { label: t('releases.detail.quality.matched'), value: formatCount(matched), detail: t('releases.detail.quality.matchedHint') }), _jsx(ReleaseMetricCard, { label: t('releases.detail.quality.mismatched'), value: formatCount(mismatched), detail: t('releases.detail.quality.mismatchedHint') }), _jsx(ReleaseMetricCard, { label: t('releases.detail.quality.submitted'), value: formatCount(submitted), detail: t('releases.detail.quality.submittedHint') })] }), points.length > 0 ? (_jsxs("div", { className: "rounded-lg border bg-card p-4", children: [_jsxs("div", { className: "mb-4 flex flex-wrap items-center justify-between gap-3", children: [_jsx("div", { children: _jsx("div", { className: "text-[14px] font-semibold", children: t('releases.detail.quality.chartTitle') }) }), _jsx(QualityLegend, {})] }), _jsx(QualityMetricsChart, { data: points })] })) : (_jsx("div", { className: "rounded-lg border bg-card p-8 text-center text-sm text-muted-foreground", children: t('releases.detail.quality.noComparable') }))] })) : null] }));
872
+ }
873
+ function QualityLegend() {
874
+ const { t } = useI18n();
875
+ const items = [
876
+ { key: 'score', label: t('releases.detail.quality.matchRate') },
877
+ ];
878
+ return (_jsx("div", { className: "flex flex-wrap items-center gap-3 text-[11.5px] text-muted-foreground", children: items.map((item) => (_jsxs("div", { className: "inline-flex items-center gap-1.5", children: [_jsx("span", { className: "size-2 rounded-full", style: { background: QUALITY_LINE_COLORS[item.key] }, "aria-hidden": "true" }), _jsx("span", { children: item.label })] }, item.key))) }));
879
+ }
880
+ function QualityMetricsChart({ data }) {
881
+ const { t } = useI18n();
882
+ const metricLabels = useMemo(() => ({
883
+ score: t('releases.detail.quality.matchRate'),
884
+ }), [t]);
885
+ return (_jsx("div", { className: "h-[320px] min-w-0 w-full", children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", minWidth: 1, minHeight: 1, initialDimension: { width: 960, height: 320 }, children: _jsxs(RechartsLineChart, { data: data, margin: { top: 12, right: 16, bottom: 8, left: 0 }, children: [_jsx(CartesianGrid, { strokeDasharray: "2 3", vertical: false, stroke: "var(--border)" }), _jsx(XAxis, { dataKey: "x", axisLine: false, tickLine: false, tick: {
886
+ fontSize: 10,
887
+ fontFamily: 'JetBrains Mono, ui-monospace, monospace',
888
+ fill: 'var(--muted-foreground)',
889
+ } }), _jsx(YAxis, { axisLine: false, tickLine: false, domain: [0, 100], tick: {
890
+ fontSize: 10,
891
+ fontFamily: 'JetBrains Mono, ui-monospace, monospace',
892
+ fill: 'var(--muted-foreground)',
893
+ }, tickFormatter: (value) => `${value}%`, width: 42 }), _jsx(Tooltip, { cursor: { stroke: 'var(--border)', strokeDasharray: '4 4' }, content: (props) => (_jsx(QualityChartTooltip, { ...props, metricLabels: metricLabels, submittedLabel: t('releases.detail.quality.submitted') })) }), ['score'].map((key) => (_jsx(Line, { type: "monotone", dataKey: key, name: metricLabels[key], stroke: QUALITY_LINE_COLORS[key], strokeWidth: 2, dot: { r: 3, strokeWidth: 1.5 }, activeDot: { r: 4 }, isAnimationActive: false }, key)))] }) }) }));
894
+ }
895
+ function QualityChartTooltip({ active, payload, label, metricLabels, submittedLabel, }) {
896
+ const { t } = useI18n();
897
+ const formatDateTimeOrDash = useDateTimeOrDash();
898
+ if (!active || !payload || payload.length === 0)
899
+ return null;
900
+ const point = payload[0]?.payload;
901
+ if (!point)
902
+ return null;
903
+ return (_jsxs("div", { className: "min-w-[220px] rounded-md border bg-popover px-2.5 py-2 text-[12px] shadow-md", children: [_jsxs("div", { className: "mb-1 font-mono text-[10.5px] text-muted-foreground", children: [label, " \u00B7 ", formatDateTimeOrDash(point.updatedAt)] }), _jsx("div", { className: "font-semibold", children: point.name }), _jsxs("div", { className: "mt-0.5 text-[11.5px] text-muted-foreground", children: [point.releaseVariantLabel, " \u00B7 ", point.promptVersionLabel, " \u00B7 ", point.modelName] }), _jsxs("div", { className: "mt-2 space-y-0.5", children: [['score'].map((key) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "size-2 rounded-full", style: { background: QUALITY_LINE_COLORS[key] }, "aria-hidden": true }), _jsx("span", { className: "text-muted-foreground", children: metricLabels[key] }), _jsx("span", { className: "ml-auto font-mono", children: formatQualityPercent(point[key]) })] }, key))), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: t('releases.detail.quality.matched') }), _jsx("span", { className: "ml-auto font-mono", children: formatCount(point.matched) })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: t('releases.detail.quality.mismatched') }), _jsx("span", { className: "ml-auto font-mono", children: formatCount(point.mismatched) })] }), _jsxs("div", { className: "mt-1 flex items-center gap-2 border-t pt-1", children: [_jsx("span", { className: "text-muted-foreground", children: submittedLabel }), _jsxs("span", { className: "ml-auto font-mono", children: [formatCount(point.submitted), " / ", formatCount(point.total)] })] })] })] }));
904
+ }
905
+ function buildReleaseEventMeta(event) {
906
+ const parts = [
907
+ event.status,
908
+ event.trafficRatio !== null ? `${Math.round(event.trafficRatio * 100)}%` : null,
909
+ event.trafficMode,
910
+ event.submitReason,
911
+ ].filter((value) => Boolean(value));
912
+ return parts.join(' · ') || event.id;
913
+ }
914
+ function formatReleaseEventVariant(event) {
915
+ if (!event.releaseVariantId)
916
+ return null;
917
+ const label = event.releaseVariantLabel ?? formatShortId(event.releaseVariantId);
918
+ const promptVersion = event.promptVersionLabel ?? formatShortId(event.promptVersionId);
919
+ const model = event.modelName ?? formatShortId(event.modelId);
920
+ return `${label} · ${promptVersion} · ${model}`;
921
+ }
922
+ function HistoryPane({ line, productionHistory, releaseEvents, loading, }) {
923
+ const { t } = useI18n();
924
+ const formatDateTimeOrDash = useDateTimeOrDash();
925
+ const items = useMemo(() => {
926
+ if (releaseEvents.length > 0) {
927
+ return releaseEvents.map((event) => ({
928
+ id: event.id,
929
+ event: event.operation,
930
+ title: `${event.laneType === 'production' ? t('releases.detail.history.productionLane') : t('releases.detail.history.canaryLane')} · ${event.promptVersionLabel ?? event.id.slice(0, 8)}`,
931
+ createdAt: event.createdAt,
932
+ meta: buildReleaseEventMeta(event),
933
+ variant: formatReleaseEventVariant(event),
934
+ }));
935
+ }
936
+ const prod = productionHistory.map((item) => ({
937
+ id: item.id,
938
+ event: item.eventType,
939
+ title: item.promptVersionLabel ?? item.id.slice(0, 8),
940
+ createdAt: item.createdAt,
941
+ meta: item.submitReason || item.status,
942
+ variant: null,
943
+ }));
944
+ const canary = line.canary
945
+ ? [
946
+ {
947
+ id: line.canary.id,
948
+ event: line.canary.status === 'running' ? 'ratio_change' : 'create_canary',
949
+ title: `${line.canary.promptVersionLabel ?? line.canary.id.slice(0, 8)} · ${Math.round(line.canary.trafficRatio * 100)}%`,
950
+ createdAt: line.canary.updatedAt,
951
+ meta: line.canary.description ?? line.canary.status,
952
+ variant: line.canary.releaseVariantLabel
953
+ ? `${line.canary.releaseVariantLabel} · ${line.canary.promptVersionLabel ?? '-'} · ${line.canary.modelName ?? '-'}`
954
+ : null,
955
+ },
956
+ ]
957
+ : [];
958
+ return [...canary, ...prod].sort((left, right) => (right.createdAt ? Date.parse(right.createdAt) : 0) - (left.createdAt ? Date.parse(left.createdAt) : 0));
959
+ }, [line.canary, productionHistory, releaseEvents, t]);
960
+ const showLoader = useDelayedLoading(loading);
961
+ if (loading) {
962
+ return showLoader ? _jsx(PlatformLoader, { className: "py-8", size: "sm" }) : null;
963
+ }
964
+ if (items.length === 0) {
965
+ return (_jsx("div", { className: "rounded-lg border bg-card p-10 text-center text-sm text-muted-foreground", children: t('releases.detail.history.empty') }));
966
+ }
967
+ return (_jsxs("div", { className: "relative space-y-3 pl-8", children: [_jsx("div", { className: "absolute bottom-0 left-[11px] top-0 w-0.5 bg-border" }), items.map((item, index) => (_jsxs("div", { className: "relative rounded-lg border bg-card", children: [_jsx("div", { className: "absolute left-[-27px] top-[18px] size-3.5 rounded-full border-2", style: {
968
+ background: index === 0 ? 'var(--status-canary-dot)' : 'var(--card)',
969
+ borderColor: index === 0 ? 'var(--status-canary-dot)' : 'var(--border)',
970
+ boxShadow: index === 0 ? '0 0 0 4px color-mix(in srgb, var(--status-canary-dot) 25%, transparent)' : undefined,
971
+ } }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b px-4 py-3", children: [_jsx(ReleaseEventPill, { event: item.event }), _jsx("span", { className: "text-[14px] font-semibold", children: item.title }), _jsxs("span", { className: "ml-auto font-mono text-[11.5px] text-muted-foreground", children: [formatDateTimeOrDash(item.createdAt), " \u00B7 ", item.id.slice(0, 8)] })] }), _jsxs("div", { className: "space-y-2 px-4 py-3 text-[12.5px] text-muted-foreground", children: [item.variant ? (_jsxs("div", { children: [_jsx("span", { className: "font-medium text-foreground", children: t('releases.detail.history.variant') }), _jsx("span", { className: "ml-2 font-mono", children: item.variant })] })) : null, _jsx("div", { children: item.meta })] })] }, item.id)))] }));
972
+ }
973
+ //# sourceMappingURL=release-line-detail-page.js.map