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