fix(media,canvas): 对象存储参考素材优先 URL,并同步清理图片引用提示词 (#434)
* fix(media): 对象存储参考素材 - 协议支持 URL 时优先发送签名链接 开启对象存储后,支持 URL 的上游协议优先发送 OSS/COS/七牛/S3 签名链接;必须传字节的协议仍走 Base64 或 multipart。本地资源回退字节,签发失败直接报错,不自动重发生成。 * fix(canvas): 图片引用清理 - 取消引用时同步移除提示词引用
This commit is contained in:
@@ -51,6 +51,100 @@ export function normalizeCanvasNodeMentionTokens(prompt: string, references: Can
|
||||
}, prompt);
|
||||
}
|
||||
|
||||
const CANVAS_RESOURCE_MENTION_BOUNDARY = /(?:$|\s|[,.!?;:,。!?;:、)\]}】)])/;
|
||||
|
||||
function canvasResourceReferenceMentionTokens(reference: CanvasResourceReference) {
|
||||
const tokens = [canvasResourceMentionToken(reference), `@${reference.label}`];
|
||||
if (reference.nodeId && !reference.assetId && reference.kind !== "skill") tokens.push(canvasNodeMentionToken(reference.nodeId));
|
||||
return [...new Set(tokens.filter(Boolean))];
|
||||
}
|
||||
|
||||
function removeCanvasResourceReferenceTokens(prompt: string, references: CanvasResourceReference[]) {
|
||||
return compactRemovedCanvasMentionPrompt(references.reduce((value, reference) => {
|
||||
let next = value;
|
||||
for (const token of canvasResourceReferenceMentionTokens(reference)) {
|
||||
next = removeCanvasMentionToken(next, token);
|
||||
}
|
||||
return next;
|
||||
}, prompt));
|
||||
}
|
||||
|
||||
function rewriteCanvasPromptAfterReferenceChange(prompt: string, previousReferences: CanvasResourceReference[], nextReferences: CanvasResourceReference[]) {
|
||||
const previousCanvasReferences = canvasNodeBoundReferences(previousReferences);
|
||||
const nextCanvasReferences = canvasNodeBoundReferences(nextReferences);
|
||||
if (!previousCanvasReferences.length || !canvasReferenceIdentityChanged(previousCanvasReferences, nextCanvasReferences)) return prompt;
|
||||
|
||||
const nextByNodeId = new Map(nextCanvasReferences.map((reference) => [reference.nodeId, reference]));
|
||||
let value = prompt;
|
||||
previousCanvasReferences.forEach((reference) => {
|
||||
const nodeToken = canvasNodeMentionToken(reference.nodeId);
|
||||
canvasResourceReferenceMentionTokens(reference).forEach((token) => {
|
||||
if (token !== nodeToken) value = replaceCanvasMentionToken(value, token, nodeToken);
|
||||
});
|
||||
});
|
||||
previousCanvasReferences.filter((reference) => !nextByNodeId.has(reference.nodeId)).forEach((reference) => {
|
||||
value = removeCanvasMentionToken(value, canvasNodeMentionToken(reference.nodeId));
|
||||
});
|
||||
value = normalizeCanvasNodeMentionTokens(value, nextCanvasReferences);
|
||||
return value === prompt ? prompt : compactRemovedCanvasMentionPrompt(value);
|
||||
}
|
||||
|
||||
export function applyCanvasConnectionPromptSync(previousNodes: CanvasNodeData[], previousConnections: CanvasConnection[], nextNodes: CanvasNodeData[], nextConnections: CanvasConnection[]) {
|
||||
const previousMap = buildCanvasNodeMentionReferenceMap(previousNodes, previousConnections, nextNodes);
|
||||
const nextMap = buildCanvasNodeMentionReferenceMap(nextNodes, nextConnections, nextNodes);
|
||||
let changed = false;
|
||||
const mapped = nextNodes.map((node) => {
|
||||
const previousPrompt = node.metadata?.composerContent ?? node.metadata?.prompt ?? "";
|
||||
const nextPrompt = rewriteCanvasPromptAfterReferenceChange(previousPrompt, previousMap.get(node.id) || [], nextMap.get(node.id) || []);
|
||||
if (nextPrompt === previousPrompt) return node;
|
||||
changed = true;
|
||||
return writeCanvasNodePrompt(node, nextPrompt);
|
||||
});
|
||||
return changed ? mapped : nextNodes;
|
||||
}
|
||||
|
||||
function canvasNodeBoundReferences(references: CanvasResourceReference[]) {
|
||||
return references.filter((reference) => reference.nodeId && !reference.assetId && reference.kind !== "skill");
|
||||
}
|
||||
|
||||
function canvasReferenceIdentityChanged(previousReferences: CanvasResourceReference[], nextReferences: CanvasResourceReference[]) {
|
||||
if (previousReferences.length !== nextReferences.length) return true;
|
||||
const nextLabelByNodeId = new Map(nextReferences.map((reference) => [reference.nodeId, reference.label]));
|
||||
return previousReferences.some((reference) => nextLabelByNodeId.get(reference.nodeId) !== reference.label);
|
||||
}
|
||||
|
||||
function writeCanvasNodePrompt(node: CanvasNodeData, prompt: string) {
|
||||
const hasExistingContent = (node.type === CanvasNodeType.Text && Boolean(node.metadata?.content?.trim())) || (node.type === CanvasNodeType.Image && Boolean(node.metadata?.content));
|
||||
const promptTemplateMetadata = node.metadata?.promptTemplateOperation
|
||||
? { promptTemplateOperation: undefined, promptTemplateVariables: undefined }
|
||||
: {};
|
||||
return {
|
||||
...node,
|
||||
metadata: hasExistingContent
|
||||
? { ...node.metadata, ...promptTemplateMetadata, composerContent: prompt }
|
||||
: { ...node.metadata, ...promptTemplateMetadata, prompt, composerContent: prompt },
|
||||
};
|
||||
}
|
||||
|
||||
function removeCanvasMentionToken(value: string, token: string) {
|
||||
return replaceCanvasMentionToken(value, token, "");
|
||||
}
|
||||
|
||||
function replaceCanvasMentionToken(value: string, token: string, replacement: string) {
|
||||
if (!token) return value;
|
||||
const escapedToken = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
return value.replace(new RegExp(`${escapedToken}(?=${CANVAS_RESOURCE_MENTION_BOUNDARY.source})`, "gu"), replacement);
|
||||
}
|
||||
|
||||
function compactRemovedCanvasMentionPrompt(value: string) {
|
||||
return value
|
||||
.replace(/[ \t]+\n/g, "\n")
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.replace(/[ \t]{2,}/g, " ")
|
||||
.replace(/^[ \t]+|[ \t]+$/g, "")
|
||||
.replace(/^\n+|\n+$/g, "");
|
||||
}
|
||||
|
||||
export function buildAssetMentionReferences(assets: Asset[]): CanvasResourceReference[] {
|
||||
return assets.flatMap((asset): CanvasResourceReference[] => {
|
||||
if (asset.kind === "model") return [];
|
||||
|
||||
Reference in New Issue
Block a user