fix: 使用特殊标记保持占位图显示
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

问题:对话消息发送后占位图消失
解决:
1. 后端在对话消息末尾加上 <!-- GENERATING_IMAGE --> 标记
2. 前端检测到此标记时继续显示占位图
3. 渲染时清理掉这个标记(用户看不到)
4. 数据库保存时不带标记(纯对话)

流程:
1. 用户发送请求
2. AI回复对话(带标记)+ 占位图继续显示
3. 图片生成后显示

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Your Name 2026-02-01 02:12:08 +08:00
parent 920fb5aa95
commit ddada59db1
2 changed files with 47 additions and 17 deletions

View File

@ -987,21 +987,26 @@ export async function POST(request: Request) {
execute: async ({ writer }) => { execute: async ({ writer }) => {
try { try {
// 🆕 如果是智能模式且有对话回复,先发送对话消息让用户看到 // 🆕 如果是智能模式且有对话回复,先发送对话消息让用户看到
if (smartAutoFriendlyMessage) { // 在消息末尾加上特殊标记,告诉前端"图片正在生成中"
const messageWithMarker = smartAutoFriendlyMessage
? `${smartAutoFriendlyMessage}\n\n<!-- GENERATING_IMAGE -->`
: null;
if (messageWithMarker) {
const chatMessageId = generateUUID(); const chatMessageId = generateUUID();
console.log("[Chat API] 💬 Sending conversational reply first:", smartAutoFriendlyMessage); console.log("[Chat API] 💬 Sending conversational reply first:", smartAutoFriendlyMessage);
// 发送对话消息 // 发送带标记的对话消息
writer.write({ writer.write({
type: "data-appendMessage", type: "data-appendMessage",
data: JSON.stringify({ data: JSON.stringify({
id: chatMessageId, id: chatMessageId,
role: "assistant", role: "assistant",
parts: [{ type: "text", text: smartAutoFriendlyMessage }], parts: [{ type: "text", text: messageWithMarker }],
}), }),
}); });
// 保存对话消息到数据库 // 保存对话消息到数据库(不带标记,纯对话)
await saveMessages({ await saveMessages({
messages: [ messages: [
{ {
@ -1014,9 +1019,6 @@ export async function POST(request: Request) {
}, },
], ],
}); });
// 🆕 给前端一点时间渲染第一条消息
await new Promise(resolve => setTimeout(resolve, 500));
} }
// 只传递最后一条用户消息 // 只传递最后一条用户消息
@ -1204,21 +1206,26 @@ export async function POST(request: Request) {
execute: async ({ writer }) => { execute: async ({ writer }) => {
try { try {
// 🆕 如果是智能模式且有对话回复,先发送对话消息让用户看到 // 🆕 如果是智能模式且有对话回复,先发送对话消息让用户看到
if (smartAutoFriendlyMessage) { // 在消息末尾加上特殊标记,告诉前端"图片正在生成中"
const messageWithMarker = smartAutoFriendlyMessage
? `${smartAutoFriendlyMessage}\n\n<!-- GENERATING_IMAGE -->`
: null;
if (messageWithMarker) {
const chatMessageId = generateUUID(); const chatMessageId = generateUUID();
console.log("[Chat API] 💬 Volcano: Sending conversational reply first:", smartAutoFriendlyMessage); console.log("[Chat API] 💬 Volcano: Sending conversational reply first:", smartAutoFriendlyMessage);
// 发送对话消息 // 发送带标记的对话消息
writer.write({ writer.write({
type: "data-appendMessage", type: "data-appendMessage",
data: JSON.stringify({ data: JSON.stringify({
id: chatMessageId, id: chatMessageId,
role: "assistant", role: "assistant",
parts: [{ type: "text", text: smartAutoFriendlyMessage }], parts: [{ type: "text", text: messageWithMarker }],
}), }),
}); });
// 保存对话消息到数据库 // 保存对话消息到数据库(不带标记,纯对话)
await saveMessages({ await saveMessages({
messages: [ messages: [
{ {
@ -1231,9 +1238,6 @@ export async function POST(request: Request) {
}, },
], ],
}); });
// 🆕 给前端一点时间渲染第一条消息
await new Promise(resolve => setTimeout(resolve, 500));
} }
// 转换消息,火山引擎支持 URL 和 base64 // 转换消息,火山引擎支持 URL 和 base64

View File

@ -158,6 +158,11 @@ function PureMessages({
const isAutoModeWithImage = selectedModelId === "auto" && const isAutoModeWithImage = selectedModelId === "auto" &&
lastUserMessage?.parts?.some((p: any) => p.type === "file" || p.type === "image"); lastUserMessage?.parts?.some((p: any) => p.type === "file" || p.type === "image");
// 🆕 检查是否有"图片正在生成"的标记
const isGeneratingImage = lastMessage?.parts?.some(
(part: any) => part.type === "text" && part.text?.includes("<!-- GENERATING_IMAGE -->")
);
// 对于图片模型,检查是否有图片内容(包含 ![thumb| 或 ![thumb||| 标记) // 对于图片模型,检查是否有图片内容(包含 ![thumb| 或 ![thumb||| 标记)
// 🆕 在智能自动模式下,需要检查最后一条用户消息之后的所有 assistant 消息 // 🆕 在智能自动模式下,需要检查最后一条用户消息之后的所有 assistant 消息
let hasImageContent = false; let hasImageContent = false;
@ -173,6 +178,11 @@ function PureMessages({
part.text.includes("![Generated Image") part.text.includes("![Generated Image")
)) ))
); );
// 🆕 如果有"正在生成"标记,强制认为没有图片内容(继续显示占位图)
if (isGeneratingImage) {
hasImageContent = false;
}
} else { } else {
// 普通模式:只检查最后一条消息 // 普通模式:只检查最后一条消息
hasImageContent = lastMessage?.parts && lastMessage.parts.some( hasImageContent = lastMessage?.parts && lastMessage.parts.some(
@ -267,7 +277,22 @@ function PureMessages({
<div className="mx-auto flex min-w-0 max-w-4xl flex-col gap-4 px-2 py-4 md:gap-6 md:px-4"> <div className="mx-auto flex min-w-0 max-w-4xl flex-col gap-4 px-2 py-4 md:gap-6 md:px-4">
{messages.length === 0 && <Greeting />} {messages.length === 0 && <Greeting />}
{messages.map((message, index) => ( {messages.map((message, index) => {
// 🆕 清理消息中的内部标记(如 <!-- GENERATING_IMAGE -->)
const cleanedMessage = {
...message,
parts: message.parts?.map((part: any) => {
if (part.type === "text" && part.text) {
return {
...part,
text: part.text.replace(/\n*<!-- GENERATING_IMAGE -->\n*/g, "")
};
}
return part;
})
};
return (
<PreviewMessage <PreviewMessage
chatId={chatId} chatId={chatId}
isLoading={ isLoading={
@ -275,7 +300,7 @@ function PureMessages({
} }
isReadonly={isReadonly} isReadonly={isReadonly}
key={message.id} key={message.id}
message={message} message={cleanedMessage}
regenerate={regenerate} regenerate={regenerate}
sendMessage={sendMessage} sendMessage={sendMessage}
requiresScrollPadding={ requiresScrollPadding={
@ -298,7 +323,8 @@ function PureMessages({
isProcessingExtend={isProcessingExtend} isProcessingExtend={isProcessingExtend}
isProcessingAngleEdit={isProcessingAngleEdit} isProcessingAngleEdit={isProcessingAngleEdit}
/> />
))} );
})}
{/* 等待动画:根据模型类型显示不同的加载效果 */} {/* 等待动画:根据模型类型显示不同的加载效果 */}
{/* 🆕 支持恢复的生成状态(用户离开再返回) */} {/* 🆕 支持恢复的生成状态(用户离开再返回) */}