Merge remote-tracking branch 'origin/pr-579' into codex/pr-557-589-security-integration

This commit is contained in:
ddCat
2026-09-22 11:05:07 +08:00
3 changed files with 104 additions and 8 deletions

View File

@@ -139,25 +139,72 @@ function parseStoryboardRowsPayload(raw: string): { title?: string; rows?: Array
}
}
}
if (parsed.text && typeof parsed.text === "object") {
try {
const nested = parseStoryboardRowsPayload(JSON.stringify(parsed.text));
if (Array.isArray(nested.rows)) return nested;
} catch {
// 对象形式的 text 不是分镜载荷时,交由统一缺行校验报错。
}
}
return parsed;
}
function normalizeStoryboardText(value: unknown): string {
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
if (Array.isArray(value)) return value.map(normalizeStoryboardText).filter(Boolean).join("、");
if (!value || typeof value !== "object") return "";
const record = value as Record<string, unknown>;
for (const key of ["text", "content", "value", "description", "name"]) {
if (!(key in record)) continue;
const nested = normalizeStoryboardText(record[key]);
if (nested) return nested;
}
return "";
}
function normalizeStoryboardRow(row: Partial<StoryboardRow>): Partial<StoryboardRow> {
return {
...row,
plotDescription: normalizeStoryboardText(row.plotDescription),
dialogue: normalizeStoryboardText(row.dialogue),
narrativeIntent: normalizeStoryboardText(row.narrativeIntent),
viewerPOV: normalizeStoryboardText(row.viewerPOV),
performanceBlocking: normalizeStoryboardText(row.performanceBlocking),
shotSize: normalizeStoryboardText(row.shotSize),
emotion: normalizeStoryboardText(row.emotion),
lightingAndAtmosphere: normalizeStoryboardText(row.lightingAndAtmosphere),
audioEffects: normalizeStoryboardText(row.audioEffects),
camera: normalizeStoryboardText(row.camera),
motion: normalizeStoryboardText(row.motion),
timeBeats: normalizeStoryboardText(row.timeBeats),
imageGenerationPrompt: normalizeStoryboardText(row.imageGenerationPrompt),
videoMotionPrompt: normalizeStoryboardText(row.videoMotionPrompt),
continuityOut: normalizeStoryboardText(row.continuityOut),
negativePrompt: normalizeStoryboardText(row.negativePrompt),
};
}
export function storyboardRowsFromTask(task: GenerationTask) {
const result = parseStoryboardRowsPayload(task.resultJson || "{}");
if (!Array.isArray(result.rows) || !result.rows.length) throw new Error("分镜任务没有返回镜头行");
return {
title: result.title?.trim(),
// 旧任务和部分模型会把 title 返回成对象/数组;恢复路径必须只接受字符串,
// 否则可选链只能防 undefined,仍会在对象上调用 trim 使整页恢复失败。
title: typeof result.title === "string" ? result.title.trim() : undefined,
rows: result.rows.map((row, index) => {
const source = row && typeof row === "object" ? row : {};
const next = createStoryboardRow(index + 1, {
...row,
...normalizeStoryboardRow(source),
id: `shot-${Date.now()}-${index + 1}-${Math.random().toString(36).slice(2, 6)}`,
shotNumber: index + 1,
status: "idle",
assetBindings: normalizeStoryboardAssetBindings(row.assetBindings),
assetBindings: normalizeStoryboardAssetBindings(Array.isArray(source.assetBindings) ? source.assetBindings : undefined),
});
next.characters = Array.isArray(row.characters) ? row.characters : [];
next.mustHave = Array.isArray(row.mustHave) ? row.mustHave : [];
next.optionalDetails = Array.isArray(row.optionalDetails) ? row.optionalDetails : [];
next.characters = Array.isArray(source.characters) ? source.characters : [];
next.mustHave = Array.isArray(source.mustHave) ? source.mustHave : [];
next.optionalDetails = Array.isArray(source.optionalDetails) ? source.optionalDetails : [];
return next;
}),
};

View File

@@ -498,7 +498,27 @@ function omittedImageQuality(value: string | undefined) {
export function parseBackendGenerationResult(task: GenerationTask): BackendGenerationResult {
if (!task.resultJson) throw new Error("后端任务没有返回结果");
const result = JSON.parse(task.resultJson) as BackendGenerationResult;
const result = JSON.parse(task.resultJson) as BackendGenerationResult & { text?: unknown };
if (!result || typeof result !== "object") throw new Error("后端任务结果格式错误");
return result;
return { ...result, text: normalizeBackendText(result.text) };
}
function normalizeBackendText(value: unknown): string | undefined {
if (typeof value === "string") return value;
if (value === null || value === undefined) return undefined;
if (Array.isArray(value)) {
const text = value.map((item) => normalizeBackendText(item)).filter((item): item is string => Boolean(item)).join("");
return text || undefined;
}
if (typeof value === "object") {
const record = value as Record<string, unknown>;
for (const key of ["text", "content", "output_text", "value"]) {
if (!(key in record)) continue;
const nested = normalizeBackendText(record[key]);
if (nested !== undefined) return nested;
}
// 某些结构化文本任务直接把 JSON 载荷放进 text 对象,保留 JSON 供上层契约解析。
return JSON.stringify(value);
}
return String(value);
}