shuishen
12 hours ago 385be2eca72eb3833efa4be0a0088b34e764788a
apps/workbench-console/src/api/artifacts.ts
@@ -1,7 +1,9 @@
export interface Detection { class_id: number; class_name: string; confidence: number; bbox_xyxy: number[]; }
export interface GeoFeature { type?: string; properties?: Record<string, unknown>; geometry?: { type?: string; coordinates?: unknown }; }
export interface DetectionImage { file: string; annotated_file: string; width: number; height: number; detections: Detection[]; }
export interface DetectionRun { detection_count: number; processed_images: number; elapsed_seconds: number; device: string; model: string; confidence: number; input_dir: string; notes: string[]; created_at?: string; }
export type ComputeDevice = "auto" | "cpu" | "cuda";
export interface ExecutionMetadata { requested_device: ComputeDevice; actual_device: "cpu" | "cuda"; environment: string; torch_version: string; fallback_used: boolean; fallback_reason: string | null; }
export interface DetectionRun { detection_count: number; processed_images: number; elapsed_seconds: number; device: string; requested_device?: ComputeDevice; execution?: ExecutionMetadata; model: string; confidence: number; input_dir: string; notes: string[]; created_at?: string; }
export interface DetectionCase { id: string; label: string; note: string; artifactRoot: string; inputRoot: string; createdAt: string; run: DetectionRun; images: DetectionImage[]; }
export interface SemanticImage { file: string; width: number; height: number; mask_file: string; overlay_file: string; raster_file: string; vector_file: string; class_pixel_counts: Record<string, number>; georeferenced: boolean; vectorizer: string; elapsed_seconds: number; }
export interface SemanticRun { capability: string; classification: string; task_id?: string; task_name?: string; created_at?: string; geoai_version: string; method: string; model: string; device: string; thresholds: Record<string, number>; input_count: number; processed_images: number; elapsed_seconds: number; images: SemanticImage[]; classes: Array<{ id: number; key: string; label: string; color: number[] }>; limitations: string[]; input_dir?: string; raw_input_dir?: string; }
@@ -24,11 +26,11 @@
export interface AnomalyRun { capability: string; classification: string; created_at?: string; versions: Record<string, string>; method: string; model: string; device: string; reference_count: number; reference_tile_count: number; input_count: number; reference_images: Array<{ file: string; tile_count: number }>; feature_names: string[]; parameters: { tile_size: number; stride: number; threshold_quantile: number; random_state: number; spatial_mode_requested?: "auto" | "global" | "aligned"; rule_mode_selected?: "global" | "aligned" }; thresholds: { rule_score: number; isolation_score: number }; images: AnomalyImage[]; elapsed_seconds: number; limitations: string[]; }
export interface AnomalyCase { id: string; label: string; note: string; artifactRoot: string; inputRoot: string; referenceRoot: string; createdAt: string; run: AnomalyRun; candidates: Record<string, AnomalyCandidate[]>; }
export interface ChangeFeature { type: "Feature"; properties: { feature_id?: number; area_pixels?: number; mean_probability?: number; max_probability?: number; bounds_pixel?: number[]; confidence?: number; class?: number }; geometry: { type: string; coordinates: unknown }; }
export interface ChangeRun { schema_version: number; capability: string; classification: string; geoai_version: string; method: string; model: string; device: string; processing_mode?: "image" | "geotiff"; requested_processing_mode?: "auto" | "image" | "geotiff"; thresholds: Record<string, number>; tile_size: number; overlap: number; max_dimension?: number; effective_max_dimension?: number; input_files: string[]; input_shape: number[]; processed_shape: number[]; registration: { method: string; matches: number; inliers: number; inlier_ratio: number; valid_ratio: number }; valid_pixel_ratio: number; raw_changed_pixels: number; changed_pixels: number; changed_pixel_ratio: number; vector_feature_count: number; rectangle_feature_count?: number; generic_difference?: { candidate_pixels: number; candidate_pixel_ratio: number; components_after_filter: number }; georeferenced: boolean; coordinate_basis: string; crs: string | null; elapsed_seconds: number; limitations: string[]; artifacts: { probability_raster: string; raw_mask_raster: string; mask_raster: string; generic_difference_mask?: string; overlay: string; model_overlay?: string; before_processed_preview?: string; after_registered_preview?: string; vector: string; rectangle_vector?: string; rectangle_vector_wgs84?: string | null; features: string }; created_at?: string; }
export interface ChangeRun { schema_version: number; capability: string; classification: string; geoai_version: string; method: string; model: string; device: string; requested_device?: ComputeDevice; execution?: ExecutionMetadata; processing_mode?: "image" | "geotiff"; requested_processing_mode?: "auto" | "image" | "geotiff"; thresholds: Record<string, number>; tile_size: number; overlap: number; max_dimension?: number; effective_max_dimension?: number; input_files: string[]; input_shape: number[]; processed_shape: number[]; registration: { method: string; matches: number; inliers: number; inlier_ratio: number; valid_ratio: number }; valid_pixel_ratio: number; raw_changed_pixels: number; changed_pixels: number; changed_pixel_ratio: number; vector_feature_count: number; rectangle_feature_count?: number; generic_difference?: { candidate_pixels: number; candidate_pixel_ratio: number; components_after_filter: number }; georeferenced: boolean; coordinate_basis: string; crs: string | null; elapsed_seconds: number; limitations: string[]; artifacts: { probability_raster: string; raw_mask_raster: string; mask_raster: string; generic_difference_mask?: string; overlay: string; model_overlay?: string; before_processed_preview?: string; after_registered_preview?: string; vector: string; rectangle_vector?: string; rectangle_vector_wgs84?: string | null; features: string }; created_at?: string; }
export interface ChangeCase { id: string; label: string; note: string; artifactRoot: string; beforeImage: string; afterImage: string; rawAfterImage?: string; registeredBeforeImage?: string; registeredAfterImage?: string; createdAt: string; run: ChangeRun; features: ChangeFeature[]; }
export interface ChangeScanResult { id: string; label: string; threshold: number; minimumAreaPixels: number; cleanedComponents: number; changedPixels: number; changedPixelRatio: number; vectorFeatureCount: number; fullVectorFeatureCount?: number | null; rectangleFeatureCount?: number | null; overlay: string; mask: string; regions: string; vector?: string | null; rectangleVector?: string | null; rectangleVectorWgs84?: string | null; }
export interface ChangeParameterScan { id: string; label: string; note: string; artifactRoot: string; sourceRun?: string; userSubmitted?: boolean; contactSheet?: string | null; results: ChangeScanResult[]; }
export interface ChangeScanJob { id: string; status: "queued" | "running" | "completed" | "failed"; phase?: "inference" | "parameter-scan" | "vectorization" | "done" | "error"; scanId?: string; error?: string; thresholds?: number[]; minimumAreas?: number[]; }
export interface ChangeScanJob { id: string; status: "queued" | "running" | "completed" | "failed"; phase?: "inference" | "parameter-scan" | "vectorization" | "done" | "error"; scanId?: string; error?: string; thresholds?: number[]; minimumAreas?: number[]; requestedDevice?: ComputeDevice; device?: "cpu" | "cuda"; environment?: string; torchVersion?: string; fallbackUsed?: boolean; fallbackReason?: string | null; }
export interface TrajectoryEvent { event_id: string; event_type: string; track_ids: string[]; start_time: string; end_time: string; duration_seconds: number; }
export interface TrajectoryCaseRun { case_id: string; input_count: number; track_count: number; event_count: number; dropped_duplicate_observations: number; elapsed_seconds: number; device: string; input: string; thresholds: Record<string, number>; created_at?: string; }
export interface TrajectorySummary { track_id: string; entity_type: string; point_count: string; distance_m: string; average_speed_mps: string; behavior_labels: string; }
@@ -48,12 +50,20 @@
export interface AnomalyJob { id: string; runId: string; status: "queued" | "running" | "complete" | "failed"; createdAt: string; finishedAt?: string; error?: string; run?: AnomalyDefinition; }
export interface PhotoReconstructionUploadRef { uploadId: string; role: "photo"; name: string; size: number; sha256: string; }
export interface PointCloudUploadRef { uploadId: string; role: "pointcloud"; name: string; size: number; sha256: string; }
export interface PhotoReconstructionJob { id: string; runId: string; inputImages: number; usePositionPriors: boolean; status: "queued" | "running" | "complete" | "failed"; stage: "queued" | "sparse_sfm" | "dense_mvs" | "complete" | "failed"; createdAt: string; startedAt?: string; finishedAt?: string; error?: string; run?: PointCloudDefinition; }
export interface PointCloudAnnotationSource { id: string; runId: string; label: string; artifactRoot: string; file: string; url: string; sha256: string; pointCount: number; sourceKind: string; }
export interface PhotoReconstructionProgress { percent: number; stage: string; message: string; inputImages: number; updatedAt?: string; estimate: boolean; }
export interface PhotoReconstructionJob { id: string; runId: string; inputImages: number; usePositionPriors: boolean; status: "queued" | "running" | "complete" | "failed"; stage: "queued" | "sparse_sfm" | "dense_mvs" | "complete" | "failed"; createdAt: string; startedAt?: string; finishedAt?: string; error?: string; progress?: PhotoReconstructionProgress; run?: PointCloudDefinition; }
export interface PointCloudAnnotationSource { id: string; runId: string; label: string; artifactRoot: string; file: string; url: string; sha256: string; pointCount: number; sourceKind: string; sourceHasRgb?: boolean; detailAvailable?: boolean; detailFile?: string | null; }
export interface PointCloudAnnotationSourceDeletionPlan { sourceId: string; label: string; sourceKind: string; runId: string; artifactRoot: string; outputDirectories: number; rawDirectories: number; processedDirectories: number; annotationRevisions: number; trainingRuns: number; inferenceRuns: number; siblingSources: number; removesOriginalUpload: boolean; preservesExternalInputs: boolean; }
export interface PointCloudAnnotationSourceRemoval { sourceId: string; runId: string; removed: Pick<PointCloudAnnotationSourceDeletionPlan, "outputDirectories" | "rawDirectories" | "processedDirectories" | "annotationRevisions" | "trainingRuns" | "inferenceRuns" | "siblingSources">; preservedExternalInputs: boolean; }
export interface PointCloudAnnotationClass { code: number; key: string; label: string; color: number[]; builtIn: boolean; }
export interface PointCloudAnnotation { id: string; sourceId: string; createdAt: string; labelCount: number; classCounts: Record<string, number>; path: string; }
export interface PointCloudTrainingJob { id: string; annotationId: string; status: "queued" | "running" | "complete" | "failed"; stage: string; device: string; createdAt: string; error?: string; artifactRoot?: string; metrics?: string; model?: string; preview?: string; }
export interface PointCloudAnnotationSourceJob { id: string; runId: string; inputName: string; status: "queued" | "running" | "complete" | "failed"; stage: string; createdAt: string; error?: string; }
export interface PointCloudTrainingJob { id: string; annotationId: string; trainer?: "rgb_xyz_baseline" | "multiview_local_attention_baseline"; status: "queued" | "running" | "complete" | "failed"; stage: string; requestedDevice?: ComputeDevice; device: string; environment?: string; torchVersion?: string; fallbackUsed?: boolean; fallbackReason?: string | null; createdAt: string; error?: string; artifactRoot?: string; metrics?: string; model?: string; preview?: string; }
export interface PointCloudSemanticModel { id: string; label: string; artifactRoot: string; model: string; metrics: string; createdAt: string; classes: Record<string, { key: string; label: string; color: number[] }>; testF1: Record<string, number>; }
export interface PointCloudInferenceJob { id: string; runId: string; modelId: string; inputName: string; status: "queued" | "running" | "complete" | "failed"; stage: string; device: string; createdAt: string; error?: string; artifactRoot?: string; metadata?: string; preview?: string; classifiedLas?: string; classCounts?: string; summary?: string; }
export interface PointCloudInferenceJob { id: string; runId: string; modelId: string; sourceId?: string; inputName: string; candidateConfidence?: number; status: "queued" | "running" | "complete" | "failed"; stage: string; requestedDevice?: ComputeDevice; device: string; environment?: string; torchVersion?: string; fallbackUsed?: boolean; fallbackReason?: string | null; createdAt: string; error?: string; artifactRoot?: string; metadata?: string; preview?: string; classifiedLas?: string; classCounts?: string; summary?: string; candidateFile?: string | null; }
export interface PointCloudAutoAnnotationReview { runId: string; sourceId: string; correctionCount: number; savedAt: string; }
export interface RunDeletionPlan { capability: string; runId: string; label: string; removable: boolean; reason?: string | null; outputDirectories: string[]; rawDirectories: string[]; processedDirectories: string[]; dependentDirectories: string[]; preservesExternalInputs: boolean; }
export interface PointCloudSemanticModelDeletionPlan { modelId: string; label: string; removable: boolean; trainingDirectories: string[]; inferenceDirectories: string[]; autoAnnotationDirectories: string[]; rawDirectories: string[]; processedDirectories: string[]; preservesAnnotationRevisions: boolean; preservesExternalInputs: boolean; }
export const artifactUrl = (path: string) => `/${path.replace(/\\/g, "/").split("/").map(encodeURIComponent).join("/")}`;
@@ -151,7 +161,7 @@
async function postRun<T>(path: string, body: object): Promise<T> { return responseJson<T>(await fetch(artifactUrl(path), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) })); }
export async function createTrajectoryRun(files: { flight: UploadFilePayload; route: UploadFilePayload; restricted: UploadFilePayload; flyable?: UploadFilePayload }) { return postRun<{ run: TrajectoryDefinition }>("api/trajectory/runs", { files }); }
export async function createDetectionRun(images: UploadFilePayload[]) { return postRun<{ run: DetectionDefinition }>("api/object-detection/runs", { images }); }
export async function createDetectionRun(images: UploadFilePayload[], device: ComputeDevice = "auto") { return postRun<{ run: DetectionDefinition }>("api/object-detection/runs", { images, device }); }
export async function createSemanticRun(images: UploadFilePayload[], taskId: string) { return postRun<{ run: SemanticDefinition }>("api/semantic-mapping/runs", { images, taskId }); }
export async function createMeasurementRun(rasters: UploadFilePayload[]) { return postRun<{ run: MeasurementDefinition }>("api/spatial-measurement/runs", { rasters }); }
export async function createPointCloudRun(pointClouds: Array<UploadFilePayload | PointCloudUploadRef>) { return postRun<{ run: PointCloudDefinition }>("api/3d-pointcloud/runs", { pointClouds }); }
@@ -169,16 +179,30 @@
export async function createPhotoReconstructionRun(photos: PhotoReconstructionUploadRef[], usePositionPriors: boolean) { return postRun<{ job: PhotoReconstructionJob }>("api/3d-pointcloud/photo-reconstruction-runs", { photos, usePositionPriors }); }
export async function loadPhotoReconstructionJob(jobId: string) { return (await getJson<{ job: PhotoReconstructionJob }>(`api/3d-pointcloud/photo-reconstruction-jobs/${jobId}`)).job; }
export async function loadPointCloudAnnotationSources() { return (await getJson<{ sources: PointCloudAnnotationSource[] }>("api/3d-pointcloud/annotation-sources")).sources; }
export async function loadPointCloudAnnotationSourceDeletionPlan(sourceId: string) { return (await getJson<{ plan: PointCloudAnnotationSourceDeletionPlan }>(`api/3d-pointcloud/annotation-source-deletion-plans/${sourceId}`)).plan; }
export async function deletePointCloudAnnotationSource(sourceId: string) { return responseJson<{ removed: PointCloudAnnotationSourceRemoval }>(await fetch(artifactUrl(`api/3d-pointcloud/annotation-sources/${sourceId}`), { method: "DELETE" })); }
export async function loadPointCloudAnnotationClasses() { return (await getJson<{ classes: PointCloudAnnotationClass[] }>("api/3d-pointcloud/annotation-classes")).classes; }
export async function loadPointCloudAnnotations() { return (await getJson<{ annotations: PointCloudAnnotation[] }>("api/3d-pointcloud/annotations")).annotations; }
export async function createPointCloudAnnotationClass(value: { key: string; label: string; color: number[] }) { return postRun<{ class: PointCloudAnnotationClass }>("api/3d-pointcloud/annotation-classes", value); }
export async function deletePointCloudAnnotationClass(code: number) { return responseJson<{ deletedCode: number }>(await fetch(artifactUrl(`api/3d-pointcloud/annotation-classes/${code}`), { method: "DELETE" })); }
export async function createPointCloudAnnotationSourceRun(pointCloud: PointCloudUploadRef) { return postRun<{ job: PointCloudAnnotationSourceJob }>("api/3d-pointcloud/annotation-source-runs", { pointCloud }); }
export async function loadPointCloudAnnotationSourceJob(jobId: string) { return (await getJson<{ job: PointCloudAnnotationSourceJob }>(`api/3d-pointcloud/annotation-source-jobs/${jobId}`)).job; }
export async function createPointCloudAnnotation(sourceId: string, labels: Array<[number, number]>) { return postRun<{ annotation: PointCloudAnnotation }>("api/3d-pointcloud/annotations", { sourceId, labels }); }
export async function deletePointCloudAnnotation(annotationId: string) { return responseJson<{ deletedId: string }>(await fetch(artifactUrl(`api/3d-pointcloud/annotations/${encodeURIComponent(annotationId)}`), { method: "DELETE" })); }
export async function createPointCloudTrainingRun(annotationId: string, device: "auto" | "cpu" | "cuda" = "cpu") { return postRun<{ job: PointCloudTrainingJob }>("api/3d-pointcloud/training-runs", { annotationId, device }); }
export async function createPointCloudTrainingRun(annotationId: string, device: ComputeDevice = "auto") { return postRun<{ job: PointCloudTrainingJob }>("api/3d-pointcloud/training-runs", { annotationId, device }); }
export async function loadPointCloudTrainingJob(jobId: string) { return (await getJson<{ job: PointCloudTrainingJob }>(`api/3d-pointcloud/training-jobs/${jobId}`)).job; }
export async function loadPointCloudSemanticModels() { return (await getJson<{ models: PointCloudSemanticModel[] }>("api/3d-pointcloud/semantic-models")).models; }
export async function createPointCloudModelInference(modelId: string, pointCloud: PointCloudUploadRef) { return postRun<{ job: PointCloudInferenceJob }>("api/3d-pointcloud/model-inference-runs", { modelId, pointCloud }); }
export async function loadPointCloudSemanticModelDeletionPlan(modelId: string) { return (await getJson<{ plan: PointCloudSemanticModelDeletionPlan }>(`api/3d-pointcloud/semantic-model-deletion-plans/${encodeURIComponent(modelId)}`)).plan; }
export async function deletePointCloudSemanticModel(modelId: string) { return responseJson<{ removed: { modelId: string; removedDirectories: string[] } }>(await fetch(artifactUrl(`api/3d-pointcloud/semantic-models/${encodeURIComponent(modelId)}`), { method: "DELETE" })); }
export async function createPointCloudModelInference(modelId: string, pointCloud: PointCloudUploadRef, device: ComputeDevice = "auto") { return postRun<{ job: PointCloudInferenceJob }>("api/3d-pointcloud/model-inference-runs", { modelId, pointCloud, device }); }
export async function createPointCloudAutoAnnotation(modelId: string, sourceId: string, candidateConfidence = 0.95, device: ComputeDevice = "auto") { return postRun<{ job: PointCloudInferenceJob }>("api/3d-pointcloud/auto-annotation-runs", { modelId, sourceId, candidateConfidence, device }); }
export async function loadLatestPointCloudAutoAnnotation(sourceId: string, modelId: string) { const query = new URLSearchParams({ sourceId, modelId }); return (await responseJson<{ job: PointCloudInferenceJob | null }>(await fetch(`/api/3d-pointcloud/auto-annotation-runs/latest?${query.toString()}`, { cache: "no-store" }))).job; }
export async function loadPointCloudAutoAnnotationReview(runId: string) { return (await getJson<{ review: { corrections?: Array<[number, number]> } | null }>(`api/3d-pointcloud/auto-annotation-review-drafts/${encodeURIComponent(runId)}`)).review; }
export async function savePointCloudAutoAnnotationReview(runId: string, sourceId: string, corrections: Array<[number, number]>) { return postRun<{ review: PointCloudAutoAnnotationReview }>("api/3d-pointcloud/auto-annotation-review-drafts", { runId, sourceId, corrections }); }
export async function acceptPointCloudAutoAnnotation(runId: string, sourceId: string, baseAnnotationId?: string) { return postRun<{ annotation: PointCloudAnnotation }>("api/3d-pointcloud/auto-annotation-acceptances", { runId, sourceId, baseAnnotationId }); }
export async function loadPointCloudModelInferenceJob(jobId: string) { return (await getJson<{ job: PointCloudInferenceJob }>(`api/3d-pointcloud/model-inference-jobs/${jobId}`)).job; }
export async function createRiskRuleRun(files: { observations: UploadFilePayload; zones: UploadFilePayload; rules: UploadFilePayload }) { return postRun<{ run: RiskRuleDefinition }>("api/risk-rule-engine/runs", { files }); }
export async function createChangeRun(files: { before: UploadFilePayload; after: UploadFilePayload }, threshold = 0.5, maxDimension = 0, processingMode: "auto" | "image" | "geotiff" = "auto") { return postRun<{ run: ChangeDefinition }>("api/change-detection/runs", { files, threshold, maxDimension, processingMode }); }
export async function createChangeRun(files: { before: UploadFilePayload; after: UploadFilePayload }, threshold = 0.5, maxDimension = 0, processingMode: "auto" | "image" | "geotiff" = "auto", device: ComputeDevice = "auto") { return postRun<{ run: ChangeDefinition }>("api/change-detection/runs", { files, threshold, maxDimension, processingMode, device }); }
export async function uploadChangeFile(file: File, role: "before" | "after"): Promise<ChangeUploadRef> {
  const uploadId = globalThis.crypto.randomUUID().replaceAll("-", "");
  const query = new URLSearchParams({ role });
@@ -187,8 +211,8 @@
  const response = await fetch(`/api/change-detection/uploads/${uploadId}?${query.toString()}`, { method: "PUT", headers: { "Content-Type": "application/octet-stream", "X-Upload-Name": encodeURIComponent(file.name) }, body: file });
  return responseJson<ChangeUploadRef>(response);
}
export async function createChangeRunFromUploads(uploads: { before: ChangeUploadRef; after: ChangeUploadRef }, threshold = 0.5, maxDimension = 0, processingMode: "auto" | "image" | "geotiff" = "auto") { return postRun<{ run: ChangeDefinition }>("api/change-detection/runs", { uploads, threshold, maxDimension, processingMode }); }
export async function createChangeScanFromUploads(uploads: { before: ChangeUploadRef; after: ChangeUploadRef }, thresholds: number[], minimumAreas: number[], maxDimension = 0, processingMode: "auto" | "image" | "geotiff" = "auto") { return postRun<{ job: ChangeScanJob }>("api/change-detection/scans", { uploads, thresholds, minimumAreas, maxDimension, processingMode }); }
export async function createChangeRunFromUploads(uploads: { before: ChangeUploadRef; after: ChangeUploadRef }, threshold = 0.5, maxDimension = 0, processingMode: "auto" | "image" | "geotiff" = "auto", device: ComputeDevice = "auto") { return postRun<{ run: ChangeDefinition }>("api/change-detection/runs", { uploads, threshold, maxDimension, processingMode, device }); }
export async function createChangeScanFromUploads(uploads: { before: ChangeUploadRef; after: ChangeUploadRef }, thresholds: number[], minimumAreas: number[], maxDimension = 0, processingMode: "auto" | "image" | "geotiff" = "auto", device: ComputeDevice = "auto") { return postRun<{ job: ChangeScanJob }>("api/change-detection/scans", { uploads, thresholds, minimumAreas, maxDimension, processingMode, device }); }
export async function getChangeScanJob(jobId: string) { return (await getJson<{ job: ChangeScanJob }>(`api/change-detection/scan-jobs/${jobId}`)).job; }
export async function promoteChangeScan(scanId: string, resultId: string) { return postRun<{ run: ChangeDefinition }>(`api/change-detection/scans/${encodeURIComponent(scanId)}/promote`, { resultId }); }
export async function uploadAnomalyFile(file: File, role: "reference" | "input"): Promise<AnomalyUploadRef> {
@@ -199,4 +223,6 @@
}
export async function createAnomalyRun(uploads: { reference: AnomalyUploadRef[]; input: AnomalyUploadRef[] }, parameters: { tileSize: number; stride: number; thresholdQuantile: number; randomState: number }) { return postRun<{ job: AnomalyJob }>("api/anomaly-detection/runs", { uploads, ...parameters }); }
export async function loadAnomalyJob(jobId: string) { return (await getJson<{ job: AnomalyJob }>(`api/anomaly-detection/jobs/${jobId}`)).job; }
export async function loadRunDeletionPlan(capability: string, runId: string) { return (await getJson<{ plan: RunDeletionPlan }>(`api/runs/${encodeURIComponent(capability)}/${encodeURIComponent(runId)}/deletion-plan`)).plan; }
export async function deleteRun(capability: string, runId: string) { return responseJson<{ removed: { capability: string; runId: string; removedDirectories: string[] } }>(await fetch(artifactUrl(`api/runs/${encodeURIComponent(capability)}/${encodeURIComponent(runId)}`), { method: "DELETE" })); }
export function readFileAsPayload(file: File): Promise<UploadFilePayload> { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = () => reject(new Error(`无法读取 ${file.name}`)); reader.onload = () => { const value = String(reader.result ?? ""); resolve({ name: file.name, content: value.slice(value.indexOf(",") + 1) }); }; reader.readAsDataURL(file); }); }