Fix agent workspace chat request validation
This commit is contained in:
parent
d133af4cbe
commit
25b20e1730
@ -141,6 +141,72 @@ interface AgentGenerationWorkspaceDarkV2Props {
|
||||
|
||||
interface PickedAttachment extends Attachment {}
|
||||
|
||||
const MAX_CHAT_TEXT_PART_LENGTH = 2000;
|
||||
const MAX_CHAT_HIDDEN_PROMPT_LENGTH = 4000;
|
||||
const MAX_CHAT_ATTACHMENT_NAME_LENGTH = 100;
|
||||
const MAX_AGENT_MODE_QUERY_LENGTH = 64;
|
||||
const MAX_AGENT_MODE_REFERENCE_COUNT = 40;
|
||||
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 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?: PickedAttachment | null,
|
||||
): PickedAttachment | 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,
|
||||
};
|
||||
}
|
||||
|
||||
interface GeneratedImageGroup {
|
||||
messageId: string;
|
||||
imageUrls: string[];
|
||||
@ -1368,6 +1434,12 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
fetch: fetchWithErrorHandlers,
|
||||
prepareSendMessagesRequest(request) {
|
||||
const transportState = transportStateRef.current;
|
||||
const resolvedAgentPresetId =
|
||||
transportState.selectedAgentPresetId || selectedAgentPresetId || undefined;
|
||||
const resolvedSelectedModelId =
|
||||
transportState.selectedModelId !== "auto"
|
||||
? transportState.selectedModelId
|
||||
: selectedAgentPreset?.defaultModelId || transportState.selectedModelId;
|
||||
const replacementModePayload =
|
||||
transportState.selectedAgentPresetType === "image" &&
|
||||
(transportState.isDetailReplacementPreset || transportState.isSkuReplacementPreset)
|
||||
@ -1375,9 +1447,12 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
modeType: transportState.isDetailReplacementPreset
|
||||
? ("detail_replacement" as const)
|
||||
: ("sku_replacement" as const),
|
||||
query: transportState.isDetailReplacementPreset
|
||||
? transportState.detailReplacementQueryInput.trim()
|
||||
: transportState.skuReplacementQueryInput.trim(),
|
||||
query: clampChatRequestText(
|
||||
transportState.isDetailReplacementPreset
|
||||
? transportState.detailReplacementQueryInput.trim()
|
||||
: transportState.skuReplacementQueryInput.trim(),
|
||||
MAX_AGENT_MODE_QUERY_LENGTH
|
||||
),
|
||||
queryType: transportState.isDetailReplacementPreset
|
||||
? ("spu" as const)
|
||||
: transportState.skuReplacementQueryType,
|
||||
@ -1389,12 +1464,15 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
? transportState.detailReplacementReferenceCards
|
||||
: transportState.skuReplacementReferenceCards
|
||||
)
|
||||
.slice(0, MAX_AGENT_MODE_REFERENCE_COUNT)
|
||||
.filter((item) => Boolean(String(item.imageUrl || "").trim()))
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
id: String(item.id || "").slice(0, 128),
|
||||
label: String(item.label || "").slice(0, 255),
|
||||
imageUrl: String(item.imageUrl || "").trim(),
|
||||
candidateId: item.candidateId || null,
|
||||
candidateId: item.candidateId
|
||||
? String(item.candidateId).slice(0, 128)
|
||||
: null,
|
||||
sortOrder: item.sortOrder ?? 0,
|
||||
assignedProductIndex:
|
||||
transportState.selectedProductAttachmentCount > 0
|
||||
@ -1411,22 +1489,23 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
id: rawChatId,
|
||||
message: request.messages.at(-1),
|
||||
hiddenPrompt:
|
||||
generationRequestSnapshotRef.current.hiddenPrompt ||
|
||||
hiddenPromptRef.current ||
|
||||
undefined,
|
||||
agentPresetId:
|
||||
transportState.selectedAgentPresetId || selectedAgentPresetId || undefined,
|
||||
selectedChatModel:
|
||||
transportState.selectedModelId !== "auto"
|
||||
? transportState.selectedModelId
|
||||
: selectedAgentPreset?.defaultModelId || transportState.selectedModelId,
|
||||
clampChatRequestText(
|
||||
generationRequestSnapshotRef.current.hiddenPrompt ||
|
||||
hiddenPromptRef.current ||
|
||||
"",
|
||||
MAX_CHAT_HIDDEN_PROMPT_LENGTH
|
||||
) || undefined,
|
||||
agentPresetId: isValidChatUuid(resolvedAgentPresetId)
|
||||
? resolvedAgentPresetId
|
||||
: undefined,
|
||||
selectedChatModel: String(resolvedSelectedModelId || "auto").trim() || "auto",
|
||||
composerMode:
|
||||
selectedAgentPreset?.type === "image" ? ("image" as const) : ("agent" as const),
|
||||
selectedVisibilityType: "private",
|
||||
imageParams: transportState.imageParams,
|
||||
contextPayload: {
|
||||
source: "chat" as const,
|
||||
projectId,
|
||||
projectId: isValidChatUuid(projectId) ? projectId : undefined,
|
||||
},
|
||||
agentModePayload: replacementModePayload,
|
||||
},
|
||||
@ -2486,10 +2565,38 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
toast.error(`当前模式需要上传 ${requiredUploadCount} 张图片`);
|
||||
return;
|
||||
}
|
||||
const resolvedModelId =
|
||||
selectedModelId !== "auto"
|
||||
? selectedModelId
|
||||
: selectedAgentPreset?.defaultModelId || selectedModelId;
|
||||
if (!resolvedModelId) {
|
||||
toast.error("当前智能体未配置模型");
|
||||
return;
|
||||
}
|
||||
if (
|
||||
resolvedModelId !== "auto" &&
|
||||
!selectableModels.some((model) => model.id === resolvedModelId)
|
||||
) {
|
||||
toast.error("当前智能体绑定的模型不存在或已停用,请重新编辑智能体模型配置");
|
||||
return;
|
||||
}
|
||||
const parts: Array<{ type: "text"; text: string } | { type: "file"; mediaType: string; name: string; url: string; assetId?: string }> = [];
|
||||
attachments.forEach((attachment) => {
|
||||
const sanitizedAttachments = attachments
|
||||
.map((attachment) => sanitizeChatAttachment(attachment))
|
||||
.filter((attachment): attachment is PickedAttachment => Boolean(attachment));
|
||||
if (hasAgentUploadSlots && sanitizedAttachments.length < requiredUploadCount) {
|
||||
toast.error(`当前模式需要上传 ${requiredUploadCount} 张有效图片`);
|
||||
return;
|
||||
}
|
||||
sanitizedAttachments.forEach((attachment) => {
|
||||
if (!attachment) return;
|
||||
parts.push({ type: "file", mediaType: attachment.contentType || "image/jpeg", name: attachment.name || "参考图", url: attachment.url, assetId: attachment.assetId });
|
||||
parts.push({
|
||||
type: "file",
|
||||
mediaType: normalizeChatMediaType(attachment.contentType),
|
||||
name: normalizeChatAttachmentName(attachment.name),
|
||||
url: attachment.url,
|
||||
assetId: isValidChatUuid(attachment.assetId) ? attachment.assetId : undefined,
|
||||
});
|
||||
});
|
||||
const resolvedWorkflowContext = getLatestWorkflowContext?.() ?? workflowContext;
|
||||
let textContent = input.trim();
|
||||
@ -2499,6 +2606,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
if (!textContent && selectedAgentPreset?.type === "text" && selectedAgentPreset) {
|
||||
textContent = `请按「${selectedAgentPreset.name}」输出内容`;
|
||||
}
|
||||
textContent = clampChatRequestText(textContent, MAX_CHAT_TEXT_PART_LENGTH);
|
||||
if (textContent) {
|
||||
parts.push({ type: "text", text: textContent });
|
||||
}
|
||||
@ -2507,7 +2615,10 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
return;
|
||||
}
|
||||
const nextHiddenPrompt = resolvedWorkflowContext?.promptText
|
||||
? `【工作流设定】\n${resolvedWorkflowContext.promptText}\n\n请严格遵循以上工作流设定输出内容,并保持整体结果一致。`
|
||||
? clampChatRequestText(
|
||||
`【工作流设定】\n${resolvedWorkflowContext.promptText}\n\n请严格遵循以上工作流设定输出内容,并保持整体结果一致。`,
|
||||
MAX_CHAT_HIDDEN_PROMPT_LENGTH
|
||||
)
|
||||
: null;
|
||||
hiddenPromptRef.current = nextHiddenPrompt;
|
||||
generationRequestSnapshotRef.current = {
|
||||
@ -2553,6 +2664,8 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
requiredUploadCount,
|
||||
selectedAgentPreset,
|
||||
selectedAgentPresetId,
|
||||
selectableModels,
|
||||
selectedModelId,
|
||||
sendMessage,
|
||||
setPendingGeneration,
|
||||
skuReplacementReferenceCards.length,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user