@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,732 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import Link from 'next/link';
4
+ import { useRouter } from 'next/navigation';
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
+ import { AlertTriangle, Calculator, Check, ChevronRight, Database, FileText, FlaskConical, Image as ImageIcon, Loader2, Play, Plus, Search, Sparkles, X, } from 'lucide-react';
7
+ import { Button, Input, cn } from '@proofhound/ui';
8
+ import { Main } from '@proofhound/ui/layout';
9
+ import { PromptVersionPickerRow, PromptVersionPickerTag, PromptLanguageSelect } from '../../components';
10
+ import { useI18n } from '../../i18n';
11
+ import { formatLatencySeconds, getApiErrorMessage, isProjectNameTaken } from '../../lib';
12
+ import { useOptimizations, useCreateOptimization } from '../../hooks';
13
+ import { useDatasets } from '../../hooks';
14
+ import { useExperiments } from '../../hooks';
15
+ import { useProjectModels } from '../../hooks';
16
+ import { usePrompt, usePrompts } from '../../hooks';
17
+ import { useDateTimeFormatter, useDelayedLoading } from '../../hooks';
18
+ import { composePromptPreview } from '../prompts/prompt-preview';
19
+ import { renderPromptPreviewParts } from '../prompts/prompt-preview-parts';
20
+ import { VARIABLE_TONE_CLASSES } from '../prompts/prompt-ui';
21
+ import { DEFAULT_PROMPT_LANGUAGE, } from '@proofhound/shared';
22
+ import { optimizationTone } from './optimization-theme';
23
+ const METRIC_LABEL_KEY = {
24
+ accuracy: 'optimizations.new.optimization.metric.accuracy',
25
+ precision: 'optimizations.new.optimization.metric.precision',
26
+ recall: 'optimizations.new.optimization.metric.recall',
27
+ };
28
+ const COMPARATOR_LABEL_KEY = {
29
+ gte: 'optimizations.new.optimization.comparator.gte',
30
+ gt: 'optimizations.new.optimization.comparator.gt',
31
+ lte: 'optimizations.new.optimization.comparator.lte',
32
+ };
33
+ const PROMPT_LANGUAGE_LABEL_KEY = {
34
+ 'zh-CN': 'promptLanguage.zhCN',
35
+ 'en-US': 'promptLanguage.enUS',
36
+ };
37
+ const PROMPT_STATUS_LABEL_KEY = {
38
+ editable: 'optimizations.new.origin.promptStatus.editable',
39
+ frozen: 'optimizations.new.origin.promptStatus.frozen',
40
+ };
41
+ // ---------------------------------------------------------------------------
42
+ // helpers
43
+ // ---------------------------------------------------------------------------
44
+ function formatTemplate(template, values) {
45
+ return template.replace(/\{(\w+)\}/g, (_, key) => {
46
+ const value = values[key];
47
+ return value === undefined ? `{${key}}` : String(value);
48
+ });
49
+ }
50
+ function formatThousand(value) {
51
+ return value.toLocaleString('en-US').replace(/,/g, ' ');
52
+ }
53
+ function defaultName() {
54
+ const now = new Date();
55
+ const yyyy = now.getFullYear();
56
+ const mm = String(now.getMonth() + 1).padStart(2, '0');
57
+ const dd = String(now.getDate()).padStart(2, '0');
58
+ return `optm-${yyyy}-${mm}${dd}-`;
59
+ }
60
+ function formatPrice(value) {
61
+ if (!Number.isFinite(value))
62
+ return '0';
63
+ if (value === 0)
64
+ return '0';
65
+ if (value >= 1)
66
+ return value
67
+ .toFixed(2)
68
+ .replace(/\.00$/, '')
69
+ .replace(/(\.\d)0$/, '$1');
70
+ return value.toFixed(3).replace(/0+$/, '').replace(/\.$/, '');
71
+ }
72
+ function mapPromptVersionToOption(prompt, version, formatDateTime) {
73
+ const promptLanguage = version.promptLanguage ?? DEFAULT_PROMPT_LANGUAGE;
74
+ return {
75
+ id: version.id,
76
+ name: prompt.name,
77
+ version: `v${version.versionNumber}`,
78
+ promptLanguage,
79
+ isLatest: version.versionNumber === prompt.latestVersionNumber,
80
+ isOnline: version.versionNumber === prompt.currentOnlineVersionNumber,
81
+ status: version.status,
82
+ updatedAt: formatDateTime(version.createdAt),
83
+ variables: version.variables.map((variable) => ({
84
+ name: variable.name,
85
+ type: variable.type,
86
+ required: variable.required,
87
+ })),
88
+ promptPreview: composePromptPreview({
89
+ body: version.body,
90
+ outputSchema: version.outputSchema,
91
+ promptLanguage,
92
+ }),
93
+ template: version.body,
94
+ };
95
+ }
96
+ // model.{rpm|tpm|concurrency}.limit = -1 means unlimited
97
+ function formatModelLimit(limit) {
98
+ return limit < 0 ? '∞' : formatThousand(limit);
99
+ }
100
+ // expected_output is the ground truth (used for judgment); it is neither a prompt input field nor
101
+ // metadata — injecting it into the prompt would leak the answer; backend toLoopFieldWhitelist also strips it as a backstop.
102
+ function deriveDatasetFields(dataset) {
103
+ if (!dataset)
104
+ return { inputs: [], metas: [] };
105
+ const inputs = [];
106
+ const metas = [];
107
+ for (const field of dataset.fieldSchema) {
108
+ if (field.role === 'expected_output')
109
+ continue;
110
+ if (field.role === 'metadata')
111
+ metas.push(field);
112
+ else
113
+ inputs.push(field);
114
+ }
115
+ return { inputs, metas };
116
+ }
117
+ function classScopes(dataset) {
118
+ if (!dataset)
119
+ return [];
120
+ return dataset.categoryDistribution.categories.map((category) => category.label);
121
+ }
122
+ // ---- Lightweight metric formatters used inside the detail panel (not extracted; the experiment detail page has a similar one with a different signature) ----
123
+ function formatMetricFraction(value, digits = 3) {
124
+ if (typeof value !== 'number' || !Number.isFinite(value))
125
+ return '—';
126
+ return value.toFixed(digits);
127
+ }
128
+ function formatMetricInteger(value) {
129
+ if (typeof value !== 'number' || !Number.isFinite(value))
130
+ return '—';
131
+ return value.toLocaleString('en-US');
132
+ }
133
+ function formatMetricCost(value) {
134
+ if (typeof value !== 'number' || !Number.isFinite(value))
135
+ return '—';
136
+ if (value === 0)
137
+ return '$0';
138
+ if (value < 0.01)
139
+ return `$${value.toFixed(4)}`;
140
+ return `$${value.toFixed(2)}`;
141
+ }
142
+ function formatMetricLatencyMs(value) {
143
+ if (typeof value !== 'number' || !Number.isFinite(value))
144
+ return '—';
145
+ return `${formatLatencySeconds(value, 2)}s`;
146
+ }
147
+ // ---------------------------------------------------------------------------
148
+ // shared primitives
149
+ // ---------------------------------------------------------------------------
150
+ function Radio({ checked }) {
151
+ return (_jsx("span", { "aria-hidden": "true", className: cn('mt-0.5 inline-flex size-4 flex-none items-center justify-center rounded-full border', checked ? 'border-primary bg-primary/10' : 'border-border bg-background'), children: checked && _jsx("span", { className: "size-2 rounded-full bg-primary" }) }));
152
+ }
153
+ function FieldCheckbox({ checked, locked, ariaLabel, onClick, }) {
154
+ return (_jsx("button", { type: "button", role: "checkbox", "aria-checked": checked, "aria-disabled": locked || undefined, "aria-label": ariaLabel, disabled: locked, onClick: onClick, className: cn('inline-flex size-3.5 flex-none items-center justify-center rounded-[3px] border transition-colors', locked
155
+ ? cn(optimizationTone.positive.border, optimizationTone.positive.bg, optimizationTone.positive.text)
156
+ : checked
157
+ ? 'border-primary bg-primary text-primary-foreground'
158
+ : 'border-foreground/50 bg-background'), children: (checked || locked) && _jsx(Check, { className: "size-2.5", strokeWidth: 3 }) }));
159
+ }
160
+ function StepIndicator({ step, state, title, detail, }) {
161
+ return (_jsxs("div", { className: cn('flex min-w-0 items-center gap-2.5 rounded-md border bg-card px-3 py-2', state === 'current' && cn('border-primary/40 bg-primary/5'), state === 'done' && cn(optimizationTone.positive.border, optimizationTone.positive.bg)), children: [_jsx("span", { className: cn('inline-flex size-5 flex-none items-center justify-center rounded-full border font-mono text-[11px] font-semibold', state === 'current'
162
+ ? 'border-primary bg-primary text-primary-foreground'
163
+ : state === 'done'
164
+ ? cn(optimizationTone.positive.pill, 'border-transparent')
165
+ : 'border-border bg-muted text-muted-foreground'), children: state === 'done' ? _jsx(Check, { className: "size-3" }) : step }), _jsxs("div", { className: "min-w-0 leading-tight", children: [_jsx("div", { className: "text-[12.5px] font-semibold", children: title }), _jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: detail })] })] }));
166
+ }
167
+ function StepAnchor({ index }) {
168
+ return (_jsx("span", { "aria-hidden": "true", className: cn('inline-flex size-5 items-center justify-center rounded-full border font-mono text-[11px]', optimizationTone.info.pill), children: index }));
169
+ }
170
+ function SubSectionHead({ tone, label }) {
171
+ return (_jsxs("div", { className: "mb-3 flex items-center gap-2 text-[9.5px] font-bold uppercase tracking-[0.08em] text-muted-foreground", children: [_jsx("span", { "aria-hidden": "true", className: cn('inline-block h-2.5 w-[3px] rounded-[1.5px]', tone === 'info' ? optimizationTone.info.fill : optimizationTone.positive.fill) }), label] }));
172
+ }
173
+ function MiniSearch({ value, onChange, placeholder, }) {
174
+ return (_jsxs("div", { className: "sticky top-0 z-10 flex items-center gap-2 border-b bg-muted/60 px-3 py-2 backdrop-blur", children: [_jsx(Search, { className: "size-3.5 flex-none text-muted-foreground", "aria-hidden": "true" }), _jsx("input", { value: value, onChange: (event) => onChange(event.target.value), placeholder: placeholder, className: "h-6 w-full min-w-0 bg-transparent text-[12.5px] outline-none placeholder:text-muted-foreground" })] }));
175
+ }
176
+ function Tag({ children, tone = 'neutral', }) {
177
+ return (_jsx("span", { className: cn('inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 font-mono text-[10.5px]', tone === 'info' && optimizationTone.info.pill, tone === 'positive' && optimizationTone.positive.pill, tone === 'warning' && optimizationTone.warning.pill, tone === 'neutral' && 'border-border bg-muted text-muted-foreground'), children: children }));
178
+ }
179
+ function PickerEmpty({ children }) {
180
+ return _jsx("div", { className: "px-3 py-6 text-center text-[12px] text-muted-foreground", children: children });
181
+ }
182
+ function Slider({ value, min, max, step = 1, formatValue, onChange, }) {
183
+ return (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("input", { type: "range", min: min, max: max, step: step, value: value, onChange: (event) => onChange(Number(event.target.value)), className: "flex-1 accent-primary" }), _jsx("span", { className: "w-16 text-right font-mono text-[13.5px] font-semibold tabular-nums", children: formatValue(value) })] }));
184
+ }
185
+ // ---------------------------------------------------------------------------
186
+ // origin tile
187
+ // ---------------------------------------------------------------------------
188
+ function ModeTile({ active, icon: Icon, title, description, onClick, testId, }) {
189
+ return (_jsxs("button", { type: "button", onClick: onClick, "aria-pressed": active, "data-testid": testId, className: cn('flex w-full flex-col gap-1.5 rounded-md border px-3.5 py-3 text-left transition-colors', active
190
+ ? cn(optimizationTone.info.border, 'bg-[color-mix(in_oklab,var(--status-canary-bg)_55%,var(--background))]', 'shadow-[0_0_0_1px_var(--status-canary-bd)]')
191
+ : 'border-border bg-background hover:bg-muted/40'), children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { "aria-hidden": "true", className: cn('inline-flex size-6 items-center justify-center rounded-[7px] border', active
192
+ ? cn(optimizationTone.info.pill, 'border-transparent')
193
+ : 'border-border bg-secondary text-muted-foreground'), children: _jsx(Icon, { className: "size-3.5" }) }), _jsx("span", { className: "text-[13.5px] font-semibold", children: title })] }), _jsx("span", { className: "text-[12px] leading-snug text-muted-foreground", children: description })] }));
194
+ }
195
+ // ---------------------------------------------------------------------------
196
+ // option rows (per-resource)
197
+ // ---------------------------------------------------------------------------
198
+ // ---- Experiment info panel: right column full profile (basic / quality / run params / engineering metrics / per-class) ----
199
+ function MetricCard({ label, value, highlight }) {
200
+ return (_jsxs("div", { className: cn('flex flex-col gap-0.5 rounded-md border bg-card px-3 py-2', highlight && 'border-primary/40 bg-primary/5'), children: [_jsx("span", { className: "font-mono text-[10px] uppercase tracking-wide text-muted-foreground", children: label }), _jsx("span", { className: "font-mono text-[15px] font-semibold tabular-nums text-foreground", children: value })] }));
201
+ }
202
+ function SpecLine({ label, value }) {
203
+ return (_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "min-w-[64px] flex-none text-[11px] text-muted-foreground", children: label }), _jsx("span", { className: "min-w-0 break-all font-mono text-[12px] text-foreground", children: value })] }));
204
+ }
205
+ function SectionDetails({ title, defaultOpen = false, children, testid, }) {
206
+ return (_jsxs("details", { className: "group rounded-md border bg-card", open: defaultOpen, "data-testid": testid, children: [_jsxs("summary", { className: "flex cursor-pointer select-none items-center gap-2 px-3 py-2 text-[10.5px] font-bold uppercase tracking-wide text-muted-foreground", children: [_jsx(ChevronRight, { className: "size-3.5 transition-transform group-open:rotate-90", "aria-hidden": "true" }), title] }), _jsx("div", { className: "border-t bg-background px-3 py-3", children: children })] }));
207
+ }
208
+ function ExperimentDetailPanel({ experiment, modelMissing, }) {
209
+ const { t } = useI18n();
210
+ const { formatDateTime } = useDateTimeFormatter();
211
+ if (!experiment) {
212
+ return (_jsx("div", { className: "flex min-h-[180px] items-center justify-center rounded-md border bg-card px-4 py-8 text-center text-[12px] text-muted-foreground", "data-testid": "optimization-new-experiment-detail-panel", children: t('optimizations.new.origin.experimentDetail.placeholder') }));
213
+ }
214
+ const metrics = experiment.metrics ?? null;
215
+ const runConfig = experiment.runConfig ?? null;
216
+ const perClass = metrics?.perClass ?? null;
217
+ return (_jsxs("div", { className: "space-y-2.5", "data-testid": "optimization-new-experiment-detail-panel", children: [modelMissing && (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-[11.5px] text-destructive", children: [_jsx(AlertTriangle, { className: "mt-0.5 size-3.5 flex-none", "aria-hidden": "true" }), _jsx("span", { children: t('optimizations.new.origin.experimentDetail.modelMissing') })] })), _jsxs("div", { className: "rounded-md border bg-card px-3 py-3", children: [_jsx("div", { className: "mb-2 text-[10.5px] font-bold uppercase tracking-wide text-muted-foreground", children: t('optimizations.new.origin.experimentDetail.basicSection') }), _jsxs("div", { className: "grid grid-cols-1 gap-1.5 sm:grid-cols-2", children: [_jsx(SpecLine, { label: t('optimizations.new.origin.experimentDetail.prompt'), value: `${experiment.promptName} ${experiment.promptVersionLabel}` }), _jsx(SpecLine, { label: t('optimizations.new.origin.experimentDetail.dataset'), value: `${experiment.datasetName} · ${formatThousand(experiment.datasetSamples)}` }), _jsx(SpecLine, { label: t('optimizations.new.origin.experimentDetail.model'), value: experiment.modelName }), _jsx(SpecLine, { label: t('optimizations.new.origin.experimentDetail.finishedAt'), value: formatDateTime(experiment.finishedAt) })] })] }), _jsxs("div", { className: "rounded-md border bg-card px-3 py-3", children: [_jsx("div", { className: "mb-2 text-[10.5px] font-bold uppercase tracking-wide text-muted-foreground", children: t('optimizations.new.origin.experimentDetail.qualitySection') }), _jsxs("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-4", children: [_jsx(MetricCard, { label: t('optimizations.new.optimization.metric.accuracy'), value: formatMetricFraction(metrics?.accuracy), highlight: true }), _jsx(MetricCard, { label: t('optimizations.new.optimization.metric.precision'), value: formatMetricFraction(metrics?.precision) }), _jsx(MetricCard, { label: t('optimizations.new.optimization.metric.recall'), value: formatMetricFraction(metrics?.recall) }), _jsx(MetricCard, { label: t('optimizations.new.optimization.metric.f1'), value: formatMetricFraction(metrics?.f1) })] })] }), _jsx(SectionDetails, { title: t('optimizations.new.origin.experimentDetail.runConfigSection'), testid: "optimization-new-experiment-detail-runconfig", children: _jsxs("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-4", children: [_jsx(SpecLine, { label: t('optimizations.new.origin.experimentDetail.temperature'), value: runConfig?.temperature ?? '—' }), _jsx(SpecLine, { label: t('optimizations.new.origin.experimentDetail.concurrency'), value: runConfig?.concurrency ?? '—' }), _jsx(SpecLine, { label: t('optimizations.new.origin.experimentDetail.rpm'), value: runConfig?.rpmLimit ?? '—' }), _jsx(SpecLine, { label: t('optimizations.new.origin.experimentDetail.tpm'), value: runConfig?.tpmLimit !== undefined && runConfig?.tpmLimit !== null
218
+ ? formatThousand(runConfig.tpmLimit)
219
+ : '—' })] }) }), _jsx(SectionDetails, { title: t('optimizations.new.origin.experimentDetail.engineeringSection'), testid: "optimization-new-experiment-detail-engineering", children: _jsxs("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-3", children: [_jsx(MetricCard, { label: t('optimizations.new.origin.experimentDetail.inputTokens'), value: formatMetricInteger(metrics?.inputTokens) }), _jsx(MetricCard, { label: t('optimizations.new.origin.experimentDetail.outputTokens'), value: formatMetricInteger(metrics?.outputTokens) }), _jsx(MetricCard, { label: t('optimizations.new.origin.experimentDetail.cost'), value: formatMetricCost(metrics?.costEstimate) }), _jsx(MetricCard, { label: t('optimizations.new.origin.experimentDetail.latencyAvg'), value: formatMetricLatencyMs(metrics?.averageLatencyMs) }), _jsx(MetricCard, { label: t('optimizations.new.origin.experimentDetail.latencyP50'), value: formatMetricLatencyMs(metrics?.p50LatencyMs) }), _jsx(MetricCard, { label: t('optimizations.new.origin.experimentDetail.latencyP95'), value: formatMetricLatencyMs(metrics?.p95LatencyMs) })] }) }), _jsx(SectionDetails, { title: t('optimizations.new.origin.experimentDetail.perClassSection'), testid: "optimization-new-experiment-detail-per-class", children: perClass && perClass.length > 0 ? (_jsxs("table", { className: "w-full text-[12px]", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b text-[10.5px] uppercase tracking-wide text-muted-foreground", children: [_jsx("th", { className: "py-1 text-left font-medium", children: t('optimizations.new.origin.experimentDetail.perClassLabel') }), _jsx("th", { className: "py-1 text-right font-medium", children: "P" }), _jsx("th", { className: "py-1 text-right font-medium", children: "R" }), _jsx("th", { className: "py-1 text-right font-medium", children: "F1" }), _jsx("th", { className: "py-1 text-right font-medium", children: t('optimizations.new.origin.experimentDetail.perClassSupport') })] }) }), _jsx("tbody", { children: perClass.map((entry) => (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "py-1.5 font-mono", children: entry.label }), _jsx("td", { className: "py-1.5 text-right font-mono tabular-nums", children: formatMetricFraction(entry.precision) }), _jsx("td", { className: "py-1.5 text-right font-mono tabular-nums", children: formatMetricFraction(entry.recall) }), _jsx("td", { className: "py-1.5 text-right font-mono tabular-nums", children: formatMetricFraction(entry.f1) }), _jsx("td", { className: "py-1.5 text-right font-mono tabular-nums", children: formatMetricInteger(entry.support) })] }, entry.label))) })] })) : (_jsx("div", { className: "text-[11.5px] text-muted-foreground", children: t('optimizations.new.origin.experimentDetail.perClassEmpty') })) })] }));
220
+ }
221
+ function ExperimentRow({ experiment, selected, onSelect, }) {
222
+ const { t } = useI18n();
223
+ const { formatDateTime } = useDateTimeFormatter();
224
+ return (_jsxs("button", { type: "button", onClick: onSelect, "aria-pressed": selected, className: cn('flex w-full items-start gap-2.5 border-b px-3 py-2.5 text-left transition-colors last:border-b-0 hover:bg-muted/40', selected && 'bg-[color-mix(in_oklab,var(--status-canary-bg)_55%,var(--background))]'), children: [_jsx(Radio, { checked: selected }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "flex flex-wrap items-center gap-1.5", children: _jsx("span", { className: "font-mono text-[13px] font-semibold", children: experiment.name }) }), experiment.description && (_jsx("div", { className: "mt-0.5 truncate text-[12.5px] text-muted-foreground", children: experiment.description })), _jsx("div", { className: "mt-1 text-[11.5px] text-muted-foreground", children: formatTemplate(t('optimizations.new.origin.experimentCreatedAt'), {
225
+ date: formatDateTime(experiment.createdAt),
226
+ }) })] })] }));
227
+ }
228
+ function PromptRow({ prompt, selected, onSelect, }) {
229
+ const { t } = useI18n();
230
+ const { formatDateTime } = useDateTimeFormatter();
231
+ const statusKey = PROMPT_STATUS_LABEL_KEY[prompt.latestVersionStatus];
232
+ return (_jsxs("button", { type: "button", onClick: onSelect, "aria-pressed": selected, className: cn('flex w-full items-start gap-2.5 border-b px-3 py-2.5 text-left transition-colors last:border-b-0 hover:bg-muted/40', selected && 'bg-[color-mix(in_oklab,var(--status-canary-bg)_55%,var(--background))]'), children: [_jsx(Radio, { checked: selected }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx("span", { className: "font-mono text-[13px] font-semibold", children: prompt.name }), statusKey && (_jsx(Tag, { tone: prompt.latestVersionStatus === 'frozen' ? 'positive' : 'neutral', children: t(statusKey) }))] }), _jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-1.5", children: [_jsx(Tag, { children: formatTemplate(t('optimizations.new.origin.promptVersionCount'), {
233
+ count: prompt.latestVersionNumber,
234
+ }) }), prompt.currentOnlineVersionNumber ? (_jsx(Tag, { tone: "positive", children: formatTemplate(t('optimizations.new.origin.promptOnlineVersion'), {
235
+ version: `v${prompt.currentOnlineVersionNumber}`,
236
+ }) })) : (_jsx(Tag, { children: t('optimizations.new.origin.promptNoOnlineVersion') }))] }), _jsxs("div", { className: "mt-1 text-[11.5px] text-muted-foreground", children: [prompt.createdByDisplayName ? `@${prompt.createdByDisplayName}` : '@unknown', " \u00B7", ' ', formatDateTime(prompt.updatedAt)] })] })] }));
237
+ }
238
+ function PromptVersionRow({ option, selected, onSelect, }) {
239
+ const { t } = useI18n();
240
+ return (_jsx(PromptVersionPickerRow, { version: option.version, status: option.status, variables: option.variables, selected: selected, onSelect: onSelect, badges: option.isOnline ? (_jsx(PromptVersionPickerTag, { tone: "positive", children: t('optimizations.new.origin.promptVersionOnline') })) : option.isLatest ? (_jsx(PromptVersionPickerTag, { tone: "info", children: t('optimizations.new.origin.promptVersionLatest') })) : null, createdAt: option.updatedAt, trailing: _jsx(PromptVersionPickerTag, { children: t(PROMPT_LANGUAGE_LABEL_KEY[option.promptLanguage]) }) }));
241
+ }
242
+ function PromptVersionPreview({ option }) {
243
+ const { t } = useI18n();
244
+ const previewParts = useMemo(() => renderPromptPreviewParts(option?.promptPreview ?? '', option?.variables ?? []), [option]);
245
+ if (!option) {
246
+ return (_jsx("div", { className: "border-t px-3 py-6 text-center text-[12px] text-muted-foreground", "data-testid": "optimization-new-prompt-preview", children: t('optimizations.new.origin.promptPreviewEmpty') }));
247
+ }
248
+ return (_jsx("div", { className: "border-t px-3 py-3", "data-testid": "optimization-new-prompt-preview", children: _jsxs("div", { children: [_jsxs("div", { className: "mb-2 flex flex-wrap items-center justify-between gap-2", children: [_jsx("span", { className: "font-mono text-[10.5px] uppercase tracking-wide text-muted-foreground", children: t('optimizations.new.origin.promptPreview') }), _jsxs("span", { className: "font-mono text-[10.5px] text-muted-foreground", children: [option.name, " \u00B7 ", option.version] })] }), _jsx("pre", { className: "max-h-[360px] overflow-auto whitespace-pre-wrap break-words rounded-md border bg-muted px-3 py-2 font-mono text-[11.5px] leading-relaxed text-foreground", children: previewParts.map((part, index) => {
249
+ if (part.kind === 'text')
250
+ return _jsx("span", { children: part.value }, index);
251
+ const tone = part.varType
252
+ ? VARIABLE_TONE_CLASSES[part.varType]
253
+ : 'border-muted-foreground/30 bg-muted/40 text-muted-foreground';
254
+ return (_jsx("span", { className: cn('inline rounded border px-1 font-mono text-[11px]', tone), "data-variable-name": part.name, children: part.value }, index));
255
+ }) })] }) }));
256
+ }
257
+ function DatasetRow({ dataset, selected, onSelect, }) {
258
+ const { t } = useI18n();
259
+ const meta = formatTemplate(t('optimizations.new.origin.datasetMeta'), {
260
+ samples: formatThousand(dataset.sampleCount),
261
+ fields: dataset.fieldSchema.length,
262
+ });
263
+ return (_jsxs("button", { type: "button", onClick: onSelect, "aria-pressed": selected, className: cn('flex w-full items-start gap-2.5 border-b px-3 py-2.5 text-left transition-colors last:border-b-0 hover:bg-muted/40', selected && 'bg-[color-mix(in_oklab,var(--status-canary-bg)_55%,var(--background))]'), children: [_jsx(Radio, { checked: selected }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx("span", { className: "font-mono text-[13px] font-semibold", children: dataset.name }), dataset.hasImages && (_jsxs(Tag, { tone: "info", children: [_jsx(ImageIcon, { className: "size-2.5", "aria-hidden": "true" }), t('optimizations.new.origin.datasetHasImages')] }))] }), dataset.description && (_jsx("div", { className: "mt-0.5 truncate text-[12.5px] text-muted-foreground", children: dataset.description })), _jsx("div", { className: "mt-1 text-[11.5px] text-muted-foreground", children: meta })] })] }));
264
+ }
265
+ function ModelOptionRow({ model, selected, onSelect, }) {
266
+ const { t } = useI18n();
267
+ const ctx = model.contextWindowTokens
268
+ ? formatTemplate(t('optimizations.new.experiment.modelCap.ctx'), {
269
+ value: model.contextWindowTokens >= 1000
270
+ ? `${Math.round(model.contextWindowTokens / 1000)}K`
271
+ : String(model.contextWindowTokens),
272
+ })
273
+ : null;
274
+ const unlimited = t('optimizations.new.experiment.modelUnlimited');
275
+ const rpmText = model.rpm.limit < 0 ? unlimited : formatThousand(model.rpm.limit);
276
+ const tpmText = model.tpm.limit < 0 ? unlimited : formatThousand(model.tpm.limit);
277
+ return (_jsxs("button", { type: "button", onClick: onSelect, "aria-pressed": selected, className: cn('flex w-full items-start gap-2.5 border-b px-3 py-2.5 text-left transition-colors last:border-b-0 hover:bg-muted/40', selected && 'bg-[color-mix(in_oklab,var(--status-canary-bg)_55%,var(--background))]'), children: [_jsx(Radio, { checked: selected }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx("span", { className: "font-mono text-[13px] font-semibold", children: model.name }), model.status === 'disabled' && _jsx(Tag, { tone: "neutral", children: t('optimizations.new.experiment.modelDisabled') }), model.status === 'testing' && _jsx(Tag, { tone: "warning", children: t('optimizations.new.experiment.modelTesting') })] }), _jsx("div", { className: "mt-0.5 text-[12px] text-muted-foreground", children: model.providerType }), _jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-1.5", children: [ctx && _jsx(Tag, { children: ctx }), model.capabilities.image !== 'none' && (_jsx(Tag, { tone: "info", children: t('optimizations.new.experiment.modelCap.vision') })), _jsx(Tag, { children: formatTemplate(t('optimizations.new.experiment.modelRpmTpm'), { rpm: rpmText, tpm: tpmText }) })] })] }), _jsxs("div", { className: "flex-none text-right font-mono text-[11.5px] text-muted-foreground", children: [_jsx("div", { className: "font-semibold text-foreground", children: formatTemplate(t('optimizations.new.experiment.modelPriceLabel'), {
278
+ input: formatPrice(model.pricing.inputPerMillion),
279
+ output: formatPrice(model.pricing.outputPerMillion),
280
+ }) }), _jsx("div", { className: "text-[10.5px] text-muted-foreground", children: t('optimizations.new.experiment.modelPriceUnit') })] })] }));
281
+ }
282
+ // ---------------------------------------------------------------------------
283
+ // main page
284
+ // ---------------------------------------------------------------------------
285
+ export function OptimizationNewPage({ projectId, initialDatasetId, initialPromptId, initialPromptVersionId, initialSourceExperimentId, }) {
286
+ const { t } = useI18n();
287
+ const { formatDateTime } = useDateTimeFormatter();
288
+ // basic
289
+ const [name, setName] = useState(defaultName);
290
+ const [description, setDescription] = useState('');
291
+ const [optimizationHint, setOptimizationHint] = useState('');
292
+ // origin
293
+ const [originMode, setOriginMode] = useState(initialSourceExperimentId ? 'experiment' : initialDatasetId ? 'dataset' : initialPromptId ? 'prompt' : 'experiment');
294
+ const [selectedExperimentId, setSelectedExperimentId] = useState(initialSourceExperimentId ?? '');
295
+ const [selectedPromptId, setSelectedPromptId] = useState(initialPromptId ?? '');
296
+ const [selectedPromptVersionId, setSelectedPromptVersionId] = useState(initialPromptVersionId ?? '');
297
+ const [selectedDatasetId, setSelectedDatasetId] = useState(initialDatasetId ?? '');
298
+ const [experimentSearch, setExperimentSearch] = useState('');
299
+ const [promptSearch, setPromptSearch] = useState('');
300
+ const [datasetSearch, setDatasetSearch] = useState('');
301
+ // experiment config
302
+ const [selectedModelId, setSelectedModelId] = useState('');
303
+ const [modelSearch, setModelSearch] = useState('');
304
+ const [temperature, setTemperature] = useState(0);
305
+ const [concurrency, setConcurrency] = useState(8);
306
+ const [rpm, setRpm] = useState(60);
307
+ const [tpm, setTpm] = useState(120_000);
308
+ const [sampleTimeoutSeconds, setSampleTimeoutSeconds] = useState(20);
309
+ const [retries, setRetries] = useState(0);
310
+ const [imageEncoding, setImageEncoding] = useState('url');
311
+ // optimization
312
+ const [goals, setGoals] = useState([
313
+ { id: 'g1', metric: 'accuracy', comparator: 'gte', target: '0.90', scope: 'overall' },
314
+ ]);
315
+ const [analysisModelId, setAnalysisModelId] = useState('');
316
+ const [analysisModelSearch, setAnalysisModelSearch] = useState('');
317
+ const [maxRounds, setMaxRounds] = useState(5);
318
+ const [noImprovementRounds, setNoImprovementRounds] = useState(0);
319
+ const [initialSamplingRounds, setInitialSamplingRounds] = useState(1);
320
+ const [initialSamplesPerRound, setInitialSamplesPerRound] = useState(20);
321
+ const [datasetPromptLanguage, setDatasetPromptLanguage] = useState(DEFAULT_PROMPT_LANGUAGE);
322
+ // meta-field user overrides keyed by `${datasetId}:${fieldName}` so changing
323
+ // datasets resets the field state cleanly without an effect
324
+ const [metaOverrides, setMetaOverrides] = useState({});
325
+ // data hooks
326
+ const experimentsQuery = useExperiments(projectId);
327
+ const optimizationsQuery = useOptimizations(projectId);
328
+ const promptsQuery = usePrompts(projectId);
329
+ const datasetsQuery = useDatasets(projectId);
330
+ const modelsQuery = useProjectModels(projectId, { autoRefresh: false });
331
+ const experiments = useMemo(() => experimentsQuery.data?.data ?? [], [experimentsQuery.data]);
332
+ const optimizations = useMemo(() => optimizationsQuery.data?.data ?? [], [optimizationsQuery.data]);
333
+ const prompts = useMemo(() => promptsQuery.data?.data ?? [], [promptsQuery.data]);
334
+ const datasets = useMemo(() => datasetsQuery.data?.data ?? [], [datasetsQuery.data]);
335
+ const models = useMemo(() => modelsQuery.data?.data ?? [], [modelsQuery.data]);
336
+ const optimizationNameTaken = useMemo(() => isProjectNameTaken(name, optimizations), [optimizations, name]);
337
+ // Effective selections: user choice or fall back to first-fit. Derived (no setState in render).
338
+ // experiment fallback uses successExperiments[0]: after list filtering, running/failed etc. are excluded;
339
+ // must align here, otherwise the form falls onto an invisible experiment when the user has not selected one
340
+ const successExperimentsHead = useMemo(() => experiments.find((item) => item.status === 'success') ?? null, [experiments]);
341
+ const effectiveExperimentId = selectedExperimentId || successExperimentsHead?.id || '';
342
+ const effectivePromptId = selectedPromptId || prompts[0]?.id || '';
343
+ // Start = experiment and the user has not manually picked a model: experiment model / analysis model defaults follow the experiment's bound model;
344
+ // when the bound model has been soft-deleted, returns undefined and falls back to models[0]
345
+ const experimentDefaultModelId = useMemo(() => {
346
+ if (originMode !== 'experiment')
347
+ return undefined;
348
+ const exp = experiments.find((item) => item.id === effectiveExperimentId);
349
+ if (!exp)
350
+ return undefined;
351
+ return models.find((m) => m.id === exp.modelId)?.id;
352
+ }, [originMode, effectiveExperimentId, experiments, models]);
353
+ const effectiveModelId = selectedModelId || experimentDefaultModelId || models[0]?.id || '';
354
+ const effectiveAnalysisModelId = analysisModelId || experimentDefaultModelId || models[0]?.id || '';
355
+ // resolve currently-selected resource
356
+ const selectedExperiment = useMemo(() => experiments.find((item) => item.id === effectiveExperimentId), [experiments, effectiveExperimentId]);
357
+ const selectedPrompt = useMemo(() => prompts.find((item) => item.id === effectivePromptId), [prompts, effectivePromptId]);
358
+ const promptDetailQuery = usePrompt(projectId, selectedPrompt?.id ?? '');
359
+ const experimentsLoading = useDelayedLoading(experimentsQuery.isLoading);
360
+ const promptsLoading = useDelayedLoading(promptsQuery.isLoading);
361
+ const promptDetailLoading = useDelayedLoading(promptDetailQuery.isLoading);
362
+ const datasetsLoading = useDelayedLoading(datasetsQuery.isLoading);
363
+ const modelsLoading = useDelayedLoading(modelsQuery.isLoading);
364
+ const promptVersions = useMemo(() => selectedPrompt && promptDetailQuery.data
365
+ ? promptDetailQuery.data.versions.map((version) => mapPromptVersionToOption(selectedPrompt, version, formatDateTime))
366
+ : [], [formatDateTime, promptDetailQuery.data, selectedPrompt]);
367
+ const preferredPromptVersion = useMemo(() => {
368
+ if (promptVersions.length === 0)
369
+ return null;
370
+ const fromUrl = initialPromptVersionId
371
+ ? promptVersions.find((option) => option.id === initialPromptVersionId)
372
+ : null;
373
+ const online = selectedPrompt?.currentOnlineVersionNumber
374
+ ? promptVersions.find((option) => option.version === `v${selectedPrompt.currentOnlineVersionNumber}`)
375
+ : null;
376
+ const latest = promptVersions.find((option) => option.isLatest);
377
+ return fromUrl ?? online ?? latest ?? promptVersions[0] ?? null;
378
+ }, [initialPromptVersionId, promptVersions, selectedPrompt]);
379
+ const effectivePromptVersionId = selectedPromptVersionId && promptVersions.some((option) => option.id === selectedPromptVersionId)
380
+ ? selectedPromptVersionId
381
+ : (preferredPromptVersion?.id ?? '');
382
+ const selectedPromptVersion = useMemo(() => promptVersions.find((option) => option.id === effectivePromptVersionId) ?? null, [effectivePromptVersionId, promptVersions]);
383
+ // Dataset selection priority: user choice → (prompt mode) prompt.defaultDatasetId → first dataset.
384
+ const effectiveDatasetId = selectedDatasetId ||
385
+ (originMode === 'prompt' ? (selectedPrompt?.defaultDatasetId ?? '') : '') ||
386
+ datasets[0]?.id ||
387
+ '';
388
+ const selectedDataset = useMemo(() => datasets.find((item) => item.id === effectiveDatasetId), [datasets, effectiveDatasetId]);
389
+ const impliedDatasetId = originMode === 'experiment' ? (selectedExperiment?.datasetId ?? null) : effectiveDatasetId || null; // Both prompt and dataset modes use effectiveDatasetId
390
+ const impliedDataset = useMemo(() => (impliedDatasetId ? (datasets.find((item) => item.id === impliedDatasetId) ?? null) : null), [datasets, impliedDatasetId]);
391
+ const { inputs: inputFields, metas: metaFields } = useMemo(() => deriveDatasetFields(impliedDataset), [impliedDataset]);
392
+ const selectedModel = useMemo(() => models.find((item) => item.id === effectiveModelId), [models, effectiveModelId]);
393
+ const selectedAnalysisModel = useMemo(() => models.find((item) => item.id === effectiveAnalysisModelId), [models, effectiveAnalysisModelId]);
394
+ // filtered lists — the experiment list only accepts status==='success' as a baseline candidate;
395
+ // only completed experiments have trustworthy run parameters + metrics that can be group-imported
396
+ const successExperiments = useMemo(() => experiments.filter((item) => item.status === 'success'), [experiments]);
397
+ const filteredExperiments = useMemo(() => {
398
+ const query = experimentSearch.trim().toLowerCase();
399
+ if (!query)
400
+ return successExperiments;
401
+ return successExperiments.filter((item) => `${item.name} ${item.description ?? ''} ${item.promptName} ${item.datasetName} ${item.modelName}`
402
+ .toLowerCase()
403
+ .includes(query));
404
+ }, [successExperiments, experimentSearch]);
405
+ const filteredPrompts = useMemo(() => {
406
+ const query = promptSearch.trim().toLowerCase();
407
+ if (!query)
408
+ return prompts;
409
+ return prompts.filter((item) => item.name.toLowerCase().includes(query));
410
+ }, [prompts, promptSearch]);
411
+ const filteredDatasets = useMemo(() => {
412
+ const query = datasetSearch.trim().toLowerCase();
413
+ if (!query)
414
+ return datasets;
415
+ return datasets.filter((item) => `${item.name} ${item.description ?? ''}`.toLowerCase().includes(query));
416
+ }, [datasets, datasetSearch]);
417
+ const filteredModels = useMemo(() => {
418
+ const query = modelSearch.trim().toLowerCase();
419
+ if (!query)
420
+ return models;
421
+ return models.filter((item) => `${item.name} ${item.providerType} ${item.providerModelId}`.toLowerCase().includes(query));
422
+ }, [models, modelSearch]);
423
+ const filteredAnalysisModels = useMemo(() => {
424
+ const query = analysisModelSearch.trim().toLowerCase();
425
+ if (!query)
426
+ return models;
427
+ return models.filter((item) => `${item.name} ${item.providerType} ${item.providerModelId}`.toLowerCase().includes(query));
428
+ }, [models, analysisModelSearch]);
429
+ // ---- Run parameter auto-sync helper (avoiding §4.21: all setState goes through a callback, not a useEffect listening to the same-name state) ----
430
+ // Look up the model in the project model list; returns undefined when not found (soft-deleted)
431
+ const modelOf = useCallback((id) => (id ? models.find((item) => item.id === id) : undefined), [models]);
432
+ // Selecting an experiment = whole-group override: modelId / analysisModelId(= modelId) / temperature / concurrency / rpm / tpm
433
+ // Experiment runConfig priority → model limit fallback → hardcoded backstop
434
+ const applyExperimentDefaults = useCallback((exp) => {
435
+ setSelectedExperimentId(exp.id);
436
+ const expModel = modelOf(exp.modelId);
437
+ if (expModel) {
438
+ setSelectedModelId(exp.modelId);
439
+ setAnalysisModelId(exp.modelId);
440
+ }
441
+ setTemperature(exp.runConfig?.temperature ?? 0);
442
+ setConcurrency(exp.runConfig?.concurrency ?? expModel?.concurrency.limit ?? 8);
443
+ setRpm(exp.runConfig?.rpmLimit ?? expModel?.rpm.limit ?? 60);
444
+ setTpm(exp.runConfig?.tpmLimit ?? expModel?.tpm.limit ?? 120_000);
445
+ setSampleTimeoutSeconds(exp.runConfig?.sampleTimeoutSeconds ?? 20);
446
+ setRetries(exp.runConfig?.retries ?? 0);
447
+ setImageEncoding(exp.runConfig?.imageEncoding ?? 'url');
448
+ }, [modelOf]);
449
+ // Switch model / switch away from experiment mode → run parameters follow the model default (temperature has no model field; keep user value)
450
+ const applyModelDefaults = useCallback((model) => {
451
+ setSelectedModelId(model.id);
452
+ setConcurrency(model.concurrency.limit > 0 ? model.concurrency.limit : 8);
453
+ setRpm(model.rpm.limit > 0 ? model.rpm.limit : 60);
454
+ setTpm(model.tpm.limit > 0 ? model.tpm.limit : 120_000);
455
+ }, []);
456
+ const handlePromptSelect = useCallback((promptId) => {
457
+ setSelectedPromptId(promptId);
458
+ setSelectedPromptVersionId('');
459
+ }, []);
460
+ // Switching start mode (experiment → other only) triggers applyModelDefaults, falling back to the run parameters bound to the experiment
461
+ const handleOriginModeChange = useCallback((next) => {
462
+ setOriginMode((current) => {
463
+ if (current === 'experiment' && next !== 'experiment' && selectedModel) {
464
+ applyModelDefaults(selectedModel);
465
+ }
466
+ return next;
467
+ });
468
+ }, [applyModelDefaults, selectedModel]);
469
+ // URL ?sourceExperimentId=<success-id> one-shot sync entry (the only allowed useEffect)
470
+ // ref sentinel ensures setState-induced re-renders do not retrigger this effect, so no loop
471
+ const didInitialSyncRef = useRef(false);
472
+ useEffect(() => {
473
+ if (didInitialSyncRef.current)
474
+ return;
475
+ if (!initialSourceExperimentId)
476
+ return;
477
+ if (experiments.length === 0)
478
+ return;
479
+ const exp = experiments.find((item) => item.id === initialSourceExperimentId);
480
+ if (exp && exp.status === 'success') {
481
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- URL entry first-time sync; ref guarantees one-shot
482
+ applyExperimentDefaults(exp);
483
+ }
484
+ didInitialSyncRef.current = true;
485
+ }, [experiments, initialSourceExperimentId, applyExperimentDefaults]);
486
+ // scope options derived from current dataset's category distribution
487
+ const scopeOptions = useMemo(() => classScopes(impliedDataset), [impliedDataset]);
488
+ // goal handlers
489
+ const addGoal = () => {
490
+ const id = `g${goals.length + 1}-${Date.now()}`;
491
+ setGoals((current) => [...current, { id, metric: 'recall', comparator: 'gte', target: '0.80', scope: 'overall' }]);
492
+ };
493
+ const updateGoal = (id, patch) => {
494
+ setGoals((current) => current.map((goal) => (goal.id === id ? { ...goal, ...patch } : goal)));
495
+ };
496
+ const removeGoal = (id) => {
497
+ setGoals((current) => current.filter((goal) => goal.id !== id));
498
+ };
499
+ // field selection handlers (override-based; key namespaced by datasetId)
500
+ const metaOverrideKey = (fieldName) => (impliedDataset ? `${impliedDataset.id}:${fieldName}` : fieldName);
501
+ const isMetaChecked = (fieldName) => metaOverrides[metaOverrideKey(fieldName)] ?? false;
502
+ const toggleMetaField = (fieldName) => {
503
+ const key = metaOverrideKey(fieldName);
504
+ setMetaOverrides((current) => ({ ...current, [key]: !(current[key] ?? false) }));
505
+ };
506
+ // origin picker status
507
+ const originStatus = originMode === 'experiment'
508
+ ? selectedExperiment
509
+ ? 'done'
510
+ : 'current'
511
+ : originMode === 'prompt'
512
+ ? selectedPrompt && selectedPromptVersion
513
+ ? 'done'
514
+ : 'current'
515
+ : selectedDataset
516
+ ? 'done'
517
+ : 'current';
518
+ const expStatus = selectedModel ? 'done' : 'current';
519
+ const optStatus = goals.length > 0 && selectedAnalysisModel ? 'done' : 'current';
520
+ // estimate (mockup-aligned synthetic numbers)
521
+ const sampleCount = impliedDataset?.sampleCount ?? selectedExperiment?.datasetSamples ?? 0;
522
+ const totalRuns = maxRounds;
523
+ const totalAnalysisTokens = maxRounds * 160_000;
524
+ const totalExperimentTokens = sampleCount * 2500 * totalRuns;
525
+ const experimentCost = selectedModel
526
+ ? ((totalExperimentTokens * 0.7) / 1_000_000) * selectedModel.pricing.inputPerMillion +
527
+ ((totalExperimentTokens * 0.3) / 1_000_000) * selectedModel.pricing.outputPerMillion
528
+ : 0;
529
+ const analysisCost = selectedAnalysisModel
530
+ ? ((totalAnalysisTokens * 0.7) / 1_000_000) * selectedAnalysisModel.pricing.inputPerMillion +
531
+ ((totalAnalysisTokens * 0.3) / 1_000_000) * selectedAnalysisModel.pricing.outputPerMillion
532
+ : 0;
533
+ const totalCost = experimentCost + analysisCost;
534
+ // --------------------------- submit ---------------------------
535
+ const router = useRouter();
536
+ const createMutation = useCreateOptimization(projectId);
537
+ const [submitError, setSubmitError] = useState(null);
538
+ const buildDto = () => {
539
+ const trimmedName = name.trim();
540
+ if (!trimmedName) {
541
+ setSubmitError(t('optimizations.new.error.nameRequired'));
542
+ return null;
543
+ }
544
+ if (optimizationNameTaken) {
545
+ setSubmitError(t('common.formError.nameTaken'));
546
+ return null;
547
+ }
548
+ if (!effectiveDatasetId && originMode === 'dataset') {
549
+ setSubmitError(t('optimizations.new.error.datasetRequired'));
550
+ return null;
551
+ }
552
+ if (!effectiveModelId) {
553
+ setSubmitError(t('optimizations.new.error.modelRequired'));
554
+ return null;
555
+ }
556
+ if (!effectiveAnalysisModelId) {
557
+ setSubmitError(t('optimizations.new.error.analysisModelRequired'));
558
+ return null;
559
+ }
560
+ let startingMode;
561
+ let sourceExperimentId = null;
562
+ let promptIdValue = null;
563
+ let baseVersionIdValue = null;
564
+ let resolvedDatasetId;
565
+ if (originMode === 'experiment') {
566
+ if (!selectedExperiment) {
567
+ setSubmitError(t('optimizations.new.error.experimentRequired'));
568
+ return null;
569
+ }
570
+ startingMode = 'from_experiment';
571
+ sourceExperimentId = selectedExperiment.id;
572
+ resolvedDatasetId = selectedExperiment.datasetId;
573
+ }
574
+ else if (originMode === 'prompt') {
575
+ if (!selectedPrompt) {
576
+ setSubmitError(t('optimizations.new.error.promptRequired'));
577
+ return null;
578
+ }
579
+ if (!selectedPromptVersion) {
580
+ setSubmitError(t('optimizations.new.error.promptVersionRequired'));
581
+ return null;
582
+ }
583
+ if (!effectiveDatasetId) {
584
+ setSubmitError(t('optimizations.new.error.datasetRequired'));
585
+ return null;
586
+ }
587
+ startingMode = 'from_prompt_version';
588
+ promptIdValue = selectedPrompt.id;
589
+ baseVersionIdValue = selectedPromptVersion.id;
590
+ resolvedDatasetId = effectiveDatasetId;
591
+ }
592
+ else {
593
+ if (!selectedDataset) {
594
+ setSubmitError(t('optimizations.new.error.datasetRequired'));
595
+ return null;
596
+ }
597
+ startingMode = 'from_dataset_only';
598
+ resolvedDatasetId = selectedDataset.id;
599
+ }
600
+ const numericGoals = goals.map((goal) => ({
601
+ metric: goal.metric,
602
+ comparator: goal.comparator,
603
+ target: Number(goal.target),
604
+ scope: goal.scope,
605
+ }));
606
+ if (numericGoals.some((goal) => !Number.isFinite(goal.target) || goal.target < 0 || goal.target > 1)) {
607
+ setSubmitError(t('optimizations.new.error.goalTargetInvalid'));
608
+ return null;
609
+ }
610
+ if (numericGoals.length === 0) {
611
+ setSubmitError(t('optimizations.new.error.goalsRequired'));
612
+ return null;
613
+ }
614
+ const selectedMetaFields = metaFields.filter((field) => isMetaChecked(field.name)).map((field) => field.name);
615
+ const inputFieldNames = inputFields.map((field) => field.name);
616
+ const fieldWhitelist = impliedDataset ? { inputFields: inputFieldNames, metaFields: selectedMetaFields } : null;
617
+ const strategyConfig = startingMode === 'from_dataset_only' ? { initialSamplingRounds, initialSamplesPerRound } : undefined;
618
+ return {
619
+ name: trimmedName,
620
+ description: description.trim() ? description.trim() : null,
621
+ optimizationHint: optimizationHint.trim() ? optimizationHint.trim() : null,
622
+ promptLanguage: originMode === 'dataset' ? datasetPromptLanguage : undefined,
623
+ strategy: 'error_pattern_analysis',
624
+ strategyConfig,
625
+ startingMode,
626
+ sourceExperimentId,
627
+ promptId: promptIdValue,
628
+ baseVersionId: baseVersionIdValue,
629
+ datasetId: resolvedDatasetId,
630
+ experimentModelId: effectiveModelId,
631
+ analysisModelId: effectiveAnalysisModelId,
632
+ goals: numericGoals,
633
+ fieldWhitelist,
634
+ runConfig: {
635
+ temperature,
636
+ concurrency,
637
+ rpmLimit: rpm,
638
+ tpmLimit: tpm,
639
+ sampleTimeoutSeconds,
640
+ retries,
641
+ imageEncoding,
642
+ },
643
+ loopLimits: {
644
+ maxRounds,
645
+ stopAfterNoImprovementRounds: noImprovementRounds,
646
+ },
647
+ };
648
+ };
649
+ const handleSubmit = async () => {
650
+ setSubmitError(null);
651
+ const dto = buildDto();
652
+ if (!dto)
653
+ return;
654
+ try {
655
+ const created = await createMutation.mutateAsync(dto);
656
+ router.push(`/optimizations/${created.id}`);
657
+ }
658
+ catch (error) {
659
+ const message = getApiErrorMessage(error);
660
+ setSubmitError(message === 'optimization_name_taken'
661
+ ? t('common.formError.nameTaken')
662
+ : (message ?? t('optimizations.new.error.submitFailed')));
663
+ }
664
+ };
665
+ const isSubmitting = createMutation.isPending;
666
+ return (_jsx(Main, { className: "gap-0 bg-muted/35 p-0", children: _jsxs("div", { className: "mx-auto w-full max-w-[1760px] px-4 py-6 sm:px-6 lg:px-8", "data-testid": "optimization-new-page", children: [_jsx("div", { className: "mb-4 flex flex-wrap items-center gap-2 text-[12.5px] text-muted-foreground", children: _jsx(Link, { href: `/optimizations`, className: "hover:text-foreground", children: t('optimizations.new.backToList') }) }), _jsxs("div", { className: "mb-5 flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-[26px] font-semibold", children: t('optimizations.new.title') }), _jsx("p", { className: "mt-1 text-[12.5px] text-muted-foreground", children: t('optimizations.new.subtitle') })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { asChild: true, type: "button", variant: "ghost", size: "sm", className: "h-9", children: _jsx(Link, { href: `/optimizations`, children: t('optimizations.new.cancel') }) }), _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "h-9", disabled: isSubmitting, children: t('optimizations.new.saveDraft') }), _jsxs(Button, { type: "button", size: "sm", className: "h-9 gap-1", onClick: handleSubmit, disabled: isSubmitting || optimizationNameTaken, "data-testid": "optimization-new-submit", children: [isSubmitting ? _jsx(Loader2, { className: "size-3.5 animate-spin" }) : _jsx(Play, { className: "size-3.5" }), t('optimizations.new.start')] })] })] }), submitError && (_jsxs("div", { role: "alert", className: cn('mb-4 flex items-start gap-2 rounded-md border px-3 py-2 text-[12.5px]', 'border-destructive/40 bg-destructive/10 text-destructive'), children: [_jsx(AlertTriangle, { className: "mt-0.5 size-3.5 flex-none", "aria-hidden": "true" }), _jsx("span", { className: "flex-1", children: submitError }), _jsx("button", { type: "button", onClick: () => setSubmitError(null), "aria-label": t('optimizations.new.error.dismiss'), className: "ml-2 inline-flex size-5 items-center justify-center rounded text-destructive/80 hover:text-destructive", children: _jsx(X, { className: "size-3.5" }) })] })), _jsxs("div", { className: "mb-5 grid grid-cols-1 gap-3 sm:grid-cols-3", children: [_jsx(StepIndicator, { step: 1, state: originStatus, title: t('optimizations.new.steps.naming'), detail: t('optimizations.new.steps.namingDetail') }), _jsx(StepIndicator, { step: 2, state: expStatus, title: t('optimizations.new.steps.experiment'), detail: t('optimizations.new.steps.experimentDetail') }), _jsx(StepIndicator, { step: 3, state: optStatus, title: t('optimizations.new.steps.optimization'), detail: t('optimizations.new.steps.optimizationDetail') })] }), _jsxs("div", { className: "grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_340px]", children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-4", children: [_jsxs("section", { className: "rounded-lg border bg-card", "data-testid": "optimization-new-step-naming", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b px-5 py-3", children: [_jsx(StepAnchor, { index: 1 }), _jsx("h3", { className: "text-[14px] font-semibold", children: t('optimizations.new.steps.naming') }), _jsx("span", { className: "text-[12px] text-muted-foreground", children: t('optimizations.new.steps.namingDetail') })] }), _jsxs("div", { className: "space-y-5 p-5", children: [_jsxs("div", { children: [_jsx(SubSectionHead, { tone: "info", label: t('optimizations.new.naming.section') }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs("label", { className: "block text-[12.5px] font-medium", children: [t('optimizations.new.naming.name'), " ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { value: name, onChange: (event) => setName(event.target.value), className: "font-mono text-[13px]", placeholder: t('optimizations.new.naming.namePlaceholder'), "aria-invalid": optimizationNameTaken || undefined, "data-testid": "optimization-new-name" }), optimizationNameTaken ? (_jsx("div", { className: "text-[11px] text-destructive", children: t('common.formError.nameTaken') })) : (_jsx("div", { className: "text-[11px] text-muted-foreground", children: t('optimizations.new.naming.nameHelp') }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.naming.description') }), _jsx(Input, { value: description, onChange: (event) => setDescription(event.target.value), placeholder: t('optimizations.new.naming.descriptionPlaceholder') })] })] })] }), _jsxs("div", { className: "border-t border-dashed pt-5", children: [_jsx(SubSectionHead, { tone: "info", label: t('optimizations.new.naming.startingPoint') }), _jsxs("label", { className: "mb-2 block text-[12.5px] font-medium", children: [t('optimizations.new.origin.label'), " ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsxs("div", { className: "grid grid-cols-1 gap-2.5 sm:grid-cols-3", children: [_jsx("div", { className: "contents", children: _jsx(ModeTile, { testId: "optimization-new-origin-mode-experiment", active: originMode === 'experiment', icon: FlaskConical, title: t('optimizations.new.origin.experimentTitle'), description: t('optimizations.new.origin.experimentDesc'), onClick: () => handleOriginModeChange('experiment') }) }), _jsx("div", { className: "contents", children: _jsx(ModeTile, { testId: "optimization-new-origin-mode-prompt", active: originMode === 'prompt', icon: FileText, title: t('optimizations.new.origin.promptTitle'), description: t('optimizations.new.origin.promptDesc'), onClick: () => handleOriginModeChange('prompt') }) }), _jsx("div", { className: "contents", children: _jsx(ModeTile, { testId: "optimization-new-origin-mode-dataset", active: originMode === 'dataset', icon: Database, title: t('optimizations.new.origin.datasetTitle'), description: t('optimizations.new.origin.datasetDesc'), onClick: () => handleOriginModeChange('dataset') }) })] }), _jsxs("div", { className: "mt-4 space-y-2", "data-testid": `optimization-new-origin-picker-${originMode}`, children: [_jsxs("label", { className: "block text-[12.5px] font-medium", children: [originMode === 'experiment'
667
+ ? t('optimizations.new.origin.experimentPicker')
668
+ : originMode === 'prompt'
669
+ ? t('optimizations.new.origin.promptPicker')
670
+ : t('optimizations.new.origin.datasetPicker'), ' ', _jsx("span", { className: "text-destructive", children: "*" })] }), originMode === 'experiment' ? (_jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-[minmax(0,300px)_1fr]", children: [_jsxs("div", { className: "max-h-[360px] overflow-y-auto overflow-x-hidden rounded-md border bg-background", children: [_jsx(MiniSearch, { value: experimentSearch, onChange: setExperimentSearch, placeholder: t('optimizations.new.origin.searchExperiment') }), experimentsLoading ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.experimentLoading') })) : experimentsQuery.isError ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.experimentError') })) : filteredExperiments.length === 0 ? (_jsx(PickerEmpty, { children: experiments.length === 0
671
+ ? t('optimizations.new.origin.experimentEmpty')
672
+ : t('optimizations.new.origin.experimentEmptySuccess') })) : (filteredExperiments.map((experiment) => (_jsx(ExperimentRow, { experiment: experiment, selected: experiment.id === effectiveExperimentId, onSelect: () => applyExperimentDefaults(experiment) }, experiment.id))))] }), _jsx(ExperimentDetailPanel, { experiment: selectedExperiment ?? null, modelMissing: !!selectedExperiment && !modelOf(selectedExperiment.modelId) })] })) : originMode === 'prompt' ? (_jsxs("div", { className: "rounded-md border bg-background", children: [_jsx(MiniSearch, { value: promptSearch, onChange: setPromptSearch, placeholder: t('optimizations.new.origin.searchPrompt') }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]", children: [_jsxs("div", { className: "border-b md:border-b-0 md:border-r", children: [_jsx("div", { className: "border-b px-3 py-2 font-mono text-[10.5px] uppercase tracking-wide text-muted-foreground", children: t('optimizations.new.origin.promptColumn') }), _jsx("div", { className: "max-h-[360px] overflow-y-auto overflow-x-hidden", children: promptsLoading ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.promptLoading') })) : promptsQuery.isError ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.promptError') })) : filteredPrompts.length === 0 ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.promptEmpty') })) : (filteredPrompts.map((prompt) => (_jsx("div", { "data-testid": `optimization-new-prompt-row-${prompt.id}`, children: _jsx(PromptRow, { prompt: prompt, selected: prompt.id === effectivePromptId, onSelect: () => handlePromptSelect(prompt.id) }) }, prompt.id)))) })] }), _jsxs("div", { children: [_jsx("div", { className: "border-b px-3 py-2 font-mono text-[10.5px] uppercase tracking-wide text-muted-foreground", children: t('optimizations.new.origin.versionColumn') }), _jsx("div", { className: "max-h-[360px] overflow-y-auto overflow-x-hidden", children: promptDetailLoading ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.promptVersionLoading') })) : promptDetailQuery.isError ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.promptVersionError') })) : promptVersions.length === 0 ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.promptVersionEmpty') })) : (promptVersions.map((version) => (_jsx("div", { "data-testid": `optimization-new-version-row-${version.id}`, children: _jsx(PromptVersionRow, { option: version, selected: version.id === effectivePromptVersionId, onSelect: () => setSelectedPromptVersionId(version.id) }) }, version.id)))) })] })] }), _jsx(PromptVersionPreview, { option: selectedPromptVersion })] })) : (_jsxs("div", { className: "max-h-[360px] overflow-y-auto overflow-x-hidden rounded-md border bg-background", children: [_jsx(MiniSearch, { value: datasetSearch, onChange: setDatasetSearch, placeholder: t('optimizations.new.origin.searchDataset') }), datasetsLoading ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.datasetLoading') })) : datasetsQuery.isError ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.datasetError') })) : filteredDatasets.length === 0 ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.datasetEmpty') })) : (filteredDatasets.map((dataset) => (_jsx("div", { "data-testid": `optimization-new-dataset-row-${dataset.id}`, children: _jsx(DatasetRow, { dataset: dataset, selected: dataset.id === effectiveDatasetId, onSelect: () => setSelectedDatasetId(dataset.id) }) }, dataset.id))))] }))] }), originMode === 'dataset' && (_jsxs("div", { className: cn('mt-3 flex items-start gap-2 rounded-md border px-3 py-2 text-[12px]', optimizationTone.info.border, optimizationTone.info.bg), "data-testid": "optimization-new-dataset-first-version-hint", children: [_jsx(Sparkles, { className: "mt-0.5 size-3.5 shrink-0 text-[var(--status-canary-fg)]", "aria-hidden": "true" }), _jsx("span", { children: t('optimizations.new.origin.datasetFirstVersionHint') })] })), originMode === 'prompt' && (_jsxs("div", { className: "mt-4 space-y-2", "data-testid": "optimization-new-origin-prompt-dataset-picker", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [_jsxs("label", { className: "block text-[12.5px] font-medium", children: [t('optimizations.new.origin.promptDatasetPicker'), ' ', _jsx("span", { className: "text-destructive", children: "*" })] }), selectedPrompt?.defaultDatasetId &&
673
+ effectiveDatasetId &&
674
+ effectiveDatasetId !== selectedPrompt.defaultDatasetId && (_jsxs("span", { className: cn('inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-[10.5px]', optimizationTone.warning.pill), children: [_jsx(AlertTriangle, { className: "size-3", "aria-hidden": "true" }), t('optimizations.new.origin.promptDatasetMismatch')] })), selectedPrompt?.defaultDatasetId && effectiveDatasetId === selectedPrompt.defaultDatasetId && (_jsxs("span", { className: cn('inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-[10.5px]', optimizationTone.positive.pill), children: [_jsx(Check, { className: "size-3", "aria-hidden": "true" }), t('optimizations.new.origin.promptDatasetDefault')] }))] }), _jsxs("div", { className: "max-h-[360px] overflow-y-auto overflow-x-hidden rounded-md border bg-background", children: [_jsx(MiniSearch, { value: datasetSearch, onChange: setDatasetSearch, placeholder: t('optimizations.new.origin.searchDataset') }), datasetsLoading ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.datasetLoading') })) : datasetsQuery.isError ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.datasetError') })) : filteredDatasets.length === 0 ? (_jsx(PickerEmpty, { children: t('optimizations.new.origin.datasetEmpty') })) : (filteredDatasets.map((dataset) => (_jsx("div", { "data-testid": `optimization-new-dataset-row-${dataset.id}`, children: _jsx(DatasetRow, { dataset: dataset, selected: dataset.id === effectiveDatasetId, onSelect: () => setSelectedDatasetId(dataset.id) }) }, dataset.id))))] })] }))] })] })] }), _jsxs("section", { className: "rounded-lg border bg-card", "data-testid": "optimization-new-step-experiment", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b px-5 py-3", children: [_jsx(StepAnchor, { index: 2 }), _jsx("h3", { className: "text-[14px] font-semibold", children: t('optimizations.new.steps.experiment') }), _jsx("span", { className: "text-[12px] text-muted-foreground", children: t('optimizations.new.steps.experimentDetail') })] }), _jsxs("div", { className: "space-y-5 p-5", children: [_jsxs("div", { children: [_jsxs("label", { className: "mb-1.5 block text-[12.5px] font-medium", children: [t('optimizations.new.experiment.modelPicker'), " ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsxs("div", { className: "max-h-[360px] overflow-y-auto overflow-x-hidden rounded-md border bg-background", children: [_jsx(MiniSearch, { value: modelSearch, onChange: setModelSearch, placeholder: t('optimizations.new.experiment.searchModel') }), modelsLoading ? (_jsx(PickerEmpty, { children: t('optimizations.new.experiment.modelLoading') })) : modelsQuery.isError ? (_jsx(PickerEmpty, { children: t('optimizations.new.experiment.modelError') })) : filteredModels.length === 0 ? (_jsx(PickerEmpty, { children: t('optimizations.new.experiment.modelEmpty') })) : (filteredModels.map((model) => (_jsx("div", { "data-testid": `optimization-new-model-row-${model.id}`, children: _jsx(ModelOptionRow, { model: model, selected: model.id === effectiveModelId, onSelect: () => applyModelDefaults(model) }) }, model.id))))] })] }), _jsxs("div", { className: "border-t border-dashed pt-5", children: [_jsx(SubSectionHead, { tone: "info", label: t('optimizations.new.experiment.params') }), _jsxs("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.experiment.temperature') }), _jsx(Input, { value: temperature, onChange: (event) => {
675
+ const next = Number(event.target.value);
676
+ if (Number.isFinite(next))
677
+ setTemperature(next);
678
+ }, className: "font-mono text-[13px]", "data-testid": "optimization-new-temperature" })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.experiment.concurrency') }), _jsx(Input, { value: concurrency, onChange: (event) => {
679
+ const next = Number(event.target.value);
680
+ if (Number.isFinite(next))
681
+ setConcurrency(next);
682
+ }, className: "font-mono text-[13px]", "data-testid": "optimization-new-concurrency" }), _jsx("div", { className: "font-mono text-[10.5px] text-muted-foreground", "data-testid": "optimization-new-concurrency-limit", children: selectedModel
683
+ ? formatTemplate(t('optimizations.new.experiment.modelLimitSuffix'), {
684
+ limit: formatModelLimit(selectedModel.concurrency.limit),
685
+ })
686
+ : t('optimizations.new.experiment.modelLimitMissing') })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.experiment.rpm') }), _jsx(Input, { value: rpm, onChange: (event) => {
687
+ const next = Number(event.target.value);
688
+ if (Number.isFinite(next))
689
+ setRpm(next);
690
+ }, className: "font-mono text-[13px]", "data-testid": "optimization-new-rpm" }), _jsx("div", { className: "font-mono text-[10.5px] text-muted-foreground", "data-testid": "optimization-new-rpm-limit", children: selectedModel
691
+ ? formatTemplate(t('optimizations.new.experiment.modelLimitSuffix'), {
692
+ limit: formatModelLimit(selectedModel.rpm.limit),
693
+ })
694
+ : t('optimizations.new.experiment.modelLimitMissing') })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.experiment.tpm') }), _jsx(Input, { value: formatThousand(tpm), onChange: (event) => {
695
+ const next = Number(event.target.value.replace(/\s/g, ''));
696
+ if (Number.isFinite(next))
697
+ setTpm(next);
698
+ }, className: "font-mono text-[13px]", "data-testid": "optimization-new-tpm" }), _jsx("div", { className: "font-mono text-[10.5px] text-muted-foreground", "data-testid": "optimization-new-tpm-limit", children: selectedModel
699
+ ? formatTemplate(t('optimizations.new.experiment.modelLimitSuffix'), {
700
+ limit: formatModelLimit(selectedModel.tpm.limit),
701
+ })
702
+ : t('optimizations.new.experiment.modelLimitMissing') })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.experiment.sampleTimeoutSeconds') }), _jsx(Input, { value: sampleTimeoutSeconds, onChange: (event) => {
703
+ const next = Number(event.target.value);
704
+ if (Number.isFinite(next) && next > 0)
705
+ setSampleTimeoutSeconds(next);
706
+ }, className: "font-mono text-[13px]", "data-testid": "optimization-new-sample-timeout" }), _jsx("div", { className: "font-mono text-[10.5px] text-muted-foreground", children: t('optimizations.new.experiment.sampleTimeoutSecondsHint') })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.experiment.retries') }), _jsx(Input, { value: retries, onChange: (event) => {
707
+ const next = Number(event.target.value);
708
+ if (Number.isFinite(next) && next >= 0)
709
+ setRetries(next);
710
+ }, className: "font-mono text-[13px]", "data-testid": "optimization-new-retries" }), _jsx("div", { className: "font-mono text-[10.5px] text-muted-foreground", children: t('optimizations.new.experiment.retriesHint') })] }), (impliedDataset?.hasImages ||
711
+ (originMode === 'experiment' && selectedExperiment?.datasetHasImages)) && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.experiment.imageEncoding') }), _jsxs("div", { className: "inline-flex rounded-md border bg-background p-0.5", "data-testid": "optimization-new-image-encoding", children: [_jsx("button", { type: "button", onClick: () => setImageEncoding('url'), className: cn('cursor-pointer rounded px-3 py-1 font-mono text-[12px] transition-colors', imageEncoding === 'url'
712
+ ? 'bg-primary text-primary-foreground'
713
+ : 'text-muted-foreground hover:bg-muted'), "data-testid": "optimization-new-image-encoding-url", children: t('optimizations.new.experiment.imageEncodingUrl') }), _jsx("button", { type: "button", onClick: () => setImageEncoding('base64'), className: cn('cursor-pointer rounded px-3 py-1 font-mono text-[12px] transition-colors', imageEncoding === 'base64'
714
+ ? 'bg-primary text-primary-foreground'
715
+ : 'text-muted-foreground hover:bg-muted'), "data-testid": "optimization-new-image-encoding-base64", children: t('optimizations.new.experiment.imageEncodingBase64') })] })] }))] })] })] })] }), _jsxs("section", { className: "rounded-lg border bg-card", "data-testid": "optimization-new-step-optimization", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b px-5 py-3", children: [_jsx(StepAnchor, { index: 3 }), _jsx("h3", { className: "text-[14px] font-semibold", children: t('optimizations.new.steps.optimization') }), _jsx("span", { className: "text-[12px] text-muted-foreground", children: t('optimizations.new.steps.optimizationDetail') })] }), _jsxs("div", { className: "space-y-6 p-5", children: [_jsxs("div", { children: [_jsx(SubSectionHead, { tone: "info", label: t('optimizations.new.optimization.hintSection') }), originMode === 'dataset' && (_jsx(PromptLanguageSelect, { value: datasetPromptLanguage, onChange: setDatasetPromptLanguage, helpKey: "optimizations.new.optimization.datasetPromptLanguageHelp", className: "mb-4 max-w-[420px]", triggerClassName: "h-8" })), _jsx("label", { htmlFor: "optimization-new-optimization-hint", className: "mb-1.5 block text-[12.5px] font-medium", children: t('optimizations.new.optimization.hint') }), _jsx("textarea", { id: "optimization-new-optimization-hint", "data-testid": "optimization-new-optimization-hint", value: optimizationHint, onChange: (event) => setOptimizationHint(event.target.value), maxLength: 4000, rows: 4, className: "min-h-[96px] w-full resize-y rounded-md border bg-background px-3 py-2 text-[12.5px] text-foreground shadow-sm outline-none transition-colors placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring", placeholder: t('optimizations.new.optimization.hintPlaceholder') }), _jsx("div", { className: "mt-1 flex justify-end font-mono text-[10.5px] text-muted-foreground", children: formatTemplate(t('optimizations.new.optimization.hintCounter'), {
716
+ count: optimizationHint.length,
717
+ }) })] }), _jsxs("div", { children: [_jsx(SubSectionHead, { tone: "info", label: t('optimizations.new.optimization.goalsSection') }), goals.length === 0 ? (_jsx(PickerEmpty, { children: t('optimizations.new.optimization.goalEmpty') })) : (_jsx("div", { className: "space-y-2", children: goals.map((goal) => (_jsxs("div", { className: "grid grid-cols-1 items-center gap-2 sm:grid-cols-[1.4fr_0.8fr_0.8fr_1.1fr_28px]", children: [_jsx("select", { value: goal.metric, onChange: (event) => updateGoal(goal.id, { metric: event.target.value }), className: "h-8 rounded-md border bg-background px-2.5 font-mono text-[12.5px] text-foreground", "aria-label": t('optimizations.new.optimization.metric.accuracy'), "data-testid": "optimization-new-goal-metric", children: Object.keys(METRIC_LABEL_KEY).map((metric) => (_jsx("option", { value: metric, children: t(METRIC_LABEL_KEY[metric]) }, metric))) }), _jsx("select", { value: goal.comparator, onChange: (event) => updateGoal(goal.id, { comparator: event.target.value }), className: "h-8 rounded-md border bg-background px-2.5 font-mono text-[12.5px] text-foreground", "aria-label": t('optimizations.new.optimization.comparator.gte'), "data-testid": "optimization-new-goal-comparator", children: Object.keys(COMPARATOR_LABEL_KEY).map((cmp) => (_jsx("option", { value: cmp, children: t(COMPARATOR_LABEL_KEY[cmp]) }, cmp))) }), _jsx(Input, { value: goal.target, onChange: (event) => updateGoal(goal.id, { target: event.target.value }), className: "h-8 font-mono text-[12.5px]", "data-testid": "optimization-new-goal-target" }), _jsxs("select", { value: goal.scope, onChange: (event) => updateGoal(goal.id, { scope: event.target.value }), className: "h-8 rounded-md border bg-background px-2.5 font-mono text-[12.5px] text-foreground", "aria-label": t('optimizations.new.optimization.scope.overall'), children: [_jsx("option", { value: "overall", children: t('optimizations.new.optimization.scope.overall') }), scopeOptions.map((label) => (_jsx("option", { value: label, children: formatTemplate(t('optimizations.new.optimization.scope.class'), { label }) }, label)))] }), _jsx("button", { type: "button", onClick: () => removeGoal(goal.id), "aria-label": t('optimizations.new.optimization.removeGoal'), className: "inline-flex size-7 items-center justify-center self-center justify-self-end rounded-md border border-border bg-background text-muted-foreground transition-colors hover:border-destructive/40 hover:bg-destructive/10 hover:text-destructive", children: _jsx(X, { className: "size-3.5" }) })] }, goal.id))) })), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: addGoal, className: "mt-2 h-8 gap-1 self-start", "data-testid": "optimization-new-goal-add", children: [_jsx(Plus, { className: "size-3.5" }), t('optimizations.new.optimization.addGoal')] })] }), _jsxs("div", { className: "border-t border-dashed pt-5", children: [_jsx(SubSectionHead, { tone: "positive", label: t('optimizations.new.optimization.iterSection') }), _jsx("label", { className: "mb-2 block text-[12.5px] font-medium", children: t('optimizations.new.optimization.fields') }), !impliedDataset ? (_jsx("div", { className: "rounded-md border border-dashed bg-muted/30 px-3 py-6 text-center text-[12px] text-muted-foreground", children: originMode === 'prompt'
718
+ ? t('optimizations.new.optimization.fieldsHint')
719
+ : datasetsLoading
720
+ ? t('optimizations.new.optimization.fieldsLoading')
721
+ : t('optimizations.new.optimization.fieldsHint') })) : (_jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsxs("div", { className: "max-h-[360px] overflow-y-auto overflow-x-hidden rounded-md border bg-background", children: [_jsxs("div", { className: "flex items-center justify-between border-b bg-muted/60 px-3 py-2", children: [_jsxs("span", { className: "flex items-center gap-2 text-[12.5px] font-semibold", children: [_jsx("span", { "aria-hidden": "true", className: cn('inline-block size-1.5 rounded-full', optimizationTone.info.fill) }), t('optimizations.new.optimization.inputFields')] }), _jsx("span", { className: "font-mono text-[10.5px] text-muted-foreground", children: t('optimizations.new.optimization.inputFieldsHint') })] }), inputFields.length > 0 ? (inputFields.map((field) => (_jsxs("div", { className: "flex items-center gap-2.5 border-b px-3 py-2 font-mono text-[12px] last:border-b-0", children: [_jsx(FieldCheckbox, { checked: true, locked: true, ariaLabel: field.name }), _jsx("span", { className: "flex-1 font-medium text-foreground", children: field.name }), _jsx(Tag, { children: field.type })] }, field.name)))) : (_jsx(PickerEmpty, { children: t('optimizations.new.optimization.fieldsInputEmpty') }))] }), _jsxs("div", { className: "max-h-[360px] overflow-y-auto overflow-x-hidden rounded-md border bg-background", children: [_jsxs("div", { className: "flex items-center justify-between border-b bg-muted/60 px-3 py-2", children: [_jsxs("span", { className: "flex items-center gap-2 text-[12.5px] font-semibold", children: [_jsx("span", { "aria-hidden": "true", className: cn('inline-block size-1.5 rounded-full', optimizationTone.warning.fill) }), t('optimizations.new.optimization.metaFields')] }), _jsx("span", { className: "font-mono text-[10.5px] text-muted-foreground", children: t('optimizations.new.optimization.metaFieldsHint') })] }), metaFields.length > 0 ? (metaFields.map((field) => (_jsxs("div", { className: "flex items-center gap-2.5 border-b px-3 py-2 font-mono text-[12px] last:border-b-0", children: [_jsx(FieldCheckbox, { checked: isMetaChecked(field.name), ariaLabel: field.name, onClick: () => toggleMetaField(field.name) }), _jsx("span", { className: "flex-1 font-medium text-foreground", children: field.name }), _jsx(Tag, { children: field.type })] }, field.name)))) : (_jsx(PickerEmpty, { children: t('optimizations.new.optimization.fieldsMetaEmpty') }))] })] })), _jsxs("div", { className: "mt-5", children: [_jsxs("label", { className: "mb-1.5 block text-[12.5px] font-medium", children: [t('optimizations.new.optimization.analysisModel'), " ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx("div", { className: "text-[11px] text-muted-foreground", children: t('optimizations.new.optimization.analysisModelHelp') }), _jsxs("div", { className: "mt-2 max-h-[360px] overflow-y-auto overflow-x-hidden rounded-md border bg-background", children: [_jsx(MiniSearch, { value: analysisModelSearch, onChange: setAnalysisModelSearch, placeholder: t('optimizations.new.optimization.searchAnalysisModel') }), modelsLoading ? (_jsx(PickerEmpty, { children: t('optimizations.new.experiment.modelLoading') })) : modelsQuery.isError ? (_jsx(PickerEmpty, { children: t('optimizations.new.experiment.modelError') })) : filteredAnalysisModels.length === 0 ? (_jsx(PickerEmpty, { children: t('optimizations.new.experiment.modelEmpty') })) : (filteredAnalysisModels.map((model) => (_jsx("div", { "data-testid": `optimization-new-analysis-model-row-${model.id}`, children: _jsx(ModelOptionRow, { model: model, selected: model.id === effectiveAnalysisModelId, onSelect: () => setAnalysisModelId(model.id) }) }, model.id))))] })] }), _jsxs("div", { className: "mt-5 grid grid-cols-1 gap-4 sm:grid-cols-2", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.optimization.maxRounds') }), _jsx(Slider, { value: maxRounds, min: 3, max: 20, onChange: setMaxRounds, formatValue: (value) => formatTemplate(t('optimizations.new.optimization.maxRoundsValue'), { value }) })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.optimization.noImprovement') }), _jsx(Slider, { value: noImprovementRounds, min: 0, max: 5, onChange: setNoImprovementRounds, formatValue: (value) => value === 0
722
+ ? t('optimizations.new.optimization.noImprovementUnlimited')
723
+ : formatTemplate(t('optimizations.new.optimization.noImprovementValue'), { value }) })] })] }), originMode === 'dataset' && (_jsxs("div", { className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.optimization.initialSamplingRounds') }), _jsx(Slider, { value: initialSamplingRounds, min: 1, max: 10, onChange: setInitialSamplingRounds, formatValue: (value) => formatTemplate(t('optimizations.new.optimization.initialSamplingRoundsValue'), { value }) })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "block text-[12.5px] font-medium", children: t('optimizations.new.optimization.initialSamplesPerRound') }), _jsx(Slider, { value: initialSamplesPerRound, min: 5, max: 200, step: 5, onChange: setInitialSamplesPerRound, formatValue: (value) => formatTemplate(t('optimizations.new.optimization.initialSamplesPerRoundValue'), { value }) })] })] }))] })] })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("div", { className: "text-[11.5px] text-muted-foreground", children: t('optimizations.new.footerNote') }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-9", disabled: isSubmitting, children: t('optimizations.new.saveDraft') }), _jsx(Button, { asChild: true, type: "button", variant: "outline", size: "sm", className: "h-9", children: _jsx(Link, { href: `/optimizations`, children: t('optimizations.new.cancel') }) }), _jsxs(Button, { type: "button", size: "sm", className: "h-9 gap-1", onClick: handleSubmit, disabled: isSubmitting || optimizationNameTaken, children: [isSubmitting ? _jsx(Loader2, { className: "size-3.5 animate-spin" }) : _jsx(Play, { className: "size-3.5" }), t('optimizations.new.start')] })] })] })] }), _jsx("aside", { className: "flex flex-col gap-3 xl:sticky xl:top-20 xl:max-h-[calc(100vh-6rem)] xl:overflow-y-auto xl:overscroll-contain xl:pr-1", "data-testid": "optimization-new-side-panel", children: _jsxs("div", { className: cn('rounded-lg border p-4', optimizationTone.info.border, 'bg-[color-mix(in_oklab,var(--status-canary-bg)_50%,var(--card))]', optimizationTone.info.text), children: [_jsxs("div", { className: "mb-3 flex items-center gap-2 text-[13px] font-semibold", children: [_jsx(Calculator, { className: "size-3.5", "aria-hidden": "true" }), t('optimizations.new.estimate.title')] }), _jsxs("div", { className: "space-y-2 text-[12.5px]", children: [_jsx(EstimateRow, { label: t('optimizations.new.estimate.experimentTokens'), valueLabel: totalExperimentTokens > 0
724
+ ? `~ ${(totalExperimentTokens / 1_000_000).toFixed(1)} M`
725
+ : t('optimizations.new.estimate.unknown') }), _jsx(EstimateRow, { label: t('optimizations.new.estimate.experimentCost'), valueLabel: experimentCost > 0 ? `$ ${experimentCost.toFixed(2)}` : t('optimizations.new.estimate.unknown') }), _jsx(EstimateRow, { label: t('optimizations.new.estimate.analysisTokens'), valueLabel: totalAnalysisTokens > 0
726
+ ? `~ ${(totalAnalysisTokens / 1_000_000).toFixed(2)} M`
727
+ : t('optimizations.new.estimate.unknown') }), _jsx(EstimateRow, { label: t('optimizations.new.estimate.analysisCost'), valueLabel: analysisCost > 0 ? `$ ${analysisCost.toFixed(2)}` : t('optimizations.new.estimate.unknown') }), _jsxs("div", { className: cn('mt-1 flex items-baseline justify-between border-t pt-2 text-[13px]', optimizationTone.info.border), children: [_jsx("span", { className: "opacity-80", children: formatTemplate(t('optimizations.new.estimate.totalCost'), { rounds: maxRounds }) }), _jsxs("span", { className: "font-mono text-[16px] font-semibold", children: ["$ ", totalCost.toFixed(2)] })] })] })] }) })] })] }) }));
728
+ }
729
+ function EstimateRow({ label, valueLabel, subLabel }) {
730
+ return (_jsxs("div", { className: cn('flex items-baseline justify-between gap-3 border-b border-dashed pb-2 last:border-b-0 last:pb-0', optimizationTone.info.border), children: [_jsx("span", { className: "opacity-80", children: label }), _jsxs("span", { className: "font-mono font-semibold tabular-nums", children: [valueLabel, subLabel && _jsx("span", { className: "ml-1 text-[11px] font-medium text-muted-foreground", children: subLabel })] })] }));
731
+ }
732
+ //# sourceMappingURL=optimization-new-page.js.map