use axum::http::StatusCode; use serde::Serialize; use serde_json::json; use shared_contracts::external_generation::{ ExternalGenerationJobStatus, ExternalGenerationJobStatusRecord, }; use shared_kernel::{build_prefixed_uuid_id, offset_datetime_to_unix_micros}; use spacetime_client::{ExternalGenerationJobEnqueueRecordInput, ExternalGenerationJobRecord}; use crate::{http_error::AppError, request_context::RequestContext, state::AppState}; pub(crate) const EDITOR_IMAGE_GENERATION_JOB_KIND: &str = "editor_image_generation"; pub(crate) const EDITOR_IMAGE_EDIT_JOB_KIND: &str = "editor_image_edit"; pub(crate) const EDITOR_ICON_SPRITESHEET_GENERATION_JOB_KIND: &str = "editor_icon_spritesheet_generation"; pub(crate) const EDITOR_UI_DESIGN_ASSET_EXTRACTION_JOB_KIND: &str = "editor_ui_design_asset_extraction"; pub(crate) const EDITOR_CHARACTER_ANIMATION_GENERATION_JOB_KIND: &str = "editor_character_animation_generation"; pub(crate) const EDITOR_VIDEO_GENERATION_JOB_KIND: &str = "editor_video_generation"; pub(crate) const EDITOR_SOUND_EFFECT_GENERATION_JOB_KIND: &str = "editor_sound_effect_generation"; pub(crate) const EDITOR_BACKGROUND_MUSIC_GENERATION_JOB_KIND: &str = "editor_background_music_generation"; pub(crate) const EDITOR_GENERATION_QUEUE_SOURCE_MODULE: &str = "editor-canvas"; const EDITOR_GENERATION_QUEUE_PROVIDER: &str = "editor-generation-worker"; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct EditorGenerationQueuedResponse { pub(crate) queue_state: ExternalGenerationJobStatusRecord, } pub(crate) async fn enqueue_editor_generation_job( state: &AppState, _request_context: &RequestContext, owner_user_id: &str, job_kind: &str, source_entity_id: impl Into, request_label: impl Into, payload: &T, ) -> Result where T: Serialize, { let job_id = build_prefixed_uuid_id("extgen-"); let request_payload_json = serde_json::to_string(payload).map_err(|error| { AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({ "provider": EDITOR_GENERATION_QUEUE_PROVIDER, "message": format!("编辑器 worker 任务参数序列化失败:{error}"), })) })?; let now_micros = current_utc_micros(); state .spacetime_client() .enqueue_external_generation_job(ExternalGenerationJobEnqueueRecordInput { dedupe_key: format!("editor-canvas:{job_kind}:{job_id}"), job_id, job_kind: job_kind.to_string(), owner_user_id: owner_user_id.to_string(), source_module: EDITOR_GENERATION_QUEUE_SOURCE_MODULE.to_string(), source_entity_id: source_entity_id.into(), request_label: request_label.into(), request_payload_json, max_attempts: 1, available_at_micros: now_micros, created_at_micros: now_micros, }) .await .map_err(|error| { AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ "provider": EDITOR_GENERATION_QUEUE_PROVIDER, "message": error.to_string(), })) }) } pub(crate) fn editor_generation_queue_state( job: ExternalGenerationJobRecord, ) -> ExternalGenerationJobStatusRecord { ExternalGenerationJobStatusRecord { operation_id: job.job_id, status: ExternalGenerationJobStatus::Queued, phase_label: job.request_label, phase_detail: "排队中。".to_string(), progress: 8, error: job.last_error_message, updated_at_micros: job.updated_at_micros, } } pub(crate) fn editor_generation_source_entity_id( project_id: Option<&str>, fallback: &str, ) -> String { project_id .map(str::trim) .filter(|value| !value.is_empty()) .unwrap_or(fallback) .to_string() } fn current_utc_micros() -> i64 { offset_datetime_to_unix_micros(time::OffsetDateTime::now_utc()) }