use axum::{ Json, extract::{Extension, Path, State}, http::StatusCode, response::Response, }; use serde_json::json; use shared_contracts::external_generation::{ ExternalGenerationJobStatus, ExternalGenerationJobStatusRecord, ExternalGenerationJobStatusResponse, ExternalGenerationQueueOverview, ExternalGenerationQueueOverviewResponse, }; use spacetime_client::{ ExternalGenerationJobGetRecordInput, ExternalGenerationJobRecord, ExternalGenerationQueueStatsRecord, SpacetimeClientError, }; use crate::{ api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError, request_context::RequestContext, state::AppState, }; const EXTERNAL_GENERATION_PROVIDER: &str = "external_generation"; pub async fn get_external_generation_queue_overview( State(state): State, Extension(request_context): Extension, ) -> Result, Response> { let stats = state .spacetime_client() .get_external_generation_queue_stats() .await .map_err(|error| external_generation_error_response(&request_context, error))?; Ok(json_success_body( Some(&request_context), ExternalGenerationQueueOverviewResponse { overview: map_external_generation_queue_overview(stats), }, )) } pub async fn get_external_generation_job_status( State(state): State, Extension(request_context): Extension, Extension(authenticated): Extension, Path(job_id): Path, ) -> Result, Response> { let owner_user_id = authenticated.claims().user_id().to_string(); let job = state .spacetime_client() .get_external_generation_job(ExternalGenerationJobGetRecordInput { job_id, owner_user_id, }) .await .map_err(|error| external_generation_error_response(&request_context, error))?; Ok(json_success_body( Some(&request_context), ExternalGenerationJobStatusResponse { job: map_external_generation_job_status(job), }, )) } fn map_external_generation_queue_overview( stats: ExternalGenerationQueueStatsRecord, ) -> ExternalGenerationQueueOverview { ExternalGenerationQueueOverview { pending_count: stats.pending_count, running_count: stats.running_active_count, updated_at_micros: stats.now_micros, } } fn map_external_generation_job_status( job: ExternalGenerationJobRecord, ) -> ExternalGenerationJobStatusRecord { let (status, phase_detail, progress) = match job.status.as_str() { "completed" => (ExternalGenerationJobStatus::Completed, "生成已完成。", 100), "running" => (ExternalGenerationJobStatus::Running, "正在生成。", 35), "failed" => (ExternalGenerationJobStatus::Failed, "生成失败。", 0), _ => (ExternalGenerationJobStatus::Queued, "排队中。", 8), }; ExternalGenerationJobStatusRecord { operation_id: job.job_id, status, phase_label: job.request_label, phase_detail: phase_detail.to_string(), progress, error: job.last_error_message, updated_at_micros: job.updated_at_micros, } } fn external_generation_error_response( request_context: &RequestContext, error: SpacetimeClientError, ) -> Response { AppError::from_status(StatusCode::BAD_GATEWAY) .with_details(json!({ "provider": EXTERNAL_GENERATION_PROVIDER, "message": error.to_string(), })) .into_response_with_context(Some(request_context)) }