修正动画轮询精简结果的文档与测试
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
删除未被接口引用的旧动画生成响应定义 明确顶层帧与秒级时长及正式序列记录读取路径 修正 Python 自测并补充动画精简结果回归 同步外部生成方案与共享排障记忆 关联 #495 第二项
This commit is contained in:
@@ -134,7 +134,7 @@ A minimal `canvasCompletion` is:
|
||||
|
||||
Background removal preserves the source image dimensions. For normal canvas placement, the Python helper therefore requires the real `source_width` and `source_height` whenever `canvasSession` is used without an explicit `canvasWidth` plus `canvasHeight`; it never substitutes a square default. Passing `targetLayerId` instead selects in-place replacement, so the helper keeps the session's project/library fields without injecting `canvasCompletion` and rejects callers that explicitly combine both placement modes. The request `assetKind` is optional, static-image only, and must equal the authoritative source type when one exists. An in-place target must resolve to the same authoritative source object; a raw object key is bound to that target resource instead of relying on project-list order.
|
||||
|
||||
Character animation accepts `assetFolderId` and `assetLabel` and persists the generated sequence. Consume the returned animation artifacts and persisted identities; do not synthesize a duplicate animation asset from the first frame. Use complete project/library records when complete persisted state is needed.
|
||||
Character animation accepts `assetFolderId` and `assetLabel` and persists the generated sequence. In a completed polling response, use `result.frames` for the ordered frame artifacts and `result.durationSeconds` for the duration in seconds. The nested `result.resource` and `result.asset` are compact references, not complete records; they do not include `imageSequenceFrames` or `imageSequenceDurationMs`. Read the complete project (`GET /api/external/v1/editor/projects/{projectId}`) or asset library (`GET /api/external/v1/editor/assets/library`) and match `resourceId` or `assetId` to obtain those formal sequence fields, whose duration is in milliseconds. MCP equivalents are `find_assets/get_project_resources` and `find_assets/list_library`. Do not synthesize a duplicate animation asset from the first frame.
|
||||
|
||||
For the lower-level asset/resource creation endpoints, `generationInputs` follows the metadata and media-runtime boundaries described in [Generation Inputs Metadata](#generation-inputs-metadata).
|
||||
|
||||
|
||||
@@ -795,25 +795,20 @@ def _self_test() -> None:
|
||||
"model": "seedance2.0-fast",
|
||||
"prompt": "角色呼吸",
|
||||
"previewVideoPath": "/generated/preview.mp4",
|
||||
"frames": [{"imageSrc": "/generated/frame01.png", "width": 512, "height": 768}],
|
||||
"frames": [
|
||||
{"imageSrc": "/generated/frame01.png", "width": 512, "height": 768},
|
||||
{"imageSrc": "/generated/frame02.png", "width": 512, "height": 768},
|
||||
],
|
||||
"frameCount": 2,
|
||||
"durationSeconds": 4,
|
||||
"resource": {
|
||||
"resourceId": "editor-resource-demo",
|
||||
"assetKind": "character-animation",
|
||||
"sourceResourceId": "editor-resource-preview-demo",
|
||||
"imageSequenceFrames": [
|
||||
{"imageSrc": "/generated/frame01.png", "width": 512, "height": 768},
|
||||
{"imageSrc": "/generated/frame02.png", "width": 512, "height": 768},
|
||||
],
|
||||
"imageSequenceDurationMs": 4000,
|
||||
},
|
||||
"asset": {
|
||||
"assetId": "editor-asset-demo",
|
||||
"assetKind": "character-animation",
|
||||
"imageSequenceFrames": [
|
||||
{"imageSrc": "/generated/frame01.png", "width": 512, "height": 768},
|
||||
{"imageSrc": "/generated/frame02.png", "width": 512, "height": 768},
|
||||
],
|
||||
"imageSequenceDurationMs": 4000,
|
||||
},
|
||||
}
|
||||
if method == "POST":
|
||||
@@ -841,8 +836,13 @@ def _self_test() -> None:
|
||||
assert calls[1]["path"] == "/api/external/v1/generations/task-operation-demo"
|
||||
assert result["asset"]["assetId"] == "editor-asset-demo"
|
||||
assert result["asset"]["assetKind"] == "character-animation"
|
||||
assert len(result["asset"]["imageSequenceFrames"]) == 2
|
||||
assert result["asset"]["imageSequenceDurationMs"] == 4000
|
||||
assert result["resource"]["resourceId"] == "editor-resource-demo"
|
||||
assert result["frames"] == [
|
||||
{"imageSrc": "/generated/frame01.png", "width": 512, "height": 768},
|
||||
{"imageSrc": "/generated/frame02.png", "width": 512, "height": 768},
|
||||
]
|
||||
assert result["frameCount"] == 2
|
||||
assert result["durationSeconds"] == 4
|
||||
calls.clear()
|
||||
background_result = client.remove_background(
|
||||
"uploads/source.png",
|
||||
|
||||
@@ -4114,113 +4114,6 @@
|
||||
"additionalProperties": false,
|
||||
"description": "图片序列帧。数组位置是唯一播放顺序,不携带额外序号字段;每帧必须同时携带 objectKey 与 assetObjectId,imageSrc 按 objectKey 规范化为持久站内路径。"
|
||||
},
|
||||
"EditorCharacterAnimationGenerationResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ok",
|
||||
"taskId",
|
||||
"model",
|
||||
"prompt",
|
||||
"previewVideoPath",
|
||||
"frames",
|
||||
"frameCount",
|
||||
"durationSeconds",
|
||||
"frameWidth",
|
||||
"frameHeight",
|
||||
"fps",
|
||||
"priceMudPoints"
|
||||
],
|
||||
"properties": {
|
||||
"ok": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"taskId": {
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string"
|
||||
},
|
||||
"previewVideoPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"frames": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/EditorImageSequenceFrame"
|
||||
}
|
||||
},
|
||||
"frameCount": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"durationSeconds": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"frameWidth": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"frameHeight": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"fps": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"priceMudPoints": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"project": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EditorProject"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "当请求携带 canvasCompletion 且服务端成功写入画布布局时返回最新项目快照。"
|
||||
},
|
||||
"resource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EditorProjectResource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "最终透明角色动作对应的项目资源。携带 projectId 时,画布图层必须直接引用其 resourceId,不得再次创建重复资源。"
|
||||
},
|
||||
"asset": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EditorAsset"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "最终透明角色动作素材。assetKind 为 character-animation,并直接包含序列帧字段。"
|
||||
},
|
||||
"queueState": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ExternalGenerationJobStatusRecord"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"EditorVideoGenerationRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -4776,7 +4669,7 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": true,
|
||||
"description": "其它生成类型或历史结果的兼容 compact result。背景音乐(audioKind=background-music)与不带 audioKind 的图片、视频、图标序列帧、角色动作和 UI 拆解结果都落在这里。pixelArt 图片的 width/height、图标结果的 spritesheetWidth/spritesheetHeight 及关联 resource/asset 尺寸均表示最终逻辑分辨率 PNG 的实际值,不保证等于请求档位、provider 回图或画布 placeholder。该 fallback 明确排除 audioKind=sound-effect,避免吞掉 SFX 专用分支。"
|
||||
"description": "其它生成类型或历史结果的兼容 compact result。背景音乐(audioKind=background-music)与不带 audioKind 的图片、视频、图标序列帧、角色动作和 UI 拆解结果都落在这里。角色动作使用顶层 frames(有序帧产物)与 durationSeconds(秒级时长);内嵌 resource/asset 是精简引用,不包含 imageSequenceFrames/imageSequenceDurationMs。需要正式序列记录时,按返回的 resourceId/assetId 查询 GET /api/external/v1/editor/projects/{projectId} 或 GET /api/external/v1/editor/assets/library,正式 imageSequenceDurationMs 单位为毫秒。pixelArt 图片的 width/height、图标结果的 spritesheetWidth/spritesheetHeight 及关联 resource/asset 尺寸均表示最终逻辑分辨率 PNG 的实际值,不保证等于请求档位、provider 回图或画布 placeholder。该 fallback 明确排除 audioKind=sound-effect,避免吞掉 SFX 专用分支。"
|
||||
},
|
||||
"ExternalEditorGenerationCompletedResult": {
|
||||
"oneOf": [
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
这里只记录对当前开发仍有用的症状、根因、排查方法和风险边界。同一事实保留一个当前口径;退役对象的专属过程与单轮测试结果由 Git 历史追溯。遇到旧路径或版本时,以现行代码和专题文档为准。
|
||||
|
||||
## External 动画轮询的精简引用不等于完整序列记录
|
||||
|
||||
- 动画 completed 结果通过顶层 `frames` 和 `durationSeconds` 提供有序帧与秒级时长;内嵌 `resource/asset` 只提供精简引用。正式 `imageSequenceFrames/imageSequenceDurationMs` 从完整项目或素材库按返回的 ID 读取,后者时长单位为毫秒。内嵌引用没有这两个字段不能据此判定持久化丢帧,也不能用首帧重复创建动画素材。
|
||||
- 排查时分别核对真实精简函数、正式记录和 [External v1 OpenAPI](../../openapi/genarrative-external-v1.openapi.json);Python helper 的模拟响应必须遵循同一结构,不能用虚构的嵌套完整记录证明轮询契约成立。
|
||||
|
||||
## 2026-10-05 generationInputs 的保存与复用不等于自动生效或完整透传
|
||||
|
||||
- **现象 / 根因**:调用方把构图等要求只写入 `generationInputs.artSpec`,但实际生图输入没有这些要求;把 `JsonValue` 和“可复用规范”误读为服务端会自动组装提示词、完整保留全部元数据或自动用于下一次生成。
|
||||
|
||||
@@ -41,6 +41,8 @@ VectorEngine `gpt-image-2`、音频、LLM 等外部生成不能由面向外部
|
||||
|
||||
External API job 复用同一个 `result_payload_json` 列,但只额外保存 `result` compact 引用:允许 objectKey、resource/asset ID、assetObjectId、尺寸、媒体类型、taskId 和告警;禁止完整 project/canvas、大布局、Data URL、Blob URL、临时 signed URL、provider 原始响应和 lease/fencing 控制字段。普通站内 job 继续保持原 payload 语义,不能为了 External 查询把所有队列结果扩成第二套资产 read model。
|
||||
|
||||
角色动画的 completed compact result 通过顶层 `frames` 保留有序帧产物,通过 `durationSeconds` 保留秒级时长;内嵌 `resource/asset` 仍是精简引用,不包含 `imageSequenceFrames/imageSequenceDurationMs`。调用方需要正式序列记录时,按返回的 `resourceId/assetId` 从完整项目或素材库读取,正式时长单位为毫秒;不得把精简引用当完整记录,或根据首帧重复创建动画素材。OpenAPI、Skill 与 helper 自测共同遵循此读路径。
|
||||
|
||||
不带 `summary / summaries` 的旧 `get / list / acknowledge_external_generation_job*` procedure 只保留给受控内部兼容,不是 BFF 正式读取入口。
|
||||
|
||||
幂等读取回归使用 `npm run check:editor-idempotency-procedures`:隔离 standalone 验证 pending、running、completed 的原始请求身份、同键重放唯一任务及跨 owner / 不存在任务拒绝,不调用生成 Provider。脚本仅在临时构建目录注入公开测试 bootstrap hash;若设置 `GENARRATIVE_EDITOR_IDEMPOTENCY_SMOKE_WASM` 复用预编译模块,该模块也必须使用脚本中的测试 hash 构建,且不得用于部署。
|
||||
|
||||
@@ -19846,6 +19846,51 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_external_character_animation_result_keeps_frames_duration_and_references() {
|
||||
let frames = json!([
|
||||
{
|
||||
"imageSrc": "/generated/animation/frame01.png",
|
||||
"objectKey": "generated/animation/frame01.png",
|
||||
"assetObjectId": "asset-object-frame01",
|
||||
"width": 192,
|
||||
"height": 256
|
||||
},
|
||||
{
|
||||
"imageSrc": "/generated/animation/frame02.png",
|
||||
"objectKey": "generated/animation/frame02.png",
|
||||
"assetObjectId": "asset-object-frame02",
|
||||
"width": 256,
|
||||
"height": 192
|
||||
}
|
||||
]);
|
||||
let result = compact_external_api_generation_result(json!({
|
||||
"ok": true,
|
||||
"durationSeconds": 4,
|
||||
"frameCount": 2,
|
||||
"frames": frames,
|
||||
"resource": {
|
||||
"resourceId": "resource-animation",
|
||||
"objectKey": "generated/animation/frame01.png",
|
||||
"assetObjectId": "asset-object-frame01"
|
||||
},
|
||||
"asset": {
|
||||
"assetId": "asset-animation",
|
||||
"objectKey": "generated/animation/frame01.png",
|
||||
"assetObjectId": "asset-object-frame01"
|
||||
},
|
||||
"provider": "internal-provider"
|
||||
}));
|
||||
|
||||
assert_eq!(result["frames"], frames);
|
||||
assert_eq!(result["durationSeconds"], 4);
|
||||
assert_eq!(result["frameCount"], 2);
|
||||
assert_eq!(result["resource"]["resourceId"], "resource-animation");
|
||||
assert_eq!(result["asset"]["assetId"], "asset-animation");
|
||||
assert_eq!(result["resource"]["assetObjectId"], "asset-object-frame01");
|
||||
assert_eq!(result["asset"]["assetObjectId"], "asset-object-frame01");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game_creator_completed_jobs_keep_stable_results_for_all_current_media_kinds() {
|
||||
let fixtures = [
|
||||
|
||||
Reference in New Issue
Block a user