接入画板生成视频功能

新增画板底部生成视频入口、Lovart 风格面板、视频图层渲染与元数据展示。

接入 /api/editor/videos/generations 契约与后端 Ark/VectorEngine 视频任务链路。

统一编辑器生成类泥点配置,并补充 UI 设计图、参考图与生成面板结构测试。

更新编辑器技术方案、生成类面板方案和 Hermes 共享决策/踩坑记录。
This commit is contained in:
2026-06-17 20:47:27 +08:00
parent d1cd300695
commit b2fd5574db
39 changed files with 3390 additions and 238 deletions

View File

@@ -346,6 +346,38 @@ pub struct EditorCharacterAnimationGenerateResponse {
pub price_mud_points: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct EditorVideoGenerateRequest {
pub prompt: String,
pub model: String,
pub aspect_ratio: String,
pub duration_seconds: u32,
pub resolution: String,
pub mode: String,
pub sound: String,
pub price_mud_points: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct EditorVideoGenerateResponse {
pub ok: bool,
pub video_src: String,
pub width: u32,
pub height: u32,
pub source_type: String,
pub prompt: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub actual_prompt: Option<String>,
pub model: String,
pub provider: String,
pub task_id: String,
pub duration_seconds: u32,
pub resolution: String,
pub price_mud_points: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CharacterAnimationDraftPayload {
@@ -908,6 +940,53 @@ mod tests {
assert_eq!(payload["fps"], json!(8));
}
#[test]
fn editor_video_request_supports_lovart_model_contract() {
let payload = serde_json::to_value(EditorVideoGenerateRequest {
prompt: "让角色向镜头挥手".to_string(),
model: "kling3.0-omni".to_string(),
aspect_ratio: "16:9".to_string(),
duration_seconds: 5,
resolution: "480p".to_string(),
mode: "std".to_string(),
sound: "off".to_string(),
price_mud_points: 50,
})
.expect("request should serialize");
assert_eq!(payload["aspectRatio"], json!("16:9"));
assert_eq!(payload["durationSeconds"], json!(5));
assert_eq!(payload["priceMudPoints"], json!(50));
assert_eq!(payload["model"], json!("kling3.0-omni"));
}
#[test]
fn editor_video_response_uses_canvas_video_shape() {
let payload = serde_json::to_value(EditorVideoGenerateResponse {
ok: true,
video_src: "/generated-editor-videos/task-1/preview.mp4".to_string(),
width: 1280,
height: 720,
source_type: "generated".to_string(),
prompt: "让角色向镜头挥手".to_string(),
actual_prompt: Some("让角色向镜头挥手".to_string()),
model: "kling3.0-omni".to_string(),
provider: "VectorEngine".to_string(),
task_id: "task-1".to_string(),
duration_seconds: 5,
resolution: "480p".to_string(),
price_mud_points: 50,
})
.expect("response should serialize");
assert_eq!(
payload["videoSrc"],
json!("/generated-editor-videos/task-1/preview.mp4")
);
assert_eq!(payload["sourceType"], json!("generated"));
assert_eq!(payload["durationSeconds"], json!(5));
}
#[test]
fn character_workflow_cache_response_keeps_legacy_shape() {
let payload = serde_json::to_value(CharacterWorkflowCacheSaveResponse {