fix: close image placeholder on generation end
Emit generation end markers for success/failure and scope marker handling to the latest request so placeholders stop on errors.
This commit is contained in:
parent
35b190011a
commit
9fae58cfd3
@ -912,6 +912,16 @@ export async function POST(request: Request) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🆕 图片生成结束标记(成功)
|
||||||
|
writer.write({
|
||||||
|
type: "data-appendMessage",
|
||||||
|
data: JSON.stringify({
|
||||||
|
id: generateUUID(),
|
||||||
|
role: "assistant",
|
||||||
|
parts: [{ type: "text", text: "<!-- IMAGE_GENERATION_END -->" }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
// 保存消息到数据库(现在是 S3 URL,可以完整保存)
|
// 保存消息到数据库(现在是 S3 URL,可以完整保存)
|
||||||
try {
|
try {
|
||||||
await saveMessages({
|
await saveMessages({
|
||||||
@ -998,6 +1008,16 @@ export async function POST(request: Request) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🆕 图片生成结束标记(故障转移成功)
|
||||||
|
writer.write({
|
||||||
|
type: "data-appendMessage",
|
||||||
|
data: JSON.stringify({
|
||||||
|
id: generateUUID(),
|
||||||
|
role: "assistant",
|
||||||
|
parts: [{ type: "text", text: "<!-- IMAGE_GENERATION_END -->" }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
// 保存消息到数据库
|
// 保存消息到数据库
|
||||||
try {
|
try {
|
||||||
await saveMessages({
|
await saveMessages({
|
||||||
@ -1056,6 +1076,16 @@ export async function POST(request: Request) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🆕 图片生成结束标记(失败)
|
||||||
|
writer.write({
|
||||||
|
type: "data-appendMessage",
|
||||||
|
data: JSON.stringify({
|
||||||
|
id: generateUUID(),
|
||||||
|
role: "assistant",
|
||||||
|
parts: [{ type: "text", text: "<!-- IMAGE_GENERATION_END -->" }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
// 尝试保存错误消息
|
// 尝试保存错误消息
|
||||||
try {
|
try {
|
||||||
await saveMessages({
|
await saveMessages({
|
||||||
@ -1271,6 +1301,16 @@ export async function POST(request: Request) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🆕 图片生成结束标记(成功)
|
||||||
|
writer.write({
|
||||||
|
type: "data-appendMessage",
|
||||||
|
data: JSON.stringify({
|
||||||
|
id: generateUUID(),
|
||||||
|
role: "assistant",
|
||||||
|
parts: [{ type: "text", text: "<!-- IMAGE_GENERATION_END -->" }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
// 保存图片消息到数据库
|
// 保存图片消息到数据库
|
||||||
try {
|
try {
|
||||||
await saveMessages({
|
await saveMessages({
|
||||||
@ -1322,6 +1362,16 @@ export async function POST(request: Request) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🆕 图片生成结束标记(失败)
|
||||||
|
writer.write({
|
||||||
|
type: "data-appendMessage",
|
||||||
|
data: JSON.stringify({
|
||||||
|
id: generateUUID(),
|
||||||
|
role: "assistant",
|
||||||
|
parts: [{ type: "text", text: "<!-- IMAGE_GENERATION_END -->" }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await saveMessages({
|
await saveMessages({
|
||||||
messages: [
|
messages: [
|
||||||
@ -1502,6 +1552,16 @@ export async function POST(request: Request) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🆕 图片生成结束标记(成功)
|
||||||
|
writer.write({
|
||||||
|
type: "data-appendMessage",
|
||||||
|
data: JSON.stringify({
|
||||||
|
id: generateUUID(),
|
||||||
|
role: "assistant",
|
||||||
|
parts: [{ type: "text", text: "<!-- IMAGE_GENERATION_END -->" }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
// 保存图片消息到数据库
|
// 保存图片消息到数据库
|
||||||
try {
|
try {
|
||||||
await saveMessages({
|
await saveMessages({
|
||||||
@ -1552,6 +1612,16 @@ export async function POST(request: Request) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🆕 图片生成结束标记(失败)
|
||||||
|
writer.write({
|
||||||
|
type: "data-appendMessage",
|
||||||
|
data: JSON.stringify({
|
||||||
|
id: generateUUID(),
|
||||||
|
role: "assistant",
|
||||||
|
parts: [{ type: "text", text: "<!-- IMAGE_GENERATION_END -->" }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
// 尝试保存错误消息
|
// 尝试保存错误消息
|
||||||
try {
|
try {
|
||||||
await saveMessages({
|
await saveMessages({
|
||||||
|
|||||||
@ -1065,13 +1065,20 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = status === 'streaming' || status === 'submitted';
|
const isLoading = status === 'streaming' || status === 'submitted';
|
||||||
const hasGeneratingMarker = messages.some(msg =>
|
const lastUserIndexForMarkers = messages.findLastIndex(m => m.role === "user");
|
||||||
|
const messagesAfterLastUser = lastUserIndexForMarkers >= 0 ? messages.slice(lastUserIndexForMarkers + 1) : messages;
|
||||||
|
const hasGenerationStart = messagesAfterLastUser.some(msg =>
|
||||||
msg.role === "assistant" && msg.parts?.some(
|
msg.role === "assistant" && msg.parts?.some(
|
||||||
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_START -->")
|
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_START -->")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const hasGenerationEnd = messagesAfterLastUser.some(msg =>
|
||||||
|
msg.role === "assistant" && msg.parts?.some(
|
||||||
|
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_END -->")
|
||||||
|
)
|
||||||
|
);
|
||||||
const currentModel = allModels.find(m => m.id === selectedModelId);
|
const currentModel = allModels.find(m => m.id === selectedModelId);
|
||||||
const shouldShowImageLoading = hasGeneratingMarker && !!currentModel;
|
const shouldShowImageLoading = hasGenerationStart && !hasGenerationEnd && !!currentModel;
|
||||||
const loadingImageCount = imageParams.imageCount || 1;
|
const loadingImageCount = imageParams.imageCount || 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -1184,6 +1191,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
text: part.text
|
text: part.text
|
||||||
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
||||||
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
||||||
|
.replace(/\n*<!-- IMAGE_GENERATION_END -->\n*/g, "")
|
||||||
.trim()
|
.trim()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -311,6 +311,9 @@ export function Chat({
|
|||||||
const hasImageGenStartMarker = messageData.parts?.some(
|
const hasImageGenStartMarker = messageData.parts?.some(
|
||||||
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_START -->")
|
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_START -->")
|
||||||
);
|
);
|
||||||
|
const hasImageGenEndMarker = messageData.parts?.some(
|
||||||
|
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_END -->")
|
||||||
|
);
|
||||||
const hasLegacyGeneratingMarker = messageData.parts?.some(
|
const hasLegacyGeneratingMarker = messageData.parts?.some(
|
||||||
(part: any) => part.type === "text" && part.text?.includes("<!-- GENERATING_IMAGE -->")
|
(part: any) => part.type === "text" && part.text?.includes("<!-- GENERATING_IMAGE -->")
|
||||||
);
|
);
|
||||||
@ -318,11 +321,22 @@ export function Chat({
|
|||||||
if (part.type !== "text" || !part.text) return false;
|
if (part.type !== "text" || !part.text) return false;
|
||||||
const cleanedText = part.text
|
const cleanedText = part.text
|
||||||
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
||||||
|
.replace(/\n*<!-- IMAGE_GENERATION_END -->\n*/g, "")
|
||||||
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
||||||
.trim();
|
.trim();
|
||||||
return cleanedText.length === 0;
|
return cleanedText.length === 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (hasImageGenEndMarker && isMarkerOnlyMessage) {
|
||||||
|
console.log("[Chat] 🖼️ Image generation ended, hide placeholder");
|
||||||
|
if (imagePlaceholderTimerRef.current) {
|
||||||
|
clearTimeout(imagePlaceholderTimerRef.current);
|
||||||
|
imagePlaceholderTimerRef.current = null;
|
||||||
|
}
|
||||||
|
setIsGeneratingImageFlag(false);
|
||||||
|
return; // 不渲染标记消息
|
||||||
|
}
|
||||||
|
|
||||||
if (hasImageGenStartMarker && isMarkerOnlyMessage) {
|
if (hasImageGenStartMarker && isMarkerOnlyMessage) {
|
||||||
console.log("[Chat] 🖼️ Image generation started, show placeholder");
|
console.log("[Chat] 🖼️ Image generation started, show placeholder");
|
||||||
if (imagePlaceholderTimerRef.current) {
|
if (imagePlaceholderTimerRef.current) {
|
||||||
|
|||||||
@ -177,11 +177,19 @@ function PureMessages({
|
|||||||
lastUserMessage?.parts?.some((p: any) => p.type === "file" || p.type === "image");
|
lastUserMessage?.parts?.some((p: any) => p.type === "file" || p.type === "image");
|
||||||
|
|
||||||
// 🆕 检查是否有"图片正在生成"的标记(表示对话消息已发送,正在等待图片生成)
|
// 🆕 检查是否有"图片正在生成"的标记(表示对话消息已发送,正在等待图片生成)
|
||||||
const isGeneratingImage = messages.some(msg =>
|
const lastUserIndexForMarkers = messages.findLastIndex(m => m.role === "user");
|
||||||
|
const messagesAfterLastUser = lastUserIndexForMarkers >= 0 ? messages.slice(lastUserIndexForMarkers + 1) : messages;
|
||||||
|
const hasGenerationStart = messagesAfterLastUser.some(msg =>
|
||||||
msg.role === "assistant" && msg.parts?.some(
|
msg.role === "assistant" && msg.parts?.some(
|
||||||
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_START -->")
|
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_START -->")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const hasGenerationEnd = messagesAfterLastUser.some(msg =>
|
||||||
|
msg.role === "assistant" && msg.parts?.some(
|
||||||
|
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_END -->")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const isGeneratingImage = hasGenerationStart && !hasGenerationEnd;
|
||||||
|
|
||||||
// 对于图片模型,检查是否有图片内容(包含 ![thumb| 或 ![thumb||| 标记)
|
// 对于图片模型,检查是否有图片内容(包含 ![thumb| 或 ![thumb||| 标记)
|
||||||
// 🆕 在智能自动模式下,需要检查最后一条用户消息之后的所有 assistant 消息
|
// 🆕 在智能自动模式下,需要检查最后一条用户消息之后的所有 assistant 消息
|
||||||
@ -322,6 +330,7 @@ function PureMessages({
|
|||||||
const cleanedText = part.text
|
const cleanedText = part.text
|
||||||
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
||||||
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
||||||
|
.replace(/\n*<!-- IMAGE_GENERATION_END -->\n*/g, "")
|
||||||
.trim();
|
.trim();
|
||||||
return cleanedText.length > 0;
|
return cleanedText.length > 0;
|
||||||
})
|
})
|
||||||
@ -423,6 +432,7 @@ function PureMessages({
|
|||||||
text: part.text
|
text: part.text
|
||||||
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
||||||
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
||||||
|
.replace(/\n*<!-- IMAGE_GENERATION_END -->\n*/g, "")
|
||||||
.trim()
|
.trim()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -494,6 +504,7 @@ function PureMessages({
|
|||||||
const cleanedText = part.text
|
const cleanedText = part.text
|
||||||
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
||||||
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
||||||
|
.replace(/\n*<!-- IMAGE_GENERATION_END -->\n*/g, "")
|
||||||
.trim();
|
.trim();
|
||||||
return cleanedText.length > 0;
|
return cleanedText.length > 0;
|
||||||
})
|
})
|
||||||
@ -535,17 +546,25 @@ function PureMessages({
|
|||||||
const userMessageHasImage = lastUserMsg?.parts?.some((p: any) => p.type === "file" || p.type === "image");
|
const userMessageHasImage = lastUserMsg?.parts?.some((p: any) => p.type === "file" || p.type === "image");
|
||||||
|
|
||||||
// 🆕 检查是否收到了 GENERATING_IMAGE 标记(表示对话已发送,开始生图)
|
// 🆕 检查是否收到了 GENERATING_IMAGE 标记(表示对话已发送,开始生图)
|
||||||
const hasGeneratingMarker = messages.some(msg =>
|
const lastUserIndexForMarkers = messages.findLastIndex(m => m.role === "user");
|
||||||
|
const messagesAfterLastUser = lastUserIndexForMarkers >= 0 ? messages.slice(lastUserIndexForMarkers + 1) : messages;
|
||||||
|
const hasGeneratingMarker = messagesAfterLastUser.some(msg =>
|
||||||
msg.role === "assistant" && msg.parts?.some(
|
msg.role === "assistant" && msg.parts?.some(
|
||||||
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_START -->")
|
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_START -->")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const hasGenerationEnd = messagesAfterLastUser.some(msg =>
|
||||||
|
msg.role === "assistant" && msg.parts?.some(
|
||||||
|
(part: any) => part.type === "text" && part.text?.includes("<!-- IMAGE_GENERATION_END -->")
|
||||||
|
)
|
||||||
|
);
|
||||||
const hasAssistantTextReplyWithContent = messages.some(msg =>
|
const hasAssistantTextReplyWithContent = messages.some(msg =>
|
||||||
msg.role === "assistant" && msg.parts?.some((part: any) => {
|
msg.role === "assistant" && msg.parts?.some((part: any) => {
|
||||||
if (part.type !== "text" || !part.text) return false;
|
if (part.type !== "text" || !part.text) return false;
|
||||||
const cleanedText = part.text
|
const cleanedText = part.text
|
||||||
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
|
||||||
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
||||||
|
.replace(/\n*<!-- IMAGE_GENERATION_END -->\n*/g, "")
|
||||||
.trim();
|
.trim();
|
||||||
return cleanedText.length > 0;
|
return cleanedText.length > 0;
|
||||||
})
|
})
|
||||||
@ -560,7 +579,7 @@ function PureMessages({
|
|||||||
if (isAutoMode && userMessageHasImage) {
|
if (isAutoMode && userMessageHasImage) {
|
||||||
// 🆕 只用 isGeneratingImageFlag 来控制是否显示生图占位图
|
// 🆕 只用 isGeneratingImageFlag 来控制是否显示生图占位图
|
||||||
// isGeneratingImageFlag 由 chat.tsx 控制:延迟 3 秒后设置为 true,收到图片后设置为 false
|
// isGeneratingImageFlag 由 chat.tsx 控制:延迟 3 秒后设置为 true,收到图片后设置为 false
|
||||||
if (autoImagePlaceholderReady && isGeneratingImageFlag && hasAssistantTextReplyWithContent) {
|
if (autoImagePlaceholderReady && isGeneratingImageFlag && hasAssistantTextReplyWithContent && !hasGenerationEnd) {
|
||||||
return <ImageGenerationLoading imageCount={imageCount} maxImages={6} providerType="alibaba-image" modelName="智能处理" />;
|
return <ImageGenerationLoading imageCount={imageCount} maxImages={6} providerType="alibaba-image" modelName="智能处理" />;
|
||||||
}
|
}
|
||||||
// 否则显示思考动画(在等待 AI 分析或延迟期间)
|
// 否则显示思考动画(在等待 AI 分析或延迟期间)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user