feat: 图像拓展功能优化和防重复点击
- 图像拓展API改为使用URL方式,避免Base64长度限制(61440字节) - 先将图片上传到S3获取公网URL,再调用Dashscope API - 添加防重复点击功能,处理中时禁用所有图片处理按钮 - 添加自动滚动功能,点击处理按钮后自动滚动到最新消息 - 处理完成后也会自动滚动到底部显示结果
This commit is contained in:
parent
7526a0159c
commit
1f213bb92d
@ -85,26 +85,61 @@ async function uploadBase64ToS3(base64Data: string, mimeType: string = "image/jp
|
||||
export const maxDuration = 180;
|
||||
|
||||
/**
|
||||
* 图像拓展核心实现
|
||||
* @param imageBase64 - Base64编码的图片数据
|
||||
* 将图片上传到S3并返回公网URL(用于API调用)
|
||||
*/
|
||||
async function uploadImageToS3ForAPI(imageBase64: string, mimeType: string = "image/jpeg"): Promise<string> {
|
||||
try {
|
||||
// 解析 base64 数据
|
||||
const buffer = Buffer.from(imageBase64, "base64");
|
||||
|
||||
// 生成文件名
|
||||
const ext = mimeType.split("/")[1] || "jpg";
|
||||
const timestamp = Date.now();
|
||||
const randomStr = Math.random().toString(36).slice(2, 8);
|
||||
const filename = `extend-input-${timestamp}-${randomStr}.${ext}`;
|
||||
|
||||
console.log(`[Extend API] 上传原图到S3,大小: ${(buffer.length / 1024).toFixed(2)}KB`);
|
||||
|
||||
// 转换为 ArrayBuffer
|
||||
const arrayBuffer = buffer.buffer.slice(
|
||||
buffer.byteOffset,
|
||||
buffer.byteOffset + buffer.byteLength
|
||||
) as ArrayBuffer;
|
||||
|
||||
// 上传到 S3
|
||||
const result = await uploadFile(filename, arrayBuffer);
|
||||
|
||||
console.log(`[Extend API] ✅ 原图已上传到S3: ${result.url}`);
|
||||
|
||||
return result.url;
|
||||
} catch (error) {
|
||||
console.error("[Extend API] 上传到S3失败:", error);
|
||||
throw new Error(`图片上传到存储桶失败: ${error instanceof Error ? error.message : "未知错误"}。请检查系统设置中的存储配置。`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图像拓展核心实现(使用URL方式,避免Base64长度限制)
|
||||
* @param imageUrl - 图片的公网URL
|
||||
* @param apiKey - Dashscope API Key
|
||||
* @param xScale - X轴放大倍数(默认1.5)
|
||||
* @param yScale - Y轴放大倍数(默认1.5)
|
||||
* @returns 拓展后的图片URL
|
||||
*/
|
||||
async function extendImage(
|
||||
imageBase64: string,
|
||||
imageUrl: string,
|
||||
apiKey: string,
|
||||
xScale: number = 1.5,
|
||||
yScale: number = 1.5
|
||||
): Promise<string> {
|
||||
console.log("[Extend API] 开始图像拓展...");
|
||||
console.log("[Extend API] 使用图片URL:", imageUrl);
|
||||
|
||||
// 1. 提交拓展任务
|
||||
// 1. 提交拓展任务(使用URL方式,避免Base64长度限制)
|
||||
const requestBody = {
|
||||
model: "image-out-painting",
|
||||
input: {
|
||||
image_base64: imageBase64,
|
||||
image_url: imageUrl,
|
||||
},
|
||||
parameters: {
|
||||
x_scale: xScale,
|
||||
@ -187,6 +222,7 @@ async function extendImage(
|
||||
throw new Error(`图像拓展任务超时(等待${maxAttempts * 2 + 3}秒)`);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 图像拓展API路由
|
||||
* 从数据库读取图像拓展API配置,调用Dashscope API进行图像拓展处理
|
||||
@ -286,14 +322,19 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
console.log(`[Extend API] 图片Base64长度: ${imageBase64.length} 字符`);
|
||||
|
||||
// 3. 调用图像拓展API
|
||||
// 3. 先将图片上传到S3获取公网URL(使用URL方式避免Base64长度限制)
|
||||
console.log("[Extend API] 上传原图到S3获取公网URL...");
|
||||
const inputImageUrl = await uploadImageToS3ForAPI(imageBase64, "image/jpeg");
|
||||
console.log("[Extend API] 原图URL:", inputImageUrl);
|
||||
|
||||
// 4. 调用图像拓展API(使用URL方式)
|
||||
const startTime = Date.now();
|
||||
const xScale = config.xScale || 1.5;
|
||||
const yScale = config.yScale || 1.5;
|
||||
|
||||
console.log(`[Extend API] 拓展参数: X=${xScale}, Y=${yScale}`);
|
||||
|
||||
const extendedImageUrl = await extendImage(imageBase64, config.apiKey, xScale, yScale);
|
||||
const extendedImageUrl = await extendImage(inputImageUrl, config.apiKey, xScale, yScale);
|
||||
console.log("[Extend API] 拓展后图片URL:", extendedImageUrl);
|
||||
|
||||
// 4. 下载拓展后的图片
|
||||
@ -350,3 +391,4 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -111,6 +111,10 @@ export function Chat({
|
||||
const [usage, setUsage] = useState<AppUsage | undefined>(initialLastContext);
|
||||
const [showCreditCardAlert, setShowCreditCardAlert] = useState(false);
|
||||
const [currentModelId, setCurrentModelId] = useState(initialChatModel);
|
||||
// 图片处理状态,防止重复点击
|
||||
const [isProcessingEnhance, setIsProcessingEnhance] = useState(false);
|
||||
const [isProcessingRmbg, setIsProcessingRmbg] = useState(false);
|
||||
const [isProcessingExtend, setIsProcessingExtend] = useState(false);
|
||||
const currentModelIdRef = useRef(currentModelId);
|
||||
|
||||
// 🆕 视频参数状态
|
||||
@ -502,6 +506,13 @@ export function Chat({
|
||||
|
||||
// 处理图片变清晰
|
||||
const handleImageEnhance = async (imageUrl: string) => {
|
||||
// 防止重复点击
|
||||
if (isProcessingEnhance || isProcessingRmbg || isProcessingExtend) {
|
||||
toast.info("正在处理中,请稍候...");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsProcessingEnhance(true);
|
||||
const messageId = generateUUID();
|
||||
|
||||
// 创建临时加载消息
|
||||
@ -514,6 +525,17 @@ export function Chat({
|
||||
|
||||
setMessages((prev) => [...prev, loadingMessage]);
|
||||
|
||||
// 延迟滚动到底部,确保DOM已更新
|
||||
setTimeout(() => {
|
||||
const messagesContainer = document.querySelector('[role="log"]');
|
||||
if (messagesContainer) {
|
||||
messagesContainer.scrollTo({
|
||||
top: messagesContainer.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
try {
|
||||
// 获取图片并转换为Base64
|
||||
const response = await fetch(imageUrl);
|
||||
@ -562,6 +584,17 @@ export function Chat({
|
||||
prev.map((msg) => (msg.id === messageId ? finalMessage : msg))
|
||||
);
|
||||
|
||||
// 滚动到底部,显示处理结果
|
||||
setTimeout(() => {
|
||||
const messagesContainer = document.querySelector('[role="log"]');
|
||||
if (messagesContainer) {
|
||||
messagesContainer.scrollTo({
|
||||
top: messagesContainer.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// 保存消息到数据库
|
||||
try {
|
||||
await fetch("/api/messages", {
|
||||
@ -593,12 +626,15 @@ export function Chat({
|
||||
toast.error(error instanceof Error ? error.message : "变清晰失败,请稍后重试");
|
||||
// 移除加载消息
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
} finally {
|
||||
setIsProcessingEnhance(false);
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
toast.error("图片读取失败");
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
setIsProcessingEnhance(false);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(blob);
|
||||
@ -606,11 +642,19 @@ export function Chat({
|
||||
console.error("[变清晰] 错误:", error);
|
||||
toast.error(error instanceof Error ? error.message : "变清晰失败,请稍后重试");
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
setIsProcessingEnhance(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理图片抠图
|
||||
const handleImageRmbg = async (imageUrl: string) => {
|
||||
// 防止重复点击
|
||||
if (isProcessingEnhance || isProcessingRmbg || isProcessingExtend) {
|
||||
toast.info("正在处理中,请稍候...");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsProcessingRmbg(true);
|
||||
const messageId = generateUUID();
|
||||
|
||||
// 创建临时加载消息
|
||||
@ -623,6 +667,17 @@ export function Chat({
|
||||
|
||||
setMessages((prev) => [...prev, loadingMessage]);
|
||||
|
||||
// 延迟滚动到底部,确保DOM已更新
|
||||
setTimeout(() => {
|
||||
const messagesContainer = document.querySelector('[role="log"]');
|
||||
if (messagesContainer) {
|
||||
messagesContainer.scrollTo({
|
||||
top: messagesContainer.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
try {
|
||||
// 获取图片并转换为Base64
|
||||
const response = await fetch(imageUrl);
|
||||
@ -671,6 +726,17 @@ export function Chat({
|
||||
prev.map((msg) => (msg.id === messageId ? finalMessage : msg))
|
||||
);
|
||||
|
||||
// 滚动到底部,显示处理结果
|
||||
setTimeout(() => {
|
||||
const messagesContainer = document.querySelector('[role="log"]');
|
||||
if (messagesContainer) {
|
||||
messagesContainer.scrollTo({
|
||||
top: messagesContainer.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// 保存消息到数据库
|
||||
try {
|
||||
await fetch("/api/messages", {
|
||||
@ -702,12 +768,15 @@ export function Chat({
|
||||
toast.error(error instanceof Error ? error.message : "抠图失败,请稍后重试");
|
||||
// 移除加载消息
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
} finally {
|
||||
setIsProcessingRmbg(false);
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
toast.error("图片读取失败");
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
setIsProcessingRmbg(false);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(blob);
|
||||
@ -715,11 +784,19 @@ export function Chat({
|
||||
console.error("[抠图] 错误:", error);
|
||||
toast.error(error instanceof Error ? error.message : "抠图失败,请稍后重试");
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
setIsProcessingRmbg(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理图像拓展
|
||||
const handleImageExtend = async (imageUrl: string) => {
|
||||
// 防止重复点击
|
||||
if (isProcessingEnhance || isProcessingRmbg || isProcessingExtend) {
|
||||
toast.info("正在处理中,请稍候...");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsProcessingExtend(true);
|
||||
const messageId = generateUUID();
|
||||
|
||||
// 创建临时加载消息
|
||||
@ -734,6 +811,17 @@ export function Chat({
|
||||
|
||||
setMessages((prev) => [...prev, loadingMessage]);
|
||||
|
||||
// 延迟滚动到底部,确保DOM已更新
|
||||
setTimeout(() => {
|
||||
const messagesContainer = document.querySelector('[role="log"]');
|
||||
if (messagesContainer) {
|
||||
messagesContainer.scrollTo({
|
||||
top: messagesContainer.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
try {
|
||||
// 获取图片并转换为Base64
|
||||
const response = await fetch(imageUrl);
|
||||
@ -782,6 +870,17 @@ export function Chat({
|
||||
prev.map((msg) => (msg.id === messageId ? finalMessage : msg))
|
||||
);
|
||||
|
||||
// 滚动到底部,显示处理结果
|
||||
setTimeout(() => {
|
||||
const messagesContainer = document.querySelector('[role="log"]');
|
||||
if (messagesContainer) {
|
||||
messagesContainer.scrollTo({
|
||||
top: messagesContainer.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// 保存消息到数据库
|
||||
try {
|
||||
await fetch("/api/messages", {
|
||||
@ -813,12 +912,15 @@ export function Chat({
|
||||
toast.error(error instanceof Error ? error.message : "图像拓展失败,请稍后重试");
|
||||
// 移除加载消息
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
} finally {
|
||||
setIsProcessingExtend(false);
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
toast.error("图片读取失败");
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
setIsProcessingExtend(false);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(blob);
|
||||
@ -826,6 +928,7 @@ export function Chat({
|
||||
console.error("[图像拓展] 错误:", error);
|
||||
toast.error(error instanceof Error ? error.message : "图像拓展失败,请稍后重试");
|
||||
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
setIsProcessingExtend(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -861,6 +964,9 @@ export function Chat({
|
||||
onImageEnhance={handleImageEnhance}
|
||||
onImageRmbg={handleImageRmbg}
|
||||
onImageExtend={handleImageExtend}
|
||||
isProcessingEnhance={isProcessingEnhance}
|
||||
isProcessingRmbg={isProcessingRmbg}
|
||||
isProcessingExtend={isProcessingExtend}
|
||||
pendingGeneration={pendingGeneration}
|
||||
imageCount={imageParams?.imageCount || 1}
|
||||
/>
|
||||
|
||||
@ -12,10 +12,13 @@ type ResponseProps = ComponentProps<typeof Streamdown> & {
|
||||
onImageEnhance?: (imageUrl: string) => void;
|
||||
onImageRmbg?: (imageUrl: string) => void;
|
||||
onImageExtend?: (imageUrl: string) => void;
|
||||
isProcessingEnhance?: boolean;
|
||||
isProcessingRmbg?: boolean;
|
||||
isProcessingExtend?: boolean;
|
||||
};
|
||||
|
||||
export const Response = memo(
|
||||
({ className, onImageEdit, onDrawingComplete, onImageEnhance, onImageRmbg, onImageExtend, ...props }: ResponseProps) => (
|
||||
({ className, onImageEdit, onDrawingComplete, onImageEnhance, onImageRmbg, onImageExtend, isProcessingEnhance, isProcessingRmbg, isProcessingExtend, ...props }: ResponseProps) => (
|
||||
<Streamdown
|
||||
className={cn(
|
||||
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_code]:whitespace-pre-wrap [&_code]:break-words [&_pre]:max-w-full [&_pre]:overflow-x-auto",
|
||||
@ -34,6 +37,9 @@ export const Response = memo(
|
||||
onImageEnhance={onImageEnhance}
|
||||
onImageRmbg={onImageRmbg}
|
||||
onImageExtend={onImageExtend}
|
||||
isProcessingEnhance={isProcessingEnhance}
|
||||
isProcessingRmbg={isProcessingRmbg}
|
||||
isProcessingExtend={isProcessingExtend}
|
||||
/>;
|
||||
},
|
||||
video: ({ src, alt }: { src?: string; alt?: string }) => {
|
||||
@ -52,7 +58,10 @@ export const Response = memo(
|
||||
prevProps.onDrawingComplete === nextProps.onDrawingComplete &&
|
||||
prevProps.onImageEnhance === nextProps.onImageEnhance &&
|
||||
prevProps.onImageRmbg === nextProps.onImageRmbg &&
|
||||
prevProps.onImageExtend === nextProps.onImageExtend
|
||||
prevProps.onImageExtend === nextProps.onImageExtend &&
|
||||
prevProps.isProcessingEnhance === nextProps.isProcessingEnhance &&
|
||||
prevProps.isProcessingRmbg === nextProps.isProcessingRmbg &&
|
||||
prevProps.isProcessingExtend === nextProps.isProcessingExtend
|
||||
);
|
||||
|
||||
Response.displayName = "Response";
|
||||
|
||||
@ -14,6 +14,9 @@ export function ImagePreview({
|
||||
onImageEnhance,
|
||||
onImageRmbg,
|
||||
onImageExtend,
|
||||
isProcessingEnhance,
|
||||
isProcessingRmbg,
|
||||
isProcessingExtend,
|
||||
imageIndex
|
||||
}: {
|
||||
src: string;
|
||||
@ -28,6 +31,9 @@ export function ImagePreview({
|
||||
onImageEnhance?: (imageUrl: string) => void;
|
||||
onImageRmbg?: (imageUrl: string) => void;
|
||||
onImageExtend?: (imageUrl: string) => void;
|
||||
isProcessingEnhance?: boolean;
|
||||
isProcessingRmbg?: boolean;
|
||||
isProcessingExtend?: boolean;
|
||||
imageIndex?: number;
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@ -226,11 +232,14 @@ export function ImagePreview({
|
||||
<div className="absolute top-2 left-2 flex gap-1.5 z-10">
|
||||
{onImageEnhance && (
|
||||
<button
|
||||
className="flex items-center gap-1.5 rounded-full bg-blue-600/90 hover:bg-blue-700 px-2.5 py-1.5 text-white transition-colors shadow-lg"
|
||||
className="flex items-center gap-1.5 rounded-full bg-blue-600/90 hover:bg-blue-700 px-2.5 py-1.5 text-white transition-colors shadow-lg disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onImageEnhance(original);
|
||||
if (!isProcessingEnhance && !isProcessingRmbg && !isProcessingExtend) {
|
||||
onImageEnhance(original);
|
||||
}
|
||||
}}
|
||||
disabled={isProcessingEnhance || isProcessingRmbg || isProcessingExtend}
|
||||
type="button"
|
||||
aria-label="变清晰"
|
||||
title="变清晰"
|
||||
@ -241,11 +250,14 @@ export function ImagePreview({
|
||||
)}
|
||||
{onImageRmbg && (
|
||||
<button
|
||||
className="flex items-center gap-1.5 rounded-full bg-purple-600/90 hover:bg-purple-700 px-2.5 py-1.5 text-white transition-colors shadow-lg"
|
||||
className="flex items-center gap-1.5 rounded-full bg-purple-600/90 hover:bg-purple-700 px-2.5 py-1.5 text-white transition-colors shadow-lg disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onImageRmbg(original);
|
||||
if (!isProcessingEnhance && !isProcessingRmbg && !isProcessingExtend) {
|
||||
onImageRmbg(original);
|
||||
}
|
||||
}}
|
||||
disabled={isProcessingEnhance || isProcessingRmbg || isProcessingExtend}
|
||||
type="button"
|
||||
aria-label="抠图"
|
||||
title="抠图"
|
||||
@ -256,11 +268,14 @@ export function ImagePreview({
|
||||
)}
|
||||
{onImageExtend && (
|
||||
<button
|
||||
className="flex items-center gap-1.5 rounded-full bg-orange-600/90 hover:bg-orange-700 px-2.5 py-1.5 text-white transition-colors shadow-lg"
|
||||
className="flex items-center gap-1.5 rounded-full bg-orange-600/90 hover:bg-orange-700 px-2.5 py-1.5 text-white transition-colors shadow-lg disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onImageExtend(original);
|
||||
if (!isProcessingEnhance && !isProcessingRmbg && !isProcessingExtend) {
|
||||
onImageExtend(original);
|
||||
}
|
||||
}}
|
||||
disabled={isProcessingEnhance || isProcessingRmbg || isProcessingExtend}
|
||||
type="button"
|
||||
aria-label="图像拓展"
|
||||
title="图像拓展"
|
||||
|
||||
@ -151,6 +151,9 @@ const PurePreviewMessage = ({
|
||||
onImageEnhance,
|
||||
onImageRmbg,
|
||||
onImageExtend,
|
||||
isProcessingEnhance,
|
||||
isProcessingRmbg,
|
||||
isProcessingExtend,
|
||||
}: {
|
||||
chatId: string;
|
||||
message: ChatMessage;
|
||||
@ -166,6 +169,9 @@ const PurePreviewMessage = ({
|
||||
onImageEnhance?: (imageUrl: string) => void;
|
||||
onImageRmbg?: (imageUrl: string) => void;
|
||||
onImageExtend?: (imageUrl: string) => void;
|
||||
isProcessingEnhance?: boolean;
|
||||
isProcessingRmbg?: boolean;
|
||||
isProcessingExtend?: boolean;
|
||||
}) => {
|
||||
const [mode, setMode] = useState<"view" | "edit">("view");
|
||||
|
||||
@ -299,6 +305,9 @@ const PurePreviewMessage = ({
|
||||
onImageEnhance={onImageEnhance}
|
||||
onImageRmbg={onImageRmbg}
|
||||
onImageExtend={onImageExtend}
|
||||
isProcessingEnhance={isProcessingEnhance}
|
||||
isProcessingRmbg={isProcessingRmbg}
|
||||
isProcessingExtend={isProcessingExtend}
|
||||
>
|
||||
{sanitizeText(part.content)}
|
||||
</Response>
|
||||
|
||||
@ -40,6 +40,9 @@ type MessagesProps = {
|
||||
onImageEnhance?: (imageUrl: string) => void;
|
||||
onImageRmbg?: (imageUrl: string) => void;
|
||||
onImageExtend?: (imageUrl: string) => void;
|
||||
isProcessingEnhance?: boolean;
|
||||
isProcessingRmbg?: boolean;
|
||||
isProcessingExtend?: boolean;
|
||||
pendingGeneration?: PendingGeneration | null; // 🆕 持久化的生成状态
|
||||
imageCount?: number; // 🆕 用户选择的图片数量
|
||||
};
|
||||
@ -59,6 +62,9 @@ function PureMessages({
|
||||
onImageEnhance,
|
||||
onImageRmbg,
|
||||
onImageExtend,
|
||||
isProcessingEnhance = false,
|
||||
isProcessingRmbg = false,
|
||||
isProcessingExtend = false,
|
||||
pendingGeneration,
|
||||
imageCount = 1,
|
||||
}: MessagesProps) {
|
||||
@ -176,7 +182,7 @@ function PureMessages({
|
||||
}
|
||||
}, [status, messages, isGeminiModel, isQianwenVideoModel, isVolcanoVisionModel, isVideoModel]);
|
||||
|
||||
// 🆕 监听新消息添加,当用户发送带图片的消息时,强制滚动到底部
|
||||
// 🆕 监听新消息添加,当用户发送带图片的消息或添加加载消息时,强制滚动到底部
|
||||
const prevMessagesLengthRef = useRef(messages.length);
|
||||
useEffect(() => {
|
||||
if (messages.length > prevMessagesLengthRef.current) {
|
||||
@ -191,6 +197,17 @@ function PureMessages({
|
||||
setTimeout(() => scrollToBottom("smooth"), 100);
|
||||
}
|
||||
}
|
||||
// 如果是新添加的助手消息且是加载消息(正在变清晰/抠图/图像拓展),强制滚动到底部
|
||||
if (lastMessage?.role === "assistant") {
|
||||
const firstPart = lastMessage.parts?.[0];
|
||||
if (firstPart && "text" in firstPart) {
|
||||
const loadingText = firstPart.text;
|
||||
if (loadingText === "正在变清晰..." || loadingText === "正在抠图..." || loadingText === "正在图像拓展...") {
|
||||
// 延迟一小段时间确保DOM已更新
|
||||
setTimeout(() => scrollToBottom("smooth"), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
prevMessagesLengthRef.current = messages.length;
|
||||
}, [messages, scrollToBottom]);
|
||||
@ -229,6 +246,9 @@ function PureMessages({
|
||||
onImageEnhance={onImageEnhance}
|
||||
onImageRmbg={onImageRmbg}
|
||||
onImageExtend={onImageExtend}
|
||||
isProcessingEnhance={isProcessingEnhance}
|
||||
isProcessingRmbg={isProcessingRmbg}
|
||||
isProcessingExtend={isProcessingExtend}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user