fix: 使用特殊标记保持占位图显示
问题:对话消息发送后占位图消失 解决: 1. 后端在对话消息末尾加上 <!-- GENERATING_IMAGE --> 标记 2. 前端检测到此标记时继续显示占位图 3. 渲染时清理掉这个标记(用户看不到) 4. 数据库保存时不带标记(纯对话) 流程: 1. 用户发送请求 2. AI回复对话(带标记)+ 占位图继续显示 3. 图片生成后显示 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
920fb5aa95
commit
ddada59db1
@ -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
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{/* 等待动画:根据模型类型显示不同的加载效果 */}
|
{/* 等待动画:根据模型类型显示不同的加载效果 */}
|
||||||
{/* 🆕 支持恢复的生成状态(用户离开再返回) */}
|
{/* 🆕 支持恢复的生成状态(用户离开再返回) */}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user