diff --git a/src/components/HistoryPanel.tsx b/src/components/HistoryPanel.tsx
index d3e4ca1..3ab8d42 100644
--- a/src/components/HistoryPanel.tsx
+++ b/src/components/HistoryPanel.tsx
@@ -61,10 +61,10 @@ export const HistoryPanel: React.FC = () => {
// 分页状态
const [currentPage, setCurrentPage] = useState(1);
- const itemsPerPage = 30; // 每页显示的项目数
+ const itemsPerPage = 20; // 减少每页显示的项目数
// 悬浮预览状态
- const [hoveredImage, setHoveredImage] = useState<{url: string, title: string, width?: number, height?: number, size?: number} | null>(null);
+ const [hoveredImage, setHoveredImage] = useState<{url: string, title: string, width?: number, height?: number} | null>(null);
const [previewPosition, setPreviewPosition] = useState<{x: number, y: number}>({x: 0, y: 0});
const generations = currentProject?.generations || [];
@@ -76,7 +76,7 @@ export const HistoryPanel: React.FC = () => {
const uploadResult = generationOrEdit.uploadResults[index];
if (uploadResult.success && uploadResult.url) {
// 添加参数以降低图片质量
- return `${uploadResult.url}?x-oss-process=image/quality,q_50`;
+ return `${uploadResult.url}?x-oss-process=image/quality,q_30`; // 降低质量到30%
}
}
return null;
@@ -480,7 +480,7 @@ export const HistoryPanel: React.FC = () => {
变体
- {filteredGenerations.length + filteredEdits.length}/1000
+ {filteredGenerations.length + filteredEdits.length}/100
{filteredGenerations.length === 0 && filteredEdits.length === 0 ? (
@@ -530,25 +530,16 @@ export const HistoryPanel: React.FC = () => {
// 创建图像对象以获取尺寸
const img = new Image();
img.onload = () => {
- // 计算文件大小(仅对base64数据)
- let size = 0;
- if (imageUrl.startsWith('data:')) {
- // 估算base64数据大小
- const base64Data = imageUrl.split(',')[1];
- size = Math.round((base64Data.length * 3) / 4);
- }
-
setHoveredImage({
url: imageUrl,
title: `生成记录 G${globalIndex + 1}`,
width: img.width,
- height: img.height,
- size: size
+ height: img.height
});
// 计算预览位置,确保不超出屏幕边界
- const previewWidth = 500;
- const previewHeight = 500;
+ const previewWidth = 300; // 减小预览窗口大小
+ const previewHeight = 300;
const offsetX = 10;
const offsetY = 10;
@@ -596,13 +587,12 @@ export const HistoryPanel: React.FC = () => {
url: imageUrl,
title: `生成记录 G${globalIndex + 1}`,
width: 0,
- height: 0,
- size: 0
+ height: 0
});
// 计算预览位置
- const previewWidth = 500;
- const previewHeight = 500;
+ const previewWidth = 300;
+ const previewHeight = 300;
const offsetX = 10;
const offsetY = 10;
@@ -641,8 +631,8 @@ export const HistoryPanel: React.FC = () => {
}}
onMouseMove={(e) => {
// 调整预览位置以避免被遮挡
- const previewWidth = 500;
- const previewHeight = 500;
+ const previewWidth = 300;
+ const previewHeight = 300;
const offsetX = 10;
const offsetY = 10;
@@ -688,9 +678,9 @@ export const HistoryPanel: React.FC = () => {
}}
>
{(() => {
- // 优先使用上传后的远程链接,如果没有则使用原始链接
+ // 优先使用上传后的远程链接
const imageUrl = getUploadedImageUrl(generation, 0) ||
- (generation.outputAssets && generation.outputAssets.length > 0 ? generation.outputAssets[0].url : null);
+ (generation.outputAssets && generation.outputAssets.length > 0 && generation.outputAssets[0].url ? generation.outputAssets[0].url : null);
if (imageUrl) {
return

;
@@ -757,25 +747,16 @@ export const HistoryPanel: React.FC = () => {
// 创建图像对象以获取尺寸
const img = new Image();
img.onload = () => {
- // 计算文件大小(仅对base64数据)
- let size = 0;
- if (imageUrl.startsWith('data:')) {
- // 估算base64数据大小
- const base64Data = imageUrl.split(',')[1];
- size = Math.round((base64Data.length * 3) / 4);
- }
-
setHoveredImage({
url: imageUrl,
title: `编辑记录 E${globalIndex + 1}`,
width: img.width,
- height: img.height,
- size: size
+ height: img.height
});
// 计算预览位置,确保不超出屏幕边界
- const previewWidth = 500;
- const previewHeight = 500;
+ const previewWidth = 300;
+ const previewHeight = 300;
const offsetX = 10;
const offsetY = 10;
@@ -816,13 +797,12 @@ export const HistoryPanel: React.FC = () => {
url: imageUrl,
title: `编辑记录 E${globalIndex + 1}`,
width: 0,
- height: 0,
- size: 0
+ height: 0
});
// 计算预览位置
- const previewWidth = 500;
- const previewHeight = 500;
+ const previewWidth = 300;
+ const previewHeight = 300;
const offsetX = 10;
const offsetY = 10;
@@ -865,8 +845,8 @@ export const HistoryPanel: React.FC = () => {
}}
onMouseMove={(e) => {
// 调整预览位置以避免被遮挡
- const previewWidth = 500;
- const previewHeight = 500;
+ const previewWidth = 300;
+ const previewHeight = 300;
const offsetX = 10;
const offsetY = 10;
@@ -904,9 +884,9 @@ export const HistoryPanel: React.FC = () => {
}}
>
{(() => {
- // 优先使用上传后的远程链接,如果没有则使用原始链接
+ // 优先使用上传后的远程链接
const imageUrl = getUploadedImageUrl(edit, 0) ||
- (edit.outputAssets && edit.outputAssets.length > 0 ? edit.outputAssets[0].url : null);
+ (edit.outputAssets && edit.outputAssets.length > 0 && edit.outputAssets[0].url ? edit.outputAssets[0].url : null);
if (imageUrl) {
return

;
@@ -1042,7 +1022,7 @@ export const HistoryPanel: React.FC = () => {
// 获取上传后的远程链接(如果存在)
// 参考图像在uploadResults中从索引1开始(索引0是生成的图像)
const uploadedUrl = gen.uploadResults && gen.uploadResults[index + 1] && gen.uploadResults[index + 1].success
- ? `${gen.uploadResults[index + 1].url}?x-oss-process=image/quality,q_50`
+ ? `${gen.uploadResults[index + 1].url}?x-oss-process=image/quality,q_30`
: null;
const displayUrl = uploadedUrl || asset.url;
@@ -1150,7 +1130,7 @@ export const HistoryPanel: React.FC = () => {
// 获取上传后的远程链接(如果存在)
// 参考图像在uploadResults中从索引1开始(索引0是生成的图像)
const uploadedUrl = parentGen.uploadResults && parentGen.uploadResults[index + 1] && parentGen.uploadResults[index + 1].success
- ? `${parentGen.uploadResults[index + 1].url}?x-oss-process=image/quality,q_50`
+ ? `${parentGen.uploadResults[index + 1].url}?x-oss-process=image/quality,q_30`
: null;
const displayUrl = uploadedUrl || asset.url;
@@ -1219,7 +1199,25 @@ export const HistoryPanel: React.FC = () => {
if (imageUrl) {
// 处理数据URL和常规URL
- if (imageUrl.startsWith('data:')) {
+ if (imageUrl.startsWith('data:') || imageUrl.startsWith('blob:')) {
+ // 对于Blob URL,我们需要获取实际的Blob数据
+ if (imageUrl.startsWith('blob:')) {
+ // 从AppStore获取Blob
+ const blob = useAppStore.getState().getBlob(imageUrl);
+ if (blob) {
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = `nano-banana-${Date.now()}.png`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ return;
+ }
+ }
+
+ // 对于数据URL,直接下载
const link = document.createElement('a');
link.href = imageUrl;
link.download = `nano-banana-${Date.now()}.png`;
@@ -1266,8 +1264,8 @@ export const HistoryPanel: React.FC = () => {
style={{
left: `${previewPosition.x}px`,
top: `${previewPosition.y}px`,
- maxWidth: '300px',
- maxHeight: '300px'
+ maxWidth: '200px', // 减小最大宽度
+ maxHeight: '200px'
}}
>
@@ -1276,7 +1274,7 @@ export const HistoryPanel: React.FC = () => {

{/* 图像信息 */}
@@ -1286,12 +1284,6 @@ export const HistoryPanel: React.FC = () => {
{hoveredImage.width} × {hoveredImage.height}
)}
- {hoveredImage.size > 0 && (
-
- 大小:
- {Math.round(hoveredImage.size / 1024)} KB
-
- )}
)}
diff --git a/src/components/ImageCanvas.tsx b/src/components/ImageCanvas.tsx
index f9766ec..d4e7553 100644
--- a/src/components/ImageCanvas.tsx
+++ b/src/components/ImageCanvas.tsx
@@ -77,6 +77,13 @@ export const ImageCanvas: React.FC = () => {
img.src = canvasImage;
} else {
+ // 当没有图像时,清理之前的图像对象
+ if (image) {
+ // 清理图像对象以释放内存
+ image.onload = null;
+ image.onerror = null;
+ image.src = '';
+ }
setImage(null);
}
@@ -86,9 +93,18 @@ export const ImageCanvas: React.FC = () => {
if (img) {
img.onload = null;
img.onerror = null;
+ // 清理图像源以释放内存
+ img.src = '';
+ }
+
+ // 清理之前的图像对象
+ if (image) {
+ image.onload = null;
+ image.onerror = null;
+ image.src = '';
}
};
- }, [canvasImage, stageSize, setCanvasZoom, setCanvasPan]);
+ }, [canvasImage, stageSize, setCanvasZoom, setCanvasPan, image]);
// 处理舞台大小调整
useEffect(() => {
diff --git a/src/components/PromptComposer.tsx b/src/components/PromptComposer.tsx
index 612b3d7..001236f 100644
--- a/src/components/PromptComposer.tsx
+++ b/src/components/PromptComposer.tsx
@@ -4,7 +4,7 @@ import { Button } from './ui/Button';
import { useAppStore } from '../store/useAppStore';
import { useImageGeneration, useImageEditing } from '../hooks/useImageGeneration';
import { Upload, Wand2, Edit3, MousePointer, HelpCircle, ChevronDown, ChevronRight, RotateCcw } from 'lucide-react';
-import { blobToBase64 } from '../utils/imageUtils';
+import { blobToBase64, urlToBlob } from '../utils/imageUtils';
import { PromptHints } from './PromptHints';
import { cn } from '../utils/cn';
@@ -32,6 +32,7 @@ export const PromptComposer: React.FC = () => {
showPromptPanel,
setShowPromptPanel,
clearBrushStrokes,
+ addBlob
} = useAppStore();
const { generate, cancelGeneration } = useImageGeneration();
@@ -42,17 +43,45 @@ export const PromptComposer: React.FC = () => {
const [isDragOver, setIsDragOver] = useState(false);
const fileInputRef = useRef