Files
Genarrative/src/components/image-editor/ImageCanvasMetadataModalView.tsx
T
lhk229 bcff8f1957
Project CI / Native shell tests (push) Successful in 13m23s
Project CI / Repository checks (push) Successful in 1m12s
Project CI / Frontend tests (push) Successful in 2m55s
Project CI / Backend tests (push) Successful in 4m0s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
修复 SFX 音效信息页元数据整块丢失 (#153)
读边界的内联媒体脱敏在每层对象上无差别删除 null 值,把 SFX 自动时长 metadata 里
合法的 requestedDurationSeconds: null 一并删掉;客户端据此判定整份 soundEffect 非法,
音频信息页的「生成输入」退化成空,Task 也被截成 0 这类误导值。手动指定时长的音效不受
影响,图片路径的 generationInputs 没有可为 null 的字段,所以只有部分 SFX 可见。

- sanitize_editor_payload_media_value 改为返回「是否被抹除」,只删除本次抹掉的内联
  媒体,调用方原有的显式 null 保留;脱敏强度不变,内联媒体与保留字段仍连键消失
- SFX 自动时长的 requestedDurationSeconds 接受显式 null 与整个键缺失两种同义形态
- 音频信息页 Task 按图层 assetKind 判定,不再随 soundEffect 元数据缺失降级成截断值

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/153
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-08-09 13:47:16 +08:00

130 lines
5.5 KiB
TypeScript

import { EDITOR_SOUND_EFFECT_MODEL } from '../../../packages/shared/src/contracts/editorAudio';
import { UnifiedModal } from '../common/UnifiedModal';
import type { CanvasLayer } from './ImageCanvasEditorTypes';
import { formatTaskIdForDisplay } from './ImageCanvasExportModel';
import {
formatEditorGenerationInputFieldValue,
formatLayerImageType,
getEditorLayerModelDisplayName,
isEditorUserVisibleGenerationInputField,
} from './ImageCanvasGenerationModel';
export type ImageCanvasMetadataModalViewProps = {
layer: CanvasLayer | null;
onClose: () => void;
};
function formatReferenceType(refType: 'project-resource' | 'asset') {
return refType === 'project-resource' ? '项目资源' : '素材';
}
export function ImageCanvasMetadataModalView({
layer,
onClose,
}: ImageCanvasMetadataModalViewProps) {
const visibleGenerationInputFields =
layer?.generationInputs?.fields.filter(
isEditorUserVisibleGenerationInputField,
) ?? [];
const generationInputReferences = layer?.generationInputs?.references ?? [];
const mediaLabel =
layer?.mediaType === 'audio'
? '音频'
: layer?.mediaType === 'video'
? '视频'
: layer?.mediaType === 'image-sequence'
? '序列帧'
: '图片';
// TODO: Remove this override after full dark style support.
return (
<UnifiedModal
open={Boolean(layer)}
title={`${mediaLabel}信息`}
size="sm"
portalTheme="light"
closeLabel={`关闭${mediaLabel}信息`}
onClose={onClose}
panelClassName="image-canvas-editor__metadata-dialog"
bodyClassName="image-canvas-editor__metadata-body"
>
{layer ? (
<dl className="image-canvas-editor__metadata-grid">
<dt>{mediaLabel}类型</dt>
<dd>{formatLayerImageType(layer)}</dd>
<dt>生成输入</dt>
<dd className="image-canvas-editor__metadata-inputs">
{visibleGenerationInputFields.length ||
generationInputReferences.length ? (
<>
{visibleGenerationInputFields.map((field) => (
<div
key={`${field.title}-${field.value}`}
className="image-canvas-editor__metadata-input-field"
>
<span className="image-canvas-editor__metadata-input-title">
{field.title}
</span>
<span>{formatEditorGenerationInputFieldValue(field)}</span>
</div>
))}
{generationInputReferences.length ? (
<div className="image-canvas-editor__metadata-reference-list">
{generationInputReferences.map((reference) => (
<div
key={`${reference.title}-${reference.label ?? ''}-${reference.refType}-${reference.refId}`}
className="image-canvas-editor__metadata-reference-card"
>
<span className="image-canvas-editor__metadata-reference-copy">
<span className="image-canvas-editor__metadata-input-title">
{reference.title}
</span>
<span>{reference.label?.trim() || '参考素材'}</span>
<span className="image-canvas-editor__metadata-reference-id">
{formatReferenceType(reference.refType)} ·{' '}
{reference.refId}
</span>
</span>
</div>
))}
</div>
) : null}
</>
) : (
'-'
)}
</dd>
<dt>Model</dt>
<dd>{getEditorLayerModelDisplayName(layer.model)}</dd>
{layer.mediaType !== 'audio' ? (
<>
<dt>Resolution</dt>
<dd>
{layer.originalWidth} x {layer.originalHeight} px
</dd>
</>
) : null}
<dt>Task</dt>
<dd>
{/* 中文注释:音效的 taskId 是 operation id 形态,`formatTaskIdForDisplay` 的
「取最后一段数字」会把它截成 `0`、`1234` 这类误导值。判据要回答的是「这个
taskId 由谁生成」,而不是「这个图层被归成哪一类」,所以落在 model 上:它是
资源行权威字段(registered 图层的 serializeLayer 不回写它),改「素材标签」
不影响它,水合时 resolveHydratedLayerModel 也优先取资源行的值;后端
canonicalize_editor_sound_effect_model 只接受 eleven_text_to_sound_v2 一个取值。
用 assetKind / resourceAssetKind 判都踩过坑:前者是 assetKindOverride ??
resourceAssetKind,用户改一次标签就翻转;后者在 legacy 布局(快照没有
assetKindOverride 键,旧 assetKind 被当成 override)和资源行查不到时都是 null。
soundEffect 那一支只是补充——它和生成 action 一样长在 generationInputs 上,
元数据整块缺失时会一并消失,而那正是这里要兜住的场景。 */}
{layer.model === EDITOR_SOUND_EFFECT_MODEL ||
layer.generationInputs?.soundEffect?.schemaVersion === 2
? layer.taskId || '-'
: formatTaskIdForDisplay(layer.taskId)}
</dd>
</dl>
) : null}
</UnifiedModal>
);
}