Fix agent mode chat request sanitization
This commit is contained in:
parent
17dcfcd55d
commit
d133af4cbe
@ -1340,11 +1340,75 @@ const DETAIL_GENERATION_MODE_OPTIONS: Array<{
|
||||
];
|
||||
|
||||
const SESSION_TITLE_MAX_LEN = 18;
|
||||
const MAX_CHAT_TEXT_PART_LENGTH = 2000;
|
||||
const MAX_CHAT_HIDDEN_PROMPT_LENGTH = 4000;
|
||||
const MAX_CHAT_AUTO_CONTEXT_LENGTH = 4000;
|
||||
const MAX_CHAT_ATTACHMENT_NAME_LENGTH = 100;
|
||||
const CHAT_UUID_REGEX =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
const ALLOWED_CHAT_MEDIA_TYPES = new Set([
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/gif",
|
||||
"image/jpg",
|
||||
"image/bmp",
|
||||
"image/svg+xml",
|
||||
]);
|
||||
|
||||
function toRawCanvasChatId(value: string): string {
|
||||
return value.startsWith("canvas-") ? value.replace("canvas-", "") : value;
|
||||
}
|
||||
|
||||
function isValidChatUuid(value?: string | null): value is string {
|
||||
return Boolean(value && CHAT_UUID_REGEX.test(String(value).trim()));
|
||||
}
|
||||
|
||||
function isValidChatHttpUrl(value?: string | null): value is string {
|
||||
if (!value) return false;
|
||||
try {
|
||||
const parsed = new URL(String(value).trim());
|
||||
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function clampChatRequestText(value?: string | null, maxLength = MAX_CHAT_TEXT_PART_LENGTH): string {
|
||||
return String(value || "").slice(0, maxLength);
|
||||
}
|
||||
|
||||
function normalizeChatAttachmentName(value?: string | null): string {
|
||||
const normalized = String(value || "").trim() || "参考图";
|
||||
return normalized.slice(0, MAX_CHAT_ATTACHMENT_NAME_LENGTH);
|
||||
}
|
||||
|
||||
function normalizeChatMediaType(value?: string | null): string {
|
||||
const normalized = String(value || "").trim().toLowerCase();
|
||||
if (normalized === "image/pjpeg") return "image/jpeg";
|
||||
if (normalized === "image/x-png") return "image/png";
|
||||
if (ALLOWED_CHAT_MEDIA_TYPES.has(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
return "image/jpeg";
|
||||
}
|
||||
|
||||
function sanitizeChatAttachment(
|
||||
attachment?: PickedObjectAttachment | null,
|
||||
): PickedObjectAttachment | null {
|
||||
if (!attachment || !isValidChatHttpUrl(attachment.url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...attachment,
|
||||
url: attachment.url,
|
||||
name: normalizeChatAttachmentName(attachment.name),
|
||||
contentType: normalizeChatMediaType(attachment.contentType),
|
||||
assetId: isValidChatUuid(attachment.assetId) ? attachment.assetId : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function getSessionStorageKey(canvasId: string, scope: "canvas" | "agent"): string {
|
||||
return `canvas-chat-sessions:${scope}:${canvasId}`;
|
||||
}
|
||||
@ -4637,9 +4701,11 @@ export function CanvasChatPanel({
|
||||
if (!launchPayload?.autoSend) return;
|
||||
if (!selectedModelId || status !== "ready") return;
|
||||
|
||||
const launchAttachments = launchPayload.attachments || [];
|
||||
const launchAttachments = (launchPayload.attachments || [])
|
||||
.map((attachment) => sanitizeChatAttachment(attachment as PickedObjectAttachment))
|
||||
.filter((attachment): attachment is PickedObjectAttachment => Boolean(attachment));
|
||||
const textContent =
|
||||
launchPayload.prompt?.trim() ||
|
||||
clampChatRequestText(launchPayload.prompt?.trim() || "", MAX_CHAT_TEXT_PART_LENGTH) ||
|
||||
(launchAttachments.length > 0 ? "请基于这些图片继续生成设计方案" : "");
|
||||
|
||||
if (!textContent && launchAttachments.length === 0) {
|
||||
@ -4649,10 +4715,10 @@ export function CanvasChatPanel({
|
||||
|
||||
const parts: any[] = launchAttachments.map((attachment) => ({
|
||||
type: "file" as const,
|
||||
mediaType: attachment.contentType || "image/jpeg",
|
||||
name: attachment.name || "参考图",
|
||||
mediaType: normalizeChatMediaType(attachment.contentType),
|
||||
name: normalizeChatAttachmentName(attachment.name),
|
||||
url: attachment.url,
|
||||
assetId: attachment.assetId,
|
||||
assetId: isValidChatUuid(attachment.assetId) ? attachment.assetId : undefined,
|
||||
}));
|
||||
|
||||
if (textContent) {
|
||||
@ -5193,36 +5259,40 @@ export function CanvasChatPanel({
|
||||
const imageNodes = nodes.filter(
|
||||
(node) =>
|
||||
node.type === "image" &&
|
||||
typeof (node.data as any)?.src === "string" &&
|
||||
String((node.data as any).src).trim().length > 0,
|
||||
isValidChatHttpUrl(String((node.data as any)?.src || "").trim()),
|
||||
);
|
||||
|
||||
if (imageNodes.length === 0) return undefined;
|
||||
|
||||
const effectiveComposerMode = activeComposerModeRef.current || activeComposerMode;
|
||||
const shouldIsolateDirectModeContext = effectiveComposerMode !== "agent";
|
||||
if (shouldIsolateDirectModeContext && selectedImageNodes.length === 0) {
|
||||
const selectedImageNodesForContext = selectedImageNodes.filter((node) =>
|
||||
isValidChatHttpUrl(String((node.data as any)?.src || "").trim())
|
||||
);
|
||||
|
||||
if (shouldIsolateDirectModeContext && selectedImageNodesForContext.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const MAX_CONTEXT_IMAGES = 12;
|
||||
const selectedIds = new Set(selectedImageNodes.map((node) => node.id));
|
||||
const selectedContextNodes = selectedImageNodesForContext.slice(0, 20);
|
||||
const selectedIds = new Set(selectedContextNodes.map((node) => node.id));
|
||||
const prioritizedNodes =
|
||||
shouldIsolateDirectModeContext
|
||||
? selectedImageNodes
|
||||
: selectedImageNodes.length > 0
|
||||
? selectedContextNodes
|
||||
: selectedContextNodes.length > 0
|
||||
? [
|
||||
...selectedImageNodes,
|
||||
...selectedContextNodes,
|
||||
...imageNodes.filter((node) => !selectedIds.has(node.id)),
|
||||
]
|
||||
: imageNodes;
|
||||
const contextNodes = prioritizedNodes.slice(0, MAX_CONTEXT_IMAGES);
|
||||
const selectedAssetIds = Array.from(
|
||||
new Set(
|
||||
selectedImageNodes
|
||||
selectedContextNodes
|
||||
.map((node) => {
|
||||
const assetId = (node.data as any)?.assetId;
|
||||
return typeof assetId === "string" && assetId.trim() ? assetId.trim() : null;
|
||||
return isValidChatUuid(assetId) ? assetId.trim() : null;
|
||||
})
|
||||
.filter((value): value is string => Boolean(value)),
|
||||
),
|
||||
@ -5230,17 +5300,16 @@ export function CanvasChatPanel({
|
||||
|
||||
return {
|
||||
source: "canvas" as const,
|
||||
projectId: canvasId || undefined,
|
||||
selectedNodeIds: selectedImageNodes.map((node) => node.id),
|
||||
projectId: isValidChatUuid(canvasId) ? canvasId : undefined,
|
||||
selectedNodeIds: selectedContextNodes.map((node) => node.id).slice(0, 20),
|
||||
selectedAssetIds,
|
||||
focusPolicy: selectedImageNodes.length > 0 ? ("selected" as const) : ("auto" as const),
|
||||
focusPolicy: selectedContextNodes.length > 0 ? ("selected" as const) : ("auto" as const),
|
||||
canvasImageNodes: contextNodes.map((node) => ({
|
||||
nodeId: node.id,
|
||||
assetId:
|
||||
typeof (node.data as any)?.assetId === "string" && (node.data as any).assetId.trim()
|
||||
? String((node.data as any).assetId).trim()
|
||||
: undefined,
|
||||
url: String((node.data as any)?.src || ""),
|
||||
assetId: isValidChatUuid((node.data as any)?.assetId)
|
||||
? String((node.data as any).assetId).trim()
|
||||
: undefined,
|
||||
url: String((node.data as any)?.src || "").trim(),
|
||||
title:
|
||||
typeof (node.data as any)?.title === "string"
|
||||
? String((node.data as any).title).slice(0, 256)
|
||||
@ -7477,7 +7546,9 @@ export function CanvasChatPanel({
|
||||
// 构建 parts 数组:文件在前,文本在后(与 multimodal-input 一致)
|
||||
const parts: any[] = [];
|
||||
|
||||
let effectiveAttachments = [...attachments];
|
||||
let effectiveAttachments = attachments
|
||||
.map((attachment) => sanitizeChatAttachment(attachment))
|
||||
.filter((attachment): attachment is PickedObjectAttachment => Boolean(attachment));
|
||||
if (hasAgentUploadSlots) {
|
||||
if (effectiveAttachments.length > agentRequiredUploadCount) {
|
||||
effectiveAttachments = effectiveAttachments.slice(0, agentRequiredUploadCount);
|
||||
@ -7521,10 +7592,10 @@ export function CanvasChatPanel({
|
||||
const att = a as PickedObjectAttachment;
|
||||
parts.push({
|
||||
type: "file" as const,
|
||||
mediaType: a.contentType,
|
||||
name: a.name,
|
||||
mediaType: normalizeChatMediaType(a.contentType),
|
||||
name: normalizeChatAttachmentName(a.name),
|
||||
url: a.url,
|
||||
assetId: a.assetId,
|
||||
assetId: isValidChatUuid(a.assetId) ? a.assetId : undefined,
|
||||
hasAnnotations: hasAnnotatedImages, // 🆕 标记图片是否有标注
|
||||
pickedObject: att.pickedObject, // 🆕 包含物品信息
|
||||
});
|
||||
@ -7573,6 +7644,7 @@ export function CanvasChatPanel({
|
||||
if (!textContent && selectedSkillTag) {
|
||||
textContent = `请按「${selectedSkillTag.name}」标签的要求处理。`;
|
||||
}
|
||||
textContent = clampChatRequestText(textContent, MAX_CHAT_TEXT_PART_LENGTH);
|
||||
|
||||
// 🆕 隐藏提示词(不在 UI 中展示)
|
||||
const hiddenSegments: string[] = [];
|
||||
@ -7625,10 +7697,12 @@ export function CanvasChatPanel({
|
||||
);
|
||||
}
|
||||
const hiddenPrompt = hiddenSegments.join('\n\n').trim();
|
||||
hiddenPromptRef.current = hiddenPrompt || null;
|
||||
hiddenPromptRef.current =
|
||||
clampChatRequestText(hiddenPrompt, MAX_CHAT_HIDDEN_PROMPT_LENGTH) || null;
|
||||
|
||||
const autoContext = buildAutoContext();
|
||||
autoContextRef.current = autoContext ? autoContext : null;
|
||||
autoContextRef.current =
|
||||
clampChatRequestText(autoContext, MAX_CHAT_AUTO_CONTEXT_LENGTH) || null;
|
||||
|
||||
if (textContent) {
|
||||
parts.push({ type: "text" as const, text: textContent });
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user