fix(canvas): 角色卡引用 - 修复三视图读取与本地图片生成(#650)

fix(canvas): 角色卡引用 - 修复三视图读取与本地图片生成
This commit is contained in:
ddcat666
2026-09-30 11:58:54 +08:00
committed by GitHub
22 changed files with 686 additions and 28 deletions

View File

@@ -10,7 +10,7 @@ import { isNeutralColorGrade, resolveCanvasColorGradeReference } from "@/lib/can
import { getNodeResourceKind } from "@/lib/canvas/node-registry";
import { mediaConversionSourceFingerprint } from "@/lib/media-conversion/contracts";
import { resolveCanvasDrawingReference } from "@/lib/canvas/canvas-drawing-reference";
import { compileCharacterReferencePrompt } from "@/lib/canvas/canvas-character-reference";
import { compileCharacterReferencePrompt, normalizeCharacterImageMentions } from "@/lib/canvas/canvas-character-reference";
import { nodeReferenceImage } from "@/lib/canvas/canvas-project-generation";
import { isCanvasWorkflowProvider } from "@/lib/canvas/canvas-workflow";
import { audioFileExtension } from "@/lib/character-voice-formats";
@@ -301,11 +301,13 @@ export function generationInputMentionLabel(input: NodeGenerationInput, inputs:
}
export function normalizeGenerationNodeMentionTokens(prompt: string, inputs: NodeGenerationInput[]) {
const labelByNodeId = new Map(generationSlotEntries(inputs).map(({ input, label }) => [input.nodeId, label]));
return prompt.replace(/@\[node:([^\]]+)\]/g, (token, nodeId: string) => {
const slots = generationSlotEntries(inputs);
const labelByNodeId = new Map(slots.map(({ input, label }) => [input.nodeId, label]));
const normalized = prompt.replace(/@\[node:([^\]]+)\]/g, (token, nodeId: string) => {
const label = labelByNodeId.get(nodeId);
return label ? `@${label}` : token;
});
return normalizeCharacterImageMentions(normalized, slots.filter(({ input }) => input.type === "image").length, slots.filter(({ input }) => input.type === "character").map(({ label }) => label));
}
function hasResolvableGenerationMention(prompt: string, inputs: NodeGenerationInput[]) {

View File

@@ -18,6 +18,14 @@ export type CharacterBreakdown = {
voiceTimbre: string;
};
export function normalizeCharacterImageMentions(prompt: string, imageCount: number, characterLabels: string[]) {
if (imageCount > 0 || !characterLabels.length) return prompt;
return prompt.replace(/@图片([1-9]\d*)(?=$|\s|[,.!?;:,。!?;:、)\]}】)])/g, (token, index: string) => {
const label = characterLabels[Number(index) - 1];
return label ? "@" + label : token;
});
}
export function refreshCanvasCharacterReferenceNodes(nodes: CanvasNodeData[], assets: ProjectAsset[]) {
const characters = new Map(assets.filter((asset) => asset.category === "character" && asset.character).map((asset) => [asset.id, asset]));
let changed = false;

View File

@@ -1,4 +1,5 @@
import { imageReferenceLabel } from "@/lib/image-reference-prompt";
import { normalizeCharacterImageMentions } from "@/lib/canvas/canvas-character-reference";
import { canvasNodeVideoPreviewUrl, canvasVideoAssetPreviewUrl } from "@/lib/canvas/canvas-media-preview";
import { writeCanvasNodePrompt } from "@/lib/canvas/canvas-node-prompt";
import { getNodeResourceKind } from "@/lib/canvas/node-registry";
@@ -113,10 +114,12 @@ export function canvasResourceMentionToken(reference: CanvasResourceReference) {
}
export function normalizeCanvasNodeMentionTokens(prompt: string, references: CanvasResourceReference[]) {
return references.reduce((value, reference) => {
const normalized = references.reduce((value, reference) => {
if (!reference.nodeId || reference.assetId || reference.kind === "skill" || reference.kind === "tool") return value;
return value.split(canvasNodeMentionToken(reference.nodeId)).join(`@${reference.label}`);
}, prompt);
const active = references.filter((reference) => reference.active && !reference.assetId);
return normalizeCharacterImageMentions(normalized, active.filter((reference) => reference.kind === "image").length, active.filter((reference) => reference.kind === "character").map((reference) => reference.label));
}
/** 将提示词内已连接素材的名称替换为对应引用;同名的每处指代都会保留为独立引用。 */