fix: preserve SnapGen reference image order
This commit is contained in:
parent
91a855c041
commit
560491aabb
@ -120,6 +120,7 @@ import {
|
||||
shouldBypassCanvasImagePointsPrecheckForAgentControl,
|
||||
shouldPreserveCanvasAgentSlotOnlyContext,
|
||||
} from "@/lib/canvas/agent-client-intent";
|
||||
import { orderItemsBySelectionIds } from "@/lib/canvas/selection-order";
|
||||
import { getCanvasSkillIconComponent } from "@/components/canvas/canvas-skill-icons";
|
||||
import { ImageParamsBar, type ImageGenerationParams } from "@/components/image-params-bar";
|
||||
import { VideoParamsBar, type VideoGenerationParams } from "@/components/video-params-bar";
|
||||
@ -2190,13 +2191,16 @@ export function CanvasChatPanel({
|
||||
);
|
||||
|
||||
// 获取选中的图片节点
|
||||
const selectedImageNodes = useMemo(
|
||||
() =>
|
||||
const selectedImageNodes = useMemo(() => {
|
||||
const selectedNodeIdSet = new Set(selectedNodeIds);
|
||||
return orderItemsBySelectionIds(
|
||||
nodes.filter(
|
||||
(node) => selectedNodeIds.includes(node.id) && isCanvasImageReadyForAi(node)
|
||||
(node) => selectedNodeIdSet.has(node.id) && isCanvasImageReadyForAi(node)
|
||||
),
|
||||
[nodes, selectedNodeIds]
|
||||
);
|
||||
selectedNodeIds,
|
||||
(node) => node.id,
|
||||
);
|
||||
}, [nodes, selectedNodeIds]);
|
||||
const selectedImageNodesRef = useRef<CanvasNode[]>([]);
|
||||
const generationAnchorNodesRef = useRef<CanvasNode[]>([]);
|
||||
|
||||
@ -6410,9 +6414,11 @@ export function CanvasChatPanel({
|
||||
|
||||
const MAX_CONTEXT_IMAGES = 12;
|
||||
const MAX_AGENT_CANDIDATE_NODES = 30;
|
||||
const selectedCanvasNodes = nodes
|
||||
.filter((node) => selectedNodeIdSet.has(node.id))
|
||||
.slice(0, 20);
|
||||
const selectedCanvasNodes = orderItemsBySelectionIds(
|
||||
nodes.filter((node) => selectedNodeIdSet.has(node.id)),
|
||||
effectiveSelectedNodeIds,
|
||||
(node) => node.id,
|
||||
).slice(0, 20);
|
||||
const selectedContextNodes = selectedImageNodesForContext.slice(0, 20);
|
||||
generationAnchorNodesRef.current = selectedContextNodes.slice(0, 4);
|
||||
const selectedIds = new Set(selectedContextNodes.map((node) => node.id));
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import "server-only";
|
||||
|
||||
import { extractImageUrlsFromText, inferOriginalImageUrl } from "@/lib/image-url";
|
||||
import { orderItemsBySelectionIds } from "@/lib/canvas/selection-order";
|
||||
import type { DBMessage } from "@/lib/db/schema";
|
||||
import type {
|
||||
CanvasImageNodeContext,
|
||||
@ -228,9 +229,16 @@ export function resolveChatMediaInput(
|
||||
);
|
||||
|
||||
const canvasImageRefs = toCanvasImageRefs(params.contextPayload?.canvasImageNodes, maxImages);
|
||||
const selectedNodeIds = new Set(params.contextPayload?.selectedNodeIds || []);
|
||||
const selectedNodeIds = (params.contextPayload?.selectedNodeIds || [])
|
||||
.map((id) => String(id || "").trim())
|
||||
.filter(Boolean);
|
||||
const selectedNodeIdSet = new Set(selectedNodeIds);
|
||||
const selectedImageRefs = dedupeRefs(
|
||||
canvasImageRefs.filter((ref) => ref.isSelected || (ref.nodeId && selectedNodeIds.has(ref.nodeId))),
|
||||
orderItemsBySelectionIds(
|
||||
canvasImageRefs.filter((ref) => ref.isSelected || (ref.nodeId && selectedNodeIdSet.has(ref.nodeId))),
|
||||
selectedNodeIds,
|
||||
(ref) => ref.nodeId,
|
||||
),
|
||||
maxImages,
|
||||
);
|
||||
const history = extractHistoryImageRefs({
|
||||
|
||||
@ -2,6 +2,7 @@ import "server-only";
|
||||
|
||||
import sharp from "sharp";
|
||||
import { uploadFile } from "@/lib/storage/service";
|
||||
import { buildSnapgenReferenceOrderPrompt } from "@/lib/ai/providers/snapgen-reference-order";
|
||||
import {
|
||||
SNAPGEN_GPT_IMAGE_2_REQUEST_ASPECT_RATIOS,
|
||||
SNAPGEN_STANDARD_REQUEST_ASPECT_RATIOS,
|
||||
@ -876,9 +877,10 @@ export function createSnapgenImage({ apiKey, baseURL }: SnapgenImageConfig) {
|
||||
const outputFormat = normalizeOutputFormat(options.imageParams?.outputFormat);
|
||||
const maxImages = options.imageGenConfig?.maxImages || (apiFormat === "snapgen-grok-image" ? 8 : apiFormat === "snapgen-meta-ai-image" ? 4 : 1);
|
||||
const imageCount = clampImageCount(options.imageParams?.imageCount, maxImages);
|
||||
const promptForRequest = buildSnapgenReferenceOrderPrompt(prompt, imageUrls);
|
||||
const formData = await buildRequestForm({
|
||||
apiFormat,
|
||||
prompt,
|
||||
prompt: promptForRequest,
|
||||
runtimeModelId,
|
||||
imageUrls,
|
||||
aspectRatio,
|
||||
@ -908,6 +910,7 @@ export function createSnapgenImage({ apiKey, baseURL }: SnapgenImageConfig) {
|
||||
: apiFormat === "snapgen-generic-image"
|
||||
? "file_urls"
|
||||
: "files",
|
||||
referenceOrderPrompt: promptForRequest !== prompt,
|
||||
});
|
||||
|
||||
const createPayload = await requestSnapgenMultipart({
|
||||
|
||||
14
lib/ai/providers/snapgen-reference-order.ts
Normal file
14
lib/ai/providers/snapgen-reference-order.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export function buildSnapgenReferenceOrderPrompt(prompt: string, imageUrls: string[]) {
|
||||
const normalizedPrompt = String(prompt || "").trim();
|
||||
const referenceCount = imageUrls.filter((url) => String(url || "").trim()).length;
|
||||
if (referenceCount < 2) return normalizedPrompt;
|
||||
|
||||
return [
|
||||
`参考图顺序说明:本次请求包含 ${referenceCount} 张参考图,顺序与上传/选中顺序一致。`,
|
||||
"第 1 张参考图优先作为主参考;如果用户提到“第一张图/图1/主图/原图”,对应第 1 张参考图,除非用户明确另行指定。",
|
||||
"第 2 张及后续参考图仅作为辅助参考,按用户提示使用。",
|
||||
normalizedPrompt,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
40
lib/canvas/selection-order.ts
Normal file
40
lib/canvas/selection-order.ts
Normal file
@ -0,0 +1,40 @@
|
||||
export function orderItemsBySelectionIds<T>(
|
||||
items: T[],
|
||||
selectedIds: string[],
|
||||
getItemId: (item: T) => string | null | undefined,
|
||||
) {
|
||||
const normalizedSelectedIds = Array.from(
|
||||
new Set(
|
||||
selectedIds
|
||||
.map((id) => String(id || "").trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
);
|
||||
if (normalizedSelectedIds.length === 0) return items;
|
||||
|
||||
const itemById = new Map<string, T>();
|
||||
for (const item of items) {
|
||||
const id = String(getItemId(item) || "").trim();
|
||||
if (id && !itemById.has(id)) {
|
||||
itemById.set(id, item);
|
||||
}
|
||||
}
|
||||
|
||||
const orderedItems: T[] = [];
|
||||
const orderedItemSet = new Set<T>();
|
||||
for (const id of normalizedSelectedIds) {
|
||||
const item = itemById.get(id);
|
||||
if (item && !orderedItemSet.has(item)) {
|
||||
orderedItems.push(item);
|
||||
orderedItemSet.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
if (!orderedItemSet.has(item)) {
|
||||
orderedItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return orderedItems;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user