@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.
- package/LICENSE +190 -0
- package/dist/components/annotations/annotation-claim-dialog.d.ts +13 -0
- package/dist/components/annotations/annotation-claim-dialog.d.ts.map +1 -0
- package/dist/components/annotations/annotation-claim-dialog.js +60 -0
- package/dist/components/annotations/annotation-claim-dialog.js.map +1 -0
- package/dist/components/charts/source-legend.d.ts +10 -0
- package/dist/components/charts/source-legend.d.ts.map +1 -0
- package/dist/components/charts/source-legend.js +15 -0
- package/dist/components/charts/source-legend.js.map +1 -0
- package/dist/components/charts/source-stacked-bar.d.ts +22 -0
- package/dist/components/charts/source-stacked-bar.d.ts.map +1 -0
- package/dist/components/charts/source-stacked-bar.js +43 -0
- package/dist/components/charts/source-stacked-bar.js.map +1 -0
- package/dist/components/index.d.ts +15 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +17 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/json-object-textarea.d.ts +11 -0
- package/dist/components/json-object-textarea.d.ts.map +1 -0
- package/dist/components/json-object-textarea.js +82 -0
- package/dist/components/json-object-textarea.js.map +1 -0
- package/dist/components/model-context-window-input.d.ts +15 -0
- package/dist/components/model-context-window-input.d.ts.map +1 -0
- package/dist/components/model-context-window-input.js +57 -0
- package/dist/components/model-context-window-input.js.map +1 -0
- package/dist/components/model-probe-status.d.ts +11 -0
- package/dist/components/model-probe-status.d.ts.map +1 -0
- package/dist/components/model-probe-status.js +34 -0
- package/dist/components/model-probe-status.js.map +1 -0
- package/dist/components/prompt-diff/prompt-diff-split-view.d.ts +28 -0
- package/dist/components/prompt-diff/prompt-diff-split-view.d.ts.map +1 -0
- package/dist/components/prompt-diff/prompt-diff-split-view.js +101 -0
- package/dist/components/prompt-diff/prompt-diff-split-view.js.map +1 -0
- package/dist/components/prompt-language-select.d.ts +13 -0
- package/dist/components/prompt-language-select.d.ts.map +1 -0
- package/dist/components/prompt-language-select.js +14 -0
- package/dist/components/prompt-language-select.js.map +1 -0
- package/dist/components/prompt-version-picker-row.d.ts +27 -0
- package/dist/components/prompt-version-picker-row.d.ts.map +1 -0
- package/dist/components/prompt-version-picker-row.js +50 -0
- package/dist/components/prompt-version-picker-row.js.map +1 -0
- package/dist/components/prompt-version-status-badge.d.ts +8 -0
- package/dist/components/prompt-version-status-badge.d.ts.map +1 -0
- package/dist/components/prompt-version-status-badge.js +31 -0
- package/dist/components/prompt-version-status-badge.js.map +1 -0
- package/dist/components/quick-fill/quick-fill-picker.d.ts +42 -0
- package/dist/components/quick-fill/quick-fill-picker.d.ts.map +1 -0
- package/dist/components/quick-fill/quick-fill-picker.js +46 -0
- package/dist/components/quick-fill/quick-fill-picker.js.map +1 -0
- package/dist/components/time-zone-preference-menu.d.ts +10 -0
- package/dist/components/time-zone-preference-menu.d.ts.map +1 -0
- package/dist/components/time-zone-preference-menu.js +33 -0
- package/dist/components/time-zone-preference-menu.js.map +1 -0
- package/dist/contracts/index.d.ts +13 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +7 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/features/index.d.ts +3 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +4 -0
- package/dist/features/index.js.map +1 -0
- package/dist/features/model-quick-fill/model-preset-draft.d.ts +19 -0
- package/dist/features/model-quick-fill/model-preset-draft.d.ts.map +1 -0
- package/dist/features/model-quick-fill/model-preset-draft.js +22 -0
- package/dist/features/model-quick-fill/model-preset-draft.js.map +1 -0
- package/dist/features/model-quick-fill/model-preset-quick-fill.d.ts +7 -0
- package/dist/features/model-quick-fill/model-preset-quick-fill.d.ts.map +1 -0
- package/dist/features/model-quick-fill/model-preset-quick-fill.js +73 -0
- package/dist/features/model-quick-fill/model-preset-quick-fill.js.map +1 -0
- package/dist/hooks/annotation.d.ts +253 -0
- package/dist/hooks/annotation.d.ts.map +1 -0
- package/dist/hooks/annotation.js +79 -0
- package/dist/hooks/annotation.js.map +1 -0
- package/dist/hooks/canary-release.d.ts +674 -0
- package/dist/hooks/canary-release.d.ts.map +1 -0
- package/dist/hooks/canary-release.js +142 -0
- package/dist/hooks/canary-release.js.map +1 -0
- package/dist/hooks/connector.d.ts +585 -0
- package/dist/hooks/connector.d.ts.map +1 -0
- package/dist/hooks/connector.js +157 -0
- package/dist/hooks/connector.js.map +1 -0
- package/dist/hooks/dataset.d.ts +138 -0
- package/dist/hooks/dataset.d.ts.map +1 -0
- package/dist/hooks/dataset.js +84 -0
- package/dist/hooks/dataset.js.map +1 -0
- package/dist/hooks/experiment.d.ts +376 -0
- package/dist/hooks/experiment.d.ts.map +1 -0
- package/dist/hooks/experiment.js +58 -0
- package/dist/hooks/experiment.js.map +1 -0
- package/dist/hooks/index.d.ts +18 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +18 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/model.d.ts +327 -0
- package/dist/hooks/model.d.ts.map +1 -0
- package/dist/hooks/model.js +103 -0
- package/dist/hooks/model.js.map +1 -0
- package/dist/hooks/optimization.d.ts +557 -0
- package/dist/hooks/optimization.d.ts.map +1 -0
- package/dist/hooks/optimization.js +94 -0
- package/dist/hooks/optimization.js.map +1 -0
- package/dist/hooks/production-release.d.ts +141 -0
- package/dist/hooks/production-release.d.ts.map +1 -0
- package/dist/hooks/production-release.js +51 -0
- package/dist/hooks/production-release.js.map +1 -0
- package/dist/hooks/project-monitoring.d.ts +199 -0
- package/dist/hooks/project-monitoring.d.ts.map +1 -0
- package/dist/hooks/project-monitoring.js +55 -0
- package/dist/hooks/project-monitoring.js.map +1 -0
- package/dist/hooks/prompt.d.ts +509 -0
- package/dist/hooks/prompt.d.ts.map +1 -0
- package/dist/hooks/prompt.js +127 -0
- package/dist/hooks/prompt.js.map +1 -0
- package/dist/hooks/quick-start.d.ts +219 -0
- package/dist/hooks/quick-start.d.ts.map +1 -0
- package/dist/hooks/quick-start.js +35 -0
- package/dist/hooks/quick-start.js.map +1 -0
- package/dist/hooks/release-line.d.ts +819 -0
- package/dist/hooks/release-line.d.ts.map +1 -0
- package/dist/hooks/release-line.js +52 -0
- package/dist/hooks/release-line.js.map +1 -0
- package/dist/hooks/run-result.d.ts +118 -0
- package/dist/hooks/run-result.d.ts.map +1 -0
- package/dist/hooks/run-result.js +56 -0
- package/dist/hooks/run-result.js.map +1 -0
- package/dist/hooks/token.d.ts +58 -0
- package/dist/hooks/token.d.ts.map +1 -0
- package/dist/hooks/token.js +39 -0
- package/dist/hooks/token.js.map +1 -0
- package/dist/hooks/use-auto-refresh.d.ts +30 -0
- package/dist/hooks/use-auto-refresh.d.ts.map +1 -0
- package/dist/hooks/use-auto-refresh.js +175 -0
- package/dist/hooks/use-auto-refresh.js.map +1 -0
- package/dist/hooks/use-date-time-formatter.d.ts +11 -0
- package/dist/hooks/use-date-time-formatter.d.ts.map +1 -0
- package/dist/hooks/use-date-time-formatter.js +24 -0
- package/dist/hooks/use-date-time-formatter.js.map +1 -0
- package/dist/hooks/use-delayed-loading.d.ts +25 -0
- package/dist/hooks/use-delayed-loading.d.ts.map +1 -0
- package/dist/hooks/use-delayed-loading.js +95 -0
- package/dist/hooks/use-delayed-loading.js.map +1 -0
- package/dist/i18n/index.d.ts +5678 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +5712 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/language.d.ts +17 -0
- package/dist/i18n/language.d.ts.map +1 -0
- package/dist/i18n/language.js +43 -0
- package/dist/i18n/language.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-error.d.ts +2 -0
- package/dist/lib/api-error.d.ts.map +1 -0
- package/dist/lib/api-error.js +38 -0
- package/dist/lib/api-error.js.map +1 -0
- package/dist/lib/format.d.ts +30 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +157 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/index.d.ts +8 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +8 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/model-number.d.ts +2 -0
- package/dist/lib/model-number.d.ts.map +1 -0
- package/dist/lib/model-number.js +18 -0
- package/dist/lib/model-number.js.map +1 -0
- package/dist/lib/model-provider-type.d.ts +7 -0
- package/dist/lib/model-provider-type.d.ts.map +1 -0
- package/dist/lib/model-provider-type.js +28 -0
- package/dist/lib/model-provider-type.js.map +1 -0
- package/dist/lib/project-name.d.ts +7 -0
- package/dist/lib/project-name.d.ts.map +1 -0
- package/dist/lib/project-name.js +14 -0
- package/dist/lib/project-name.js.map +1 -0
- package/dist/lib/releases/release-line-model.d.ts +55 -0
- package/dist/lib/releases/release-line-model.d.ts.map +1 -0
- package/dist/lib/releases/release-line-model.js +476 -0
- package/dist/lib/releases/release-line-model.js.map +1 -0
- package/dist/lib/time-zone.d.ts +14 -0
- package/dist/lib/time-zone.d.ts.map +1 -0
- package/dist/lib/time-zone.js +118 -0
- package/dist/lib/time-zone.js.map +1 -0
- package/dist/lib/uuid.d.ts +2 -0
- package/dist/lib/uuid.d.ts.map +1 -0
- package/dist/lib/uuid.js +5 -0
- package/dist/lib/uuid.js.map +1 -0
- package/dist/providers/display-preferences-provider.d.ts +18 -0
- package/dist/providers/display-preferences-provider.d.ts.map +1 -0
- package/dist/providers/display-preferences-provider.js +46 -0
- package/dist/providers/display-preferences-provider.js.map +1 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +5 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/project-context-provider.d.ts +8 -0
- package/dist/providers/project-context-provider.d.ts.map +1 -0
- package/dist/providers/project-context-provider.js +15 -0
- package/dist/providers/project-context-provider.js.map +1 -0
- package/dist/providers/proofhound-web-provider.d.ts +9 -0
- package/dist/providers/proofhound-web-provider.d.ts.map +1 -0
- package/dist/providers/proofhound-web-provider.js +44 -0
- package/dist/providers/proofhound-web-provider.js.map +1 -0
- package/dist/providers/refine-provider.d.ts +5 -0
- package/dist/providers/refine-provider.d.ts.map +1 -0
- package/dist/providers/refine-provider.js +23 -0
- package/dist/providers/refine-provider.js.map +1 -0
- package/dist/screens/annotations/annotation-detail-page.d.ts +5 -0
- package/dist/screens/annotations/annotation-detail-page.d.ts.map +1 -0
- package/dist/screens/annotations/annotation-detail-page.js +379 -0
- package/dist/screens/annotations/annotation-detail-page.js.map +1 -0
- package/dist/screens/annotations/annotation-new-page.d.ts +4 -0
- package/dist/screens/annotations/annotation-new-page.d.ts.map +1 -0
- package/dist/screens/annotations/annotation-new-page.js +125 -0
- package/dist/screens/annotations/annotation-new-page.js.map +1 -0
- package/dist/screens/annotations/annotation-task-model.d.ts +39 -0
- package/dist/screens/annotations/annotation-task-model.d.ts.map +1 -0
- package/dist/screens/annotations/annotation-task-model.js +88 -0
- package/dist/screens/annotations/annotation-task-model.js.map +1 -0
- package/dist/screens/annotations/annotation-ui.d.ts +33 -0
- package/dist/screens/annotations/annotation-ui.d.ts.map +1 -0
- package/dist/screens/annotations/annotation-ui.js +92 -0
- package/dist/screens/annotations/annotation-ui.js.map +1 -0
- package/dist/screens/annotations/annotations-list-page.d.ts +4 -0
- package/dist/screens/annotations/annotations-list-page.d.ts.map +1 -0
- package/dist/screens/annotations/annotations-list-page.js +82 -0
- package/dist/screens/annotations/annotations-list-page.js.map +1 -0
- package/dist/screens/connectors/connector-detail-page.d.ts +5 -0
- package/dist/screens/connectors/connector-detail-page.d.ts.map +1 -0
- package/dist/screens/connectors/connector-detail-page.js +838 -0
- package/dist/screens/connectors/connector-detail-page.js.map +1 -0
- package/dist/screens/connectors/connector-form-page.d.ts +8 -0
- package/dist/screens/connectors/connector-form-page.d.ts.map +1 -0
- package/dist/screens/connectors/connector-form-page.js +328 -0
- package/dist/screens/connectors/connector-form-page.js.map +1 -0
- package/dist/screens/connectors/connector-peek-dialog.d.ts +8 -0
- package/dist/screens/connectors/connector-peek-dialog.d.ts.map +1 -0
- package/dist/screens/connectors/connector-peek-dialog.js +24 -0
- package/dist/screens/connectors/connector-peek-dialog.js.map +1 -0
- package/dist/screens/connectors/connector-types.d.ts +36 -0
- package/dist/screens/connectors/connector-types.d.ts.map +1 -0
- package/dist/screens/connectors/connector-types.js +48 -0
- package/dist/screens/connectors/connector-types.js.map +1 -0
- package/dist/screens/connectors/connector-ui.d.ts +14 -0
- package/dist/screens/connectors/connector-ui.d.ts.map +1 -0
- package/dist/screens/connectors/connector-ui.js +40 -0
- package/dist/screens/connectors/connector-ui.js.map +1 -0
- package/dist/screens/connectors/connectors-list-page.d.ts +4 -0
- package/dist/screens/connectors/connectors-list-page.d.ts.map +1 -0
- package/dist/screens/connectors/connectors-list-page.js +220 -0
- package/dist/screens/connectors/connectors-list-page.js.map +1 -0
- package/dist/screens/dashboard/dashboard-screen.d.ts +5 -0
- package/dist/screens/dashboard/dashboard-screen.d.ts.map +1 -0
- package/dist/screens/dashboard/dashboard-screen.js +524 -0
- package/dist/screens/dashboard/dashboard-screen.js.map +1 -0
- package/dist/screens/datasets/dataset-detail-helpers.d.ts +10 -0
- package/dist/screens/datasets/dataset-detail-helpers.d.ts.map +1 -0
- package/dist/screens/datasets/dataset-detail-helpers.js +84 -0
- package/dist/screens/datasets/dataset-detail-helpers.js.map +1 -0
- package/dist/screens/datasets/dataset-detail-page.d.ts +6 -0
- package/dist/screens/datasets/dataset-detail-page.d.ts.map +1 -0
- package/dist/screens/datasets/dataset-detail-page.js +379 -0
- package/dist/screens/datasets/dataset-detail-page.js.map +1 -0
- package/dist/screens/datasets/dataset-import-runner.d.ts +17 -0
- package/dist/screens/datasets/dataset-import-runner.d.ts.map +1 -0
- package/dist/screens/datasets/dataset-import-runner.js +33 -0
- package/dist/screens/datasets/dataset-import-runner.js.map +1 -0
- package/dist/screens/datasets/dataset-mappers.d.ts +5 -0
- package/dist/screens/datasets/dataset-mappers.d.ts.map +1 -0
- package/dist/screens/datasets/dataset-mappers.js +79 -0
- package/dist/screens/datasets/dataset-mappers.js.map +1 -0
- package/dist/screens/datasets/dataset-samples-table.d.ts +21 -0
- package/dist/screens/datasets/dataset-samples-table.d.ts.map +1 -0
- package/dist/screens/datasets/dataset-samples-table.js +59 -0
- package/dist/screens/datasets/dataset-samples-table.js.map +1 -0
- package/dist/screens/datasets/dataset-transfer-progress.d.ts +30 -0
- package/dist/screens/datasets/dataset-transfer-progress.d.ts.map +1 -0
- package/dist/screens/datasets/dataset-transfer-progress.js +157 -0
- package/dist/screens/datasets/dataset-transfer-progress.js.map +1 -0
- package/dist/screens/datasets/dataset-types.d.ts +59 -0
- package/dist/screens/datasets/dataset-types.d.ts.map +1 -0
- package/dist/screens/datasets/dataset-types.js +15 -0
- package/dist/screens/datasets/dataset-types.js.map +1 -0
- package/dist/screens/datasets/dataset-ui.d.ts +49 -0
- package/dist/screens/datasets/dataset-ui.d.ts.map +1 -0
- package/dist/screens/datasets/dataset-ui.js +163 -0
- package/dist/screens/datasets/dataset-ui.js.map +1 -0
- package/dist/screens/datasets/dataset-upload-page.d.ts +4 -0
- package/dist/screens/datasets/dataset-upload-page.d.ts.map +1 -0
- package/dist/screens/datasets/dataset-upload-page.js +425 -0
- package/dist/screens/datasets/dataset-upload-page.js.map +1 -0
- package/dist/screens/datasets/dataset-upload-parser.d.ts +21 -0
- package/dist/screens/datasets/dataset-upload-parser.d.ts.map +1 -0
- package/dist/screens/datasets/dataset-upload-parser.js +536 -0
- package/dist/screens/datasets/dataset-upload-parser.js.map +1 -0
- package/dist/screens/datasets/datasets-list-page.d.ts +4 -0
- package/dist/screens/datasets/datasets-list-page.d.ts.map +1 -0
- package/dist/screens/datasets/datasets-list-page.js +249 -0
- package/dist/screens/datasets/datasets-list-page.js.map +1 -0
- package/dist/screens/experiments/experiment-detail-page.d.ts +5 -0
- package/dist/screens/experiments/experiment-detail-page.d.ts.map +1 -0
- package/dist/screens/experiments/experiment-detail-page.js +460 -0
- package/dist/screens/experiments/experiment-detail-page.js.map +1 -0
- package/dist/screens/experiments/experiment-new-page.d.ts +21 -0
- package/dist/screens/experiments/experiment-new-page.d.ts.map +1 -0
- package/dist/screens/experiments/experiment-new-page.js +427 -0
- package/dist/screens/experiments/experiment-new-page.js.map +1 -0
- package/dist/screens/experiments/experiment-option-adapter.d.ts +68 -0
- package/dist/screens/experiments/experiment-option-adapter.d.ts.map +1 -0
- package/dist/screens/experiments/experiment-option-adapter.js +225 -0
- package/dist/screens/experiments/experiment-option-adapter.js.map +1 -0
- package/dist/screens/experiments/experiment-progress.d.ts +5 -0
- package/dist/screens/experiments/experiment-progress.d.ts.map +1 -0
- package/dist/screens/experiments/experiment-progress.js +15 -0
- package/dist/screens/experiments/experiment-progress.js.map +1 -0
- package/dist/screens/experiments/experiment-repeat-href.d.ts +17 -0
- package/dist/screens/experiments/experiment-repeat-href.d.ts.map +1 -0
- package/dist/screens/experiments/experiment-repeat-href.js +32 -0
- package/dist/screens/experiments/experiment-repeat-href.js.map +1 -0
- package/dist/screens/experiments/experiment-theme.d.ts +43 -0
- package/dist/screens/experiments/experiment-theme.d.ts.map +1 -0
- package/dist/screens/experiments/experiment-theme.js +43 -0
- package/dist/screens/experiments/experiment-theme.js.map +1 -0
- package/dist/screens/experiments/experiment-ui.d.ts +36 -0
- package/dist/screens/experiments/experiment-ui.d.ts.map +1 -0
- package/dist/screens/experiments/experiment-ui.js +46 -0
- package/dist/screens/experiments/experiment-ui.js.map +1 -0
- package/dist/screens/experiments/experiment-view-model.d.ts +118 -0
- package/dist/screens/experiments/experiment-view-model.d.ts.map +1 -0
- package/dist/screens/experiments/experiment-view-model.js +114 -0
- package/dist/screens/experiments/experiment-view-model.js.map +1 -0
- package/dist/screens/experiments/experiments-comparison-view.d.ts +10 -0
- package/dist/screens/experiments/experiments-comparison-view.d.ts.map +1 -0
- package/dist/screens/experiments/experiments-comparison-view.js +215 -0
- package/dist/screens/experiments/experiments-comparison-view.js.map +1 -0
- package/dist/screens/experiments/experiments-list-page.d.ts +4 -0
- package/dist/screens/experiments/experiments-list-page.d.ts.map +1 -0
- package/dist/screens/experiments/experiments-list-page.js +364 -0
- package/dist/screens/experiments/experiments-list-page.js.map +1 -0
- package/dist/screens/experiments/experiments-table.d.ts +18 -0
- package/dist/screens/experiments/experiments-table.d.ts.map +1 -0
- package/dist/screens/experiments/experiments-table.js +97 -0
- package/dist/screens/experiments/experiments-table.js.map +1 -0
- package/dist/screens/experiments/run-result-detail-sheet.d.ts +9 -0
- package/dist/screens/experiments/run-result-detail-sheet.d.ts.map +1 -0
- package/dist/screens/experiments/run-result-detail-sheet.js +89 -0
- package/dist/screens/experiments/run-result-detail-sheet.js.map +1 -0
- package/dist/screens/experiments/run-result-display.d.ts +28 -0
- package/dist/screens/experiments/run-result-display.d.ts.map +1 -0
- package/dist/screens/experiments/run-result-display.js +124 -0
- package/dist/screens/experiments/run-result-display.js.map +1 -0
- package/dist/screens/experiments/run-result-labels.d.ts +16 -0
- package/dist/screens/experiments/run-result-labels.d.ts.map +1 -0
- package/dist/screens/experiments/run-result-labels.js +52 -0
- package/dist/screens/experiments/run-result-labels.js.map +1 -0
- package/dist/screens/index.d.ts +29 -0
- package/dist/screens/index.d.ts.map +1 -0
- package/dist/screens/index.js +43 -0
- package/dist/screens/index.js.map +1 -0
- package/dist/screens/models/model-form-page.d.ts +9 -0
- package/dist/screens/models/model-form-page.d.ts.map +1 -0
- package/dist/screens/models/model-form-page.js +876 -0
- package/dist/screens/models/model-form-page.js.map +1 -0
- package/dist/screens/models/model-view-model.d.ts +66 -0
- package/dist/screens/models/model-view-model.d.ts.map +1 -0
- package/dist/screens/models/model-view-model.js +36 -0
- package/dist/screens/models/model-view-model.js.map +1 -0
- package/dist/screens/models/models-list-page.d.ts +4 -0
- package/dist/screens/models/models-list-page.d.ts.map +1 -0
- package/dist/screens/models/models-list-page.js +352 -0
- package/dist/screens/models/models-list-page.js.map +1 -0
- package/dist/screens/models/project-model-adapter.d.ts +5 -0
- package/dist/screens/models/project-model-adapter.d.ts.map +1 -0
- package/dist/screens/models/project-model-adapter.js +63 -0
- package/dist/screens/models/project-model-adapter.js.map +1 -0
- package/dist/screens/monitoring/big-chart-card.d.ts +36 -0
- package/dist/screens/monitoring/big-chart-card.d.ts.map +1 -0
- package/dist/screens/monitoring/big-chart-card.js +32 -0
- package/dist/screens/monitoring/big-chart-card.js.map +1 -0
- package/dist/screens/monitoring/monitoring-filter-strip.d.ts +14 -0
- package/dist/screens/monitoring/monitoring-filter-strip.d.ts.map +1 -0
- package/dist/screens/monitoring/monitoring-filter-strip.js +36 -0
- package/dist/screens/monitoring/monitoring-filter-strip.js.map +1 -0
- package/dist/screens/monitoring/project-monitoring-page.d.ts +9 -0
- package/dist/screens/monitoring/project-monitoring-page.d.ts.map +1 -0
- package/dist/screens/monitoring/project-monitoring-page.js +240 -0
- package/dist/screens/monitoring/project-monitoring-page.js.map +1 -0
- package/dist/screens/monitoring/ranking-cards.d.ts +23 -0
- package/dist/screens/monitoring/ranking-cards.d.ts.map +1 -0
- package/dist/screens/monitoring/ranking-cards.js +78 -0
- package/dist/screens/monitoring/ranking-cards.js.map +1 -0
- package/dist/screens/optimizations/optimization-detail-page.d.ts +5 -0
- package/dist/screens/optimizations/optimization-detail-page.d.ts.map +1 -0
- package/dist/screens/optimizations/optimization-detail-page.js +934 -0
- package/dist/screens/optimizations/optimization-detail-page.js.map +1 -0
- package/dist/screens/optimizations/optimization-mappers.d.ts +56 -0
- package/dist/screens/optimizations/optimization-mappers.d.ts.map +1 -0
- package/dist/screens/optimizations/optimization-mappers.js +142 -0
- package/dist/screens/optimizations/optimization-mappers.js.map +1 -0
- package/dist/screens/optimizations/optimization-new-page.d.ts +8 -0
- package/dist/screens/optimizations/optimization-new-page.d.ts.map +1 -0
- package/dist/screens/optimizations/optimization-new-page.js +732 -0
- package/dist/screens/optimizations/optimization-new-page.js.map +1 -0
- package/dist/screens/optimizations/optimization-theme.d.ts +43 -0
- package/dist/screens/optimizations/optimization-theme.d.ts.map +1 -0
- package/dist/screens/optimizations/optimization-theme.js +46 -0
- package/dist/screens/optimizations/optimization-theme.js.map +1 -0
- package/dist/screens/optimizations/optimization-ui.d.ts +60 -0
- package/dist/screens/optimizations/optimization-ui.d.ts.map +1 -0
- package/dist/screens/optimizations/optimization-ui.js +164 -0
- package/dist/screens/optimizations/optimization-ui.js.map +1 -0
- package/dist/screens/optimizations/optimizations-list-page.d.ts +4 -0
- package/dist/screens/optimizations/optimizations-list-page.d.ts.map +1 -0
- package/dist/screens/optimizations/optimizations-list-page.js +358 -0
- package/dist/screens/optimizations/optimizations-list-page.js.map +1 -0
- package/dist/screens/prompts/prompt-body-editor.d.ts +15 -0
- package/dist/screens/prompts/prompt-body-editor.d.ts.map +1 -0
- package/dist/screens/prompts/prompt-body-editor.js +165 -0
- package/dist/screens/prompts/prompt-body-editor.js.map +1 -0
- package/dist/screens/prompts/prompt-dataset-variables.d.ts +4 -0
- package/dist/screens/prompts/prompt-dataset-variables.d.ts.map +1 -0
- package/dist/screens/prompts/prompt-dataset-variables.js +27 -0
- package/dist/screens/prompts/prompt-dataset-variables.js.map +1 -0
- package/dist/screens/prompts/prompt-detail-page.d.ts +5 -0
- package/dist/screens/prompts/prompt-detail-page.d.ts.map +1 -0
- package/dist/screens/prompts/prompt-detail-page.js +1013 -0
- package/dist/screens/prompts/prompt-detail-page.js.map +1 -0
- package/dist/screens/prompts/prompt-model.d.ts +77 -0
- package/dist/screens/prompts/prompt-model.d.ts.map +1 -0
- package/dist/screens/prompts/prompt-model.js +152 -0
- package/dist/screens/prompts/prompt-model.js.map +1 -0
- package/dist/screens/prompts/prompt-preview-parts.d.ts +12 -0
- package/dist/screens/prompts/prompt-preview-parts.d.ts.map +1 -0
- package/dist/screens/prompts/prompt-preview-parts.js +32 -0
- package/dist/screens/prompts/prompt-preview-parts.js.map +1 -0
- package/dist/screens/prompts/prompt-preview.d.ts +10 -0
- package/dist/screens/prompts/prompt-preview.d.ts.map +1 -0
- package/dist/screens/prompts/prompt-preview.js +16 -0
- package/dist/screens/prompts/prompt-preview.js.map +1 -0
- package/dist/screens/prompts/prompt-ui.d.ts +30 -0
- package/dist/screens/prompts/prompt-ui.d.ts.map +1 -0
- package/dist/screens/prompts/prompt-ui.js +51 -0
- package/dist/screens/prompts/prompt-ui.js.map +1 -0
- package/dist/screens/prompts/prompts-list-page.d.ts +5 -0
- package/dist/screens/prompts/prompts-list-page.d.ts.map +1 -0
- package/dist/screens/prompts/prompts-list-page.js +208 -0
- package/dist/screens/prompts/prompts-list-page.js.map +1 -0
- package/dist/screens/quick-start/quick-start-screen.d.ts +2 -0
- package/dist/screens/quick-start/quick-start-screen.d.ts.map +1 -0
- package/dist/screens/quick-start/quick-start-screen.js +486 -0
- package/dist/screens/quick-start/quick-start-screen.js.map +1 -0
- package/dist/screens/releases/release-line-detail-page.d.ts +5 -0
- package/dist/screens/releases/release-line-detail-page.d.ts.map +1 -0
- package/dist/screens/releases/release-line-detail-page.js +973 -0
- package/dist/screens/releases/release-line-detail-page.js.map +1 -0
- package/dist/screens/releases/release-line-ui.d.ts +30 -0
- package/dist/screens/releases/release-line-ui.d.ts.map +1 -0
- package/dist/screens/releases/release-line-ui.js +197 -0
- package/dist/screens/releases/release-line-ui.js.map +1 -0
- package/dist/screens/releases/release-new-model.d.ts +5 -0
- package/dist/screens/releases/release-new-model.d.ts.map +1 -0
- package/dist/screens/releases/release-new-model.js +18 -0
- package/dist/screens/releases/release-new-model.js.map +1 -0
- package/dist/screens/releases/release-new-page.d.ts +6 -0
- package/dist/screens/releases/release-new-page.d.ts.map +1 -0
- package/dist/screens/releases/release-new-page.js +816 -0
- package/dist/screens/releases/release-new-page.js.map +1 -0
- package/dist/screens/releases/release-topology-canvas.d.ts +13 -0
- package/dist/screens/releases/release-topology-canvas.d.ts.map +1 -0
- package/dist/screens/releases/release-topology-canvas.js +856 -0
- package/dist/screens/releases/release-topology-canvas.js.map +1 -0
- package/dist/screens/releases/releases-list-page.d.ts +4 -0
- package/dist/screens/releases/releases-list-page.d.ts.map +1 -0
- package/dist/screens/releases/releases-list-page.js +86 -0
- package/dist/screens/releases/releases-list-page.js.map +1 -0
- package/dist/screens/settings/settings-page.d.ts +2 -0
- package/dist/screens/settings/settings-page.d.ts.map +1 -0
- package/dist/screens/settings/settings-page.js +250 -0
- package/dist/screens/settings/settings-page.js.map +1 -0
- package/dist/styles/globals.css +961 -0
- package/package.json +84 -0
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { Fragment, useCallback, useMemo, useState } from 'react';
|
|
6
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
7
|
+
import { AlertTriangle, ArrowDownNarrowWide, ArrowUpNarrowWide, Ban, Check, ChevronDown, ChevronRight, ChevronsDown, ChevronsUp, Clock, FileText, FlaskConical, Hourglass, Play, Square, Wrench, } from 'lucide-react';
|
|
8
|
+
import { Button, Progress, formatProgressLabel, Switch, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, TableActionIconButton, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, DetailPageSkeleton, cn, } from '@proofhound/ui';
|
|
9
|
+
import { Main } from '@proofhound/ui/layout';
|
|
10
|
+
import { PromptDiffSplitView } from '../../components';
|
|
11
|
+
import { useOptimization, useControlOptimization } from '../../hooks';
|
|
12
|
+
import { useDateTimeFormatter } from '../../hooks';
|
|
13
|
+
import { useDelayedLoading } from '../../hooks';
|
|
14
|
+
import { AUTO_REFRESH_INTERVAL_MS, useAutoRefresh } from '../../hooks';
|
|
15
|
+
import { useI18n } from '../../i18n';
|
|
16
|
+
import { getApiErrorMessage } from '../../lib';
|
|
17
|
+
import { optimizationTone } from './optimization-theme';
|
|
18
|
+
import { OptimizationStatusBadge, OriginBadge } from './optimization-ui';
|
|
19
|
+
import { OPTIMIZATION_ORIGIN_LABEL_KEYS, STARTING_MODE_TO_ORIGIN } from './optimization-mappers';
|
|
20
|
+
function parseDate(value) {
|
|
21
|
+
if (!value)
|
|
22
|
+
return null;
|
|
23
|
+
const date = new Date(value);
|
|
24
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
25
|
+
}
|
|
26
|
+
function getOptimizationDurationSeconds(detail) {
|
|
27
|
+
const startedAt = Date.parse(detail.startedAt ?? detail.createdAt);
|
|
28
|
+
const endedAt = detail.finishedAt
|
|
29
|
+
? Date.parse(detail.finishedAt)
|
|
30
|
+
: detail.status === 'running'
|
|
31
|
+
? Date.now()
|
|
32
|
+
: Date.parse(detail.updatedAt);
|
|
33
|
+
if (!Number.isFinite(startedAt) || !Number.isFinite(endedAt) || endedAt < startedAt)
|
|
34
|
+
return null;
|
|
35
|
+
return Math.round((endedAt - startedAt) / 1000);
|
|
36
|
+
}
|
|
37
|
+
function getDurationParts(totalSeconds) {
|
|
38
|
+
if (totalSeconds === null || totalSeconds < 0 || !Number.isFinite(totalSeconds))
|
|
39
|
+
return null;
|
|
40
|
+
const seconds = Math.round(totalSeconds);
|
|
41
|
+
const days = Math.floor(seconds / 86400);
|
|
42
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
43
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
44
|
+
const remainingSeconds = seconds % 60;
|
|
45
|
+
if (days > 0) {
|
|
46
|
+
return [
|
|
47
|
+
{ label: 'd', value: days },
|
|
48
|
+
{ label: 'h', value: hours, pad: true },
|
|
49
|
+
{ label: 'm', value: minutes, pad: true },
|
|
50
|
+
{ label: 's', value: remainingSeconds, pad: true },
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
if (hours > 0) {
|
|
54
|
+
return [
|
|
55
|
+
{ label: 'h', value: hours },
|
|
56
|
+
{ label: 'm', value: minutes, pad: true },
|
|
57
|
+
{ label: 's', value: remainingSeconds, pad: true },
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
if (minutes > 0) {
|
|
61
|
+
return [
|
|
62
|
+
{ label: 'm', value: minutes },
|
|
63
|
+
{ label: 's', value: remainingSeconds, pad: true },
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
return [{ label: 's', value: remainingSeconds }];
|
|
67
|
+
}
|
|
68
|
+
function formatTemplate(template, values) {
|
|
69
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
70
|
+
const value = values[key];
|
|
71
|
+
return value === undefined ? `{${key}}` : String(value);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function formatMetric(value, fractionDigits = 3) {
|
|
75
|
+
if (!Number.isFinite(value))
|
|
76
|
+
return '—';
|
|
77
|
+
return value.toFixed(fractionDigits);
|
|
78
|
+
}
|
|
79
|
+
const OPTIMIZATION_METRIC_LABEL_KEYS = {
|
|
80
|
+
accuracy: 'optimizations.metrics.accuracy',
|
|
81
|
+
precision: 'optimizations.metrics.precision',
|
|
82
|
+
recall: 'optimizations.metrics.recall',
|
|
83
|
+
f1: 'optimizations.metrics.f1',
|
|
84
|
+
fpr: 'optimizations.metrics.fpr',
|
|
85
|
+
inputTokens: 'optimizations.metrics.inputTokens',
|
|
86
|
+
outputTokens: 'optimizations.metrics.outputTokens',
|
|
87
|
+
costEstimate: 'optimizations.metrics.costEstimate',
|
|
88
|
+
averageLatencyMs: 'optimizations.metrics.averageLatencyMs',
|
|
89
|
+
p50LatencyMs: 'optimizations.metrics.p50LatencyMs',
|
|
90
|
+
p95LatencyMs: 'optimizations.metrics.p95LatencyMs',
|
|
91
|
+
};
|
|
92
|
+
const OPTIMIZATION_METRIC_ALIASES = {
|
|
93
|
+
acc: 'accuracy',
|
|
94
|
+
accuracy: 'accuracy',
|
|
95
|
+
precision: 'precision',
|
|
96
|
+
recall: 'recall',
|
|
97
|
+
f1: 'f1',
|
|
98
|
+
fpr: 'fpr',
|
|
99
|
+
inputtokens: 'inputTokens',
|
|
100
|
+
outputtokens: 'outputTokens',
|
|
101
|
+
costestimate: 'costEstimate',
|
|
102
|
+
averagelatencyms: 'averageLatencyMs',
|
|
103
|
+
p50latencyms: 'p50LatencyMs',
|
|
104
|
+
p95latencyms: 'p95LatencyMs',
|
|
105
|
+
};
|
|
106
|
+
const INTEGER_METRIC_KEYS = new Set(['inputTokens', 'outputTokens']);
|
|
107
|
+
const LATENCY_METRIC_KEYS = new Set(['averageLatencyMs', 'p50LatencyMs', 'p95LatencyMs']);
|
|
108
|
+
function resolveOptimizationMetricKey(label) {
|
|
109
|
+
const normalized = label.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
|
|
110
|
+
return OPTIMIZATION_METRIC_ALIASES[normalized] ?? null;
|
|
111
|
+
}
|
|
112
|
+
function formatMetricCost(value) {
|
|
113
|
+
if (!Number.isFinite(value))
|
|
114
|
+
return '—';
|
|
115
|
+
if (value === 0)
|
|
116
|
+
return '$0';
|
|
117
|
+
return `$${value < 0.01 ? value.toFixed(4) : value.toFixed(2)}`;
|
|
118
|
+
}
|
|
119
|
+
function formatMetricDisplayValue(label, value) {
|
|
120
|
+
const metricKey = resolveOptimizationMetricKey(label);
|
|
121
|
+
if (!metricKey)
|
|
122
|
+
return formatMetric(value);
|
|
123
|
+
if (INTEGER_METRIC_KEYS.has(metricKey))
|
|
124
|
+
return formatThousand(Math.round(value));
|
|
125
|
+
if (LATENCY_METRIC_KEYS.has(metricKey))
|
|
126
|
+
return `${formatThousand(Math.round(value))} ms`;
|
|
127
|
+
if (metricKey === 'costEstimate')
|
|
128
|
+
return formatMetricCost(value);
|
|
129
|
+
return formatMetric(value);
|
|
130
|
+
}
|
|
131
|
+
function formatMetricDisplayLabel(label, t) {
|
|
132
|
+
const metricKey = resolveOptimizationMetricKey(label);
|
|
133
|
+
return metricKey ? t(OPTIMIZATION_METRIC_LABEL_KEYS[metricKey]) : label;
|
|
134
|
+
}
|
|
135
|
+
function formatDelta(value) {
|
|
136
|
+
if (value === null || !Number.isFinite(value))
|
|
137
|
+
return '—';
|
|
138
|
+
return `${value >= 0 ? '+' : '−'}${Math.abs(value).toFixed(3)}`;
|
|
139
|
+
}
|
|
140
|
+
function formatThousand(value) {
|
|
141
|
+
return value.toLocaleString('en-US').replace(/,/g, ' ');
|
|
142
|
+
}
|
|
143
|
+
const OVERALL_TREND_SCOPE = 'overall';
|
|
144
|
+
const TREND_METRIC_KEYS = [
|
|
145
|
+
'accuracy',
|
|
146
|
+
'precision',
|
|
147
|
+
'recall',
|
|
148
|
+
'f1',
|
|
149
|
+
'fpr',
|
|
150
|
+
];
|
|
151
|
+
const SCORE_TREND_METRICS = new Set(['accuracy', 'precision', 'recall', 'f1', 'fpr']);
|
|
152
|
+
const LOWER_IS_BETTER_TREND_METRICS = new Set([
|
|
153
|
+
'fpr',
|
|
154
|
+
'inputTokens',
|
|
155
|
+
'outputTokens',
|
|
156
|
+
'costEstimate',
|
|
157
|
+
'averageLatencyMs',
|
|
158
|
+
'p50LatencyMs',
|
|
159
|
+
'p95LatencyMs',
|
|
160
|
+
]);
|
|
161
|
+
const TREND_SERIES_STYLES = [
|
|
162
|
+
{ line: 'text-[var(--status-canary-dot)]', text: optimizationTone.info.text },
|
|
163
|
+
{ line: 'text-[var(--status-running-dot)]', text: optimizationTone.positive.text },
|
|
164
|
+
{ line: 'text-[var(--status-pending-dot)]', text: optimizationTone.warning.text },
|
|
165
|
+
{ line: 'text-destructive', text: optimizationTone.danger.text },
|
|
166
|
+
{ line: 'text-[var(--status-archived-dot)]', text: optimizationTone.muted.text },
|
|
167
|
+
];
|
|
168
|
+
function trendScopeIdForGoalScope(scope) {
|
|
169
|
+
return scope === OVERALL_TREND_SCOPE ? OVERALL_TREND_SCOPE : `class:${scope}`;
|
|
170
|
+
}
|
|
171
|
+
function trendClassLabel(scopeId) {
|
|
172
|
+
return scopeId === OVERALL_TREND_SCOPE ? null : scopeId.slice('class:'.length);
|
|
173
|
+
}
|
|
174
|
+
function goalMatchesTrendScope(goal, scopeId) {
|
|
175
|
+
return trendScopeIdForGoalScope(goal.scope) === scopeId;
|
|
176
|
+
}
|
|
177
|
+
function uniqueValues(values) {
|
|
178
|
+
return Array.from(new Set(values));
|
|
179
|
+
}
|
|
180
|
+
function computeF1(precision, recall) {
|
|
181
|
+
if (precision === null || recall === null)
|
|
182
|
+
return null;
|
|
183
|
+
const denominator = precision + recall;
|
|
184
|
+
return denominator > 0 ? (2 * precision * recall) / denominator : null;
|
|
185
|
+
}
|
|
186
|
+
function metricBetterIsLower(metricKey) {
|
|
187
|
+
return LOWER_IS_BETTER_TREND_METRICS.has(metricKey);
|
|
188
|
+
}
|
|
189
|
+
function readMetricCell(cells, metricKey) {
|
|
190
|
+
for (const cell of cells ?? []) {
|
|
191
|
+
if (resolveOptimizationMetricKey(cell.label) === metricKey && Number.isFinite(cell.value))
|
|
192
|
+
return cell.value;
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
function readTrendMetricValue(input, scopeId, metricKey) {
|
|
197
|
+
const classLabel = trendClassLabel(scopeId);
|
|
198
|
+
if (!classLabel) {
|
|
199
|
+
const overall = input.experimentResult?.overallRow;
|
|
200
|
+
if (metricKey === 'accuracy' && typeof overall?.accuracy === 'number')
|
|
201
|
+
return overall.accuracy;
|
|
202
|
+
if (metricKey === 'precision' && typeof overall?.precision === 'number')
|
|
203
|
+
return overall.precision;
|
|
204
|
+
if (metricKey === 'recall' && typeof overall?.recall === 'number')
|
|
205
|
+
return overall.recall;
|
|
206
|
+
if (metricKey === 'f1') {
|
|
207
|
+
return readMetricCell(input.metrics, metricKey) ?? computeF1(overall?.precision ?? null, overall?.recall ?? null);
|
|
208
|
+
}
|
|
209
|
+
return readMetricCell(input.metrics, metricKey);
|
|
210
|
+
}
|
|
211
|
+
const row = input.experimentResult?.classRows.find((item) => item.label === classLabel);
|
|
212
|
+
if (!row)
|
|
213
|
+
return null;
|
|
214
|
+
if (metricKey === 'accuracy')
|
|
215
|
+
return typeof row.accuracy === 'number' ? row.accuracy : null;
|
|
216
|
+
if (metricKey === 'precision')
|
|
217
|
+
return row.precision;
|
|
218
|
+
if (metricKey === 'recall')
|
|
219
|
+
return row.recall;
|
|
220
|
+
if (metricKey === 'f1')
|
|
221
|
+
return typeof row.f1 === 'number' ? row.f1 : computeF1(row.precision, row.recall);
|
|
222
|
+
if (metricKey === 'fpr')
|
|
223
|
+
return typeof row.fpr === 'number' ? row.fpr : null;
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
function buildTrendScopeOptions(detail) {
|
|
227
|
+
const classLabels = uniqueValues([
|
|
228
|
+
...detail.goals.filter((goal) => goal.scope !== OVERALL_TREND_SCOPE).map((goal) => goal.scope),
|
|
229
|
+
...(detail.baseline?.experimentResult?.classRows ?? []).map((row) => row.label),
|
|
230
|
+
...detail.rounds.flatMap((round) => round.experimentResult?.classRows.map((row) => row.label) ?? []),
|
|
231
|
+
]).filter(Boolean);
|
|
232
|
+
return [
|
|
233
|
+
{ id: OVERALL_TREND_SCOPE, kind: 'overall' },
|
|
234
|
+
...classLabels.map((label) => ({ id: `class:${label}`, kind: 'class', label })),
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
function buildTrendMetricOptions(detail, scopeId) {
|
|
238
|
+
const metricKeys = new Set();
|
|
239
|
+
const goalMetricKeys = detail.goals
|
|
240
|
+
.filter((goal) => goalMatchesTrendScope(goal, scopeId))
|
|
241
|
+
.map((goal) => resolveOptimizationMetricKey(goal.metric))
|
|
242
|
+
.filter((key) => key !== null);
|
|
243
|
+
for (const key of goalMetricKeys)
|
|
244
|
+
metricKeys.add(key);
|
|
245
|
+
const sources = [
|
|
246
|
+
{ experimentResult: detail.baseline?.experimentResult, metrics: detail.baseline?.metrics },
|
|
247
|
+
...detail.rounds.map((round) => ({ experimentResult: round.experimentResult, metrics: round.metrics })),
|
|
248
|
+
];
|
|
249
|
+
for (const key of TREND_METRIC_KEYS) {
|
|
250
|
+
if (sources.some((source) => readTrendMetricValue(source, scopeId, key) !== null)) {
|
|
251
|
+
metricKeys.add(key);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const options = [];
|
|
255
|
+
for (const key of TREND_METRIC_KEYS) {
|
|
256
|
+
if (metricKeys.has(key))
|
|
257
|
+
options.push({ value: key });
|
|
258
|
+
}
|
|
259
|
+
return options;
|
|
260
|
+
}
|
|
261
|
+
function buildTrendChartViewModel(detail, requestedScopeId, requestedMetricValue) {
|
|
262
|
+
const scopeOptions = buildTrendScopeOptions(detail);
|
|
263
|
+
const goalScope = detail.goals.find((goal) => goal.scope !== OVERALL_TREND_SCOPE)?.scope;
|
|
264
|
+
const goalScopeId = goalScope ? trendScopeIdForGoalScope(goalScope) : OVERALL_TREND_SCOPE;
|
|
265
|
+
const defaultScopeId = scopeOptions.some((option) => option.id === goalScopeId) ? goalScopeId : OVERALL_TREND_SCOPE;
|
|
266
|
+
const scopeId = requestedScopeId && scopeOptions.some((option) => option.id === requestedScopeId)
|
|
267
|
+
? requestedScopeId
|
|
268
|
+
: defaultScopeId;
|
|
269
|
+
const metricOptions = buildTrendMetricOptions(detail, scopeId);
|
|
270
|
+
const matchingGoalKeys = detail.goals
|
|
271
|
+
.filter((goal) => goalMatchesTrendScope(goal, scopeId))
|
|
272
|
+
.map((goal) => resolveOptimizationMetricKey(goal.metric))
|
|
273
|
+
.filter((key) => key !== null);
|
|
274
|
+
const defaultMetricValue = uniqueValues(matchingGoalKeys).find((key) => metricOptions.some((option) => option.value === key)) ??
|
|
275
|
+
metricOptions[0]?.value ??
|
|
276
|
+
null;
|
|
277
|
+
const metricValue = requestedMetricValue && metricOptions.some((option) => option.value === requestedMetricValue)
|
|
278
|
+
? requestedMetricValue
|
|
279
|
+
: defaultMetricValue;
|
|
280
|
+
const selectedMetricKeys = metricValue ? [metricValue] : [];
|
|
281
|
+
const sortedRounds = detail.rounds
|
|
282
|
+
.filter((round) => round.isBaseline !== true && round.index > 0)
|
|
283
|
+
.slice()
|
|
284
|
+
.sort((a, b) => a.index - b.index);
|
|
285
|
+
const baselineRound = detail.rounds.find((round) => round.isBaseline === true);
|
|
286
|
+
const baselineSource = {
|
|
287
|
+
experimentResult: detail.baseline?.experimentResult ?? baselineRound?.experimentResult,
|
|
288
|
+
metrics: detail.baseline?.metrics ?? baselineRound?.metrics,
|
|
289
|
+
};
|
|
290
|
+
const hasBaseline = selectedMetricKeys.some((key) => readTrendMetricValue(baselineSource, scopeId, key) !== null);
|
|
291
|
+
const slots = [
|
|
292
|
+
...(hasBaseline ? [{ id: 'baseline', kind: 'baseline', roundIndex: 0 }] : []),
|
|
293
|
+
...sortedRounds.map((round) => ({ id: `round:${round.index}`, kind: 'round', roundIndex: round.index })),
|
|
294
|
+
];
|
|
295
|
+
const goalByMetric = new Map();
|
|
296
|
+
for (const goal of detail.goals.filter((item) => goalMatchesTrendScope(item, scopeId))) {
|
|
297
|
+
const key = resolveOptimizationMetricKey(goal.metric);
|
|
298
|
+
if (key && !goalByMetric.has(key))
|
|
299
|
+
goalByMetric.set(key, goal);
|
|
300
|
+
}
|
|
301
|
+
const series = selectedMetricKeys
|
|
302
|
+
.map((metricKey, index) => {
|
|
303
|
+
const values = slots.map((slot) => {
|
|
304
|
+
if (slot.kind === 'baseline')
|
|
305
|
+
return readTrendMetricValue(baselineSource, scopeId, metricKey);
|
|
306
|
+
const round = sortedRounds.find((item) => item.index === slot.roundIndex);
|
|
307
|
+
return round ? readTrendMetricValue(round, scopeId, metricKey) : null;
|
|
308
|
+
});
|
|
309
|
+
if (!values.some((value) => value !== null))
|
|
310
|
+
return null;
|
|
311
|
+
const betterIsLower = metricBetterIsLower(metricKey);
|
|
312
|
+
let bestSlotIndex;
|
|
313
|
+
values.forEach((value, slotIndex) => {
|
|
314
|
+
if (value === null)
|
|
315
|
+
return;
|
|
316
|
+
const bestValue = bestSlotIndex === undefined ? null : values[bestSlotIndex];
|
|
317
|
+
if (bestValue === null || bestValue === undefined || (betterIsLower ? value < bestValue : value > bestValue)) {
|
|
318
|
+
bestSlotIndex = slotIndex;
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
return {
|
|
322
|
+
id: `${scopeId}:${metricKey}`,
|
|
323
|
+
metricKey,
|
|
324
|
+
labelKey: OPTIMIZATION_METRIC_LABEL_KEYS[metricKey],
|
|
325
|
+
values,
|
|
326
|
+
target: goalByMetric.get(metricKey)?.target,
|
|
327
|
+
bestSlotIndex,
|
|
328
|
+
style: TREND_SERIES_STYLES[index % TREND_SERIES_STYLES.length],
|
|
329
|
+
};
|
|
330
|
+
})
|
|
331
|
+
.filter((item) => item !== null);
|
|
332
|
+
const currentSlotIndex = slots.findIndex((slot) => slot.kind === 'round' && slot.roundIndex === detail.currentRound) >= 0
|
|
333
|
+
? slots.findIndex((slot) => slot.kind === 'round' && slot.roundIndex === detail.currentRound)
|
|
334
|
+
: detail.currentRound <= 0 && hasBaseline
|
|
335
|
+
? 0
|
|
336
|
+
: Math.max(0, slots.length - 1);
|
|
337
|
+
return {
|
|
338
|
+
scopeOptions,
|
|
339
|
+
metricOptions,
|
|
340
|
+
defaultScopeId,
|
|
341
|
+
defaultMetricValue,
|
|
342
|
+
slots,
|
|
343
|
+
series,
|
|
344
|
+
hasBaseline,
|
|
345
|
+
currentSlotIndex,
|
|
346
|
+
primaryBestSlotIndex: series[0]?.bestSlotIndex,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function formatTrendScopeOption(option, t) {
|
|
350
|
+
if (option.kind === 'overall')
|
|
351
|
+
return t('optimizations.detail.trend.scopeOverall');
|
|
352
|
+
return formatTemplate(t('optimizations.detail.trend.scopeClass'), { label: option.label ?? '' });
|
|
353
|
+
}
|
|
354
|
+
function formatTrendMetricOption(option, t) {
|
|
355
|
+
return t(OPTIMIZATION_METRIC_LABEL_KEYS[option.value]);
|
|
356
|
+
}
|
|
357
|
+
function buildTrendLinePath(values, xForIndex, yToPx) {
|
|
358
|
+
let path = '';
|
|
359
|
+
let open = false;
|
|
360
|
+
values.forEach((value, index) => {
|
|
361
|
+
if (value === null) {
|
|
362
|
+
open = false;
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
path += `${open ? 'L' : 'M'}${xForIndex(index).toFixed(2)},${yToPx(value).toFixed(2)} `;
|
|
366
|
+
open = true;
|
|
367
|
+
});
|
|
368
|
+
return path.trim();
|
|
369
|
+
}
|
|
370
|
+
function deriveTrendScale(series) {
|
|
371
|
+
const values = series.flatMap((item) => [
|
|
372
|
+
...item.values.filter((value) => value !== null),
|
|
373
|
+
...(typeof item.target === 'number' ? [item.target] : []),
|
|
374
|
+
]);
|
|
375
|
+
if (values.length === 0)
|
|
376
|
+
return { min: 0, max: 1, ticks: [0, 0.25, 0.5, 0.75, 1] };
|
|
377
|
+
const minValue = Math.min(...values);
|
|
378
|
+
const maxValue = Math.max(...values);
|
|
379
|
+
const ratioScale = series.every((item) => SCORE_TREND_METRICS.has(item.metricKey));
|
|
380
|
+
const span = Math.max(0.0001, maxValue - minValue);
|
|
381
|
+
const padding = ratioScale ? Math.max(0.02, span * 0.18) : Math.max(span * 0.18, maxValue * 0.03, 1);
|
|
382
|
+
const min = ratioScale ? Math.max(0, minValue - padding) : Math.max(0, minValue - padding);
|
|
383
|
+
const max = ratioScale ? Math.min(1, maxValue + padding) : maxValue + padding;
|
|
384
|
+
const safeMax = max <= min ? min + (ratioScale ? 0.1 : 1) : max;
|
|
385
|
+
const step = (safeMax - min) / 4;
|
|
386
|
+
return { min, max: safeMax, ticks: Array.from({ length: 5 }, (_, i) => min + step * i) };
|
|
387
|
+
}
|
|
388
|
+
function formatTrendAxisValue(value, metricKey) {
|
|
389
|
+
if (!metricKey)
|
|
390
|
+
return value.toFixed(2);
|
|
391
|
+
if (INTEGER_METRIC_KEYS.has(metricKey))
|
|
392
|
+
return formatThousand(Math.round(value));
|
|
393
|
+
if (LATENCY_METRIC_KEYS.has(metricKey))
|
|
394
|
+
return formatThousand(Math.round(value));
|
|
395
|
+
if (metricKey === 'costEstimate')
|
|
396
|
+
return formatMetricCost(value);
|
|
397
|
+
return value.toFixed(2);
|
|
398
|
+
}
|
|
399
|
+
function BestPointer({ label, className }) {
|
|
400
|
+
return (_jsx("span", { className: cn('inline-flex items-center gap-1 rounded-md bg-[var(--status-canary-dot)] px-1.5 py-0.5 font-mono text-[10px] font-semibold uppercase tracking-wide text-white', className), children: label }));
|
|
401
|
+
}
|
|
402
|
+
function PhaseTag({ phase, currentRound, maxRounds, }) {
|
|
403
|
+
const { t } = useI18n();
|
|
404
|
+
const phaseKey = phase === 'analysis'
|
|
405
|
+
? 'optimizations.detail.control.phase.analysis'
|
|
406
|
+
: phase === 'experiment'
|
|
407
|
+
? 'optimizations.detail.control.phase.experiment'
|
|
408
|
+
: phase === 'paused'
|
|
409
|
+
? 'optimizations.detail.control.phase.paused'
|
|
410
|
+
: 'optimizations.detail.control.phase.finishing';
|
|
411
|
+
return (_jsxs("span", { className: cn('inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 font-mono text-[11.5px]', optimizationTone.info.pill), children: [_jsx("span", { className: cn('size-1.5 animate-pulse rounded-full', optimizationTone.info.dot), "aria-hidden": "true" }), formatTemplate(t('optimizations.detail.control.round'), {
|
|
412
|
+
current: currentRound,
|
|
413
|
+
total: maxRounds,
|
|
414
|
+
phase: t(phaseKey),
|
|
415
|
+
})] }));
|
|
416
|
+
}
|
|
417
|
+
function FailureBanner({ detail }) {
|
|
418
|
+
const { t } = useI18n();
|
|
419
|
+
if (detail.status !== 'failed')
|
|
420
|
+
return null;
|
|
421
|
+
if (!detail.summary && !detail.analysisFailureReason)
|
|
422
|
+
return null;
|
|
423
|
+
const reason = detail.summary?.reason ?? t('optimizations.detail.failureBanner.noDetail');
|
|
424
|
+
return (_jsxs("div", { className: "mb-4 flex items-start gap-3 rounded-md border border-destructive/40 bg-destructive/5 px-4 py-3 text-[12.5px] text-destructive", children: [_jsx(AlertTriangle, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": "true" }), _jsxs("div", { className: "flex-1", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-wide", children: t('optimizations.detail.failureBanner.title') }), _jsx("p", { className: "mt-1 break-words text-foreground", children: reason }), detail.analysisFailureReason && (_jsxs("p", { className: "mt-2 text-[11.5px] text-destructive/85", children: [_jsx("span", { className: "font-mono uppercase tracking-wide", children: t('optimizations.detail.failureBanner.analysisLabel') }), ": ", _jsx("span", { className: "break-words text-foreground", children: detail.analysisFailureReason })] }))] })] }));
|
|
425
|
+
}
|
|
426
|
+
function TimePoint({ date, includeDate }) {
|
|
427
|
+
const { formatDate, formatTime } = useDateTimeFormatter();
|
|
428
|
+
if (!date)
|
|
429
|
+
return _jsx("span", { className: "font-mono text-[10.5px] text-muted-foreground", children: "\u2014" });
|
|
430
|
+
return (_jsxs("span", { className: "inline-flex items-baseline gap-1 font-mono tabular-nums", children: [includeDate && _jsx("span", { className: "text-[10px] text-muted-foreground", children: formatDate(date, { fallback: '—' }) }), _jsx("span", { className: "text-[10.5px] font-semibold text-foreground sm:text-[11px]", children: formatTime(date, { fallback: '—' }) })] }));
|
|
431
|
+
}
|
|
432
|
+
function OptimizationTimingSubtitle({ detail, className }) {
|
|
433
|
+
const { formatDate } = useDateTimeFormatter();
|
|
434
|
+
const startDate = parseDate(detail.startedAt ?? detail.createdAt);
|
|
435
|
+
const finishedDate = parseDate(detail.finishedAt);
|
|
436
|
+
const duration = getDurationParts(getOptimizationDurationSeconds(detail));
|
|
437
|
+
const comparisonEndDate = finishedDate ?? (detail.status === 'running' ? new Date() : parseDate(detail.updatedAt));
|
|
438
|
+
const includeDate = Boolean(startDate &&
|
|
439
|
+
comparisonEndDate &&
|
|
440
|
+
formatDate(startDate, { fallback: '' }) !== formatDate(comparisonEndDate, { fallback: '' }));
|
|
441
|
+
return (_jsxs("div", { className: cn('flex w-fit max-w-full flex-col items-center gap-0.5', className), children: [_jsxs("div", { className: cn('flex flex-wrap items-baseline gap-x-1 gap-y-0.5 font-mono tabular-nums', optimizationTone.positive.text), children: [_jsx(Hourglass, { className: "size-3 self-center", "aria-hidden": "true" }), duration ? (_jsx(_Fragment, { children: duration.map((part) => (_jsxs("span", { className: "inline-flex items-baseline gap-0.5", children: [_jsx("span", { className: "text-[10px] font-semibold leading-none tracking-normal sm:text-[11px]", children: part.pad ? String(part.value).padStart(2, '0') : part.value }), _jsx("span", { className: "text-[8px] font-semibold sm:text-[8.5px]", children: part.label })] }, part.label))) })) : (_jsx("span", { className: "text-[10px] font-semibold leading-none tracking-normal sm:text-[11px]", children: "\u2014" }))] }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-1.5 gap-y-0.5", children: [_jsx(Play, { className: cn('size-2 fill-current stroke-current', optimizationTone.positive.text), "aria-hidden": "true" }), _jsx(TimePoint, { date: startDate, includeDate: includeDate }), _jsx("span", { className: "font-mono text-[10.5px] text-muted-foreground/70 sm:text-[11px]", "aria-hidden": "true", children: "\u2192" }), _jsx(TimePoint, { date: finishedDate, includeDate: includeDate })] })] }));
|
|
442
|
+
}
|
|
443
|
+
function OptimizationProgressCard({ detail }) {
|
|
444
|
+
const { t } = useI18n();
|
|
445
|
+
const control = detail.controlStrip;
|
|
446
|
+
const maxRounds = Math.max(1, detail.maxRounds);
|
|
447
|
+
const currentRound = Math.min(detail.currentRound, maxRounds);
|
|
448
|
+
const percent = Math.min(100, (currentRound / maxRounds) * 100);
|
|
449
|
+
const progressLabel = formatProgressLabel({
|
|
450
|
+
value: currentRound,
|
|
451
|
+
max: maxRounds,
|
|
452
|
+
percent,
|
|
453
|
+
fractionDigits: 1,
|
|
454
|
+
});
|
|
455
|
+
const goalScopes = uniqueValues(detail.goals.map((goal) => goal.scope)).map((scope) => scope === OVERALL_TREND_SCOPE
|
|
456
|
+
? t('optimizations.detail.trend.scopeOverall')
|
|
457
|
+
: formatTemplate(t('optimizations.detail.trend.scopeClass'), { label: scope }));
|
|
458
|
+
const goalMetrics = uniqueValues(detail.goals.map((goal) => goal.metric)).map((metric) => formatMetricDisplayLabel(metric, t));
|
|
459
|
+
return (_jsxs("section", { className: "mb-4 rounded-lg border bg-card", "data-testid": "optimization-detail-progress", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b px-4 py-3", children: [_jsx("h2", { className: "text-[13px] font-semibold", children: t('optimizations.detail.progress.title') }), control ? (_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1.5", children: [_jsx(PhaseTag, { phase: control.phase, currentRound: control.currentRound, maxRounds: control.maxRounds }), _jsxs("span", { className: "font-mono text-[11.5px] text-muted-foreground", children: [_jsx("b", { className: "font-bold text-foreground", children: formatThousand(control.samplesDone) }), _jsxs("span", { className: "ml-1", children: ["/ ", formatThousand(control.samplesTotal)] })] }), _jsxs("span", { className: "font-mono text-[11.5px] text-muted-foreground", children: [formatTemplate(t('optimizations.detail.control.roundEta'), { value: control.roundRemaining }), _jsx("span", { className: "px-1 text-muted-foreground/70", "aria-hidden": "true", children: "\u00B7" }), formatTemplate(t('optimizations.detail.control.totalEta'), { value: control.totalRemaining })] })] })) : null] }), _jsxs("div", { className: "space-y-3 p-5", children: [_jsx(Progress, { value: currentRound, max: maxRounds, label: progressLabel }), _jsxs("div", { className: "flex flex-wrap items-end justify-between gap-x-6 gap-y-3", children: [_jsx("div", { className: "flex min-w-[260px] max-w-full flex-col gap-1.5", children: _jsxs("div", { className: "grid gap-1 font-mono text-[11px] text-muted-foreground sm:grid-cols-3", children: [_jsxs("span", { className: "min-w-0", children: [t('optimizations.detail.progress.goalScope'), ' ', _jsx("span", { className: "text-foreground", children: goalScopes.join(' / ') || '—' })] }), _jsxs("span", { className: "min-w-0", children: [t('optimizations.detail.progress.goalMetric'), ' ', _jsx("span", { className: "text-foreground", children: goalMetrics.join(' / ') || '—' })] }), _jsxs("span", { className: "min-w-0", children: [t('optimizations.detail.progress.actualVsTarget'), ' ', _jsx("span", { className: "inline-flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-foreground", children: detail.goalProgress.length > 0
|
|
460
|
+
? detail.goalProgress.map((goal, index) => {
|
|
461
|
+
const achieved = goal.achieved === 'hit' || goal.achieved === 'critical';
|
|
462
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1", children: [index > 0 && (_jsx("span", { className: "text-muted-foreground", "aria-hidden": "true", children: "/" })), _jsxs("span", { children: [goal.currentText, " vs ", goal.targetText] }), achieved && (_jsx(Check, { className: cn('size-3', optimizationTone.positive.text), "aria-hidden": "true" }))] }, `${goal.label}-${index}`));
|
|
463
|
+
})
|
|
464
|
+
: '—' })] })] }) }), _jsx(OptimizationTimingSubtitle, { detail: detail, className: "ml-auto" })] })] })] }));
|
|
465
|
+
}
|
|
466
|
+
function KvRow({ label, children }) {
|
|
467
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx("span", { className: "font-mono text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: label }), _jsx("span", { className: "truncate font-mono text-[12px] text-foreground", children: children })] }));
|
|
468
|
+
}
|
|
469
|
+
function ScopeChip({ tone, label }) {
|
|
470
|
+
return (_jsx("span", { className: cn('inline-flex items-center rounded-full border px-2 py-0.5 text-[10.5px]', tone === 'overall' ? 'border-border bg-secondary/60 text-foreground' : optimizationTone.warning.pill), children: label }));
|
|
471
|
+
}
|
|
472
|
+
const STARTING_MODE_DESC_KEY = {
|
|
473
|
+
from_experiment: 'optimizations.new.origin.experimentDesc',
|
|
474
|
+
from_prompt_version: 'optimizations.new.origin.promptDesc',
|
|
475
|
+
from_dataset_only: 'optimizations.new.origin.datasetDesc',
|
|
476
|
+
};
|
|
477
|
+
function ConfigSection({ detail }) {
|
|
478
|
+
const { t } = useI18n();
|
|
479
|
+
const [open, setOpen] = useState(true);
|
|
480
|
+
const cfg = detail.experimentConfig;
|
|
481
|
+
const iter = detail.iterationConfig;
|
|
482
|
+
const origin = STARTING_MODE_TO_ORIGIN[detail.startingMode];
|
|
483
|
+
const originRef = detail.sourceExperimentName ?? detail.promptName ?? detail.datasetName;
|
|
484
|
+
const originLabel = t(OPTIMIZATION_ORIGIN_LABEL_KEYS[origin]);
|
|
485
|
+
const originDesc = t(STARTING_MODE_DESC_KEY[detail.startingMode]);
|
|
486
|
+
return (_jsxs("section", { className: "overflow-hidden rounded-lg border bg-card", "data-testid": "optimization-detail-config", children: [_jsxs("button", { type: "button", onClick: () => setOpen((prev) => !prev), "aria-expanded": open, "aria-controls": "optimization-detail-config-body", "aria-label": t(open ? 'optimizations.detail.config.collapse' : 'optimizations.detail.config.expand'), className: cn('flex w-full items-center justify-between gap-3 px-4 py-2.5 text-left transition-colors hover:bg-muted/30', open && 'border-b'), children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx("h2", { className: "text-[13px] font-semibold", children: t('optimizations.detail.config.title') }), _jsx(TooltipProvider, { delayDuration: 160, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { "data-testid": "optimization-detail-config-starting-mode", className: "min-w-0", children: _jsx(OriginBadge, { origin: origin, originRef: originRef }) }) }), _jsxs(TooltipContent, { className: "max-w-xs text-[12px] leading-snug", children: [_jsx("div", { className: "font-semibold", children: originLabel }), _jsx("div", { className: "mt-0.5 text-muted-foreground", children: originDesc })] })] }) })] }), _jsx(ChevronDown, { className: cn('size-4 text-muted-foreground transition-transform duration-200', open ? 'rotate-0' : '-rotate-90'), "aria-hidden": "true" })] }), open && (_jsxs("div", { id: "optimization-detail-config-body", children: [cfg ? (_jsxs("div", { className: "px-4 py-3", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx("span", { className: cn('inline-block h-2.5 w-[3px] rounded-sm', optimizationTone.info.fill), "aria-hidden": "true" }), _jsx("span", { className: "font-mono text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: t('optimizations.detail.config.experiment') })] }), _jsxs("div", { className: "grid grid-cols-1 gap-y-2", children: [_jsx(KvRow, { label: t('optimizations.detail.config.dataset'), children: _jsx(Link, { href: "#", className: cn('underline decoration-dotted underline-offset-2', optimizationTone.info.text), children: cfg.datasetName }) }), _jsxs(KvRow, { label: t('optimizations.detail.config.prompt'), children: [_jsx(Link, { href: "#", className: cn('underline decoration-dotted underline-offset-2', optimizationTone.info.text), children: cfg.promptName }), _jsxs("span", { className: "text-muted-foreground", children: [" \u00B7 ", cfg.promptVersion] })] }), _jsx(KvRow, { label: t('optimizations.detail.config.model'), children: _jsx(Link, { href: "#", className: cn('underline decoration-dotted underline-offset-2', optimizationTone.info.text), children: cfg.modelName }) }), _jsx(KvRow, { label: t('optimizations.detail.config.baselineExperiment'), children: _jsx(Link, { href: "#", className: cn('underline decoration-dotted underline-offset-2', optimizationTone.info.text), children: cfg.baselineExperiment }) }), _jsx(KvRow, { label: t('optimizations.detail.config.temperature'), children: cfg.temperature.toFixed(1) }), _jsx(KvRow, { label: t('optimizations.detail.config.concurrency'), children: cfg.concurrency }), _jsx(KvRow, { label: t('optimizations.detail.config.rpm'), children: cfg.rpm }), _jsx(KvRow, { label: t('optimizations.detail.config.tpm'), children: formatThousand(cfg.tpm) })] })] })) : null, iter ? (_jsxs("div", { className: "border-t border-dashed px-4 py-3", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx("span", { className: cn('inline-block h-2.5 w-[3px] rounded-sm', optimizationTone.positive.fill), "aria-hidden": "true" }), _jsx("span", { className: "font-mono text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: t('optimizations.detail.config.iter') })] }), _jsxs("div", { className: "grid grid-cols-1 gap-y-2", children: [_jsx("div", { children: _jsx(KvRow, { label: t('optimizations.detail.config.goal'), children: _jsx("span", { className: "flex flex-col gap-1 font-sans text-[12px] font-normal text-foreground", children: detail.goalsLines.map((line) => (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx("span", { className: cn('size-1.5 rounded-full', line.tone === 'class' ? optimizationTone.warning.fill : optimizationTone.positive.fill), "aria-hidden": "true" }), line.label, " ", _jsx("b", { className: "font-semibold", children: line.targetText })] }, line.label))) }) }) }), _jsx(KvRow, { label: t('optimizations.detail.config.goalScope'), children: _jsx("span", { className: "flex flex-wrap gap-1 font-sans", children: detail.goalScope.kind === 'overall' ? (_jsx(ScopeChip, { tone: "overall", label: t('optimizations.detail.config.scopeOverall') })) : (_jsxs(_Fragment, { children: [_jsx(ScopeChip, { tone: "overall", label: t('optimizations.detail.config.scopeOverall') }), (detail.goalScope.classes ?? []).map((cls) => (_jsx(ScopeChip, { tone: "class", label: formatTemplate(t('optimizations.detail.config.scopeClass'), { label: cls }) }, cls)))] })) }) }), _jsx(KvRow, { label: t('optimizations.detail.config.analysisModel'), children: _jsx(Link, { href: "#", className: cn('underline decoration-dotted underline-offset-2', optimizationTone.info.text), children: iter.analysisModel }) }), _jsx(KvRow, { label: t('optimizations.detail.config.strategy'), children: iter.strategy }), _jsx(KvRow, { label: t('optimizations.detail.config.maxRounds'), children: iter.maxRounds }), _jsx(KvRow, { label: t('optimizations.detail.config.noImprovement'), children: formatTemplate(t('optimizations.detail.config.noImprovementValue'), {
|
|
487
|
+
value: iter.noImprovementStop,
|
|
488
|
+
}) })] }), detail.optimizationHint ? (_jsxs("div", { className: "mt-3 border-t border-dashed pt-3", children: [_jsx("div", { className: "mb-1 font-mono text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: t('optimizations.detail.config.optimizationHint') }), _jsx("p", { className: "whitespace-pre-wrap text-[12px] leading-relaxed text-foreground", children: detail.optimizationHint })] })) : null] })) : null] }))] }));
|
|
489
|
+
}
|
|
490
|
+
function TrendChart({ detail }) {
|
|
491
|
+
const { t } = useI18n();
|
|
492
|
+
const [selectedScopeId, setSelectedScopeId] = useState(null);
|
|
493
|
+
const [selectedMetricValue, setSelectedMetricValue] = useState(null);
|
|
494
|
+
const trend = useMemo(() => buildTrendChartViewModel(detail, selectedScopeId, selectedMetricValue), [detail, selectedMetricValue, selectedScopeId]);
|
|
495
|
+
const scopeValue = selectedScopeId && trend.scopeOptions.some((option) => option.id === selectedScopeId)
|
|
496
|
+
? selectedScopeId
|
|
497
|
+
: trend.defaultScopeId;
|
|
498
|
+
const metricValue = selectedMetricValue && trend.metricOptions.some((option) => option.value === selectedMetricValue)
|
|
499
|
+
? selectedMetricValue
|
|
500
|
+
: trend.defaultMetricValue;
|
|
501
|
+
const series = trend.series;
|
|
502
|
+
if (series.length === 0) {
|
|
503
|
+
return (_jsxs("section", { className: "rounded-lg border bg-card", "data-testid": "optimization-detail-trend", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b px-4 py-3", children: [_jsx("h2", { className: "text-[13px] font-semibold", children: t('optimizations.detail.trend.title') }), _jsx(TrendChartControls, { scopeOptions: trend.scopeOptions, metricOptions: trend.metricOptions, scopeValue: scopeValue, metricValue: metricValue, onScopeChange: (value) => setSelectedScopeId(value), onMetricChange: (value) => setSelectedMetricValue(value) })] }), _jsx("div", { className: "px-4 py-10 text-center text-[12px] text-muted-foreground", children: t('optimizations.detail.trend.empty') })] }));
|
|
504
|
+
}
|
|
505
|
+
const W = 720;
|
|
506
|
+
const H = 220;
|
|
507
|
+
const padL = 50;
|
|
508
|
+
const padR = 20;
|
|
509
|
+
const padT = 20;
|
|
510
|
+
const padB = 28;
|
|
511
|
+
const innerW = W - padL - padR;
|
|
512
|
+
const innerH = H - padT - padB;
|
|
513
|
+
const maxRoundIndex = Math.max(0, trend.slots.length - 1);
|
|
514
|
+
const totalSlots = Math.max(1, maxRoundIndex);
|
|
515
|
+
const xForIndex = (i) => padL + (i / totalSlots) * innerW;
|
|
516
|
+
const scale = deriveTrendScale(series);
|
|
517
|
+
const yToPx = (v) => padT + (1 - (Math.max(scale.min, Math.min(scale.max, v)) - scale.min) / (scale.max - scale.min)) * innerH;
|
|
518
|
+
const axisMetricKey = series[0]?.metricKey;
|
|
519
|
+
const bestPointerLabel = formatTemplate(t('optimizations.detail.trend.bestPointer'), {
|
|
520
|
+
label: detail.bestRoundLabel === 'baseline'
|
|
521
|
+
? t('optimizations.detail.round.baseline')
|
|
522
|
+
: (detail.bestRoundLabel ?? '—'),
|
|
523
|
+
});
|
|
524
|
+
return (_jsxs("section", { className: "rounded-lg border bg-card", "data-testid": "optimization-detail-trend", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b px-4 py-3", children: [_jsxs("h2", { className: "inline-flex items-center gap-2 text-[13px] font-semibold", children: [t('optimizations.detail.trend.title'), _jsx(BestPointer, { label: bestPointerLabel })] }), _jsx(TrendChartControls, { scopeOptions: trend.scopeOptions, metricOptions: trend.metricOptions, scopeValue: scopeValue, metricValue: metricValue, onScopeChange: (value) => setSelectedScopeId(value), onMetricChange: (value) => setSelectedMetricValue(value) })] }), _jsx("div", { className: "px-4 py-3", children: _jsxs("svg", { viewBox: `0 0 ${W} ${H}`, className: "block w-full", preserveAspectRatio: "xMidYMid meet", role: "img", "aria-label": t('optimizations.detail.trend.title'), style: { height: 220 }, children: [scale.ticks.map((tick) => (_jsxs("g", { children: [_jsx("line", { x1: padL, x2: W - padR, y1: yToPx(tick), y2: yToPx(tick), stroke: "currentColor", strokeDasharray: "2 3", strokeWidth: 1, vectorEffect: "non-scaling-stroke", className: "text-border opacity-70" }), _jsx("text", { x: padL - 6, y: yToPx(tick) + 3, textAnchor: "end", fill: "currentColor", className: "font-mono text-muted-foreground", style: { fontSize: 9.5 }, children: formatTrendAxisValue(tick, axisMetricKey) })] }, `y-${tick}`))), series.map((s) => {
|
|
525
|
+
if (typeof s.target !== 'number')
|
|
526
|
+
return null;
|
|
527
|
+
return (_jsx("line", { x1: padL, x2: W - padR, y1: yToPx(s.target), y2: yToPx(s.target), stroke: "currentColor", strokeDasharray: "4 4", strokeWidth: 1.2, vectorEffect: "non-scaling-stroke", className: cn(s.style.line, 'opacity-80') }, `target-${s.id}`));
|
|
528
|
+
}), series.map((s) => {
|
|
529
|
+
const path = buildTrendLinePath(s.values, xForIndex, yToPx);
|
|
530
|
+
return (_jsx("path", { d: path, fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinejoin: "round", vectorEffect: "non-scaling-stroke", className: s.style.line }, `line-${s.id}`));
|
|
531
|
+
}), series.map((s) => s.values.map((v, i) => {
|
|
532
|
+
if (v === null)
|
|
533
|
+
return null;
|
|
534
|
+
const isBest = i === s.bestSlotIndex;
|
|
535
|
+
return (_jsx("circle", { cx: xForIndex(i), cy: yToPx(v), r: isBest ? 5 : 3.5, fill: isBest ? 'currentColor' : 'var(--card)', stroke: "currentColor", strokeWidth: 1.5, vectorEffect: "non-scaling-stroke", className: s.style.line }, `pt-${s.id}-${i}`));
|
|
536
|
+
})), trend.slots.map((slot, i) => {
|
|
537
|
+
const x = xForIndex(i);
|
|
538
|
+
const isCurrent = i === trend.currentSlotIndex;
|
|
539
|
+
const isBest = trend.primaryBestSlotIndex === i;
|
|
540
|
+
const label = slot.kind === 'baseline'
|
|
541
|
+
? t('optimizations.detail.trend.baseline')
|
|
542
|
+
: isCurrent
|
|
543
|
+
? formatTemplate(t('optimizations.detail.trend.roundCurrent'), { index: slot.roundIndex })
|
|
544
|
+
: isBest
|
|
545
|
+
? formatTemplate(t('optimizations.detail.trend.roundBest'), { index: slot.roundIndex })
|
|
546
|
+
: formatTemplate(t('optimizations.detail.trend.roundShort'), { index: slot.roundIndex });
|
|
547
|
+
return (_jsxs("g", { children: [_jsx("line", { x1: x, x2: x, y1: padT + innerH, y2: padT + innerH + 6, stroke: "currentColor", strokeWidth: 1, vectorEffect: "non-scaling-stroke", className: "text-border" }), _jsx("text", { x: x, y: padT + innerH + 18, textAnchor: "middle", fill: "currentColor", className: cn('font-mono', isCurrent ? optimizationTone.info.text : 'text-muted-foreground'), style: { fontSize: 9.5, fontWeight: isCurrent ? 600 : 400 }, children: label })] }, `x-${slot.id}`));
|
|
548
|
+
}), typeof trend.primaryBestSlotIndex === 'number' && (_jsx("line", { x1: xForIndex(trend.primaryBestSlotIndex), x2: xForIndex(trend.primaryBestSlotIndex), y1: padT, y2: padT + innerH, stroke: "currentColor", strokeDasharray: "3 4", strokeWidth: 1, vectorEffect: "non-scaling-stroke", className: cn(optimizationTone.info.text, 'opacity-40') }))] }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-3 px-4 pb-3 font-mono text-[11px] text-muted-foreground", children: [series.map((s) => (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx("span", { className: cn('inline-block h-[2px] w-4 rounded-[1px] bg-current', s.style.text) }), _jsx("span", { className: s.style.text, children: t(s.labelKey) })] }, `lg-${s.id}`))), _jsx("span", { className: "text-muted-foreground", children: t('optimizations.detail.trend.legendGoal') })] })] }));
|
|
549
|
+
}
|
|
550
|
+
function TrendChartControls({ scopeOptions, metricOptions, scopeValue, metricValue, onScopeChange, onMetricChange, }) {
|
|
551
|
+
const { t } = useI18n();
|
|
552
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [_jsxs(Select, { value: scopeValue, onValueChange: onScopeChange, children: [_jsx(SelectTrigger, { className: "h-8 w-[150px] rounded-md px-2 text-[12px]", "aria-label": t('optimizations.detail.trend.scopeLabel'), children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: scopeOptions.map((option) => (_jsx(SelectItem, { value: option.id, children: formatTrendScopeOption(option, t) }, option.id))) })] }), _jsxs(Select, { value: metricValue ?? undefined, onValueChange: onMetricChange, disabled: metricOptions.length === 0, children: [_jsx(SelectTrigger, { className: "h-8 w-[150px] rounded-md px-2 text-[12px]", "aria-label": t('optimizations.detail.trend.metricLabel'), children: _jsx(SelectValue, { placeholder: t('optimizations.detail.trend.metricEmpty') }) }), _jsx(SelectContent, { children: metricOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: formatTrendMetricOption(option, t) }, option.value))) })] })] }));
|
|
553
|
+
}
|
|
554
|
+
function RoundGoalChipRow({ chips }) {
|
|
555
|
+
const safeChips = chips ?? [];
|
|
556
|
+
if (!safeChips.length)
|
|
557
|
+
return null;
|
|
558
|
+
return (_jsx("div", { className: "flex min-w-0 flex-wrap items-center justify-end gap-x-2 gap-y-1 font-mono text-[11px]", "data-testid": "round-goal-chips", children: safeChips.map((chip, i) => (_jsxs(Fragment, { children: [i > 0 && (_jsx("span", { className: "text-muted-foreground", "aria-hidden": true, children: "\u00B7" })), _jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx("span", { className: cn('size-1.5 rounded-full', chip.achieved === 'hit' ? optimizationTone.positive.dot : 'bg-muted-foreground/40'), "aria-hidden": true }), _jsx("span", { className: "text-foreground", children: chip.label }), _jsx("span", { className: "text-muted-foreground", children: chip.targetText }), _jsx("span", { className: "text-muted-foreground", children: "\u00B7" }), _jsx("span", { className: cn('font-semibold tabular-nums', chip.achieved === 'hit' && optimizationTone.positive.text), children: chip.currentText })] })] }, `${chip.label}-${i}`))) }));
|
|
559
|
+
}
|
|
560
|
+
function RoundStreamBlock({ stream }) {
|
|
561
|
+
const { t } = useI18n();
|
|
562
|
+
return (_jsxs("div", { className: cn('min-h-[80px] space-y-2 rounded-md border px-3 py-2.5 font-mono text-[12px] leading-relaxed', optimizationTone.info.border, optimizationTone.info.bg), children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[10.5px] font-semibold uppercase tracking-wide text-muted-foreground", children: [_jsx("span", { className: cn('size-1.5 animate-pulse rounded-full', optimizationTone.info.dot), "aria-hidden": "true" }), stream.stage] }), _jsx("div", { className: "space-y-2 text-foreground", children: stream.segments.map((seg, idx) => (_jsxs("p", { className: "leading-relaxed", children: [seg.kind !== 'plain' && (_jsx("span", { className: cn('mr-1 rounded px-1 font-mono text-[11px] font-semibold', optimizationTone.info.bg, optimizationTone.info.text), children: seg.kind === 'observation'
|
|
563
|
+
? t('optimizations.detail.round.stream.observation')
|
|
564
|
+
: seg.kind === 'hypothesis'
|
|
565
|
+
? t('optimizations.detail.round.stream.hypothesis')
|
|
566
|
+
: t('optimizations.detail.round.stream.rewrite') })), _jsx("span", { dangerouslySetInnerHTML: { __html: seg.text } }), idx === stream.segments.length - 1 && stream.showCursor && (_jsx("span", { className: cn('ml-1 inline-block h-3 w-1.5 align-[-2px] animate-pulse', optimizationTone.info.fill), "aria-hidden": "true" }))] }, idx))) })] }));
|
|
567
|
+
}
|
|
568
|
+
function EmptyBlockPlaceholder({ roundStatus, emptyKey, }) {
|
|
569
|
+
const { t } = useI18n();
|
|
570
|
+
const suffix = roundStatus === 'running' ? 'Running' : roundStatus === 'failed' ? 'Failed' : 'Empty';
|
|
571
|
+
const key = (emptyKey === 'errorPatterns'
|
|
572
|
+
? `optimizations.detail.round.errorPatternsEmpty${suffix === 'Empty' ? '' : suffix}`
|
|
573
|
+
: emptyKey === 'improvementSuggestions'
|
|
574
|
+
? `optimizations.detail.round.improvementSuggestionsEmpty${suffix === 'Empty' ? '' : suffix}`
|
|
575
|
+
: `optimizations.detail.round.promptDiffEmpty${suffix === 'Empty' ? '' : suffix}`);
|
|
576
|
+
return _jsx("div", { className: "border-t border-dashed px-4 py-4 text-center text-[12px] text-muted-foreground", children: t(key) });
|
|
577
|
+
}
|
|
578
|
+
// Render the three steps in a fixed order: mirrors ph_runs.optimization_round_steps + service.STEP_ORDER
|
|
579
|
+
const ROUND_STEP_ORDER = ['error_analysis', 'generate_prompt', 'experiment'];
|
|
580
|
+
const STEP_LABEL_KEY = {
|
|
581
|
+
error_analysis: 'optimizations.detail.round.steps.errorAnalysis',
|
|
582
|
+
generate_prompt: 'optimizations.detail.round.steps.generatePrompt',
|
|
583
|
+
experiment: 'optimizations.detail.round.steps.experiment',
|
|
584
|
+
};
|
|
585
|
+
const STEP_STATUS_LABEL_KEY = {
|
|
586
|
+
pending: 'optimizations.detail.round.steps.statusPending',
|
|
587
|
+
running: 'optimizations.detail.round.steps.statusRunning',
|
|
588
|
+
success: 'optimizations.detail.round.steps.statusSuccess',
|
|
589
|
+
failed: 'optimizations.detail.round.steps.statusFailed',
|
|
590
|
+
skipped: 'optimizations.detail.round.steps.statusSkipped',
|
|
591
|
+
};
|
|
592
|
+
function getStepByKind(steps, kind) {
|
|
593
|
+
return (steps ?? []).find((s) => s.step === kind);
|
|
594
|
+
}
|
|
595
|
+
// Three-dot stepper: shows which step (error_analysis / generate_prompt / experiment) the current round is at;
|
|
596
|
+
// colors all go through theme tokens, covering light / dark / twilight / electric themes.
|
|
597
|
+
function RoundStepIndicator({ steps }) {
|
|
598
|
+
const { t } = useI18n();
|
|
599
|
+
return (_jsx("div", { className: "inline-flex items-center gap-1.5", role: "list", "aria-label": t('optimizations.detail.round.steps.errorAnalysis'), "data-testid": "optimization-round-step-indicator", children: ROUND_STEP_ORDER.map((kind, idx) => {
|
|
600
|
+
const step = getStepByKind(steps, kind);
|
|
601
|
+
// Missing is treated as pending (not yet started)
|
|
602
|
+
const status = step?.status ?? 'pending';
|
|
603
|
+
const labelKey = STEP_LABEL_KEY[kind];
|
|
604
|
+
const statusKey = STEP_STATUS_LABEL_KEY[status];
|
|
605
|
+
const tooltip = formatTemplate(t('optimizations.detail.round.steps.tooltipFormat'), {
|
|
606
|
+
label: t(labelKey),
|
|
607
|
+
status: t(statusKey),
|
|
608
|
+
});
|
|
609
|
+
const dotBase = 'flex size-[14px] items-center justify-center rounded-full border';
|
|
610
|
+
let dotClass = '';
|
|
611
|
+
if (status === 'running') {
|
|
612
|
+
dotClass = cn(optimizationTone.info.border, optimizationTone.info.fill, 'animate-pulse');
|
|
613
|
+
}
|
|
614
|
+
else if (status === 'success') {
|
|
615
|
+
dotClass = cn(optimizationTone.positive.border, optimizationTone.positive.fill);
|
|
616
|
+
}
|
|
617
|
+
else if (status === 'failed') {
|
|
618
|
+
dotClass = cn(optimizationTone.danger.border, optimizationTone.danger.fill);
|
|
619
|
+
}
|
|
620
|
+
else if (status === 'skipped') {
|
|
621
|
+
dotClass = cn(optimizationTone.muted.border, 'bg-card opacity-60');
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
// pending
|
|
625
|
+
dotClass = cn(optimizationTone.muted.border, 'bg-card');
|
|
626
|
+
}
|
|
627
|
+
const showCheck = status === 'success';
|
|
628
|
+
const showAlert = status === 'failed';
|
|
629
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1", role: "listitem", children: [_jsxs("span", { title: tooltip, "aria-label": tooltip, className: cn(dotBase, dotClass), children: [showCheck && _jsx(Check, { className: "size-[10px] text-white", "aria-hidden": "true" }), showAlert && _jsx(AlertTriangle, { className: "size-[10px] text-white", "aria-hidden": "true" })] }), _jsx("span", { className: "text-[10.5px] font-medium text-muted-foreground", children: t(labelKey) }), idx < ROUND_STEP_ORDER.length - 1 && _jsx("span", { "aria-hidden": "true", className: "block h-px w-3 bg-border" })] }, kind));
|
|
630
|
+
}) }));
|
|
631
|
+
}
|
|
632
|
+
function DatasetBaselineStepIndicator({ round }) {
|
|
633
|
+
const { t } = useI18n();
|
|
634
|
+
const generateStep = getStepByKind(round.steps, 'generate_prompt');
|
|
635
|
+
const experimentStatus = round.experimentResult?.experimentStatus === 'success'
|
|
636
|
+
? 'success'
|
|
637
|
+
: round.experimentResult?.experimentStatus === 'failed'
|
|
638
|
+
? 'failed'
|
|
639
|
+
: round.experimentResult?.experimentStatus === 'running'
|
|
640
|
+
? 'running'
|
|
641
|
+
: 'pending';
|
|
642
|
+
const steps = [
|
|
643
|
+
{
|
|
644
|
+
key: 'generate_first_prompt',
|
|
645
|
+
labelKey: 'optimizations.detail.round.steps.generateFirstPrompt',
|
|
646
|
+
status: generateStep?.status ?? (round.promptDiff ? 'success' : 'pending'),
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
key: 'baseline_experiment',
|
|
650
|
+
labelKey: 'optimizations.detail.round.steps.baselineExperiment',
|
|
651
|
+
status: experimentStatus,
|
|
652
|
+
},
|
|
653
|
+
];
|
|
654
|
+
return (_jsx("div", { className: "inline-flex items-center gap-1.5", role: "list", "aria-label": t('optimizations.detail.round.datasetBaselineKind'), "data-testid": "optimization-baseline-step-indicator", children: steps.map((step, idx) => {
|
|
655
|
+
const statusKey = STEP_STATUS_LABEL_KEY[step.status];
|
|
656
|
+
const tooltip = formatTemplate(t('optimizations.detail.round.steps.tooltipFormat'), {
|
|
657
|
+
label: t(step.labelKey),
|
|
658
|
+
status: t(statusKey),
|
|
659
|
+
});
|
|
660
|
+
const dotBase = 'flex size-[14px] items-center justify-center rounded-full border';
|
|
661
|
+
const dotClass = step.status === 'running'
|
|
662
|
+
? cn(optimizationTone.info.border, optimizationTone.info.fill, 'animate-pulse')
|
|
663
|
+
: step.status === 'success'
|
|
664
|
+
? cn(optimizationTone.positive.border, optimizationTone.positive.fill)
|
|
665
|
+
: step.status === 'failed'
|
|
666
|
+
? cn(optimizationTone.danger.border, optimizationTone.danger.fill)
|
|
667
|
+
: cn(optimizationTone.muted.border, 'bg-card');
|
|
668
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1", role: "listitem", children: [_jsxs("span", { title: tooltip, "aria-label": tooltip, className: cn(dotBase, dotClass), children: [step.status === 'success' && _jsx(Check, { className: "size-[10px] text-white", "aria-hidden": "true" }), step.status === 'failed' && _jsx(AlertTriangle, { className: "size-[10px] text-white", "aria-hidden": "true" })] }), _jsx("span", { className: "text-[10.5px] font-medium text-muted-foreground", children: t(step.labelKey) }), idx < steps.length - 1 && _jsx("span", { "aria-hidden": "true", className: "block h-px w-3 bg-border" })] }, step.key));
|
|
669
|
+
}) }));
|
|
670
|
+
}
|
|
671
|
+
// Step error info bar: only shown when the step is failed and has an errorMessage.
|
|
672
|
+
// Reuse optimizationTone.danger, which auto-adapts to all 4 themes.
|
|
673
|
+
function StepErrorBanner({ step }) {
|
|
674
|
+
const { t } = useI18n();
|
|
675
|
+
if (!step || step.status !== 'failed' || !step.errorMessage)
|
|
676
|
+
return null;
|
|
677
|
+
return (_jsx("div", { className: cn('border-t border-dashed px-4 py-2', optimizationTone.danger.bg, optimizationTone.danger.border), "data-testid": `optimization-round-step-error-${step.step}`, children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(AlertTriangle, { className: cn('mt-0.5 size-3.5 shrink-0', optimizationTone.danger.text), "aria-hidden": "true" }), _jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [_jsx("div", { className: cn('text-[11.5px] font-semibold', optimizationTone.danger.text), children: t('optimizations.detail.round.steps.errorBannerTitle') }), step.errorClass && (_jsxs("div", { className: "font-mono text-[10.5px] text-muted-foreground", children: [t('optimizations.detail.round.steps.errorClassLabel'), ": ", step.errorClass] })), _jsx("div", { className: "break-words text-[11.5px] leading-snug text-foreground", children: step.errorMessage })] })] }) }));
|
|
678
|
+
}
|
|
679
|
+
function BaselineMetricStrip({ metrics }) {
|
|
680
|
+
const { t } = useI18n();
|
|
681
|
+
if (metrics.length === 0)
|
|
682
|
+
return null;
|
|
683
|
+
return (_jsx("div", { className: "grid gap-2 sm:grid-cols-3", "data-testid": "optimization-baseline-metrics", children: metrics.map((metric) => (_jsxs("div", { className: "rounded-md border bg-secondary/25 px-3 py-2", children: [_jsx("div", { className: "truncate font-mono text-[10px] uppercase tracking-wide text-muted-foreground", children: formatMetricDisplayLabel(metric.label, t) }), _jsx("div", { className: "mt-1 font-mono text-[18px] font-semibold tabular-nums text-foreground", children: formatMetricDisplayValue(metric.label, metric.value) })] }, metric.label))) }));
|
|
684
|
+
}
|
|
685
|
+
function BaselinePromptPreviewBlock({ promptPreview, open, onToggle, controlsId, }) {
|
|
686
|
+
const { t } = useI18n();
|
|
687
|
+
return (_jsxs("div", { className: "overflow-hidden rounded-md border", "data-testid": "optimization-baseline-prompt", children: [_jsx("button", { type: "button", onClick: onToggle, "aria-expanded": open, "aria-controls": controlsId, "aria-label": t(open
|
|
688
|
+
? 'optimizations.detail.round.baselinePromptCollapse'
|
|
689
|
+
: 'optimizations.detail.round.baselinePromptExpand'), className: cn('flex w-full cursor-pointer items-center justify-between gap-3 px-3 py-2 text-left text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:bg-muted/30', open && 'border-b'), children: _jsxs("span", { className: "inline-flex min-w-0 items-center gap-2", children: [_jsx(ChevronRight, { className: cn('size-3 shrink-0 text-muted-foreground transition-transform', open && 'rotate-90'), "aria-hidden": "true" }), _jsx(FileText, { className: "size-3 shrink-0", "aria-hidden": "true" }), _jsx("span", { className: "truncate", children: t('optimizations.detail.round.datasetBaselinePromptTitle') })] }) }), open && (_jsx("pre", { id: controlsId, className: "max-h-[300px] overflow-auto whitespace-pre-wrap break-words bg-muted/25 px-3 py-2.5 font-mono text-[12px] leading-relaxed text-foreground", children: promptPreview }))] }));
|
|
690
|
+
}
|
|
691
|
+
function DatasetBaselineBlock({ detail, round, promptOpen, onPromptToggle, }) {
|
|
692
|
+
const { t } = useI18n();
|
|
693
|
+
const promptText = round.promptDiff?.toText?.trim();
|
|
694
|
+
return (_jsxs("div", { className: "border-t border-dashed px-4 py-3", "data-testid": "optimization-dataset-baseline-context", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: [_jsx(FileText, { className: "size-3", "aria-hidden": "true" }), t('optimizations.detail.round.datasetBaselineTitle')] }), _jsx("div", { className: "mt-1.5 text-[12px] leading-relaxed text-muted-foreground", children: formatTemplate(t('optimizations.detail.round.datasetBaselineMeta'), {
|
|
695
|
+
dataset: detail.datasetName,
|
|
696
|
+
samples: formatThousand(detail.datasetSamples),
|
|
697
|
+
model: detail.analysisModelName,
|
|
698
|
+
}) }), round.summaryFallback && (_jsx("p", { className: "mt-2 text-[12.5px] leading-relaxed text-foreground", children: round.summaryFallback })), promptText && (_jsx("div", { className: "mt-3", children: _jsx(BaselinePromptPreviewBlock, { promptPreview: promptText, open: promptOpen, onToggle: onPromptToggle, controlsId: `optimization-round-${round.index}-baseline-prompt-preview` }) }))] }));
|
|
699
|
+
}
|
|
700
|
+
function ErrorPatternBlock({ patterns, roundStatus, open, onToggle, step, }) {
|
|
701
|
+
const { t } = useI18n();
|
|
702
|
+
const hasData = Array.isArray(patterns) && patterns.length > 0;
|
|
703
|
+
return (_jsxs("div", { className: "border-t border-dashed", "data-testid": "optimization-round-error-patterns", children: [_jsxs("button", { type: "button", onClick: onToggle, "aria-expanded": open, className: "flex w-full cursor-pointer items-center justify-between gap-3 px-4 py-2 text-left text-[11.5px] font-medium text-muted-foreground transition-colors hover:bg-muted/30", children: [_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsx(ChevronRight, { className: cn('size-3 text-muted-foreground transition-transform', open && 'rotate-90'), "aria-hidden": "true" }), t('optimizations.detail.round.errorTitle')] }), _jsx("span", { className: "rounded-full border bg-secondary px-1.5 font-mono text-[10.5px] text-muted-foreground", children: hasData
|
|
704
|
+
? formatTemplate(t('optimizations.detail.round.errorTag'), { count: patterns.length })
|
|
705
|
+
: t('optimizations.detail.round.emptyTag') })] }), open && !hasData && _jsx(EmptyBlockPlaceholder, { roundStatus: roundStatus, emptyKey: "errorPatterns" }), open && hasData && (_jsx("div", { className: "space-y-1.5 border-t border-dashed px-4 py-3", children: patterns.map((p, idx) => (_jsxs("div", { className: cn('grid items-center gap-2.5 rounded border px-2 py-1.5 [grid-template-columns:42px_minmax(0,1fr)_auto]', optimizationTone.danger.border, optimizationTone.danger.bg), children: [_jsxs("div", { className: cn('text-right font-mono text-[13px] font-bold tabular-nums', optimizationTone.danger.text), children: [p.percent, "%"] }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-[12.5px] font-medium leading-snug text-foreground", children: p.title }), _jsx("div", { className: "text-[11px] leading-snug text-muted-foreground", children: p.detail })] }), _jsxs("div", { className: "font-mono text-[11px] tabular-nums text-muted-foreground", children: [p.count.hit, " / ", p.count.total] })] }, idx))) })), _jsx(StepErrorBanner, { step: step })] }));
|
|
706
|
+
}
|
|
707
|
+
function RoundJumpButtons({ promptId, promptVersionId, experimentId, }) {
|
|
708
|
+
const { t } = useI18n();
|
|
709
|
+
const router = useRouter();
|
|
710
|
+
const hasPromptJump = Boolean(promptId && promptVersionId);
|
|
711
|
+
const hasExperimentJump = Boolean(experimentId);
|
|
712
|
+
if (!hasPromptJump && !hasExperimentJump)
|
|
713
|
+
return null;
|
|
714
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-0.5", children: [hasPromptJump && (_jsx(TableActionIconButton, { label: t('optimizations.detail.round.openPromptTooltip'), onClick: (event) => {
|
|
715
|
+
event.stopPropagation();
|
|
716
|
+
router.push(`/prompts/${promptId}?version=${promptVersionId}`);
|
|
717
|
+
}, children: _jsx(FileText, { className: "size-3.5", "aria-hidden": "true" }) })), hasExperimentJump && (_jsx(TableActionIconButton, { label: t('optimizations.detail.round.openExperimentTooltip'), onClick: (event) => {
|
|
718
|
+
event.stopPropagation();
|
|
719
|
+
router.push(`/experiments/${experimentId}`);
|
|
720
|
+
}, children: _jsx(FlaskConical, { className: "size-3.5", "aria-hidden": "true" }) }))] }));
|
|
721
|
+
}
|
|
722
|
+
// The failure banner for the error-analysis step is shown by ErrorPatternBlock; do not duplicate here (suggested improvements and error analysis share the same LLM).
|
|
723
|
+
function ImprovementSuggestionsBlock({ suggestions, roundStatus, open, onToggle, }) {
|
|
724
|
+
const { t } = useI18n();
|
|
725
|
+
const hasData = Array.isArray(suggestions) && suggestions.length > 0;
|
|
726
|
+
const toneForPriority = (priority) => {
|
|
727
|
+
if (priority === 'high')
|
|
728
|
+
return optimizationTone.danger;
|
|
729
|
+
if (priority === 'medium')
|
|
730
|
+
return optimizationTone.info;
|
|
731
|
+
if (priority === 'low')
|
|
732
|
+
return optimizationTone.muted;
|
|
733
|
+
return optimizationTone.muted;
|
|
734
|
+
};
|
|
735
|
+
const priorityLabel = (priority) => {
|
|
736
|
+
if (priority === 'high')
|
|
737
|
+
return t('optimizations.detail.round.improvementPriority.high');
|
|
738
|
+
if (priority === 'medium')
|
|
739
|
+
return t('optimizations.detail.round.improvementPriority.medium');
|
|
740
|
+
if (priority === 'low')
|
|
741
|
+
return t('optimizations.detail.round.improvementPriority.low');
|
|
742
|
+
return null;
|
|
743
|
+
};
|
|
744
|
+
return (_jsxs("div", { className: "border-t border-dashed", "data-testid": "optimization-round-improvement-suggestions", children: [_jsxs("button", { type: "button", onClick: onToggle, "aria-expanded": open, className: "flex w-full cursor-pointer items-center justify-between gap-3 px-4 py-2 text-left text-[11.5px] font-medium text-muted-foreground transition-colors hover:bg-muted/30", children: [_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsx(ChevronRight, { className: cn('size-3 text-muted-foreground transition-transform', open && 'rotate-90'), "aria-hidden": "true" }), t('optimizations.detail.round.improvementTitle')] }), _jsx("span", { className: "rounded-full border bg-secondary px-1.5 font-mono text-[10.5px] text-muted-foreground", children: hasData
|
|
745
|
+
? formatTemplate(t('optimizations.detail.round.improvementTag'), {
|
|
746
|
+
count: suggestions.length,
|
|
747
|
+
})
|
|
748
|
+
: t('optimizations.detail.round.emptyTag') })] }), open && !hasData && _jsx(EmptyBlockPlaceholder, { roundStatus: roundStatus, emptyKey: "improvementSuggestions" }), open && hasData && (_jsx("div", { className: "space-y-1.5 border-t border-dashed px-4 py-3", children: suggestions.map((s, idx) => {
|
|
749
|
+
const tone = toneForPriority(s.priority);
|
|
750
|
+
const label = priorityLabel(s.priority);
|
|
751
|
+
return (_jsxs("div", { className: cn('grid items-start gap-2.5 rounded border px-2 py-1.5 [grid-template-columns:auto_minmax(0,1fr)]', tone.border, tone.bg), children: [_jsxs("div", { className: "flex flex-col items-start gap-1 pt-[2px]", children: [label && (_jsx("span", { className: cn('inline-flex items-center rounded-full border px-1.5 py-0.5 font-mono text-[10px] uppercase tracking-wide', tone.pill), children: label })), _jsx("span", { className: "rounded border bg-secondary px-1.5 font-mono text-[10px] text-muted-foreground", children: s.section })] }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-[12.5px] font-medium leading-snug text-foreground", children: s.title }), s.detail && _jsx("div", { className: "text-[11px] leading-snug text-muted-foreground", children: s.detail })] })] }, idx));
|
|
752
|
+
}) }))] }));
|
|
753
|
+
}
|
|
754
|
+
function PromptDiffBlock({ diff, roundStatus, open, onToggle, step, }) {
|
|
755
|
+
const { t } = useI18n();
|
|
756
|
+
const hasData = Boolean(diff);
|
|
757
|
+
return (_jsxs("div", { className: "border-t border-dashed", "data-testid": "optimization-round-prompt-diff", children: [_jsxs("button", { type: "button", onClick: onToggle, "aria-expanded": open, className: "flex w-full cursor-pointer items-center justify-between gap-3 px-4 py-2 text-left text-[11.5px] font-medium text-muted-foreground transition-colors hover:bg-muted/30", children: [_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsx(ChevronRight, { className: cn('size-3 text-muted-foreground transition-transform', open && 'rotate-90'), "aria-hidden": "true" }), t('optimizations.detail.round.diffTitle')] }), _jsx("span", { className: "rounded-full border bg-secondary px-1.5 font-mono text-[10.5px] text-muted-foreground", children: hasData
|
|
758
|
+
? formatTemplate(t('optimizations.detail.round.diffTag'), {
|
|
759
|
+
from: diff.from,
|
|
760
|
+
to: diff.to,
|
|
761
|
+
})
|
|
762
|
+
: t('optimizations.detail.round.emptyTag') })] }), open && !hasData && _jsx(EmptyBlockPlaceholder, { roundStatus: roundStatus, emptyKey: "promptDiff" }), open && hasData && (_jsxs("div", { className: "border-t border-dashed px-4 py-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 pb-2 text-[11px] text-muted-foreground", children: [_jsxs("span", { className: "inline-flex items-center gap-1 rounded border bg-destructive/10 px-1.5 py-0.5 text-destructive", children: ["- ", t('optimizations.detail.round.diffRemoved')] }), _jsxs("span", { className: "inline-flex items-center gap-1 rounded border border-[var(--status-running-bd)] bg-[var(--status-running-bg)] px-1.5 py-0.5 text-[var(--status-running-fg)]", children: ["+ ", t('optimizations.detail.round.diffAdded')] })] }), _jsx(PromptDiffSplitView, { fromLabel: diff.from, toLabel: diff.to, fromText: diff.fromText, toText: diff.toText })] })), _jsx(StepErrorBanner, { step: step })] }));
|
|
763
|
+
}
|
|
764
|
+
function MetricValue({ value, comparison, showComparison, muted, }) {
|
|
765
|
+
const showDelta = showComparison && comparison;
|
|
766
|
+
return (_jsxs("span", { className: "inline-flex min-w-0 items-baseline justify-end gap-1.5", children: [_jsx("span", { className: cn(muted && 'text-muted-foreground'), children: value }), showDelta && (_jsx("span", { className: cn('text-[10.5px] font-semibold leading-none', comparison.tone === 'ok' && optimizationTone.positive.text, comparison.tone === 'bad' && optimizationTone.danger.text, comparison.tone === 'neutral' && 'text-muted-foreground'), children: formatDelta(comparison.value) }))] }));
|
|
767
|
+
}
|
|
768
|
+
function ExperimentResultBlock({ result, showMetricComparisons, }) {
|
|
769
|
+
const { t } = useI18n();
|
|
770
|
+
const total = result.samplesTotal || 1;
|
|
771
|
+
const percent = Math.min(100, (result.samplesDone / total) * 100);
|
|
772
|
+
const progressLabel = formatProgressLabel({ value: result.samplesDone, max: Math.max(1, total), percent });
|
|
773
|
+
const okWidth = result.correct && total ? (result.correct / total) * 100 : 0;
|
|
774
|
+
const badWidth = result.wrong && total ? (result.wrong / total) * 100 : 0;
|
|
775
|
+
const correctLabel = formatTemplate(t('optimizations.detail.round.correctLabel'), {
|
|
776
|
+
count: formatThousand(result.correct),
|
|
777
|
+
});
|
|
778
|
+
const wrongLabel = formatTemplate(t('optimizations.detail.round.wrongLabel'), {
|
|
779
|
+
count: formatThousand(result.wrong),
|
|
780
|
+
});
|
|
781
|
+
return (_jsxs("div", { className: "grid gap-6 lg:grid-cols-[minmax(0,1.05fr)_minmax(0,1fr)]", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between font-mono text-[11.5px] text-muted-foreground", children: [_jsxs("span", { children: [_jsx("b", { className: "font-bold text-foreground", children: formatThousand(result.samplesDone) }), " /", ' ', formatThousand(result.samplesTotal)] }), _jsxs("span", { className: cn('font-bold', optimizationTone.positive.text), children: [percent.toFixed(0), " %"] })] }), _jsx(Progress, { value: result.samplesDone, max: Math.max(1, total), label: progressLabel }), _jsx(TooltipProvider, { delayDuration: 160, children: _jsxs("div", { className: "flex h-5 overflow-hidden rounded border", children: [okWidth > 0 && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: cn('flex min-w-0 items-center justify-center overflow-hidden whitespace-nowrap px-1.5 font-mono text-[10.5px]', optimizationTone.positive.bg, optimizationTone.positive.text), style: { width: `${okWidth}%` }, "aria-label": correctLabel, children: correctLabel }) }), _jsx(TooltipContent, { children: correctLabel })] })), badWidth > 0 && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: cn('flex min-w-0 items-center justify-center overflow-hidden whitespace-nowrap px-1.5 font-mono text-[10.5px]', optimizationTone.danger.bg, optimizationTone.danger.text), style: { width: `${badWidth}%` }, "aria-label": wrongLabel, children: wrongLabel }) }), _jsx(TooltipContent, { children: wrongLabel })] }))] }) }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 font-mono text-[11px] text-muted-foreground", children: [_jsx("span", { children: formatTemplate(t('optimizations.detail.round.timing'), { elapsed: result.elapsed }) }), _jsxs("span", { children: [result.tokenSummary, " \u00B7 ", result.costLabel] })] })] }), _jsx("div", { className: "overflow-hidden rounded-md border", children: _jsxs("table", { className: "w-full font-mono text-[12px] tabular-nums", children: [_jsx("thead", { children: _jsxs("tr", { className: "bg-muted/50 text-left text-[10px] uppercase tracking-wide text-muted-foreground", children: [_jsx("th", { className: "px-2 py-1.5", children: t('optimizations.detail.round.cls.category') }), _jsx("th", { className: "px-2 py-1.5 text-right", children: t('optimizations.detail.round.cls.accuracy') }), _jsx("th", { className: "px-2 py-1.5 text-right", children: t('optimizations.detail.round.cls.precision') }), _jsx("th", { className: "px-2 py-1.5 text-right", children: t('optimizations.detail.round.cls.recall') })] }) }), _jsxs("tbody", { children: [result.overallRow && (_jsxs("tr", { className: "border-b-2 border-foreground/40 bg-muted/40 font-semibold", "data-testid": "round-overall-row", children: [_jsx("td", { className: "px-2 py-1.5 font-sans text-[12px] text-foreground", children: t('optimizations.detail.round.cls.overall') }), _jsx("td", { className: "px-2 py-1.5 text-right", children: _jsx(MetricValue, { value: result.overallRow.accuracy.toFixed(3), comparison: result.overallRow.deltas?.accuracy, showComparison: showMetricComparisons }) }), _jsx("td", { className: "px-2 py-1.5 text-right", children: _jsx(MetricValue, { value: result.overallRow.precision.toFixed(3), comparison: result.overallRow.deltas?.precision, showComparison: showMetricComparisons }) }), _jsx("td", { className: "px-2 py-1.5 text-right", children: _jsx(MetricValue, { value: result.overallRow.recall.toFixed(3), comparison: result.overallRow.deltas?.recall, showComparison: showMetricComparisons }) })] })), result.classRows.map((row) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "px-2 py-1.5 font-sans text-[12px] font-medium text-foreground", children: row.label }), _jsx("td", { className: "px-2 py-1.5 text-right", children: _jsx(MetricValue, { value: "\u2014", showComparison: false, muted: true }) }), _jsx("td", { className: "px-2 py-1.5 text-right", children: _jsx(MetricValue, { value: row.precision.toFixed(3), comparison: row.deltas?.precision, showComparison: showMetricComparisons }) }), _jsx("td", { className: "px-2 py-1.5 text-right", children: _jsx(MetricValue, { value: row.recall.toFixed(3), comparison: row.deltas?.recall, showComparison: showMetricComparisons }) })] }, row.label)))] })] }) })] }));
|
|
782
|
+
}
|
|
783
|
+
function TimelineRoundCard({ round, detail, defaultOpen, promptId, showMetricComparisons, }) {
|
|
784
|
+
const { t } = useI18n();
|
|
785
|
+
const { formatTime } = useDateTimeFormatter();
|
|
786
|
+
const [errorOpen, setErrorOpen] = useState(defaultOpen);
|
|
787
|
+
const [suggestionsOpen, setSuggestionsOpen] = useState(defaultOpen);
|
|
788
|
+
const [diffOpen, setDiffOpen] = useState(defaultOpen);
|
|
789
|
+
const [baselinePromptOpen, setBaselinePromptOpen] = useState(defaultOpen);
|
|
790
|
+
const isCurrent = round.status === 'running';
|
|
791
|
+
const isFailed = round.status === 'failed';
|
|
792
|
+
const isBest = round.isBest;
|
|
793
|
+
const isCollapsed = round.collapsed;
|
|
794
|
+
const isBaselineRound = round.isBaseline === true;
|
|
795
|
+
const errorAnalysisStep = getStepByKind(round.steps, 'error_analysis');
|
|
796
|
+
const generatePromptStep = getStepByKind(round.steps, 'generate_prompt');
|
|
797
|
+
const experimentStep = getStepByKind(round.steps, 'experiment');
|
|
798
|
+
const title = isBaselineRound
|
|
799
|
+
? t('optimizations.detail.round.baseline')
|
|
800
|
+
: formatTemplate(t('optimizations.detail.round.label'), { index: round.index });
|
|
801
|
+
const kindLabel = isBaselineRound ? t('optimizations.detail.round.datasetBaselineKind') : round.kindLabel;
|
|
802
|
+
const dotClass = cn('absolute -left-7 top-4 z-10 inline-flex size-[18px] items-center justify-center rounded-full border-2 bg-card', isBest
|
|
803
|
+
? cn('text-white border-[var(--status-canary-dot)]', optimizationTone.info.fill)
|
|
804
|
+
: isCurrent
|
|
805
|
+
? 'border-[var(--status-canary-dot)] ring-[3px] ring-[var(--status-canary-dot)]/30'
|
|
806
|
+
: round.status === 'failed'
|
|
807
|
+
? 'border-destructive'
|
|
808
|
+
: 'border-[var(--status-running-dot)]');
|
|
809
|
+
const footMain = round.totalElapsed || round.totalCost
|
|
810
|
+
? `${round.totalElapsed ? formatTemplate(t('optimizations.detail.round.timing'), { elapsed: round.totalElapsed }) + ' · ' : ''}${round.totalCost ?? ''}`
|
|
811
|
+
: round.startedAt
|
|
812
|
+
? formatTemplate(t('optimizations.detail.round.startedAt'), {
|
|
813
|
+
at: formatTime(round.startedAt, { fallback: '—' }),
|
|
814
|
+
})
|
|
815
|
+
: '';
|
|
816
|
+
return (_jsxs("article", { className: "relative pl-8", "data-testid": `optimization-round-${round.index}`, children: [_jsx("div", { "aria-hidden": "true", className: "absolute bottom-0 left-1 top-0 w-[2px] bg-border" }), _jsx("span", { className: dotClass, "aria-hidden": "true", children: isBest && _jsx("span", { className: "text-[10px] font-bold leading-none", children: "\u2605" }) }), _jsxs("div", { className: cn('overflow-hidden rounded-lg border bg-card shadow-xs', isCurrent && 'border-[color-mix(in_oklab,var(--status-canary-dot)_40%,var(--border))]', isBest && 'border-[color-mix(in_oklab,var(--status-canary-dot)_50%,var(--border))]'), children: [_jsxs("div", { className: "grid items-center gap-4 border-b bg-secondary/30 px-4 py-2.5 [grid-template-columns:minmax(0,1fr)_auto]", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-mono text-[14px] font-bold text-foreground", children: title }), _jsx(RoundJumpButtons, { promptId: promptId, promptVersionId: round.promptVersionId, experimentId: round.experimentId }), _jsx("span", { className: "font-mono text-[11px] text-muted-foreground", children: kindLabel }), isCurrent && !isFailed && (_jsxs("span", { className: cn('inline-flex items-center gap-1.5 rounded-full border px-1.5 py-0.5 text-[10.5px]', optimizationTone.info.pill), children: [_jsx("span", { className: cn('size-1 animate-pulse rounded-full', optimizationTone.info.dot), "aria-hidden": "true" }), t('optimizations.detail.round.live')] })), isFailed && (_jsxs("span", { className: cn('inline-flex items-center gap-1.5 rounded-full border px-1.5 py-0.5 text-[10.5px]', optimizationTone.danger.pill), "data-testid": `optimization-round-${round.index}-status-failed`, children: [_jsx("span", { className: cn('size-1 rounded-full', optimizationTone.danger.dot), "aria-hidden": "true" }), t('optimizations.detail.round.statusFailed')] })), round.autoPatched && (_jsxs("span", { className: cn('inline-flex items-center gap-1.5 rounded-full border px-1.5 py-0.5 text-[10.5px]', optimizationTone.warning.pill), "data-testid": `optimization-round-${round.index}-auto-patched`, title: formatTemplate(t('optimizations.detail.round.autoPatchedTooltip'), {
|
|
817
|
+
vars: (round.patchedVariables ?? []).join(', '),
|
|
818
|
+
}), children: [_jsx(Wrench, { className: "size-3", "aria-hidden": "true" }), t('optimizations.detail.round.autoPatchedBadge')] })), isBest && _jsx(BestPointer, { label: t('optimizations.detail.round.bestPointer') }), isBaselineRound ? (_jsx(DatasetBaselineStepIndicator, { round: round })) : ((round.steps?.length ?? 0) > 0 && _jsx(RoundStepIndicator, { steps: round.steps }))] }), _jsx(RoundGoalChipRow, { chips: round.goalChips })] }), !isCollapsed && (_jsx("div", { className: "px-4 py-3", children: round.experimentResult ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("h3", { className: "flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: [_jsx(FlaskConical, { className: "size-3", "aria-hidden": "true" }), t('optimizations.detail.round.experimentResult'), _jsxs("span", { className: "font-mono text-[10.5px] font-medium normal-case text-muted-foreground", children: [round.experimentId ? (_jsx(Link, { href: `/experiments/${round.experimentId}`, className: cn('underline decoration-dotted underline-offset-2', optimizationTone.info.text), children: round.experimentResult.experimentRef })) : (_jsx("span", { children: round.experimentResult.experimentRef })), ' · ', round.experimentResult.experimentStatus === 'success' ? (_jsx("span", { children: t('optimizations.detail.round.experimentDone') })) : round.experimentResult.experimentStatus === 'failed' ? (_jsx("span", { className: cn('font-semibold', optimizationTone.danger.text), children: t('optimizations.detail.round.experimentFailed') })) : (_jsx("span", { children: t('optimizations.detail.round.experimentRunning') }))] })] }), _jsx(ExperimentResultBlock, { result: round.experimentResult, showMetricComparisons: showMetricComparisons }), _jsx(StepErrorBanner, { step: experimentStep })] })) : experimentStep && experimentStep.status === 'failed' ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("h3", { className: "flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: [_jsx(FlaskConical, { className: "size-3", "aria-hidden": "true" }), t('optimizations.detail.round.experimentResult'), _jsx("span", { className: cn('font-semibold', optimizationTone.danger.text), children: t('optimizations.detail.round.experimentFailed') })] }), _jsx(StepErrorBanner, { step: experimentStep })] })) : round.stream ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("h3", { className: "flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: [_jsx(Clock, { className: "size-3", "aria-hidden": "true" }), t('optimizations.detail.round.streamHeader'), _jsx("span", { className: "font-mono text-[10.5px] font-medium normal-case text-muted-foreground", children: round.stream.analysisModel })] }), _jsx(RoundStreamBlock, { stream: round.stream })] })) : round.summaryFallback ? (_jsxs("div", { className: "space-y-2", children: [_jsx("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: t('optimizations.detail.round.summary') }), _jsxs("p", { className: "text-[12.5px] leading-relaxed text-muted-foreground", children: [round.summaryFallback, _jsxs("span", { className: "ml-1", children: ["\u00B7", ' ', _jsx(Link, { href: "#", className: cn('underline decoration-dotted underline-offset-2', optimizationTone.info.text), children: t('optimizations.detail.round.expandSummary') })] })] })] })) : null })), isBaselineRound ? (_jsx(DatasetBaselineBlock, { detail: detail, round: round, promptOpen: baselinePromptOpen, onPromptToggle: () => setBaselinePromptOpen((p) => !p) })) : (_jsxs(_Fragment, { children: [_jsx(ErrorPatternBlock, { patterns: round.errorPatterns, roundStatus: round.status, open: errorOpen, onToggle: () => setErrorOpen((p) => !p), step: errorAnalysisStep }), _jsx(ImprovementSuggestionsBlock, { suggestions: round.improvementSuggestions, roundStatus: round.status, open: suggestionsOpen, onToggle: () => setSuggestionsOpen((p) => !p) })] })), !isBaselineRound && (_jsx(PromptDiffBlock, { diff: round.promptDiff, roundStatus: round.status, open: diffOpen, onToggle: () => setDiffOpen((p) => !p), step: generatePromptStep })), (round.startedAt || round.totalElapsed || round.totalCost) && footMain && (_jsx("div", { className: "flex flex-wrap items-center gap-2 border-t bg-secondary/20 px-4 py-2 font-mono text-[11px] text-muted-foreground", children: _jsx("span", { children: footMain }) }))] })] }));
|
|
819
|
+
}
|
|
820
|
+
function BaselineRow({ detail, defaultPromptOpen }) {
|
|
821
|
+
const { t } = useI18n();
|
|
822
|
+
const [promptOpen, setPromptOpen] = useState(defaultPromptOpen);
|
|
823
|
+
const baseline = detail.baseline;
|
|
824
|
+
if (!baseline)
|
|
825
|
+
return null;
|
|
826
|
+
const promptPreview = baseline.promptPreview?.trim() ?? '';
|
|
827
|
+
const hasBody = Boolean(baseline.experimentResult || baseline.metrics.length > 0 || promptPreview);
|
|
828
|
+
return (_jsxs("article", { className: "relative pl-8", "data-testid": "optimization-baseline", children: [_jsx("div", { "aria-hidden": "true", className: "absolute left-1 top-0 h-10 w-[2px] bg-border" }), _jsx("span", { className: "absolute -left-7 top-4 z-10 inline-flex size-[18px] items-center justify-center rounded-full border-2 border-border bg-card", "aria-hidden": "true" }), _jsxs("div", { className: "overflow-hidden rounded-lg border bg-card shadow-xs", children: [_jsx("div", { className: cn('flex items-center gap-4 px-4 py-2.5', hasBody && 'border-b bg-secondary/30'), children: _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-mono text-[14px] font-bold text-muted-foreground", children: t('optimizations.detail.round.baseline') }), _jsx(RoundJumpButtons, { promptId: detail.promptId, promptVersionId: detail.baseVersionId, experimentId: detail.sourceExperimentId }), _jsx("span", { className: "font-mono text-[11px] text-muted-foreground", children: formatTemplate(t('optimizations.detail.round.baselineFrom'), {
|
|
829
|
+
experiment: baseline.baselineExperiment,
|
|
830
|
+
version: baseline.promptVersion,
|
|
831
|
+
}) })] }) }), hasBody && (_jsxs("div", { className: "space-y-4 px-4 py-3", children: [baseline.experimentResult ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("h3", { className: "flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: [_jsx(FlaskConical, { className: "size-3", "aria-hidden": "true" }), t('optimizations.detail.round.experimentResult'), _jsxs("span", { className: "font-mono text-[10.5px] font-medium normal-case text-muted-foreground", children: [baseline.experimentResult.experimentRef, ' · ', baseline.experimentResult.experimentStatus === 'success' ? (_jsx("span", { children: t('optimizations.detail.round.experimentDone') })) : baseline.experimentResult.experimentStatus === 'failed' ? (_jsx("span", { className: cn('font-semibold', optimizationTone.danger.text), children: t('optimizations.detail.round.experimentFailed') })) : (_jsx("span", { children: t('optimizations.detail.round.experimentRunning') }))] })] }), _jsx(ExperimentResultBlock, { result: baseline.experimentResult, showMetricComparisons: false })] })) : (_jsx(BaselineMetricStrip, { metrics: baseline.metrics })), promptPreview && (_jsx(BaselinePromptPreviewBlock, { promptPreview: promptPreview, open: promptOpen, onToggle: () => setPromptOpen((p) => !p), controlsId: "optimization-baseline-prompt-preview" }))] }))] })] }));
|
|
832
|
+
}
|
|
833
|
+
function BestVersionCard({ detail }) {
|
|
834
|
+
const { t } = useI18n();
|
|
835
|
+
const best = detail.bestVersion;
|
|
836
|
+
const promptId = detail.promptId;
|
|
837
|
+
const generatedAtLabel = best?.generatedAtRoundLabel === 'baseline'
|
|
838
|
+
? t('optimizations.detail.round.baseline')
|
|
839
|
+
: typeof best?.generatedAtRoundIndex === 'number'
|
|
840
|
+
? detail.startingMode === 'from_dataset_only' && best.generatedAtRoundIndex === 0
|
|
841
|
+
? t('optimizations.detail.round.baseline')
|
|
842
|
+
: formatTemplate(t('optimizations.detail.round.label'), { index: best.generatedAtRoundIndex })
|
|
843
|
+
: (best?.generatedAtRoundLabel ?? '—');
|
|
844
|
+
return (_jsxs("section", { className: "rounded-lg border bg-card", "data-testid": "optimization-detail-best", children: [_jsx("div", { className: "border-b px-4 py-2.5", children: _jsx("h3", { className: "text-[13px] font-semibold", children: t('optimizations.detail.best.title') }) }), best ? (_jsxs("div", { className: "px-4 py-3 font-mono text-[12px] leading-7", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: t('optimizations.detail.best.prompt') }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [promptId && best.promptVersionId ? (_jsx(Link, { href: `/prompts/${promptId}?version=${best.promptVersionId}`, className: cn('underline decoration-dotted underline-offset-2', optimizationTone.info.text), children: best.promptRef })) : (_jsx("span", { children: best.promptRef })), _jsx(RoundJumpButtons, { promptId: promptId, promptVersionId: best.promptVersionId, experimentId: null })] })] }), _jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: t('optimizations.detail.best.generatedAt') }), _jsx("span", { children: generatedAtLabel })] }), best.metrics.map((m) => (_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: formatMetricDisplayLabel(m.label, t) }), _jsx("span", { className: cn('tabular-nums', m.tone === 'ok' && optimizationTone.positive.text), children: formatMetricDisplayValue(m.label, m.value) })] }, m.label))), _jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: t('optimizations.detail.best.experiment') }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [best.experimentId ? (_jsx(Link, { href: `/experiments/${best.experimentId}`, className: cn('underline decoration-dotted underline-offset-2', optimizationTone.info.text), children: best.experimentRef })) : (_jsx("span", { children: best.experimentRef })), _jsx(RoundJumpButtons, { promptId: null, promptVersionId: null, experimentId: best.experimentId })] })] })] })) : (_jsx("div", { className: "px-4 py-6 text-center text-[12px] text-muted-foreground", children: t('optimizations.detail.best.empty') }))] }));
|
|
845
|
+
}
|
|
846
|
+
export function OptimizationDetailPage({ projectId, optimizationId, }) {
|
|
847
|
+
const { t } = useI18n();
|
|
848
|
+
const detailQuery = useOptimization(projectId, optimizationId);
|
|
849
|
+
const detail = detailQuery.data ?? null;
|
|
850
|
+
const [expandAll, setExpandAll] = useState(true);
|
|
851
|
+
const [timelineOrder, setTimelineOrder] = useState('desc');
|
|
852
|
+
const [showMetricComparisons, setShowMetricComparisons] = useState(false);
|
|
853
|
+
const [actionError, setActionError] = useState(null);
|
|
854
|
+
const controlMutation = useControlOptimization(projectId);
|
|
855
|
+
const queryClient = useQueryClient();
|
|
856
|
+
const onAutoRefreshTick = useCallback(async () => {
|
|
857
|
+
await queryClient.invalidateQueries({ queryKey: ['optimizations', projectId], exact: false });
|
|
858
|
+
}, [queryClient, projectId]);
|
|
859
|
+
useAutoRefresh({
|
|
860
|
+
intervalMs: AUTO_REFRESH_INTERVAL_MS,
|
|
861
|
+
enabled: detail?.status === 'running',
|
|
862
|
+
onTick: onAutoRefreshTick,
|
|
863
|
+
});
|
|
864
|
+
const hasMetricComparisons = useMemo(() => {
|
|
865
|
+
if (!detail)
|
|
866
|
+
return false;
|
|
867
|
+
return detail.rounds.some((round) => {
|
|
868
|
+
const result = round.experimentResult;
|
|
869
|
+
if (!result)
|
|
870
|
+
return false;
|
|
871
|
+
const overallDeltas = result.overallRow?.deltas ? Object.keys(result.overallRow.deltas) : [];
|
|
872
|
+
const classDeltas = result.classRows.some((row) => row.deltas && Object.keys(row.deltas).length > 0);
|
|
873
|
+
return overallDeltas.length > 0 || classDeltas;
|
|
874
|
+
});
|
|
875
|
+
}, [detail]);
|
|
876
|
+
const timelineItems = useMemo(() => {
|
|
877
|
+
if (!detail)
|
|
878
|
+
return [];
|
|
879
|
+
const rounds = detail.rounds.slice().sort((a, b) => a.index - b.index);
|
|
880
|
+
const hasRoundBaseline = rounds.some((round) => round.isBaseline === true);
|
|
881
|
+
const items = [];
|
|
882
|
+
if (timelineOrder === 'asc') {
|
|
883
|
+
if (detail.baseline && !hasRoundBaseline)
|
|
884
|
+
items.push({ kind: 'baseline' });
|
|
885
|
+
rounds.forEach((round) => items.push({ kind: 'round', round }));
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
rounds
|
|
889
|
+
.slice()
|
|
890
|
+
.reverse()
|
|
891
|
+
.forEach((round) => items.push({ kind: 'round', round }));
|
|
892
|
+
if (detail.baseline && !hasRoundBaseline)
|
|
893
|
+
items.push({ kind: 'baseline' });
|
|
894
|
+
}
|
|
895
|
+
return items;
|
|
896
|
+
}, [detail, timelineOrder]);
|
|
897
|
+
const detailLoading = useDelayedLoading(detailQuery.isLoading);
|
|
898
|
+
if (detailLoading) {
|
|
899
|
+
return (_jsx(Main, { className: "gap-0 bg-muted/35 p-0", children: _jsx("div", { className: "mx-auto w-full max-w-[1280px] px-6 py-12", "data-testid": "optimization-detail-loading", children: _jsx(DetailPageSkeleton, {}) }) }));
|
|
900
|
+
}
|
|
901
|
+
if (!detail) {
|
|
902
|
+
return (_jsx(Main, { className: "gap-0 bg-muted/35 p-0", children: _jsxs("div", { className: "mx-auto w-full max-w-[1280px] px-6 py-12", "data-testid": "optimization-detail-not-found", children: [_jsx("h1", { className: "text-[20px] font-semibold", children: t('optimizations.notFound.title') }), _jsx("p", { className: "mt-2 text-[12.5px] text-muted-foreground", children: t('optimizations.notFound.description') }), _jsx(Button, { asChild: true, variant: "outline", className: "mt-4", children: _jsx(Link, { href: `/optimizations`, children: t('optimizations.new.backToList') }) })] }) }));
|
|
903
|
+
}
|
|
904
|
+
const status = detail.status;
|
|
905
|
+
const canStop = status === 'running';
|
|
906
|
+
const canResume = status === 'stopped';
|
|
907
|
+
const canCancel = status === 'running' || status === 'stopped';
|
|
908
|
+
const mutationPending = controlMutation.isPending;
|
|
909
|
+
const handleControl = async (action) => {
|
|
910
|
+
if (action === 'cancel' && !window.confirm(t('optimizations.detail.confirm.cancel')))
|
|
911
|
+
return;
|
|
912
|
+
setActionError(null);
|
|
913
|
+
try {
|
|
914
|
+
await controlMutation.mutateAsync({ optimizationId: detail.id, action });
|
|
915
|
+
}
|
|
916
|
+
catch (error) {
|
|
917
|
+
setActionError(getApiErrorMessage(error) ?? t('optimizations.list.actionFailed'));
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
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-detail-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.detail.backToList') }) }), _jsxs("div", { className: "mb-5 flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between", children: [_jsx("div", { className: "min-w-0", children: _jsxs("h1", { className: "flex flex-wrap items-baseline gap-2 text-[24px] font-semibold tracking-tight", children: [_jsx("span", { className: "font-mono", children: detail.name }), _jsx("span", { "data-testid": "optimization-detail-status-badge", children: _jsx(OptimizationStatusBadge, { status: status }) })] }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [canStop && (_jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-9 gap-1", disabled: mutationPending, "aria-label": t('optimizations.action.stop'), "data-testid": "optimization-detail-action-stop", onClick: () => {
|
|
921
|
+
void handleControl('stop');
|
|
922
|
+
}, children: [_jsx(Square, { className: "size-4", "aria-hidden": "true" }), t('optimizations.action.stop')] })), canResume && (_jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-9 gap-1", disabled: mutationPending, "aria-label": t('optimizations.action.resume'), "data-testid": "optimization-detail-action-resume", onClick: () => {
|
|
923
|
+
void handleControl('resume');
|
|
924
|
+
}, children: [_jsx(Play, { className: "size-4", "aria-hidden": "true" }), t('optimizations.action.resume')] })), canCancel && (_jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-9 gap-1 border-destructive/40 text-destructive hover:text-destructive", disabled: mutationPending, "aria-label": t('optimizations.action.cancel'), "data-testid": "optimization-detail-action-cancel", onClick: () => {
|
|
925
|
+
void handleControl('cancel');
|
|
926
|
+
}, children: [_jsx(Ban, { className: "size-4", "aria-hidden": "true" }), t('optimizations.action.cancel')] }))] })] }), actionError && (_jsx("div", { role: "alert", "data-testid": "optimization-detail-action-error", className: "mb-3 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[12.5px] text-destructive", children: actionError })), _jsx(FailureBanner, { detail: detail }), _jsx(OptimizationProgressCard, { detail: detail }), _jsxs("div", { className: "grid grid-cols-1 gap-5 xl:grid-cols-[minmax(0,1fr)_320px]", children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-4", children: [_jsx(TrendChart, { detail: detail }), _jsxs("section", { "data-testid": "optimization-detail-timeline", children: [_jsxs("div", { className: "mb-3 flex flex-wrap items-center justify-between gap-2", children: [_jsx("h2", { className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: t(timelineOrder === 'desc'
|
|
927
|
+
? 'optimizations.detail.timeline.titleDesc'
|
|
928
|
+
: 'optimizations.detail.timeline.titleAsc') }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: cn('inline-flex h-8 items-center gap-2 rounded-md border bg-card px-2.5', !hasMetricComparisons && 'opacity-60'), children: [_jsx(Switch, { id: "optimization-metric-comparison-toggle", checked: showMetricComparisons, onCheckedChange: setShowMetricComparisons, disabled: !hasMetricComparisons, "aria-label": t('optimizations.detail.timeline.metricComparisonToggleAriaLabel'), "data-testid": "optimization-metric-comparison-toggle" }), _jsx("label", { htmlFor: "optimization-metric-comparison-toggle", className: cn('select-none text-[12px] text-muted-foreground', hasMetricComparisons ? 'cursor-pointer' : 'cursor-not-allowed'), children: t('optimizations.detail.timeline.metricComparisonToggle') })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-8 gap-1 cursor-pointer", onClick: () => setTimelineOrder((prev) => (prev === 'desc' ? 'asc' : 'desc')), "aria-pressed": timelineOrder === 'asc', "aria-label": t('optimizations.detail.timeline.orderToggleAriaLabel'), "data-testid": "optimization-timeline-order-toggle", children: [timelineOrder === 'desc' ? (_jsx(ArrowDownNarrowWide, { className: "size-3.5" })) : (_jsx(ArrowUpNarrowWide, { className: "size-3.5" })), t(timelineOrder === 'desc'
|
|
929
|
+
? 'optimizations.detail.timeline.orderDesc'
|
|
930
|
+
: 'optimizations.detail.timeline.orderAsc')] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-8 gap-1 cursor-pointer", onClick: () => setExpandAll((prev) => !prev), "aria-pressed": expandAll, children: [expandAll ? _jsx(ChevronsUp, { className: "size-3.5" }) : _jsx(ChevronsDown, { className: "size-3.5" }), t(expandAll
|
|
931
|
+
? 'optimizations.detail.timeline.collapseAll'
|
|
932
|
+
: 'optimizations.detail.timeline.expandAll')] })] })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [timelineItems.map((item) => item.kind === 'baseline' ? (_jsx(BaselineRow, { detail: detail, defaultPromptOpen: expandAll }, `baseline-${expandAll ? 'open' : 'closed'}`)) : (_jsx(TimelineRoundCard, { round: item.round, detail: detail, defaultOpen: expandAll, promptId: detail.promptId, showMetricComparisons: showMetricComparisons }, `round-${item.round.index}-${expandAll ? 'open' : 'closed'}`))), timelineItems.length === 0 && (_jsx("div", { className: "rounded-lg border bg-card py-10 text-center text-[12px] text-muted-foreground", children: t('optimizations.detail.timeline.empty') }))] })] })] }), _jsxs("aside", { className: "flex flex-col gap-3 xl:sticky xl:top-20 xl:max-h-[calc(100vh-6rem)] xl:overflow-y-auto xl:pr-1", children: [_jsx(ConfigSection, { detail: detail }), _jsx(BestVersionCard, { detail: detail })] })] })] }) }));
|
|
933
|
+
}
|
|
934
|
+
//# sourceMappingURL=optimization-detail-page.js.map
|