feat: 智能自动模式先对话再生图
流程: 1. 用户发送请求(图片+文字) 2. AI立即回复对话('好的,我来帮你...') 3. 显示占位图(正在生成中...) 4. 图片生成后显示 修改: - 后端:先发送对话消息,再调用生图API - 前端:检查用户消息后所有assistant消息是否有图片 - 只有图片出现才隐藏占位图,对话文字不影响 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
73d1ab7912
commit
ff9f45858b
@ -986,6 +986,36 @@ export async function POST(request: Request) {
|
|||||||
const alibabaStream = createUIMessageStream({
|
const alibabaStream = createUIMessageStream({
|
||||||
execute: async ({ writer }) => {
|
execute: async ({ writer }) => {
|
||||||
try {
|
try {
|
||||||
|
// 🆕 如果是智能模式且有对话回复,先发送对话消息让用户看到
|
||||||
|
if (smartAutoFriendlyMessage) {
|
||||||
|
const chatMessageId = generateUUID();
|
||||||
|
console.log("[Chat API] 💬 Sending conversational reply first:", smartAutoFriendlyMessage);
|
||||||
|
|
||||||
|
// 立即发送对话消息
|
||||||
|
writer.write({
|
||||||
|
type: "data-appendMessage",
|
||||||
|
data: JSON.stringify({
|
||||||
|
id: chatMessageId,
|
||||||
|
role: "assistant",
|
||||||
|
parts: [{ type: "text", text: smartAutoFriendlyMessage }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存对话消息到数据库
|
||||||
|
await saveMessages({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
id: chatMessageId,
|
||||||
|
chatId: id,
|
||||||
|
role: "assistant",
|
||||||
|
parts: [{ type: "text", text: smartAutoFriendlyMessage }],
|
||||||
|
attachments: [],
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 只传递最后一条用户消息
|
// 只传递最后一条用户消息
|
||||||
const lastUserMessage = uiMessages.filter((m) => m.role === "user").pop();
|
const lastUserMessage = uiMessages.filter((m) => m.role === "user").pop();
|
||||||
let messagesForAlibaba = lastUserMessage ? [lastUserMessage] : uiMessages;
|
let messagesForAlibaba = lastUserMessage ? [lastUserMessage] : uiMessages;
|
||||||
@ -1045,25 +1075,20 @@ export async function POST(request: Request) {
|
|||||||
const imageMarkdown = result.text || "图片生成失败";
|
const imageMarkdown = result.text || "图片生成失败";
|
||||||
console.log("[Chat API] Alibaba generated, length:", imageMarkdown.length);
|
console.log("[Chat API] Alibaba generated, length:", imageMarkdown.length);
|
||||||
|
|
||||||
// 生成消息 ID
|
// 生成消息 ID(图片消息)
|
||||||
const messageId = generateUUID();
|
const messageId = generateUUID();
|
||||||
|
|
||||||
// 合并对话和图片的完整内容
|
// 写入图片消息到流
|
||||||
const fullContent = smartAutoFriendlyMessage
|
|
||||||
? `${smartAutoFriendlyMessage}\n\n${imageMarkdown}`
|
|
||||||
: imageMarkdown;
|
|
||||||
|
|
||||||
// 写入完整消息到流
|
|
||||||
writer.write({
|
writer.write({
|
||||||
type: "data-appendMessage",
|
type: "data-appendMessage",
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
id: messageId,
|
id: messageId,
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
parts: [{ type: "text", text: fullContent }],
|
parts: [{ type: "text", text: imageMarkdown }],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 保存完整消息到数据库
|
// 保存图片消息到数据库
|
||||||
try {
|
try {
|
||||||
await saveMessages({
|
await saveMessages({
|
||||||
messages: [
|
messages: [
|
||||||
@ -1071,7 +1096,7 @@ export async function POST(request: Request) {
|
|||||||
id: messageId,
|
id: messageId,
|
||||||
chatId: id,
|
chatId: id,
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
parts: [{ type: "text", text: fullContent }],
|
parts: [{ type: "text", text: imageMarkdown }],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
videoStorageStatus: null,
|
videoStorageStatus: null,
|
||||||
@ -1175,6 +1200,36 @@ export async function POST(request: Request) {
|
|||||||
const volcanoStream = createUIMessageStream({
|
const volcanoStream = createUIMessageStream({
|
||||||
execute: async ({ writer }) => {
|
execute: async ({ writer }) => {
|
||||||
try {
|
try {
|
||||||
|
// 🆕 如果是智能模式且有对话回复,先发送对话消息让用户看到
|
||||||
|
if (smartAutoFriendlyMessage) {
|
||||||
|
const chatMessageId = generateUUID();
|
||||||
|
console.log("[Chat API] 💬 Volcano: Sending conversational reply first:", smartAutoFriendlyMessage);
|
||||||
|
|
||||||
|
// 立即发送对话消息
|
||||||
|
writer.write({
|
||||||
|
type: "data-appendMessage",
|
||||||
|
data: JSON.stringify({
|
||||||
|
id: chatMessageId,
|
||||||
|
role: "assistant",
|
||||||
|
parts: [{ type: "text", text: smartAutoFriendlyMessage }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存对话消息到数据库
|
||||||
|
await saveMessages({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
id: chatMessageId,
|
||||||
|
chatId: id,
|
||||||
|
role: "assistant",
|
||||||
|
parts: [{ type: "text", text: smartAutoFriendlyMessage }],
|
||||||
|
attachments: [],
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 转换消息,火山引擎支持 URL 和 base64
|
// 转换消息,火山引擎支持 URL 和 base64
|
||||||
const modelMessages = await convertMessagesWithImages(messagesForVolcano, "volcano");
|
const modelMessages = await convertMessagesWithImages(messagesForVolcano, "volcano");
|
||||||
|
|
||||||
@ -1187,25 +1242,20 @@ export async function POST(request: Request) {
|
|||||||
console.log("[Chat API] Volcano Vision generated, length:", imageMarkdown.length);
|
console.log("[Chat API] Volcano Vision generated, length:", imageMarkdown.length);
|
||||||
console.log("[Chat API] Volcano Vision image markdown:", imageMarkdown.substring(0, 200));
|
console.log("[Chat API] Volcano Vision image markdown:", imageMarkdown.substring(0, 200));
|
||||||
|
|
||||||
// 生成消息 ID
|
// 生成消息 ID(图片消息)
|
||||||
const messageId = generateUUID();
|
const messageId = generateUUID();
|
||||||
|
|
||||||
// 合并对话和图片的完整内容
|
// 写入图片消息到流
|
||||||
const fullContent = smartAutoFriendlyMessage
|
|
||||||
? `${smartAutoFriendlyMessage}\n\n${imageMarkdown}`
|
|
||||||
: imageMarkdown;
|
|
||||||
|
|
||||||
// 写入完整消息到流
|
|
||||||
writer.write({
|
writer.write({
|
||||||
type: "data-appendMessage",
|
type: "data-appendMessage",
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
id: messageId,
|
id: messageId,
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
parts: [{ type: "text", text: fullContent }],
|
parts: [{ type: "text", text: imageMarkdown }],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 保存完整消息到数据库
|
// 保存图片消息到数据库
|
||||||
try {
|
try {
|
||||||
await saveMessages({
|
await saveMessages({
|
||||||
messages: [
|
messages: [
|
||||||
@ -1213,7 +1263,7 @@ export async function POST(request: Request) {
|
|||||||
id: messageId,
|
id: messageId,
|
||||||
chatId: id,
|
chatId: id,
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
parts: [{ type: "text", text: fullContent }],
|
parts: [{ type: "text", text: imageMarkdown }],
|
||||||
attachments: [], // 必须提供 attachments 字段(数据库 notNull)
|
attachments: [], // 必须提供 attachments 字段(数据库 notNull)
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
videoStorageStatus: null,
|
videoStorageStatus: null,
|
||||||
|
|||||||
@ -152,14 +152,37 @@ function PureMessages({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🆕 智能自动模式:检查用户最后一条消息是否包含图片,如果有则视为图片编辑场景
|
||||||
|
// 注意:需要找用户的最后一条消息,而不是整个对话的最后一条
|
||||||
|
const lastUserMessage = [...messages].reverse().find(m => m.role === "user");
|
||||||
|
const isAutoModeWithImage = selectedModelId === "auto" &&
|
||||||
|
lastUserMessage?.parts?.some((p: any) => p.type === "file" || p.type === "image");
|
||||||
|
|
||||||
// 对于图片模型,检查是否有图片内容(包含 ![thumb| 或 ![thumb||| 标记)
|
// 对于图片模型,检查是否有图片内容(包含 ![thumb| 或 ![thumb||| 标记)
|
||||||
const hasImageContent = lastMessage?.parts && lastMessage.parts.some(
|
// 🆕 在智能自动模式下,需要检查最后一条用户消息之后的所有 assistant 消息
|
||||||
(part: any) => part.type === "text" && part.text && (
|
let hasImageContent = false;
|
||||||
part.text.includes("![thumb|") ||
|
if (isAutoModeWithImage) {
|
||||||
part.text.includes("![thumb|||") ||
|
// 找到最后一条用户消息的索引
|
||||||
part.text.includes("![Generated Image")
|
const lastUserIndex = messages.findLastIndex(m => m.role === "user");
|
||||||
)
|
// 检查之后所有的 assistant 消息是否包含图片
|
||||||
);
|
const assistantMessagesAfterUser = messages.slice(lastUserIndex + 1).filter(m => m.role === "assistant");
|
||||||
|
hasImageContent = assistantMessagesAfterUser.some(msg =>
|
||||||
|
msg.parts?.some((part: any) => part.type === "text" && part.text && (
|
||||||
|
part.text.includes("![thumb|") ||
|
||||||
|
part.text.includes("![thumb|||") ||
|
||||||
|
part.text.includes("![Generated Image")
|
||||||
|
))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 普通模式:只检查最后一条消息
|
||||||
|
hasImageContent = lastMessage?.parts && lastMessage.parts.some(
|
||||||
|
(part: any) => part.type === "text" && part.text && (
|
||||||
|
part.text.includes("![thumb|") ||
|
||||||
|
part.text.includes("![thumb|||") ||
|
||||||
|
part.text.includes("![Generated Image")
|
||||||
|
)
|
||||||
|
) || false;
|
||||||
|
}
|
||||||
|
|
||||||
// 对于其他模型,检查是否有实际的文本内容
|
// 对于其他模型,检查是否有实际的文本内容
|
||||||
const hasTextContent = lastMessage?.parts && lastMessage.parts.some(
|
const hasTextContent = lastMessage?.parts && lastMessage.parts.some(
|
||||||
@ -169,12 +192,6 @@ function PureMessages({
|
|||||||
// 判断是否是图片生成模型(包括 Gemini、火山、阿里)
|
// 判断是否是图片生成模型(包括 Gemini、火山、阿里)
|
||||||
const isImageGenModel = isGeminiModel || isVolcanoVisionModel || isAlibabaImageModel;
|
const isImageGenModel = isGeminiModel || isVolcanoVisionModel || isAlibabaImageModel;
|
||||||
|
|
||||||
// 🆕 智能自动模式:检查用户最后一条消息是否包含图片,如果有则视为图片编辑场景
|
|
||||||
// 注意:需要找用户的最后一条消息,而不是整个对话的最后一条
|
|
||||||
const lastUserMessage = [...messages].reverse().find(m => m.role === "user");
|
|
||||||
const isAutoModeWithImage = selectedModelId === "auto" &&
|
|
||||||
lastUserMessage?.parts?.some((p: any) => p.type === "file" || p.type === "image");
|
|
||||||
|
|
||||||
// 根据模型类型判断是否有有效内容
|
// 根据模型类型判断是否有有效内容
|
||||||
let hasContent = false;
|
let hasContent = false;
|
||||||
if (isQianwenVideoModel || isVideoModel) {
|
if (isQianwenVideoModel || isVideoModel) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user