@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,1013 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { promptVersionLabelNameSchema, } from '@proofhound/shared';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
7
|
+
import { ArrowLeft, BarChart3, CheckCircle2, Copy, Database, Eye, FlaskConical, GitCompareArrows, Info, Lock, Pencil, Plus, Save, Search, Sparkles, Tags, Trash2, Upload, X, } from 'lucide-react';
|
|
8
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, PlatformLoaderOverlay, DetailPageSkeleton, Skeleton, TableActionRow, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, ModalityIcon, ModalityIconGroup, UnusedImagesBadge, cn, } from '@proofhound/ui';
|
|
9
|
+
import { Main } from '@proofhound/ui/layout';
|
|
10
|
+
import { PromptDiffSplitView, PromptVariableModalityBadges } from '../../components';
|
|
11
|
+
import { useDatasets } from '../../hooks';
|
|
12
|
+
import { useCreatePromptDraftVersion, useDeletePromptDraftVersion, useDateTimeFormatter, usePrompt, usePromptMetrics, usePromptVersionDeleteImpact, useUpdatePrompt, useUpdatePromptDraftVersion, useUpdatePromptVersionLabel, } from '../../hooks';
|
|
13
|
+
import { useDelayedLoading } from '../../hooks';
|
|
14
|
+
import { PromptLanguageSelect } from '../../components';
|
|
15
|
+
import { useI18n } from '../../i18n';
|
|
16
|
+
import { DATASET_MODALITY_LABEL_KEYS, } from '../datasets/dataset-types';
|
|
17
|
+
import { toProjectDataset } from '../datasets/dataset-mappers';
|
|
18
|
+
import { deriveJudgmentField, toProjectPrompt, upsertJudgmentField, } from './prompt-model';
|
|
19
|
+
import { toPromptVariablesFromDataset } from './prompt-dataset-variables';
|
|
20
|
+
import { PromptBodyEditor } from './prompt-body-editor';
|
|
21
|
+
import { composePromptPreview } from './prompt-preview';
|
|
22
|
+
import { countPromptVariableUsages, renderPromptPreviewParts } from './prompt-preview-parts';
|
|
23
|
+
import { StatusBadge, VARIABLE_TONE_CLASSES, VariableToken, hasImageVariable } from './prompt-ui';
|
|
24
|
+
const LABEL_ACTION_MESSAGE_DISMISS_MS = 3000;
|
|
25
|
+
const PROMPT_VERSION_SYSTEM_LABEL_KEYS = {
|
|
26
|
+
latest: 'prompts.labels.system.latest',
|
|
27
|
+
gray: 'prompts.labels.system.gray',
|
|
28
|
+
production: 'prompts.labels.system.production',
|
|
29
|
+
};
|
|
30
|
+
const TAB_LABEL_KEYS = {
|
|
31
|
+
versions: 'prompts.detail.tab.versions',
|
|
32
|
+
metrics: 'prompts.detail.tab.metrics',
|
|
33
|
+
};
|
|
34
|
+
const PROMPT_MAIN_TAB_LABEL_KEYS = {
|
|
35
|
+
prompt: 'prompts.detail.subtab.prompt',
|
|
36
|
+
config: 'prompts.detail.subtab.config',
|
|
37
|
+
};
|
|
38
|
+
function resolveDetailTab(value) {
|
|
39
|
+
return value === 'metrics' ? 'metrics' : 'versions';
|
|
40
|
+
}
|
|
41
|
+
function resolvePromptMainTab(value) {
|
|
42
|
+
return value === 'config' ? 'config' : 'prompt';
|
|
43
|
+
}
|
|
44
|
+
const OUTPUT_SCHEMA_PANEL_STRICT_JSON_LABEL = {
|
|
45
|
+
'zh-CN': '请严格输出 JSON:',
|
|
46
|
+
'en-US': 'Return strict JSON:',
|
|
47
|
+
};
|
|
48
|
+
const UNSAVED_HISTORY_GUARD_KEY = '__proofhoundPromptUnsavedGuard';
|
|
49
|
+
const PROMPT_MODALITY_LABEL_KEYS = {
|
|
50
|
+
text: 'prompts.variableType.text',
|
|
51
|
+
image: 'prompts.variableType.image',
|
|
52
|
+
number: 'prompts.variableType.number',
|
|
53
|
+
};
|
|
54
|
+
const IMPACT_LABEL_KEYS = {
|
|
55
|
+
experiment: 'prompts.deleteImpactExperiment',
|
|
56
|
+
optimization: 'prompts.deleteImpactOptimization',
|
|
57
|
+
canary_release: 'prompts.deleteImpactCanaryRelease',
|
|
58
|
+
production_release: 'prompts.deleteImpactProductionRelease',
|
|
59
|
+
};
|
|
60
|
+
function DeleteImpactPanel({ impact, loading }) {
|
|
61
|
+
const { t } = useI18n();
|
|
62
|
+
const items = impact
|
|
63
|
+
? [...impact.experiments, ...impact.optimizations, ...impact.canaryReleases, ...impact.productionReleases]
|
|
64
|
+
: [];
|
|
65
|
+
if (loading) {
|
|
66
|
+
return (_jsx("div", { className: "rounded-md border bg-muted/35 p-3 text-sm text-muted-foreground", children: t('prompts.deleteImpactLoading') }));
|
|
67
|
+
}
|
|
68
|
+
if (items.length === 0) {
|
|
69
|
+
return (_jsx("div", { className: "rounded-md border bg-muted/35 p-3 text-sm text-muted-foreground", children: t('prompts.deleteImpactEmpty') }));
|
|
70
|
+
}
|
|
71
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "text-sm font-medium", children: [t('prompts.deleteImpactTitle'), " ", _jsx("span", { className: "font-mono", children: items.length })] }), _jsx("div", { className: "max-h-[260px] space-y-1 overflow-auto", children: items.map((item) => (_jsxs("div", { className: "flex items-center justify-between gap-3 rounded border px-2.5 py-2 text-xs", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "truncate font-medium", children: [t(IMPACT_LABEL_KEYS[item.kind]), " \u00B7 ", item.name ?? item.id] }), _jsxs("div", { className: "mt-0.5 font-mono text-[11px] text-muted-foreground", children: [item.promptVersionNumber ? `v${item.promptVersionNumber}` : '-', " \u00B7 ", item.status ?? '-'] })] }), _jsx("span", { className: "shrink-0 font-mono text-[10.5px] text-muted-foreground", children: item.id.slice(0, 8) })] }, `${item.kind}-${item.id}`))) })] }));
|
|
72
|
+
}
|
|
73
|
+
function DatasetSelectionPanel({ variables, datasets, selectedDatasetId, onSelectDataset, readOnly = false, }) {
|
|
74
|
+
const { t } = useI18n();
|
|
75
|
+
const [datasetSearch, setDatasetSearch] = useState('');
|
|
76
|
+
const selectedCount = variables.filter((variable) => variable.selected).length;
|
|
77
|
+
const datasetOptions = useMemo(() => {
|
|
78
|
+
return datasets
|
|
79
|
+
.filter((dataset) => dataset.status === 'active')
|
|
80
|
+
.map((dataset) => ({
|
|
81
|
+
id: dataset.id,
|
|
82
|
+
name: dataset.name,
|
|
83
|
+
description: dataset.description,
|
|
84
|
+
modalities: dataset.modalities,
|
|
85
|
+
hasImages: dataset.hasImages,
|
|
86
|
+
sampleCount: dataset.sampleCount,
|
|
87
|
+
fieldCount: dataset.fieldCount,
|
|
88
|
+
updatedAt: dataset.updatedAt,
|
|
89
|
+
status: dataset.status,
|
|
90
|
+
}));
|
|
91
|
+
}, [datasets]);
|
|
92
|
+
const filteredDatasets = useMemo(() => {
|
|
93
|
+
const query = datasetSearch.trim().toLowerCase();
|
|
94
|
+
if (!query)
|
|
95
|
+
return datasetOptions;
|
|
96
|
+
return datasetOptions.filter((dataset) => `${dataset.name} ${dataset.description} ${dataset.id}`.toLowerCase().includes(query));
|
|
97
|
+
}, [datasetOptions, datasetSearch]);
|
|
98
|
+
const selectedDataset = datasetOptions.find((dataset) => dataset.id === selectedDatasetId) ?? null;
|
|
99
|
+
const isUnbound = !selectedDatasetId;
|
|
100
|
+
return (_jsxs(_Fragment, { children: [isUnbound && (_jsx("div", { className: "mb-3 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs font-medium text-destructive", role: "alert", "data-testid": "prompt-dataset-unbound-warning", children: t('prompts.detail.datasetUnboundWarning') })), _jsxs("section", { className: "mb-4 overflow-hidden rounded-lg border bg-card", "aria-label": t('prompts.detail.boundDataset'), "data-testid": "prompt-dataset-selector", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b px-4 py-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "inline-flex items-center gap-1.5 text-[12.5px] font-semibold", children: [_jsx(Database, { className: "size-4 text-[var(--status-pending-fg)]" }), t('prompts.detail.boundDataset'), selectedDataset?.hasImages && !hasImageVariable(variables) && (_jsx(UnusedImagesBadge, { size: "sm", tooltip: t('prompts.detail.unusedImagesTooltip'), "aria-label": t('prompts.detail.unusedImagesTooltip') }))] }), _jsxs("div", { className: "mt-1 truncate text-[11.5px] text-muted-foreground", children: [selectedDataset
|
|
101
|
+
? t('prompts.detail.selectedDataset').replace('{dataset}', selectedDataset.name)
|
|
102
|
+
: t('prompts.detail.noDatasetSelected'), ' · ', t('prompts.detail.datasetFieldSummary')
|
|
103
|
+
.replace('{fieldCount}', String(selectedDataset?.fieldCount ?? 0))
|
|
104
|
+
.replace('{selectedCount}', String(selectedCount))] })] }), _jsxs("div", { className: "relative w-full sm:w-[260px]", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: datasetSearch, onChange: (event) => setDatasetSearch(event.target.value), placeholder: t('prompts.detail.datasetSearchPlaceholder'), className: "h-9 pl-8 text-xs" })] })] }), _jsx("div", { className: "max-h-[190px] overflow-auto p-2", children: filteredDatasets.length > 0 ? (_jsx("div", { className: "grid gap-1 sm:grid-cols-2 xl:grid-cols-3", children: filteredDatasets.map((dataset) => {
|
|
105
|
+
const selected = dataset.id === selectedDatasetId;
|
|
106
|
+
return (_jsxs("button", { type: "button", disabled: readOnly, onClick: () => onSelectDataset(dataset.id), className: cn('flex min-w-0 items-start gap-2 rounded-md border px-2.5 py-2 text-left transition-colors hover:bg-muted/40', selected ? 'border-primary bg-primary/5' : 'border-border bg-background', dataset.status === 'deleted' && 'opacity-70', readOnly && 'cursor-not-allowed opacity-60'), "aria-pressed": selected, children: [_jsx("span", { className: cn('mt-0.5 inline-flex size-3.5 flex-none items-center justify-center rounded-full border', selected ? 'border-primary bg-primary/10' : 'border-border bg-background'), "aria-hidden": "true", children: selected && _jsx("span", { className: "size-1.5 rounded-full bg-primary" }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate font-mono text-[12px] font-semibold", children: dataset.name }), _jsx("span", { className: "mt-1 block truncate text-[11px] text-muted-foreground", children: dataset.description }), _jsxs("span", { className: "mt-1 flex flex-wrap items-center gap-1", children: [_jsxs("span", { className: "rounded border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground", children: [dataset.sampleCount.toLocaleString(), " ", t('prompts.detail.samples')] }), _jsx("span", { className: "rounded border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground", children: t('prompts.detail.datasetFields').replace('{count}', String(dataset.fieldCount)) }), _jsx(ModalityIconGroup, { kinds: dataset.modalities, size: "sm", tooltips: dataset.modalities.reduce((acc, modality) => {
|
|
107
|
+
acc[modality] = t(DATASET_MODALITY_LABEL_KEYS[modality]);
|
|
108
|
+
return acc;
|
|
109
|
+
}, {}) })] })] })] }, dataset.id));
|
|
110
|
+
}) })) : (_jsx("div", { className: "px-3 py-8 text-center text-[12px] text-muted-foreground", children: t('prompts.detail.datasetNoMatch') })) })] })] }));
|
|
111
|
+
}
|
|
112
|
+
function getNextOutputFieldKey(fields) {
|
|
113
|
+
let index = fields.length + 1;
|
|
114
|
+
const existingKeys = new Set(fields.map((field) => field.key));
|
|
115
|
+
while (existingKeys.has(`field_${index}`))
|
|
116
|
+
index += 1;
|
|
117
|
+
return `field_${index}`;
|
|
118
|
+
}
|
|
119
|
+
function OutputSchemaPanel({ fields, promptLanguage, onFieldsChange, readOnly = false, }) {
|
|
120
|
+
const { t } = useI18n();
|
|
121
|
+
const updateField = (index, patch) => {
|
|
122
|
+
onFieldsChange(fields.map((field, fieldIndex) => (fieldIndex === index && !field.isJudgment ? { ...field, ...patch } : field)));
|
|
123
|
+
};
|
|
124
|
+
const addField = () => {
|
|
125
|
+
onFieldsChange([
|
|
126
|
+
...fields,
|
|
127
|
+
{
|
|
128
|
+
key: getNextOutputFieldKey(fields),
|
|
129
|
+
value: '',
|
|
130
|
+
isJudgment: false,
|
|
131
|
+
},
|
|
132
|
+
]);
|
|
133
|
+
};
|
|
134
|
+
const removeField = (index) => {
|
|
135
|
+
onFieldsChange(fields.filter((field, fieldIndex) => !(fieldIndex === index && !field.isJudgment)));
|
|
136
|
+
};
|
|
137
|
+
return (_jsxs("div", { className: "border-t bg-muted/55", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 px-4 py-2", children: [_jsx(CheckCircle2, { className: "size-3.5 text-[var(--status-canary-fg)]" }), _jsx("span", { className: "text-[12.5px] font-semibold text-muted-foreground", children: t('prompts.detail.outputSchema') }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: t('prompts.detail.outputSchemaHelp') }), _jsxs("span", { className: "ml-auto inline-flex items-center gap-1 font-mono text-[10.5px] text-[var(--status-running-fg)]", children: [_jsx("span", { className: "size-1 rounded-full bg-[var(--status-running-dot)]" }), t('prompts.detail.schemaLinked')] })] }), _jsxs("div", { className: "px-4 pb-3 font-mono text-[12px] leading-7 text-muted-foreground", children: [_jsx("div", { children: OUTPUT_SCHEMA_PANEL_STRICT_JSON_LABEL[promptLanguage] ?? OUTPUT_SCHEMA_PANEL_STRICT_JSON_LABEL['zh-CN'] }), _jsx("div", { children: '{' }), fields.map((field, index) => (_jsx("div", { className: cn('my-1 rounded-md border bg-background px-3 py-2', field.isJudgment &&
|
|
138
|
+
'border-l-2 border-l-[var(--status-pending-bd)] bg-[color-mix(in_srgb,var(--status-pending-bg)_60%,transparent)]'), "data-testid": "prompt-output-field-row", "data-judgment": field.isJudgment ? 'true' : 'false', children: _jsxs("div", { className: "grid gap-2 lg:grid-cols-[minmax(140px,0.6fr)_minmax(200px,1fr)_32px] lg:items-center", children: [_jsx(Input, { value: field.key, onChange: (event) => updateField(index, { key: event.target.value }), disabled: field.isJudgment || readOnly, "aria-label": t('prompts.detail.outputFieldKey'), placeholder: t('prompts.detail.outputFieldKeyPlaceholder'), className: "h-8 font-mono text-xs", "data-testid": field.isJudgment ? 'prompt-output-judgment-key' : 'prompt-output-field-key' }), _jsx(Input, { value: field.value, onChange: (event) => updateField(index, { value: event.target.value }), disabled: field.isJudgment || readOnly, "aria-label": t('prompts.detail.outputFieldValue'), placeholder: t('prompts.detail.outputFieldValuePlaceholder'), className: "h-8 font-mono text-xs", "data-testid": field.isJudgment ? 'prompt-output-judgment-value' : 'prompt-output-field-value' }), field.isJudgment ? (_jsx("span", { className: "inline-flex h-8 items-center justify-center text-muted-foreground", "aria-label": t('prompts.detail.outputFieldJudgmentReadonly'), title: t('prompts.detail.outputFieldJudgmentReadonly'), children: _jsx(Lock, { className: "size-3.5" }) })) : (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "size-8 text-destructive hover:text-destructive", "aria-label": t('prompts.detail.deleteOutputField'), onClick: () => removeField(index), disabled: readOnly, children: _jsx(Trash2, { className: "size-3.5" }) }))] }) }, index))), !readOnly && (_jsx("div", { className: "py-1 pl-4", children: _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-7 border-dashed px-2 text-xs", onClick: addField, children: [_jsx(Plus, { className: "size-3.5" }), t('prompts.detail.addOutputField')] }) })), _jsx("div", { children: '}' })] })] }));
|
|
139
|
+
}
|
|
140
|
+
const IMAGE_VARIABLE_TYPES = new Set(['image', 'image_url', 'image_base64']);
|
|
141
|
+
function toPromptVariableModalityKind(type) {
|
|
142
|
+
if (type === 'number')
|
|
143
|
+
return 'number';
|
|
144
|
+
if (IMAGE_VARIABLE_TYPES.has(type))
|
|
145
|
+
return 'image';
|
|
146
|
+
return 'text';
|
|
147
|
+
}
|
|
148
|
+
function VariableRow({ variable, usageCount, onInsertVariable, }) {
|
|
149
|
+
const { t } = useI18n();
|
|
150
|
+
const isImage = IMAGE_VARIABLE_TYPES.has(variable.type);
|
|
151
|
+
const canInsert = !isImage && onInsertVariable !== undefined;
|
|
152
|
+
const isUsed = usageCount > 0;
|
|
153
|
+
const modalityKind = toPromptVariableModalityKind(variable.type);
|
|
154
|
+
const modalityLabel = t(PROMPT_MODALITY_LABEL_KEYS[modalityKind]);
|
|
155
|
+
const usageLabel = t('prompts.detail.variables.usageCount').replace('{count}', String(usageCount));
|
|
156
|
+
const addToPromptLabel = t('prompts.detail.variables.addToPrompt');
|
|
157
|
+
const rowClassName = cn('group flex w-full items-center gap-2.5 border-b border-l-2 px-4 py-2.5 text-left transition-colors', isUsed ? 'border-l-primary bg-primary/5 hover:bg-primary/10' : 'border-l-transparent hover:bg-accent', canInsert &&
|
|
158
|
+
'cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2');
|
|
159
|
+
const rowContent = (_jsxs(_Fragment, { children: [_jsx(VariableToken, { variable: variable, dimmed: !isUsed }), _jsx(ModalityIcon, { kind: modalityKind, size: "sm", tooltip: modalityLabel, "aria-label": modalityLabel }), _jsxs("span", { className: "ml-auto inline-flex min-w-[116px] items-center justify-end gap-1.5", children: [_jsx("span", { className: "truncate text-[10.5px] text-muted-foreground", children: usageLabel }), canInsert && (_jsx(TooltipProvider, { delayDuration: 160, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "inline-flex size-5 shrink-0 items-center justify-center rounded-full border bg-background text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100 group-focus:opacity-100", children: _jsx(Plus, { className: "size-3.5" }) }) }), _jsx(TooltipContent, { side: "left", children: addToPromptLabel })] }) }))] })] }));
|
|
160
|
+
if (canInsert) {
|
|
161
|
+
return (_jsx("button", { type: "button", className: rowClassName, "aria-label": `${addToPromptLabel} · ${variable.name} · ${usageLabel}`, onClick: () => onInsertVariable?.(variable.name), "data-testid": `prompt-variable-insert-${variable.name}`, children: rowContent }));
|
|
162
|
+
}
|
|
163
|
+
return _jsx("div", { className: rowClassName, children: rowContent });
|
|
164
|
+
}
|
|
165
|
+
function VariablesPanel({ variables, usageCounts, onInsertVariable, hasBoundDataset, hasDatasets, onRequestDatasetBinding, onRequestDatasetUpload, }) {
|
|
166
|
+
const { t } = useI18n();
|
|
167
|
+
const usedCount = variables.filter((variable) => (usageCounts.get(variable.name) ?? 0) > 0).length;
|
|
168
|
+
const textVars = variables.filter((variable) => !IMAGE_VARIABLE_TYPES.has(variable.type));
|
|
169
|
+
const imageVars = variables.filter((variable) => IMAGE_VARIABLE_TYPES.has(variable.type));
|
|
170
|
+
const datasetActionLabel = hasDatasets
|
|
171
|
+
? t('prompts.detail.variables.bindDataset')
|
|
172
|
+
: t('prompts.detail.variables.uploadDataset');
|
|
173
|
+
const imageExample = `{
|
|
174
|
+
"role": "user",
|
|
175
|
+
"content": [
|
|
176
|
+
{ "type": "text", "text": "..." },
|
|
177
|
+
{ "type": "image_url", "image_url": { "url": "{{image_url}}" } }
|
|
178
|
+
]
|
|
179
|
+
}`;
|
|
180
|
+
return (_jsxs("aside", { className: "flex min-w-0 flex-col lg:border-l", children: [_jsxs("div", { className: "flex items-center gap-2 border-b px-4 py-2", children: [_jsx(Pencil, { className: "size-3.5 text-muted-foreground" }), _jsx("span", { className: "text-[12.5px] font-semibold", children: t('prompts.detail.variables') }), _jsxs("span", { className: "text-[11px] text-muted-foreground", children: ["\u00B7 ", usedCount, " / ", variables.length] }), !hasBoundDataset && (_jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "ml-auto h-7 px-2 text-xs", onClick: hasDatasets ? onRequestDatasetBinding : onRequestDatasetUpload, "data-testid": "prompt-variables-bind-dataset", children: [hasDatasets ? _jsx(Database, { className: "size-3.5" }) : _jsx(Upload, { className: "size-3.5" }), datasetActionLabel] }))] }), _jsxs("div", { className: "max-h-[520px] flex-1 overflow-auto", "data-testid": "prompt-variables-panel", children: [textVars.length > 0 && (_jsxs("section", { "data-testid": "prompt-variables-text-group", children: [_jsxs("header", { className: "flex items-center gap-2 bg-muted/35 px-4 py-1.5 text-[10.5px] font-medium text-muted-foreground", children: [_jsx("span", { children: t('prompts.detail.variables.textGroup') }), _jsxs("span", { className: "font-mono", children: ["\u00B7 ", textVars.length] })] }), textVars.map((variable) => (_jsx(VariableRow, { variable: variable, usageCount: usageCounts.get(variable.name) ?? 0, onInsertVariable: onInsertVariable }, variable.name)))] })), imageVars.length > 0 && (_jsxs("section", { "data-testid": "prompt-variables-image-group", children: [_jsxs("header", { className: "flex items-center gap-2 bg-muted/35 px-4 py-1.5 text-[10.5px] font-medium text-muted-foreground", children: [_jsx("span", { children: t('prompts.detail.variables.imageGroup') }), _jsxs("span", { className: "font-mono", children: ["\u00B7 ", imageVars.length] }), _jsx(TooltipProvider, { delayDuration: 200, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "inline-flex size-4 items-center justify-center rounded-full text-muted-foreground hover:text-foreground", "aria-label": t('prompts.detail.variables.imageHint'), "data-testid": "prompt-variables-image-info", children: _jsx(Info, { className: "size-3.5" }) }) }), _jsxs(TooltipContent, { side: "left", className: "max-w-[320px] text-left", children: [_jsx("p", { className: "mb-2 text-[11.5px] leading-relaxed", children: t('prompts.detail.variables.imageHint') }), _jsx("pre", { className: "overflow-auto whitespace-pre rounded bg-muted/40 p-2 font-mono text-[10.5px] text-foreground", children: imageExample })] })] }) })] }), imageVars.map((variable) => (_jsx(VariableRow, { variable: variable, usageCount: usageCounts.get(variable.name) ?? 0 }, variable.name)))] }))] }), _jsx("div", { className: "border-t bg-muted/25 px-4 py-3 text-[11.5px] text-muted-foreground", children: t('prompts.detail.variablesFooter') })] }));
|
|
181
|
+
}
|
|
182
|
+
function EditorTab({ body, promptLanguage, variables, outputFields, onBodyChange, onOutputFieldsChange, hasBoundDataset, hasDatasets, onRequestDatasetBinding, onRequestDatasetUpload, dirty, saveError, isSaving, onCancelChanges, onSaveChanges, readOnly, }) {
|
|
183
|
+
const { t } = useI18n();
|
|
184
|
+
const editorRef = useRef(null);
|
|
185
|
+
const lineCount = Math.max(1, body.split('\n').length);
|
|
186
|
+
const tokenEstimate = Math.max(1, Math.round(body.length / 4));
|
|
187
|
+
const fullPreview = useMemo(() => composePromptPreview({
|
|
188
|
+
body,
|
|
189
|
+
outputFields,
|
|
190
|
+
promptLanguage,
|
|
191
|
+
}), [body, outputFields, promptLanguage]);
|
|
192
|
+
const previewParts = useMemo(() => renderPromptPreviewParts(fullPreview, variables), [fullPreview, variables]);
|
|
193
|
+
const variableUsageCounts = useMemo(() => countPromptVariableUsages(body, variables), [body, variables]);
|
|
194
|
+
const insertVariable = useCallback((name) => {
|
|
195
|
+
editorRef.current?.insertVariable(name);
|
|
196
|
+
}, []);
|
|
197
|
+
return (_jsxs("div", { "data-testid": "prompt-editor-tab", children: [_jsxs("section", { className: "mb-4 overflow-hidden rounded-lg border bg-card", "aria-label": t('prompts.detail.editorSurface'), children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b bg-muted/35 px-4 py-2.5", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: t('prompts.detail.editorSurface') }), !readOnly && (_jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [saveError && _jsx("span", { className: "text-xs text-destructive", children: saveError }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-8", disabled: !dirty || isSaving, onClick: onCancelChanges, children: [_jsx(X, { className: "size-3.5" }), t('prompts.detail.cancelChanges')] }), _jsxs(Button, { type: "button", size: "sm", className: "h-8", disabled: !dirty || isSaving, onClick: () => void onSaveChanges(), "data-testid": "prompt-version-save", children: [_jsx(Save, { className: "size-3.5" }), isSaving ? t('common.savePending') : t('prompts.detail.saveChanges')] })] }))] }), _jsxs("div", { className: "grid lg:grid-cols-[minmax(0,1fr)_340px]", children: [_jsxs("div", { className: "min-w-0", "data-testid": "prompt-version-body", children: [_jsxs("div", { className: "flex items-center gap-2 border-b px-4 py-2", children: [_jsx(Pencil, { className: "size-3.5 text-muted-foreground" }), _jsx("span", { className: "text-[12.5px] font-semibold", children: t('prompts.detail.bodyTemplate') }), _jsx("span", { className: "text-[11.5px] text-muted-foreground", children: t('prompts.detail.bodyTemplateHelp') }), _jsxs("span", { className: "ml-auto font-mono text-[11px] text-muted-foreground", children: ["L ", lineCount, " \u00B7 token \u2248 ", tokenEstimate] })] }), _jsx(PromptBodyEditor, { ref: editorRef, value: body, onChange: onBodyChange, variables: variables, placeholder: t('prompts.detail.bodyTemplatePlaceholder'), readOnly: readOnly }), _jsx(OutputSchemaPanel, { fields: outputFields, promptLanguage: promptLanguage, onFieldsChange: onOutputFieldsChange, readOnly: readOnly })] }), _jsx(VariablesPanel, { variables: variables, usageCounts: variableUsageCounts, onInsertVariable: readOnly ? undefined : insertVariable, hasBoundDataset: hasBoundDataset, hasDatasets: hasDatasets, onRequestDatasetBinding: onRequestDatasetBinding, onRequestDatasetUpload: onRequestDatasetUpload })] })] }), _jsxs("section", { className: "mb-4 overflow-hidden rounded-lg border bg-card", "aria-label": t('prompts.detail.fullPromptPreview'), "data-testid": "prompt-full-preview", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b bg-muted/35 px-4 py-2.5", children: [_jsx(Eye, { className: "size-3.5 text-muted-foreground" }), _jsx("span", { className: "text-[12.5px] font-semibold", children: t('prompts.detail.fullPromptPreview') }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: t('prompts.detail.fullPromptPreviewHelp') })] }), _jsx("pre", { className: "overflow-auto whitespace-pre-wrap break-words px-4 py-3 font-mono text-[11.5px] leading-6 text-foreground", children: previewParts.map((part, index) => {
|
|
198
|
+
if (part.kind === 'text')
|
|
199
|
+
return _jsx("span", { children: part.value }, index);
|
|
200
|
+
const tone = part.varType
|
|
201
|
+
? VARIABLE_TONE_CLASSES[part.varType]
|
|
202
|
+
: 'border-muted-foreground/30 bg-muted/40 text-muted-foreground';
|
|
203
|
+
return (_jsx("span", { className: cn('inline rounded border px-1 font-mono text-[11px]', tone), "data-variable-name": part.name, children: `{{${part.name}}}` }, index));
|
|
204
|
+
}) })] })] }));
|
|
205
|
+
}
|
|
206
|
+
function ConfigTab({ datasets, variables, promptLanguage, onPromptLanguageChange, selectedDatasetId, onSelectDataset, saveError, readOnly, }) {
|
|
207
|
+
const { t } = useI18n();
|
|
208
|
+
return (_jsxs("div", { className: "space-y-4", "data-testid": "prompt-config-tab", children: [saveError && (_jsx("div", { className: "rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs font-medium text-destructive", role: "alert", children: saveError })), _jsxs("section", { className: "overflow-hidden rounded-lg border bg-card", "aria-label": t('prompts.detail.subtab.config'), children: [_jsx("div", { className: "border-b bg-muted/35 px-4 py-2.5 text-xs font-medium text-muted-foreground", children: t('promptLanguage.label') }), _jsx("div", { className: "px-4 py-3", children: _jsx(PromptLanguageSelect, { value: promptLanguage, onChange: onPromptLanguageChange, disabled: readOnly, helpKey: "prompts.detail.promptLanguageHelp", className: "max-w-[380px]", triggerClassName: "h-8" }) })] }), _jsx(DatasetSelectionPanel, { variables: variables, datasets: datasets, selectedDatasetId: selectedDatasetId, onSelectDataset: onSelectDataset, readOnly: readOnly })] }));
|
|
209
|
+
}
|
|
210
|
+
function getVersionLabel(version) {
|
|
211
|
+
return `v${version.version}`;
|
|
212
|
+
}
|
|
213
|
+
function renderVersionPromptPreview(version) {
|
|
214
|
+
if (!version)
|
|
215
|
+
return '';
|
|
216
|
+
return composePromptPreview({
|
|
217
|
+
body: version.body,
|
|
218
|
+
outputFields: version.outputFields,
|
|
219
|
+
promptLanguage: version.promptLanguage,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function serializePromptVariables(variables) {
|
|
223
|
+
return JSON.stringify(variables.map((variable) => ({
|
|
224
|
+
name: variable.name,
|
|
225
|
+
type: variable.type,
|
|
226
|
+
required: variable.required,
|
|
227
|
+
datasetField: variable.datasetField,
|
|
228
|
+
description: variable.description,
|
|
229
|
+
})));
|
|
230
|
+
}
|
|
231
|
+
function serializeOutputFields(fields) {
|
|
232
|
+
return JSON.stringify(fields.map((field) => ({
|
|
233
|
+
key: field.key,
|
|
234
|
+
value: field.value,
|
|
235
|
+
isJudgment: field.isJudgment,
|
|
236
|
+
})));
|
|
237
|
+
}
|
|
238
|
+
function getDatasetJudgmentField(dataset) {
|
|
239
|
+
const expectedField = dataset?.fields.find((field) => field.role === 'expected');
|
|
240
|
+
const labels = dataset?.categoryProfile.slices.map((slice) => slice.label) ?? [];
|
|
241
|
+
return deriveJudgmentField({
|
|
242
|
+
expectedOutputFieldName: expectedField?.name ?? null,
|
|
243
|
+
categoryLabels: labels,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function getPromptVersionSyncKey({ promptId, versionId, body, promptLanguage, variables, outputFields, }) {
|
|
247
|
+
return `${promptId}:${versionId}:${body}:${promptLanguage}:${serializePromptVariables(variables)}:${serializeOutputFields(outputFields)}`;
|
|
248
|
+
}
|
|
249
|
+
function VersionLabelPill({ label, onRemove }) {
|
|
250
|
+
const { t } = useI18n();
|
|
251
|
+
const system = label.type === 'system';
|
|
252
|
+
const displayName = formatPromptVersionLabel(label, t);
|
|
253
|
+
return (_jsxs("span", { className: cn('inline-flex items-center gap-1 rounded border px-1.5 py-0.5 font-mono text-[10px] font-medium', system
|
|
254
|
+
? 'border-[var(--status-running-bd)] bg-[var(--status-running-bg)] text-[var(--status-running-fg)]'
|
|
255
|
+
: 'border-border bg-muted/50 text-muted-foreground'), children: [displayName, onRemove && (_jsx("button", { type: "button", className: "inline-flex size-3.5 items-center justify-center rounded-sm hover:bg-background/70", "aria-label": t('prompts.labels.remove').replace('{label}', displayName), onClick: (event) => {
|
|
256
|
+
event.stopPropagation();
|
|
257
|
+
onRemove();
|
|
258
|
+
}, children: _jsx(X, { className: "size-2.5" }) }))] }));
|
|
259
|
+
}
|
|
260
|
+
function formatPromptVersionLabel(label, t) {
|
|
261
|
+
const key = PROMPT_VERSION_SYSTEM_LABEL_KEYS[label.name];
|
|
262
|
+
return key ? t(key) : label.name;
|
|
263
|
+
}
|
|
264
|
+
function VersionSidebar({ prompt, activeVersionId, onActivateVersion, onRequestBlankVersion, onRequestCopy, onRequestDelete, onUpdateLabel, isCopying, isDeleting, isUpdatingLabel, initialVersionId, }) {
|
|
265
|
+
const { t } = useI18n();
|
|
266
|
+
const { formatDateTime } = useDateTimeFormatter();
|
|
267
|
+
const [selectedVersions, setSelectedVersions] = useState([]);
|
|
268
|
+
const [initialVersionAppliedId, setInitialVersionAppliedId] = useState(null);
|
|
269
|
+
const [diffDialogOpen, setDiffDialogOpen] = useState(false);
|
|
270
|
+
const [versionSearch, setVersionSearch] = useState('');
|
|
271
|
+
if (initialVersionId && initialVersionAppliedId !== initialVersionId) {
|
|
272
|
+
const targetVersion = prompt.versions.find((v) => v.id === initialVersionId);
|
|
273
|
+
if (targetVersion) {
|
|
274
|
+
setInitialVersionAppliedId(initialVersionId);
|
|
275
|
+
setSelectedVersions([targetVersion.version]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const filteredVersions = useMemo(() => {
|
|
279
|
+
const query = versionSearch.trim().toLowerCase();
|
|
280
|
+
if (!query)
|
|
281
|
+
return prompt.versions;
|
|
282
|
+
return prompt.versions.filter((version) => {
|
|
283
|
+
const haystack = [
|
|
284
|
+
getVersionLabel(version),
|
|
285
|
+
version.status,
|
|
286
|
+
version.author,
|
|
287
|
+
version.createdAt,
|
|
288
|
+
...version.labels.map((label) => label.name),
|
|
289
|
+
...version.labels.map((label) => formatPromptVersionLabel(label, t)),
|
|
290
|
+
]
|
|
291
|
+
.join(' ')
|
|
292
|
+
.toLowerCase();
|
|
293
|
+
return haystack.includes(query);
|
|
294
|
+
});
|
|
295
|
+
}, [prompt.versions, t, versionSearch]);
|
|
296
|
+
const stopRowClick = useCallback((event) => {
|
|
297
|
+
event.stopPropagation();
|
|
298
|
+
}, []);
|
|
299
|
+
const compareVersions = useMemo(() => {
|
|
300
|
+
const selected = prompt.versions.filter((version) => selectedVersions.includes(version.version));
|
|
301
|
+
return selected.slice(0, 2);
|
|
302
|
+
}, [prompt.versions, selectedVersions]);
|
|
303
|
+
const fromVersion = compareVersions[1];
|
|
304
|
+
const toVersion = compareVersions[0];
|
|
305
|
+
const fromPromptPreview = useMemo(() => renderVersionPromptPreview(fromVersion), [fromVersion]);
|
|
306
|
+
const toPromptPreview = useMemo(() => renderVersionPromptPreview(toVersion), [toVersion]);
|
|
307
|
+
const toggleVersion = (version) => {
|
|
308
|
+
setSelectedVersions((current) => {
|
|
309
|
+
if (current.includes(version))
|
|
310
|
+
return current.filter((item) => item !== version);
|
|
311
|
+
return [version, ...current].slice(0, 2);
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
return (_jsxs("aside", { className: "flex min-h-[640px] flex-col border-b bg-background lg:min-h-[calc(100vh-240px)] lg:border-b-0 lg:border-r", "data-testid": "prompt-version-sidebar", children: [_jsxs("div", { className: "border-b p-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-[13px] font-semibold", children: t('prompts.detail.tab.versions') }), _jsx("div", { className: "mt-0.5 text-[11.5px] text-muted-foreground", children: t('prompts.detail.versionShowing')
|
|
315
|
+
.replace('{visible}', String(filteredVersions.length))
|
|
316
|
+
.replace('{total}', String(prompt.versions.length)) })] }), _jsxs(Button, { type: "button", size: "sm", className: "h-8 shrink-0", disabled: isCopying, onClick: onRequestBlankVersion, children: [_jsx(Plus, { className: "size-4" }), t('prompts.detail.createDraftVersion')] })] }), _jsxs("div", { className: "relative mt-3", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: versionSearch, onChange: (event) => setVersionSearch(event.target.value), placeholder: t('prompts.detail.versionSearchPlaceholder'), className: "h-9 pl-8 text-xs" })] })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-3", children: filteredVersions.length > 0 ? (_jsx("div", { className: "space-y-2", children: filteredVersions.map((version) => {
|
|
317
|
+
const selected = selectedVersions.includes(version.version);
|
|
318
|
+
const active = version.id === activeVersionId;
|
|
319
|
+
const frozen = version.frozen;
|
|
320
|
+
const editLabel = frozen ? t('prompts.detail.viewFrozenVersion') : t('prompts.detail.editVersion');
|
|
321
|
+
return (_jsx("div", { className: cn('cursor-pointer rounded-lg border bg-card p-3 transition-colors hover:bg-muted/35', active && 'border-primary bg-primary/5'), onClick: () => onActivateVersion(version.id), "data-testid": `prompt-version-row-${version.version}`, children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("input", { type: "checkbox", className: "mt-1 size-4 accent-primary", checked: selected, "aria-label": `${t('prompts.detail.compare')} ${getVersionLabel(version)}`, onClick: stopRowClick, onChange: () => toggleVersion(version.version) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: [_jsx("span", { className: "font-mono text-[13px] font-semibold", children: getVersionLabel(version) }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx(StatusBadge, { status: version.status, compact: true }), _jsx(PromptVariableModalityBadges, { variables: version.variables })] }), version.version === prompt.onlineVersion && (_jsx("span", { className: "rounded bg-[var(--status-running-bg)] px-1.5 py-0.5 font-mono text-[10px] text-[var(--status-running-fg)]", children: t('prompts.badge.online') }))] }), version.labels.length > 0 && (_jsx("div", { className: "mt-2 flex flex-wrap items-center gap-1", onClick: stopRowClick, children: version.labels.map((label) => (_jsx(VersionLabelPill, { label: label, onRemove: label.name === 'latest' || isUpdatingLabel
|
|
322
|
+
? undefined
|
|
323
|
+
: () => onUpdateLabel(label.name, null) }, label.name))) })), _jsx("div", { className: "mt-2 truncate text-[11.5px] text-muted-foreground", children: formatDateTime(version.createdAt) })] }), _jsx("div", { onClick: stopRowClick, children: _jsx(TableActionRow, { maxInline: 0, moreLabel: t('prompts.action.moreActions'), actions: [
|
|
324
|
+
{
|
|
325
|
+
key: 'edit',
|
|
326
|
+
label: editLabel,
|
|
327
|
+
icon: frozen ? Eye : Pencil,
|
|
328
|
+
onClick: () => onActivateVersion(version.id),
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
key: 'copy',
|
|
332
|
+
label: t('prompts.detail.copyVersion'),
|
|
333
|
+
icon: Copy,
|
|
334
|
+
disabled: isCopying,
|
|
335
|
+
onClick: () => onRequestCopy(version.id),
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
key: 'delete',
|
|
339
|
+
label: t('prompts.detail.deleteDraftVersion'),
|
|
340
|
+
icon: Trash2,
|
|
341
|
+
destructive: true,
|
|
342
|
+
disabled: isDeleting,
|
|
343
|
+
onClick: () => onRequestDelete(version.id),
|
|
344
|
+
},
|
|
345
|
+
] }) })] }) }, version.id));
|
|
346
|
+
}) })) : (_jsx("div", { className: "rounded-lg border border-dashed px-3 py-8 text-center text-[12px] text-muted-foreground", children: t('prompts.detail.versionNoMatch') })) }), _jsx("div", { className: "border-t p-3", children: _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-[12.5px] text-muted-foreground", children: t('prompts.detail.selectedForDiff').replace('{count}', String(selectedVersions.length)) }), compareVersions.map((version) => (_jsx("span", { className: "rounded border bg-background px-1.5 py-0.5 font-mono text-xs", children: getVersionLabel(version) }, version.version))), _jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 px-2 text-xs", onClick: () => setSelectedVersions([]), children: t('prompts.detail.clearDiff') }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-8", disabled: !fromVersion || !toVersion, onClick: () => setDiffDialogOpen(true), children: [_jsx(GitCompareArrows, { className: "size-3.5" }), t('prompts.detail.openDiff')] })] }) }), _jsx(Dialog, { open: diffDialogOpen, onOpenChange: setDiffDialogOpen, children: _jsxs(DialogContent, { className: "max-h-[86vh] max-w-[1100px] overflow-hidden p-0", children: [_jsxs(DialogHeader, { className: "border-b px-6 pt-6", children: [_jsxs(DialogTitle, { className: "flex items-center gap-2", children: [_jsx(GitCompareArrows, { className: "size-4" }), t('prompts.detail.diffRenderedPrompt')] }), _jsx(DialogDescription, { children: fromVersion && toVersion
|
|
347
|
+
? `${getVersionLabel(fromVersion)} -> ${getVersionLabel(toVersion)}`
|
|
348
|
+
: t('prompts.detail.diffNeedsTwo') })] }), _jsx("div", { className: "max-h-[68vh] space-y-3 overflow-auto px-6 pb-6", children: fromVersion && toVersion && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-[11px] text-muted-foreground", children: [_jsxs("span", { className: "inline-flex items-center gap-1 rounded border bg-destructive/10 px-1.5 py-0.5 text-destructive", children: ["- ", t('prompts.detail.diffRemoved')] }), _jsxs("span", { className: "inline-flex items-center gap-1 rounded border border-[var(--status-running-bd)] bg-[var(--status-running-bg)] px-1.5 py-0.5 text-[var(--status-running-fg)]", children: ["+ ", t('prompts.detail.diffAdded')] })] }), _jsx(PromptDiffSplitView, { fromLabel: getVersionLabel(fromVersion), toLabel: getVersionLabel(toVersion), fromText: fromPromptPreview, toText: toPromptPreview })] })) })] }) })] }));
|
|
349
|
+
}
|
|
350
|
+
function ActiveVersionLabels({ activeVersion, onUpdateLabel, isUpdatingLabel, }) {
|
|
351
|
+
const { t } = useI18n();
|
|
352
|
+
const [editing, setEditing] = useState(false);
|
|
353
|
+
const [labelInput, setLabelInput] = useState('');
|
|
354
|
+
const cleanLabelInput = labelInput.trim();
|
|
355
|
+
const labelValidation = cleanLabelInput ? promptVersionLabelNameSchema.safeParse(cleanLabelInput) : null;
|
|
356
|
+
const labelError = labelValidation && !labelValidation.success ? t('prompts.labels.invalidFormat') : null;
|
|
357
|
+
const submitLabel = async () => {
|
|
358
|
+
if (!activeVersion || !cleanLabelInput || labelError || isUpdatingLabel)
|
|
359
|
+
return;
|
|
360
|
+
await onUpdateLabel(cleanLabelInput, activeVersion.id);
|
|
361
|
+
setLabelInput('');
|
|
362
|
+
setEditing(false);
|
|
363
|
+
};
|
|
364
|
+
return (_jsxs("div", { className: "mt-2 flex min-w-0 flex-wrap items-center gap-1.5", children: [activeVersion && activeVersion.labels.length > 0 ? (activeVersion.labels.map((label) => (_jsx(VersionLabelPill, { label: label, onRemove: label.name === 'latest' || isUpdatingLabel ? undefined : () => onUpdateLabel(label.name, null) }, label.name)))) : (_jsx("span", { className: "text-xs text-muted-foreground", children: t('prompts.labels.empty') })), !editing && (_jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-7 px-2 text-xs", disabled: !activeVersion || isUpdatingLabel, onClick: () => setEditing(true), children: [_jsx(Plus, { className: "size-3.5" }), t('prompts.detail.addLabel')] })), editing && (_jsxs("form", { className: "flex flex-wrap items-center gap-1.5", onSubmit: (event) => {
|
|
365
|
+
event.preventDefault();
|
|
366
|
+
void submitLabel();
|
|
367
|
+
}, children: [_jsxs("div", { className: "flex min-w-[190px] flex-col gap-1", children: [_jsx(Input, { value: labelInput, onChange: (event) => setLabelInput(event.target.value), placeholder: t('prompts.detail.labelPlaceholder'), className: cn('h-7 w-[190px] font-mono text-xs', labelError && 'border-destructive'), "aria-invalid": Boolean(labelError), "data-testid": "prompt-version-label-input" }), labelError && _jsx("span", { className: "max-w-[260px] text-[11px] text-destructive", children: labelError })] }), _jsxs(Button, { type: "submit", variant: "outline", size: "sm", className: "h-7 px-2 text-xs", disabled: !activeVersion || !cleanLabelInput || Boolean(labelError) || isUpdatingLabel, children: [_jsx(Tags, { className: "size-3.5" }), t('prompts.detail.applyLabel')] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "size-7", onClick: () => {
|
|
368
|
+
setLabelInput('');
|
|
369
|
+
setEditing(false);
|
|
370
|
+
}, "aria-label": t('common.cancel'), children: _jsx(X, { className: "size-3.5" }) })] }))] }));
|
|
371
|
+
}
|
|
372
|
+
function formatMetricNumber(value) {
|
|
373
|
+
return value.toLocaleString();
|
|
374
|
+
}
|
|
375
|
+
function formatMetricMs(value) {
|
|
376
|
+
if (value === null)
|
|
377
|
+
return '-';
|
|
378
|
+
return `${Math.round(value).toLocaleString()} ms`;
|
|
379
|
+
}
|
|
380
|
+
function formatMetricCost(value) {
|
|
381
|
+
return `$${value.toFixed(4)}`;
|
|
382
|
+
}
|
|
383
|
+
function formatMetricPercent(value) {
|
|
384
|
+
if (value === null)
|
|
385
|
+
return '-';
|
|
386
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
387
|
+
}
|
|
388
|
+
function MetricSummaryCard({ label, value, sub }) {
|
|
389
|
+
return (_jsxs("div", { className: "rounded-lg border bg-card px-4 py-3", children: [_jsx("div", { className: "text-[11.5px] font-medium text-muted-foreground", children: label }), _jsx("div", { className: "mt-2 font-mono text-xl font-semibold leading-none", children: value }), sub && _jsx("div", { className: "mt-2 text-[11px] text-muted-foreground", children: sub })] }));
|
|
390
|
+
}
|
|
391
|
+
function PromptMetricsTab({ projectId, promptId }) {
|
|
392
|
+
const { t } = useI18n();
|
|
393
|
+
const { formatDateTime } = useDateTimeFormatter();
|
|
394
|
+
const metricsQuery = usePromptMetrics(projectId, promptId);
|
|
395
|
+
const metrics = metricsQuery.data;
|
|
396
|
+
const metricsLoading = useDelayedLoading(metricsQuery.isLoading);
|
|
397
|
+
if (metricsLoading) {
|
|
398
|
+
return (_jsxs("div", { className: "relative min-h-[420px]", "data-testid": "prompt-metrics-tab", "aria-busy": "true", children: [_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "grid grid-cols-2 gap-4 lg:grid-cols-4", children: Array.from({ length: 4 }).map((_, index) => (_jsx(Skeleton, { className: "h-24 rounded-lg" }, index))) }), _jsx(Skeleton, { className: "h-64 rounded-lg" })] }), _jsx(PlatformLoaderOverlay, {})] }));
|
|
399
|
+
}
|
|
400
|
+
if (!metrics) {
|
|
401
|
+
return (_jsx("div", { className: "rounded-lg border bg-card p-8 text-center text-sm text-muted-foreground", children: t('prompts.metrics.empty') }));
|
|
402
|
+
}
|
|
403
|
+
return (_jsxs("div", { className: "space-y-4", "data-testid": "prompt-metrics-tab", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-semibold", children: [_jsx(BarChart3, { className: "size-4 text-muted-foreground" }), t('prompts.detail.tab.metrics')] }), _jsxs("div", { className: "grid gap-3 md:grid-cols-2 xl:grid-cols-4", children: [_jsx(MetricSummaryCard, { label: t('prompts.metrics.totalRuns'), value: formatMetricNumber(metrics.totals.runCount), sub: `${t('prompts.metrics.success')} ${formatMetricNumber(metrics.totals.successCount)} · ${t('prompts.metrics.errors')} ${formatMetricNumber(metrics.totals.errorCount)}` }), _jsx(MetricSummaryCard, { label: t('prompts.metrics.totalTokens'), value: formatMetricNumber(metrics.totals.totalInputTokens + metrics.totals.totalOutputTokens), sub: `${t('prompts.metrics.inputTokens')} ${formatMetricNumber(metrics.totals.totalInputTokens)} · ${t('prompts.metrics.outputTokens')} ${formatMetricNumber(metrics.totals.totalOutputTokens)}` }), _jsx(MetricSummaryCard, { label: t('prompts.metrics.totalCost'), value: formatMetricCost(metrics.totals.totalCostEstimate) }), _jsx(MetricSummaryCard, { label: t('prompts.metrics.versionsWithRuns'), value: formatMetricNumber(metrics.versions.filter((version) => version.runCount > 0).length), sub: `${formatMetricNumber(metrics.versions.length)} ${t('prompts.detail.versionTotalSuffix')}` })] }), _jsx("section", { className: "overflow-hidden rounded-lg border bg-card", "aria-label": t('prompts.detail.tab.metrics'), children: _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full min-w-[1040px] text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b bg-muted/60 text-left text-xs font-medium text-muted-foreground", children: [_jsx("th", { className: "px-3 py-3", children: t('prompts.detail.version') }), _jsx("th", { className: "w-44 px-3 py-3", children: t('prompts.detail.labels') }), _jsx("th", { className: "w-28 px-3 py-3", children: t('prompts.table.status') }), _jsx("th", { className: "w-24 px-3 py-3 text-right", children: t('prompts.metrics.runs') }), _jsx("th", { className: "w-24 px-3 py-3 text-right", children: t('prompts.metrics.accuracy') }), _jsx("th", { className: "w-32 px-3 py-3 text-right", children: t('prompts.metrics.medianLatency') }), _jsx("th", { className: "w-32 px-3 py-3 text-right", children: t('prompts.metrics.medianTokens') }), _jsx("th", { className: "w-28 px-3 py-3 text-right", children: t('prompts.metrics.cost') }), _jsx("th", { className: "w-36 px-3 py-3", children: t('prompts.metrics.lastRun') })] }) }), _jsx("tbody", { children: metrics.versions.map((version) => (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsxs("td", { className: "px-3 py-3 font-mono font-semibold", children: ["v", version.versionNumber] }), _jsx("td", { className: "px-3 py-3", children: _jsx("div", { className: "flex flex-wrap items-center gap-1", children: version.labels.length > 0 ? (version.labels.map((label) => _jsx(VersionLabelPill, { label: label }, label.name))) : (_jsx("span", { className: "text-xs text-muted-foreground", children: "-" })) }) }), _jsx("td", { className: "px-3 py-3", children: _jsx(StatusBadge, { status: version.status, compact: true }) }), _jsx("td", { className: "px-3 py-3 text-right font-mono", children: formatMetricNumber(version.runCount) }), _jsx("td", { className: "px-3 py-3 text-right font-mono", children: formatMetricPercent(version.accuracy) }), _jsx("td", { className: "px-3 py-3 text-right font-mono", children: formatMetricMs(version.medianLatencyMs) }), _jsx("td", { className: "px-3 py-3 text-right font-mono", children: version.medianInputTokens === null && version.medianOutputTokens === null
|
|
404
|
+
? '-'
|
|
405
|
+
: `${Math.round(version.medianInputTokens ?? 0)} / ${Math.round(version.medianOutputTokens ?? 0)}` }), _jsx("td", { className: "px-3 py-3 text-right font-mono", children: formatMetricCost(version.totalCostEstimate) }), _jsx("td", { className: "px-3 py-3 font-mono text-[11.5px] text-muted-foreground", children: formatDateTime(version.lastRunAt) })] }, version.promptVersionId))) })] }) }) })] }));
|
|
406
|
+
}
|
|
407
|
+
export function PromptDetailPage({ projectId, promptId }) {
|
|
408
|
+
const { t } = useI18n();
|
|
409
|
+
const { formatDateTime } = useDateTimeFormatter();
|
|
410
|
+
const router = useRouter();
|
|
411
|
+
const pathname = usePathname();
|
|
412
|
+
const searchParams = useSearchParams();
|
|
413
|
+
const initialVersionParam = searchParams?.get('version') ?? null;
|
|
414
|
+
const urlDetailTab = resolveDetailTab(searchParams?.get('tab') ?? null);
|
|
415
|
+
const urlMainTab = resolvePromptMainTab(searchParams?.get('panel') ?? null);
|
|
416
|
+
const promptQuery = usePrompt(projectId, promptId);
|
|
417
|
+
const datasetsQuery = useDatasets(projectId);
|
|
418
|
+
const updateDraftVersionMutation = useUpdatePromptDraftVersion(projectId);
|
|
419
|
+
const updatePromptMutation = useUpdatePrompt(projectId);
|
|
420
|
+
const createDraftVersionMutation = useCreatePromptDraftVersion(projectId);
|
|
421
|
+
const deleteDraftVersionMutation = useDeletePromptDraftVersion(projectId);
|
|
422
|
+
const updateVersionLabelMutation = useUpdatePromptVersionLabel(projectId);
|
|
423
|
+
const activeTab = urlDetailTab;
|
|
424
|
+
const activeMainTab = urlMainTab;
|
|
425
|
+
const requestedVersionId = activeTab === 'versions' ? initialVersionParam : null;
|
|
426
|
+
const [body, setBody] = useState('');
|
|
427
|
+
const [savedBody, setSavedBody] = useState('');
|
|
428
|
+
const [promptLanguage, setPromptLanguage] = useState('zh-CN');
|
|
429
|
+
const [savedPromptLanguage, setSavedPromptLanguage] = useState('zh-CN');
|
|
430
|
+
const [variables, setVariables] = useState([]);
|
|
431
|
+
const [savedVariables, setSavedVariables] = useState([]);
|
|
432
|
+
const [customOutputFields, setCustomOutputFields] = useState([]);
|
|
433
|
+
const [savedCustomOutputFields, setSavedCustomOutputFields] = useState([]);
|
|
434
|
+
const [selectedDatasetId, setSelectedDatasetId] = useState(null);
|
|
435
|
+
const [activeSyncKey, setActiveSyncKey] = useState('');
|
|
436
|
+
const [pendingNavigation, setPendingNavigation] = useState(null);
|
|
437
|
+
const [unsavedDialogOpen, setUnsavedDialogOpen] = useState(false);
|
|
438
|
+
const [saveError, setSaveError] = useState(null);
|
|
439
|
+
const [deleteTargetVersionId, setDeleteTargetVersionId] = useState(null);
|
|
440
|
+
const [actionMessage, setActionMessage] = useState(null);
|
|
441
|
+
const allowNavigationRef = useRef(false);
|
|
442
|
+
const popGuardArmedRef = useRef(false);
|
|
443
|
+
const prompt = useMemo(() => (promptQuery.data ? toProjectPrompt(promptQuery.data) : null), [promptQuery.data]);
|
|
444
|
+
const deleteImpactQuery = usePromptVersionDeleteImpact(projectId, prompt?.id ?? '', deleteTargetVersionId ?? '');
|
|
445
|
+
const datasets = useMemo(() => (datasetsQuery.data?.data ?? []).map((dataset) => toProjectDataset(dataset)), [datasetsQuery.data?.data]);
|
|
446
|
+
const activeDatasets = useMemo(() => datasets.filter((dataset) => dataset.status === 'active'), [datasets]);
|
|
447
|
+
const editableVersions = useMemo(() => (prompt?.versions ?? []).filter((version) => !version.frozen), [prompt?.versions]);
|
|
448
|
+
const activeVersion = useMemo(() => {
|
|
449
|
+
if (!prompt)
|
|
450
|
+
return null;
|
|
451
|
+
if (requestedVersionId) {
|
|
452
|
+
const match = prompt.versions.find((version) => version.id === requestedVersionId);
|
|
453
|
+
if (match)
|
|
454
|
+
return match;
|
|
455
|
+
}
|
|
456
|
+
if (editableVersions.length > 0)
|
|
457
|
+
return editableVersions[0] ?? null;
|
|
458
|
+
return prompt.versions[0] ?? null;
|
|
459
|
+
}, [editableVersions, prompt, requestedVersionId]);
|
|
460
|
+
const isReadOnly = activeVersion ? activeVersion.frozen : false;
|
|
461
|
+
const activeVersionSyncKey = prompt && activeVersion
|
|
462
|
+
? getPromptVersionSyncKey({
|
|
463
|
+
promptId: prompt.id,
|
|
464
|
+
versionId: activeVersion.id,
|
|
465
|
+
body: activeVersion.body ?? '',
|
|
466
|
+
promptLanguage: activeVersion.promptLanguage,
|
|
467
|
+
variables: activeVersion.variables ?? [],
|
|
468
|
+
outputFields: activeVersion.outputFields ?? [],
|
|
469
|
+
})
|
|
470
|
+
: '';
|
|
471
|
+
if (prompt && activeVersion && activeSyncKey !== activeVersionSyncKey) {
|
|
472
|
+
const sourceBody = activeVersion.body ?? prompt.body;
|
|
473
|
+
const sourcePromptLanguage = activeVersion.promptLanguage;
|
|
474
|
+
const sourceVariables = activeVersion.variables ?? prompt.variables;
|
|
475
|
+
const sourceFields = (activeVersion.outputFields ?? prompt.outputFields).filter((field) => !field.isJudgment);
|
|
476
|
+
setActiveSyncKey(activeVersionSyncKey);
|
|
477
|
+
setBody(sourceBody);
|
|
478
|
+
setSavedBody(sourceBody);
|
|
479
|
+
setPromptLanguage(sourcePromptLanguage);
|
|
480
|
+
setSavedPromptLanguage(sourcePromptLanguage);
|
|
481
|
+
setVariables(sourceVariables);
|
|
482
|
+
setSavedVariables(sourceVariables);
|
|
483
|
+
setCustomOutputFields(sourceFields);
|
|
484
|
+
setSavedCustomOutputFields(sourceFields);
|
|
485
|
+
setSelectedDatasetId(prompt.defaultDatasetId);
|
|
486
|
+
setSaveError(null);
|
|
487
|
+
}
|
|
488
|
+
const selectedDataset = useMemo(() => (selectedDatasetId ? (datasets.find((dataset) => dataset.id === selectedDatasetId) ?? null) : null), [datasets, selectedDatasetId]);
|
|
489
|
+
const derivedJudgmentField = useMemo(() => getDatasetJudgmentField(selectedDataset), [selectedDataset]);
|
|
490
|
+
const outputFields = useMemo(() => upsertJudgmentField(customOutputFields, derivedJudgmentField), [customOutputFields, derivedJudgmentField]);
|
|
491
|
+
const handleOutputFieldsChange = useCallback((next) => {
|
|
492
|
+
setCustomOutputFields(next.filter((field) => !field.isJudgment));
|
|
493
|
+
}, []);
|
|
494
|
+
const variablesDirty = serializePromptVariables(variables) !== serializePromptVariables(savedVariables);
|
|
495
|
+
const outputFieldsDirty = serializeOutputFields(customOutputFields) !== serializeOutputFields(savedCustomOutputFields);
|
|
496
|
+
const dirty = !isReadOnly &&
|
|
497
|
+
(body !== savedBody || promptLanguage !== savedPromptLanguage || variablesDirty || outputFieldsDirty);
|
|
498
|
+
const requestUnsavedNavigation = useCallback((navigation) => {
|
|
499
|
+
setPendingNavigation(navigation);
|
|
500
|
+
setUnsavedDialogOpen(true);
|
|
501
|
+
}, []);
|
|
502
|
+
const replaceDetailUrl = useCallback(({ nextTab = activeTab, nextMainTab = activeMainTab, versionId, }) => {
|
|
503
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
504
|
+
if (nextTab === 'versions')
|
|
505
|
+
params.delete('tab');
|
|
506
|
+
else
|
|
507
|
+
params.set('tab', nextTab);
|
|
508
|
+
if (nextTab === 'versions' && nextMainTab === 'config')
|
|
509
|
+
params.set('panel', nextMainTab);
|
|
510
|
+
else
|
|
511
|
+
params.delete('panel');
|
|
512
|
+
if (versionId !== undefined) {
|
|
513
|
+
if (versionId)
|
|
514
|
+
params.set('version', versionId);
|
|
515
|
+
else
|
|
516
|
+
params.delete('version');
|
|
517
|
+
}
|
|
518
|
+
if (nextTab !== 'versions')
|
|
519
|
+
params.delete('version');
|
|
520
|
+
const query = params.toString();
|
|
521
|
+
router.replace(query ? `${pathname}?${query}` : pathname, { scroll: false });
|
|
522
|
+
}, [activeMainTab, activeTab, pathname, router, searchParams]);
|
|
523
|
+
const selectDetailTab = useCallback((tab) => {
|
|
524
|
+
replaceDetailUrl({ nextTab: tab });
|
|
525
|
+
}, [replaceDetailUrl]);
|
|
526
|
+
const selectPromptMainTab = useCallback((tab) => {
|
|
527
|
+
replaceDetailUrl({ nextTab: 'versions', nextMainTab: tab });
|
|
528
|
+
}, [replaceDetailUrl]);
|
|
529
|
+
useEffect(() => {
|
|
530
|
+
if (!dirty)
|
|
531
|
+
return undefined;
|
|
532
|
+
const onBeforeUnload = (event) => {
|
|
533
|
+
if (allowNavigationRef.current)
|
|
534
|
+
return;
|
|
535
|
+
event.preventDefault();
|
|
536
|
+
event.returnValue = '';
|
|
537
|
+
};
|
|
538
|
+
window.addEventListener('beforeunload', onBeforeUnload);
|
|
539
|
+
return () => window.removeEventListener('beforeunload', onBeforeUnload);
|
|
540
|
+
}, [dirty]);
|
|
541
|
+
useEffect(() => {
|
|
542
|
+
if (!dirty || popGuardArmedRef.current)
|
|
543
|
+
return;
|
|
544
|
+
const currentState = typeof window.history.state === 'object' && window.history.state ? window.history.state : {};
|
|
545
|
+
window.history.pushState({ ...currentState, [UNSAVED_HISTORY_GUARD_KEY]: true }, '', window.location.href);
|
|
546
|
+
popGuardArmedRef.current = true;
|
|
547
|
+
}, [dirty]);
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
const onPopState = () => {
|
|
550
|
+
if (allowNavigationRef.current || !dirty) {
|
|
551
|
+
allowNavigationRef.current = false;
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const currentState = typeof window.history.state === 'object' && window.history.state ? window.history.state : {};
|
|
555
|
+
window.history.pushState({ ...currentState, [UNSAVED_HISTORY_GUARD_KEY]: true }, '', window.location.href);
|
|
556
|
+
popGuardArmedRef.current = true;
|
|
557
|
+
requestUnsavedNavigation({ kind: 'back' });
|
|
558
|
+
};
|
|
559
|
+
window.addEventListener('popstate', onPopState);
|
|
560
|
+
return () => window.removeEventListener('popstate', onPopState);
|
|
561
|
+
}, [dirty, requestUnsavedNavigation]);
|
|
562
|
+
useEffect(() => {
|
|
563
|
+
if (!dirty)
|
|
564
|
+
return undefined;
|
|
565
|
+
const onDocumentClick = (event) => {
|
|
566
|
+
if (event.defaultPrevented ||
|
|
567
|
+
event.button !== 0 ||
|
|
568
|
+
event.metaKey ||
|
|
569
|
+
event.ctrlKey ||
|
|
570
|
+
event.shiftKey ||
|
|
571
|
+
event.altKey) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const target = event.target instanceof Element ? event.target.closest('a[href]') : null;
|
|
575
|
+
if (!(target instanceof HTMLAnchorElement))
|
|
576
|
+
return;
|
|
577
|
+
if (target.hasAttribute('download'))
|
|
578
|
+
return;
|
|
579
|
+
if (target.target && target.target !== '_self')
|
|
580
|
+
return;
|
|
581
|
+
const rawHref = target.getAttribute('href');
|
|
582
|
+
if (!rawHref || rawHref.startsWith('#'))
|
|
583
|
+
return;
|
|
584
|
+
const url = new URL(target.href, window.location.href);
|
|
585
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
586
|
+
return;
|
|
587
|
+
const currentPath = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
588
|
+
const nextPath = `${url.pathname}${url.search}${url.hash}`;
|
|
589
|
+
if (url.origin === window.location.origin && nextPath === currentPath)
|
|
590
|
+
return;
|
|
591
|
+
event.preventDefault();
|
|
592
|
+
event.stopPropagation();
|
|
593
|
+
requestUnsavedNavigation({
|
|
594
|
+
kind: 'href',
|
|
595
|
+
href: url.origin === window.location.origin ? nextPath : url.toString(),
|
|
596
|
+
external: url.origin !== window.location.origin,
|
|
597
|
+
});
|
|
598
|
+
};
|
|
599
|
+
document.addEventListener('click', onDocumentClick, true);
|
|
600
|
+
return () => document.removeEventListener('click', onDocumentClick, true);
|
|
601
|
+
}, [dirty, requestUnsavedNavigation]);
|
|
602
|
+
useEffect(() => {
|
|
603
|
+
if (!actionMessage?.autoDismiss)
|
|
604
|
+
return undefined;
|
|
605
|
+
const timer = window.setTimeout(() => {
|
|
606
|
+
setActionMessage((current) => (current === actionMessage ? null : current));
|
|
607
|
+
}, LABEL_ACTION_MESSAGE_DISMISS_MS);
|
|
608
|
+
return () => window.clearTimeout(timer);
|
|
609
|
+
}, [actionMessage]);
|
|
610
|
+
const saveDraft = useCallback(async () => {
|
|
611
|
+
if (!prompt || !activeVersion || isReadOnly) {
|
|
612
|
+
setSaveError(t('prompts.detail.noEditableDraft'));
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
try {
|
|
616
|
+
const nextPrompt = await updateDraftVersionMutation.mutateAsync({
|
|
617
|
+
promptId: prompt.id,
|
|
618
|
+
versionId: activeVersion.id,
|
|
619
|
+
body: {
|
|
620
|
+
body,
|
|
621
|
+
promptLanguage,
|
|
622
|
+
variables: variables.map(({ selected: _selected, ...variable }) => variable),
|
|
623
|
+
outputSchema: {
|
|
624
|
+
fields: outputFields.map((field) => ({
|
|
625
|
+
key: field.key,
|
|
626
|
+
value: field.value,
|
|
627
|
+
isJudgment: field.isJudgment,
|
|
628
|
+
})),
|
|
629
|
+
},
|
|
630
|
+
judgmentRules: { rules: activeVersion.judgmentRules },
|
|
631
|
+
changeReason: activeVersion.changeReason || null,
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
const nextProjectPrompt = toProjectPrompt(nextPrompt);
|
|
635
|
+
const nextActive = nextProjectPrompt.versions.find((version) => version.id === activeVersion.id) ?? null;
|
|
636
|
+
const nextBody = nextActive?.body ?? nextProjectPrompt.body ?? body;
|
|
637
|
+
const nextPromptLanguage = nextActive?.promptLanguage ?? promptLanguage;
|
|
638
|
+
const nextVariables = nextActive?.variables ?? nextProjectPrompt.variables;
|
|
639
|
+
const nextOutputFields = nextActive?.outputFields ?? nextProjectPrompt.outputFields;
|
|
640
|
+
const nextCustomFields = nextOutputFields.filter((field) => !field.isJudgment);
|
|
641
|
+
setBody(nextBody);
|
|
642
|
+
setSavedBody(nextBody);
|
|
643
|
+
setPromptLanguage(nextPromptLanguage);
|
|
644
|
+
setSavedPromptLanguage(nextPromptLanguage);
|
|
645
|
+
setVariables(nextVariables);
|
|
646
|
+
setSavedVariables(nextVariables);
|
|
647
|
+
setCustomOutputFields(nextCustomFields);
|
|
648
|
+
setSavedCustomOutputFields(nextCustomFields);
|
|
649
|
+
setActiveSyncKey(getPromptVersionSyncKey({
|
|
650
|
+
promptId: nextProjectPrompt.id,
|
|
651
|
+
versionId: activeVersion.id,
|
|
652
|
+
body: nextBody,
|
|
653
|
+
promptLanguage: nextPromptLanguage,
|
|
654
|
+
variables: nextVariables,
|
|
655
|
+
outputFields: nextOutputFields,
|
|
656
|
+
}));
|
|
657
|
+
setSaveError(null);
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
catch (error) {
|
|
661
|
+
const message = error?.response?.data?.message ??
|
|
662
|
+
error?.message ??
|
|
663
|
+
t('prompts.detail.saveFailed');
|
|
664
|
+
setSaveError(String(message));
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
}, [activeVersion, body, isReadOnly, outputFields, prompt, promptLanguage, t, updateDraftVersionMutation, variables]);
|
|
668
|
+
const autoSaveConfigVersion = useCallback(async ({ nextPromptLanguage = savedPromptLanguage, nextVariables = savedVariables, nextDataset = selectedDataset, }) => {
|
|
669
|
+
if (!prompt || !activeVersion || isReadOnly)
|
|
670
|
+
return false;
|
|
671
|
+
const nextOutputFields = upsertJudgmentField(savedCustomOutputFields, getDatasetJudgmentField(nextDataset));
|
|
672
|
+
try {
|
|
673
|
+
const nextPrompt = await updateDraftVersionMutation.mutateAsync({
|
|
674
|
+
promptId: prompt.id,
|
|
675
|
+
versionId: activeVersion.id,
|
|
676
|
+
body: {
|
|
677
|
+
body: savedBody,
|
|
678
|
+
promptLanguage: nextPromptLanguage,
|
|
679
|
+
variables: nextVariables.map(({ selected: _selected, ...variable }) => variable),
|
|
680
|
+
outputSchema: {
|
|
681
|
+
fields: nextOutputFields.map((field) => ({
|
|
682
|
+
key: field.key,
|
|
683
|
+
value: field.value,
|
|
684
|
+
isJudgment: field.isJudgment,
|
|
685
|
+
})),
|
|
686
|
+
},
|
|
687
|
+
judgmentRules: { rules: activeVersion.judgmentRules },
|
|
688
|
+
changeReason: activeVersion.changeReason || null,
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
const nextProjectPrompt = toProjectPrompt(nextPrompt);
|
|
692
|
+
const nextActive = nextProjectPrompt.versions.find((version) => version.id === activeVersion.id) ?? null;
|
|
693
|
+
const persistedBody = nextActive?.body ?? savedBody;
|
|
694
|
+
const persistedPromptLanguage = nextActive?.promptLanguage ?? nextPromptLanguage;
|
|
695
|
+
const persistedVariables = nextActive?.variables ?? nextVariables;
|
|
696
|
+
const persistedOutputFields = nextActive?.outputFields ?? nextOutputFields;
|
|
697
|
+
const persistedCustomFields = persistedOutputFields.filter((field) => !field.isJudgment);
|
|
698
|
+
setSavedBody(persistedBody);
|
|
699
|
+
setPromptLanguage(persistedPromptLanguage);
|
|
700
|
+
setSavedPromptLanguage(persistedPromptLanguage);
|
|
701
|
+
setVariables(persistedVariables);
|
|
702
|
+
setSavedVariables(persistedVariables);
|
|
703
|
+
setSavedCustomOutputFields(persistedCustomFields);
|
|
704
|
+
setActiveSyncKey(getPromptVersionSyncKey({
|
|
705
|
+
promptId: nextProjectPrompt.id,
|
|
706
|
+
versionId: activeVersion.id,
|
|
707
|
+
body: persistedBody,
|
|
708
|
+
promptLanguage: persistedPromptLanguage,
|
|
709
|
+
variables: persistedVariables,
|
|
710
|
+
outputFields: persistedOutputFields,
|
|
711
|
+
}));
|
|
712
|
+
setSaveError(null);
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
const message = error?.response?.data?.message ??
|
|
717
|
+
error?.message ??
|
|
718
|
+
t('prompts.detail.saveFailed');
|
|
719
|
+
setSaveError(String(message));
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}, [
|
|
723
|
+
activeVersion,
|
|
724
|
+
isReadOnly,
|
|
725
|
+
prompt,
|
|
726
|
+
savedBody,
|
|
727
|
+
savedCustomOutputFields,
|
|
728
|
+
savedPromptLanguage,
|
|
729
|
+
savedVariables,
|
|
730
|
+
selectedDataset,
|
|
731
|
+
t,
|
|
732
|
+
updateDraftVersionMutation,
|
|
733
|
+
]);
|
|
734
|
+
const handlePromptLanguageChange = useCallback((nextLanguage) => {
|
|
735
|
+
if (nextLanguage === promptLanguage)
|
|
736
|
+
return;
|
|
737
|
+
const previousLanguage = promptLanguage;
|
|
738
|
+
setPromptLanguage(nextLanguage);
|
|
739
|
+
void autoSaveConfigVersion({ nextPromptLanguage: nextLanguage }).then((saved) => {
|
|
740
|
+
if (!saved)
|
|
741
|
+
setPromptLanguage(previousLanguage);
|
|
742
|
+
});
|
|
743
|
+
}, [autoSaveConfigVersion, promptLanguage]);
|
|
744
|
+
const selectDataset = useCallback((datasetId) => {
|
|
745
|
+
const nextDataset = datasets.find((dataset) => dataset.id === datasetId);
|
|
746
|
+
if (!nextDataset)
|
|
747
|
+
return;
|
|
748
|
+
const previousDatasetId = selectedDatasetId;
|
|
749
|
+
const previousVariables = variables;
|
|
750
|
+
const nextVariables = toPromptVariablesFromDataset(nextDataset);
|
|
751
|
+
setSelectedDatasetId(datasetId);
|
|
752
|
+
setVariables(nextVariables);
|
|
753
|
+
void (async () => {
|
|
754
|
+
try {
|
|
755
|
+
if (prompt && prompt.defaultDatasetId !== datasetId) {
|
|
756
|
+
await updatePromptMutation.mutateAsync({ promptId: prompt.id, body: { defaultDatasetId: datasetId } });
|
|
757
|
+
}
|
|
758
|
+
const saved = await autoSaveConfigVersion({
|
|
759
|
+
nextPromptLanguage: savedPromptLanguage,
|
|
760
|
+
nextVariables,
|
|
761
|
+
nextDataset,
|
|
762
|
+
});
|
|
763
|
+
if (!saved) {
|
|
764
|
+
setSelectedDatasetId(previousDatasetId);
|
|
765
|
+
setVariables(previousVariables);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
setSelectedDatasetId(previousDatasetId);
|
|
770
|
+
setVariables(previousVariables);
|
|
771
|
+
const message = error?.response?.data?.message ??
|
|
772
|
+
error?.message ??
|
|
773
|
+
t('prompts.detail.datasetBindFailed');
|
|
774
|
+
setSaveError(String(message));
|
|
775
|
+
}
|
|
776
|
+
})();
|
|
777
|
+
}, [
|
|
778
|
+
autoSaveConfigVersion,
|
|
779
|
+
datasets,
|
|
780
|
+
prompt,
|
|
781
|
+
savedPromptLanguage,
|
|
782
|
+
selectedDatasetId,
|
|
783
|
+
t,
|
|
784
|
+
updatePromptMutation,
|
|
785
|
+
variables,
|
|
786
|
+
]);
|
|
787
|
+
const navigateWithGuard = useCallback((href) => {
|
|
788
|
+
if (!dirty) {
|
|
789
|
+
router.push(href);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
requestUnsavedNavigation({ kind: 'href', href, external: false });
|
|
793
|
+
}, [dirty, requestUnsavedNavigation, router]);
|
|
794
|
+
const closeUnsavedDialog = () => {
|
|
795
|
+
setUnsavedDialogOpen(false);
|
|
796
|
+
setPendingNavigation(null);
|
|
797
|
+
};
|
|
798
|
+
const discardAndLeave = () => {
|
|
799
|
+
const navigation = pendingNavigation;
|
|
800
|
+
setBody(savedBody);
|
|
801
|
+
setPromptLanguage(savedPromptLanguage);
|
|
802
|
+
setVariables(savedVariables);
|
|
803
|
+
setCustomOutputFields(savedCustomOutputFields);
|
|
804
|
+
setSelectedDatasetId(null);
|
|
805
|
+
setUnsavedDialogOpen(false);
|
|
806
|
+
setPendingNavigation(null);
|
|
807
|
+
runPendingNavigation(navigation);
|
|
808
|
+
};
|
|
809
|
+
const saveAndLeave = async () => {
|
|
810
|
+
const navigation = pendingNavigation;
|
|
811
|
+
const saved = await saveDraft();
|
|
812
|
+
if (!saved)
|
|
813
|
+
return;
|
|
814
|
+
setUnsavedDialogOpen(false);
|
|
815
|
+
setPendingNavigation(null);
|
|
816
|
+
runPendingNavigation(navigation);
|
|
817
|
+
};
|
|
818
|
+
const cancelDraftChanges = useCallback(() => {
|
|
819
|
+
if (!prompt)
|
|
820
|
+
return;
|
|
821
|
+
setBody(savedBody);
|
|
822
|
+
setPromptLanguage(savedPromptLanguage);
|
|
823
|
+
setVariables(savedVariables);
|
|
824
|
+
setCustomOutputFields(savedCustomOutputFields);
|
|
825
|
+
setSelectedDatasetId(prompt.defaultDatasetId);
|
|
826
|
+
setSaveError(null);
|
|
827
|
+
}, [prompt, savedBody, savedCustomOutputFields, savedPromptLanguage, savedVariables]);
|
|
828
|
+
const activateVersion = useCallback((versionId) => {
|
|
829
|
+
replaceDetailUrl({ nextTab: 'versions', versionId });
|
|
830
|
+
}, [replaceDetailUrl]);
|
|
831
|
+
const handleActivateVersion = useCallback((versionId) => {
|
|
832
|
+
if (dirty && versionId !== activeVersion?.id) {
|
|
833
|
+
requestUnsavedNavigation({ kind: 'version', versionId });
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
activateVersion(versionId);
|
|
837
|
+
}, [activateVersion, activeVersion?.id, dirty, requestUnsavedNavigation]);
|
|
838
|
+
const createBlankVersion = useCallback(async () => {
|
|
839
|
+
if (!prompt)
|
|
840
|
+
return;
|
|
841
|
+
const inheritedDataset = selectedDataset;
|
|
842
|
+
try {
|
|
843
|
+
const next = await createDraftVersionMutation.mutateAsync({
|
|
844
|
+
promptId: prompt.id,
|
|
845
|
+
body: {},
|
|
846
|
+
});
|
|
847
|
+
let nextProjectPrompt = toProjectPrompt(next);
|
|
848
|
+
let created = nextProjectPrompt.versions[0] ?? null;
|
|
849
|
+
if (created && inheritedDataset) {
|
|
850
|
+
const createdVersionId = created.id;
|
|
851
|
+
const inheritedVariables = toPromptVariablesFromDataset(inheritedDataset);
|
|
852
|
+
const inheritedOutputFields = upsertJudgmentField([], getDatasetJudgmentField(inheritedDataset));
|
|
853
|
+
const updated = await updateDraftVersionMutation.mutateAsync({
|
|
854
|
+
promptId: prompt.id,
|
|
855
|
+
versionId: createdVersionId,
|
|
856
|
+
body: {
|
|
857
|
+
body: '',
|
|
858
|
+
promptLanguage: created.promptLanguage,
|
|
859
|
+
variables: inheritedVariables.map(({ selected: _selected, ...variable }) => variable),
|
|
860
|
+
outputSchema: {
|
|
861
|
+
fields: inheritedOutputFields.map((field) => ({
|
|
862
|
+
key: field.key,
|
|
863
|
+
value: field.value,
|
|
864
|
+
isJudgment: field.isJudgment,
|
|
865
|
+
})),
|
|
866
|
+
},
|
|
867
|
+
judgmentRules: { rules: created.judgmentRules },
|
|
868
|
+
changeReason: created.changeReason || null,
|
|
869
|
+
},
|
|
870
|
+
});
|
|
871
|
+
nextProjectPrompt = toProjectPrompt(updated);
|
|
872
|
+
created = nextProjectPrompt.versions.find((version) => version.id === createdVersionId) ?? created;
|
|
873
|
+
}
|
|
874
|
+
if (created) {
|
|
875
|
+
replaceDetailUrl({ nextTab: 'versions', nextMainTab: 'prompt', versionId: created.id });
|
|
876
|
+
}
|
|
877
|
+
setActionMessage({ kind: 'success', text: t('prompts.versions.createBlankSuccess') });
|
|
878
|
+
}
|
|
879
|
+
catch (error) {
|
|
880
|
+
const message = error?.response?.data?.message ??
|
|
881
|
+
error?.message ??
|
|
882
|
+
t('prompts.versions.createBlankFailed');
|
|
883
|
+
setActionMessage({ kind: 'error', text: String(message) });
|
|
884
|
+
}
|
|
885
|
+
}, [createDraftVersionMutation, prompt, replaceDetailUrl, selectedDataset, t, updateDraftVersionMutation]);
|
|
886
|
+
const handleRequestBlankVersion = useCallback(() => {
|
|
887
|
+
if (dirty) {
|
|
888
|
+
requestUnsavedNavigation({ kind: 'blankVersion' });
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
void createBlankVersion();
|
|
892
|
+
}, [createBlankVersion, dirty, requestUnsavedNavigation]);
|
|
893
|
+
const copyVersion = useCallback(async (sourceVersionId) => {
|
|
894
|
+
if (!prompt)
|
|
895
|
+
return;
|
|
896
|
+
try {
|
|
897
|
+
const next = await createDraftVersionMutation.mutateAsync({
|
|
898
|
+
promptId: prompt.id,
|
|
899
|
+
body: { sourceVersionId },
|
|
900
|
+
});
|
|
901
|
+
const nextProjectPrompt = toProjectPrompt(next);
|
|
902
|
+
const created = nextProjectPrompt.versions[0] ?? null;
|
|
903
|
+
if (created) {
|
|
904
|
+
replaceDetailUrl({ nextTab: 'versions', nextMainTab: 'prompt', versionId: created.id });
|
|
905
|
+
}
|
|
906
|
+
setActionMessage({ kind: 'success', text: t('prompts.versions.copySuccess') });
|
|
907
|
+
}
|
|
908
|
+
catch (error) {
|
|
909
|
+
const message = error?.response?.data?.message ??
|
|
910
|
+
error?.message ??
|
|
911
|
+
t('prompts.versions.copyFailed');
|
|
912
|
+
setActionMessage({ kind: 'error', text: String(message) });
|
|
913
|
+
}
|
|
914
|
+
}, [createDraftVersionMutation, prompt, replaceDetailUrl, t]);
|
|
915
|
+
const handleRequestCopy = useCallback((sourceVersionId) => {
|
|
916
|
+
if (dirty) {
|
|
917
|
+
requestUnsavedNavigation({ kind: 'copyVersion', sourceVersionId });
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
void copyVersion(sourceVersionId);
|
|
921
|
+
}, [copyVersion, dirty, requestUnsavedNavigation]);
|
|
922
|
+
function runPendingNavigation(navigation) {
|
|
923
|
+
if (!navigation)
|
|
924
|
+
return;
|
|
925
|
+
switch (navigation.kind) {
|
|
926
|
+
case 'href':
|
|
927
|
+
allowNavigationRef.current = true;
|
|
928
|
+
if (navigation.external)
|
|
929
|
+
window.location.assign(navigation.href);
|
|
930
|
+
else
|
|
931
|
+
router.push(navigation.href);
|
|
932
|
+
return;
|
|
933
|
+
case 'back':
|
|
934
|
+
allowNavigationRef.current = true;
|
|
935
|
+
window.history.go(popGuardArmedRef.current ? -2 : -1);
|
|
936
|
+
return;
|
|
937
|
+
case 'version':
|
|
938
|
+
activateVersion(navigation.versionId);
|
|
939
|
+
return;
|
|
940
|
+
case 'blankVersion':
|
|
941
|
+
void createBlankVersion();
|
|
942
|
+
return;
|
|
943
|
+
case 'copyVersion':
|
|
944
|
+
void copyVersion(navigation.sourceVersionId);
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
const handleRequestDelete = useCallback((versionId) => {
|
|
949
|
+
setDeleteTargetVersionId(versionId);
|
|
950
|
+
}, []);
|
|
951
|
+
const handleUpdateLabel = useCallback(async (label, versionId) => {
|
|
952
|
+
if (!prompt)
|
|
953
|
+
return;
|
|
954
|
+
try {
|
|
955
|
+
await updateVersionLabelMutation.mutateAsync({
|
|
956
|
+
promptId: prompt.id,
|
|
957
|
+
body: { label, versionId },
|
|
958
|
+
});
|
|
959
|
+
setActionMessage({
|
|
960
|
+
kind: 'success',
|
|
961
|
+
text: versionId ? t('prompts.labels.updateSuccess') : t('prompts.labels.deleteSuccess'),
|
|
962
|
+
autoDismiss: true,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
catch (error) {
|
|
966
|
+
const message = error?.response?.data?.message ??
|
|
967
|
+
error?.message ??
|
|
968
|
+
t('prompts.labels.updateFailed');
|
|
969
|
+
setActionMessage({ kind: 'error', text: String(message) });
|
|
970
|
+
}
|
|
971
|
+
}, [prompt, t, updateVersionLabelMutation]);
|
|
972
|
+
const confirmDelete = useCallback(async () => {
|
|
973
|
+
if (!prompt || !deleteTargetVersionId)
|
|
974
|
+
return;
|
|
975
|
+
try {
|
|
976
|
+
await deleteDraftVersionMutation.mutateAsync({
|
|
977
|
+
promptId: prompt.id,
|
|
978
|
+
versionId: deleteTargetVersionId,
|
|
979
|
+
});
|
|
980
|
+
if (requestedVersionId === deleteTargetVersionId) {
|
|
981
|
+
replaceDetailUrl({ nextTab: 'versions', versionId: null });
|
|
982
|
+
}
|
|
983
|
+
setDeleteTargetVersionId(null);
|
|
984
|
+
setActionMessage({ kind: 'success', text: t('prompts.versions.deleteSuccess') });
|
|
985
|
+
}
|
|
986
|
+
catch (error) {
|
|
987
|
+
const message = error?.response?.data?.message ??
|
|
988
|
+
error?.message ??
|
|
989
|
+
t('prompts.versions.deleteFailed');
|
|
990
|
+
setActionMessage({ kind: 'error', text: String(message) });
|
|
991
|
+
setDeleteTargetVersionId(null);
|
|
992
|
+
}
|
|
993
|
+
}, [deleteDraftVersionMutation, deleteTargetVersionId, prompt, replaceDetailUrl, requestedVersionId, t]);
|
|
994
|
+
const deleteTargetVersion = useMemo(() => prompt && deleteTargetVersionId ? (prompt.versions.find((v) => v.id === deleteTargetVersionId) ?? null) : null, [deleteTargetVersionId, prompt]);
|
|
995
|
+
const promptLoading = useDelayedLoading(promptQuery.isLoading);
|
|
996
|
+
if (promptLoading) {
|
|
997
|
+
return (_jsx(Main, { className: "gap-0 bg-muted/35 p-0", children: _jsx("div", { className: "mx-auto w-full max-w-[1680px] px-4 pb-10 pt-6 sm:px-6 lg:px-8", "data-testid": "prompt-detail-page", children: _jsx(DetailPageSkeleton, {}) }) }));
|
|
998
|
+
}
|
|
999
|
+
if (!prompt) {
|
|
1000
|
+
return (_jsx(Main, { className: "bg-muted/35", children: _jsxs("div", { className: "mx-auto w-full max-w-3xl rounded-lg border bg-card p-8 text-center", children: [_jsx("h1", { className: "text-xl font-semibold", children: t('prompts.detail.notFoundTitle') }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: t('prompts.detail.notFoundDescription') }), _jsx(Button, { asChild: true, className: "mt-4", children: _jsx(Link, { href: `/prompts`, children: t('prompts.detail.backToList') }) })] }) }));
|
|
1001
|
+
}
|
|
1002
|
+
const promptMetaParts = [
|
|
1003
|
+
t('prompts.detail.derivedFrom').replace('{version}', `v${prompt.versions[0]?.parentVersion ?? prompt.latestVersion - 1}`),
|
|
1004
|
+
prompt.owner,
|
|
1005
|
+
formatDateTime(prompt.updatedAt),
|
|
1006
|
+
].filter(Boolean);
|
|
1007
|
+
return (_jsxs(Main, { className: "gap-0 bg-muted/35 p-0", children: [_jsxs("div", { className: "mx-auto w-full max-w-[1680px] px-4 pb-10 pt-6 sm:px-6 lg:px-8", "data-testid": "prompt-detail-page", children: [_jsxs("button", { type: "button", onClick: () => navigateWithGuard(`/prompts`), className: "mb-3 inline-flex items-center gap-1 text-[12.5px] text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "size-3.5" }), t('prompts.detail.backToList')] }), _jsx("div", { className: "mb-2", children: _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "mb-1 flex flex-wrap items-center gap-2.5", children: [_jsx("h1", { className: "text-[26px] font-semibold leading-tight", children: prompt.name }), _jsxs("span", { className: "inline-flex items-center rounded-full border bg-background px-2 py-0.5 font-mono text-[11.5px] font-medium text-muted-foreground", children: ["v", prompt.latestVersion] })] }), _jsx("div", { className: "text-[12.5px] text-muted-foreground", children: promptMetaParts.join(' · ') })] }) }), _jsx("div", { className: "mb-5 mt-3 flex items-end gap-1 border-b", children: Object.keys(TAB_LABEL_KEYS).map((tab) => (_jsx("button", { type: "button", onClick: () => selectDetailTab(tab), className: cn('border-b-2 px-4 py-2 text-[13.5px] font-medium transition-colors', activeTab === tab
|
|
1008
|
+
? 'border-primary text-foreground'
|
|
1009
|
+
: 'border-transparent text-muted-foreground hover:text-foreground'), children: t(TAB_LABEL_KEYS[tab]) }, tab))) }), activeTab === 'versions' && (_jsxs("div", { className: "grid overflow-hidden border bg-background lg:grid-cols-[340px_minmax(0,1fr)]", "data-testid": "prompt-version-workspace", children: [_jsx(VersionSidebar, { prompt: prompt, activeVersionId: activeVersion?.id ?? null, onActivateVersion: handleActivateVersion, onRequestBlankVersion: handleRequestBlankVersion, onRequestCopy: handleRequestCopy, onRequestDelete: handleRequestDelete, onUpdateLabel: handleUpdateLabel, isCopying: createDraftVersionMutation.isPending, isDeleting: deleteDraftVersionMutation.isPending, isUpdatingLabel: updateVersionLabelMutation.isPending, initialVersionId: initialVersionParam }), _jsxs("section", { className: "min-w-0 bg-background", "data-testid": "prompt-version-main", children: [_jsx("div", { className: "border-b px-5 py-4", children: _jsxs("div", { className: "flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: activeVersion && (_jsxs(_Fragment, { children: [_jsxs("span", { className: "font-mono text-sm font-semibold", children: ["v", activeVersion.version] }), _jsx("h2", { className: "truncate text-xl font-semibold leading-tight", children: prompt.name }), activeVersion.frozen ? (_jsx("span", { "data-testid": "prompt-version-frozen-badge", children: _jsx(StatusBadge, { status: activeVersion.status, compact: true }) })) : (_jsx(StatusBadge, { status: activeVersion.status, compact: true }))] })) }), _jsx(ActiveVersionLabels, { activeVersion: activeVersion, onUpdateLabel: handleUpdateLabel, isUpdatingLabel: updateVersionLabelMutation.isPending })] }), activeVersion && (_jsxs("div", { className: "flex max-w-full flex-wrap items-center justify-start gap-2 xl:justify-end", children: [actionMessage && (_jsx("span", { className: cn('text-xs', actionMessage.kind === 'success' ? 'text-[var(--status-running-fg)]' : 'text-destructive'), children: actionMessage.text })), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-8", disabled: createDraftVersionMutation.isPending, onClick: () => handleRequestCopy(activeVersion.id), children: [_jsx(Copy, { className: "size-3.5" }), t('prompts.detail.copyAsNew')] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-8", onClick: () => navigateWithGuard(`/experiments/new?promptId=${prompt.id}&promptVersionId=${activeVersion.id}`), children: [_jsx(FlaskConical, { className: "size-3.5" }), t('prompts.action.startExperiment')] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-8", onClick: () => navigateWithGuard(`/optimizations/new?promptId=${prompt.id}&promptVersionId=${activeVersion.id}`), children: [_jsx(Sparkles, { className: "size-3.5" }), t('prompts.action.startOptimization')] })] }))] }) }), _jsx("div", { className: "flex items-end gap-1 border-b px-5", children: Object.keys(PROMPT_MAIN_TAB_LABEL_KEYS).map((tab) => (_jsx("button", { type: "button", onClick: () => selectPromptMainTab(tab), className: cn('border-b-2 px-3 py-3 text-[13.5px] font-medium transition-colors', activeMainTab === tab
|
|
1010
|
+
? 'border-primary text-foreground'
|
|
1011
|
+
: 'border-transparent text-muted-foreground hover:text-foreground'), children: t(PROMPT_MAIN_TAB_LABEL_KEYS[tab]) }, tab))) }), _jsxs("div", { className: "p-5", children: [activeMainTab === 'prompt' && (_jsx(EditorTab, { body: body, promptLanguage: promptLanguage, variables: variables, outputFields: outputFields, onBodyChange: setBody, onOutputFieldsChange: handleOutputFieldsChange, hasBoundDataset: Boolean(selectedDatasetId), hasDatasets: datasetsQuery.isLoading || activeDatasets.length > 0, onRequestDatasetBinding: () => selectPromptMainTab('config'), onRequestDatasetUpload: () => navigateWithGuard('/datasets/new'), dirty: dirty, saveError: saveError, isSaving: updateDraftVersionMutation.isPending, onCancelChanges: cancelDraftChanges, onSaveChanges: saveDraft, readOnly: isReadOnly })), activeMainTab === 'config' && (_jsx(ConfigTab, { datasets: datasets, variables: variables, promptLanguage: promptLanguage, onPromptLanguageChange: handlePromptLanguageChange, selectedDatasetId: selectedDatasetId, onSelectDataset: selectDataset, saveError: saveError, readOnly: isReadOnly || updateDraftVersionMutation.isPending || updatePromptMutation.isPending }))] })] })] })), activeTab === 'metrics' && _jsx(PromptMetricsTab, { projectId: projectId, promptId: prompt.id })] }), _jsx(Dialog, { open: unsavedDialogOpen, onOpenChange: (open) => !open && closeUnsavedDialog(), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: t('prompts.detail.unsavedTitle') }), _jsx(DialogDescription, { children: t('prompts.detail.unsavedDescription') })] }), saveError && _jsx("p", { className: "text-sm text-destructive", children: saveError }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", onClick: closeUnsavedDialog, children: t('common.cancel') }), _jsx(Button, { type: "button", variant: "ghost", onClick: discardAndLeave, children: t('prompts.detail.leaveWithoutSaving') }), _jsxs(Button, { type: "button", onClick: () => void saveAndLeave(), disabled: updateDraftVersionMutation.isPending, children: [_jsx(Save, { className: "size-4" }), t('prompts.detail.saveAndLeave')] })] })] }) }), _jsx(Dialog, { open: deleteTargetVersionId !== null, onOpenChange: (open) => !open && setDeleteTargetVersionId(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: t('prompts.versions.deleteConfirmTitle') }), _jsx(DialogDescription, { children: t('prompts.versions.deleteConfirmBody').replace('{version}', deleteTargetVersion ? `v${deleteTargetVersion.version}` : '') })] }), _jsx(DeleteImpactPanel, { impact: deleteImpactQuery.data, loading: deleteImpactQuery.isLoading }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => setDeleteTargetVersionId(null), children: t('common.cancel') }), _jsxs(Button, { type: "button", variant: "destructive", onClick: () => void confirmDelete(), disabled: deleteDraftVersionMutation.isPending || deleteImpactQuery.isLoading, children: [_jsx(Trash2, { className: "size-4" }), t('common.confirmDelete')] })] })] }) })] }));
|
|
1012
|
+
}
|
|
1013
|
+
//# sourceMappingURL=prompt-detail-page.js.map
|