You've already forked Nano-Banana-AI-Image-Editor
新增 现在参考图可以拖动排序了;
修复 双参考图生成结果显示问题;
This commit is contained in:
@@ -612,11 +612,20 @@ export const HistoryPanel: React.FC<{
|
||||
)}
|
||||
onClick={() => {
|
||||
selectGeneration(generation.id);
|
||||
// 设置画布图像为参考图像,如果没有参考图像则使用第一个输出资产
|
||||
// 设置画布图像为生成结果图像,而不是参考图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (generation.sourceAssets && generation.sourceAssets.length > 0) {
|
||||
// 优先使用生成结果图像
|
||||
if (generation.outputAssets && generation.outputAssets.length > 0) {
|
||||
const asset = generation.outputAssets[0];
|
||||
if (asset.url) {
|
||||
const uploadedUrl = getUploadedImageUrl(generation, 0);
|
||||
imageUrl = uploadedUrl || asset.url;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有生成结果图像,则使用参考图像
|
||||
if (!imageUrl && generation.sourceAssets && generation.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = generation.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
@@ -628,8 +637,57 @@ export const HistoryPanel: React.FC<{
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有参考图像,则使用生成结果图像
|
||||
if (!imageUrl && generation.outputAssets && generation.outputAssets.length > 0) {
|
||||
if (imageUrl) {
|
||||
// 检查是否是Blob URL并且可能已经失效
|
||||
if (imageUrl.startsWith('blob:')) {
|
||||
// 预先检查Blob URL是否有效
|
||||
fetch(imageUrl)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
// Blob URL失效,尝试从AppStore重新获取
|
||||
const { getBlob } = useAppStore.getState();
|
||||
const blob = getBlob(imageUrl);
|
||||
if (blob) {
|
||||
console.log('从AppStore找到Blob,重新创建URL...');
|
||||
const newUrl = URL.createObjectURL(blob);
|
||||
setCanvasImage(newUrl);
|
||||
} else {
|
||||
// 如果AppStore中也没有,直接设置原URL,让ImageCanvas处理
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
} else {
|
||||
// Blob URL有效,直接使用
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 网络错误,尝试从AppStore重新获取
|
||||
const { getBlob } = useAppStore.getState();
|
||||
const blob = getBlob(imageUrl);
|
||||
if (blob) {
|
||||
console.log('从AppStore找到Blob,重新创建URL...');
|
||||
const newUrl = URL.createObjectURL(blob);
|
||||
setCanvasImage(newUrl);
|
||||
} else {
|
||||
// 如果AppStore中也没有,直接设置原URL,让ImageCanvas处理
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 非Blob URL直接设置
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
// 设置当前悬停的记录
|
||||
setHoveredRecord({type: 'generation', id: generation.id});
|
||||
|
||||
// 优先显示生成结果图像,如果没有生成结果图像则显示参考图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 优先使用生成结果图像
|
||||
if (generation.outputAssets && generation.outputAssets.length > 0) {
|
||||
const asset = generation.outputAssets[0];
|
||||
if (asset.url) {
|
||||
const uploadedUrl = getUploadedImageUrl(generation, 0);
|
||||
@@ -637,19 +695,8 @@ export const HistoryPanel: React.FC<{
|
||||
}
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
// 设置当前悬停的记录
|
||||
setHoveredRecord({type: 'generation', id: generation.id});
|
||||
|
||||
// 优先显示参考图像,如果没有参考图像则显示生成结果图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (generation.sourceAssets && generation.sourceAssets.length > 0) {
|
||||
// 如果没有生成结果图像,则使用参考图像
|
||||
if (!imageUrl && generation.sourceAssets && generation.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = generation.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
@@ -657,12 +704,6 @@ export const HistoryPanel: React.FC<{
|
||||
(generation.sourceAssets[0].url ? generation.sourceAssets[0].url : null);
|
||||
}
|
||||
|
||||
// 如果没有参考图像,则使用生成结果图像
|
||||
if (!imageUrl) {
|
||||
imageUrl = getUploadedImageUrl(generation, 0) ||
|
||||
(generation.outputAssets && generation.outputAssets.length > 0 && generation.outputAssets[0].url ? generation.outputAssets[0].url : null);
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
// 创建图像对象以获取尺寸
|
||||
const img = new Image();
|
||||
@@ -769,11 +810,20 @@ export const HistoryPanel: React.FC<{
|
||||
}}
|
||||
>
|
||||
{(() => {
|
||||
// 优先显示参考图像,如果没有参考图像则显示生成结果图像
|
||||
// 优先显示生成结果图像,如果没有生成结果图像则显示参考图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (generation.sourceAssets && generation.sourceAssets.length > 0) {
|
||||
// 优先使用生成结果图像
|
||||
if (generation.outputAssets && generation.outputAssets.length > 0) {
|
||||
const asset = generation.outputAssets[0];
|
||||
if (asset.url) {
|
||||
const uploadedUrl = getUploadedImageUrl(generation, 0);
|
||||
imageUrl = uploadedUrl || asset.url;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有生成结果图像,则使用参考图像
|
||||
if (!imageUrl && generation.sourceAssets && generation.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = generation.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
@@ -781,12 +831,6 @@ export const HistoryPanel: React.FC<{
|
||||
(generation.sourceAssets[0].url ? generation.sourceAssets[0].url : null);
|
||||
}
|
||||
|
||||
// 如果没有参考图像,则使用生成结果图像
|
||||
if (!imageUrl) {
|
||||
imageUrl = getUploadedImageUrl(generation, 0) ||
|
||||
(generation.outputAssets && generation.outputAssets.length > 0 && generation.outputAssets[0].url ? generation.outputAssets[0].url : null);
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
return <img src={imageUrl} alt="生成的变体" className="w-full h-full object-cover" />;
|
||||
} else {
|
||||
@@ -919,11 +963,20 @@ export const HistoryPanel: React.FC<{
|
||||
onClick={() => {
|
||||
selectEdit(edit.id);
|
||||
selectGeneration(null);
|
||||
// 设置画布图像为参考图像,如果没有参考图像则使用第一个输出资产
|
||||
// 设置画布图像为编辑结果图像,而不是参考图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (edit.sourceAssets && edit.sourceAssets.length > 0) {
|
||||
// 优先使用编辑结果图像
|
||||
if (edit.outputAssets && edit.outputAssets.length > 0) {
|
||||
const asset = edit.outputAssets[0];
|
||||
if (asset.url) {
|
||||
const uploadedUrl = getUploadedImageUrl(edit, 0);
|
||||
imageUrl = uploadedUrl || asset.url;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有编辑结果图像,则使用参考图像
|
||||
if (!imageUrl && edit.sourceAssets && edit.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = edit.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
@@ -935,8 +988,57 @@ export const HistoryPanel: React.FC<{
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有参考图像,则使用编辑结果图像
|
||||
if (!imageUrl && edit.outputAssets && edit.outputAssets.length > 0) {
|
||||
if (imageUrl) {
|
||||
// 检查是否是Blob URL并且可能已经失效
|
||||
if (imageUrl.startsWith('blob:')) {
|
||||
// 预先检查Blob URL是否有效
|
||||
fetch(imageUrl)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
// Blob URL失效,尝试从AppStore重新获取
|
||||
const { getBlob } = useAppStore.getState();
|
||||
const blob = getBlob(imageUrl);
|
||||
if (blob) {
|
||||
console.log('从AppStore找到Blob,重新创建URL...');
|
||||
const newUrl = URL.createObjectURL(blob);
|
||||
setCanvasImage(newUrl);
|
||||
} else {
|
||||
// 如果AppStore中也没有,直接设置原URL,让ImageCanvas处理
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
} else {
|
||||
// Blob URL有效,直接使用
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 网络错误,尝试从AppStore重新获取
|
||||
const { getBlob } = useAppStore.getState();
|
||||
const blob = getBlob(imageUrl);
|
||||
if (blob) {
|
||||
console.log('从AppStore找到Blob,重新创建URL...');
|
||||
const newUrl = URL.createObjectURL(blob);
|
||||
setCanvasImage(newUrl);
|
||||
} else {
|
||||
// 如果AppStore中也没有,直接设置原URL,让ImageCanvas处理
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 非Blob URL直接设置
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
// 设置当前悬停的记录
|
||||
setHoveredRecord({type: 'edit', id: edit.id});
|
||||
|
||||
// 优先显示编辑结果图像,如果没有编辑结果图像则显示参考图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 优先使用编辑结果图像
|
||||
if (edit.outputAssets && edit.outputAssets.length > 0) {
|
||||
const asset = edit.outputAssets[0];
|
||||
if (asset.url) {
|
||||
const uploadedUrl = getUploadedImageUrl(edit, 0);
|
||||
@@ -944,19 +1046,8 @@ export const HistoryPanel: React.FC<{
|
||||
}
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
// 设置当前悬停的记录
|
||||
setHoveredRecord({type: 'edit', id: edit.id});
|
||||
|
||||
// 优先显示参考图像,如果没有参考图像则显示编辑结果图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (edit.sourceAssets && edit.sourceAssets.length > 0) {
|
||||
// 如果没有编辑结果图像,则使用参考图像
|
||||
if (!imageUrl && edit.sourceAssets && edit.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = edit.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
@@ -964,12 +1055,6 @@ export const HistoryPanel: React.FC<{
|
||||
(edit.sourceAssets[0].url ? edit.sourceAssets[0].url : null);
|
||||
}
|
||||
|
||||
// 如果没有参考图像,则使用编辑结果图像
|
||||
if (!imageUrl) {
|
||||
imageUrl = getUploadedImageUrl(edit, 0) ||
|
||||
(edit.outputAssets && edit.outputAssets.length > 0 && edit.outputAssets[0].url ? edit.outputAssets[0].url : null);
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
// 创建图像对象以获取尺寸
|
||||
const img = new Image();
|
||||
@@ -1016,11 +1101,20 @@ export const HistoryPanel: React.FC<{
|
||||
}}
|
||||
>
|
||||
{(() => {
|
||||
// 优先显示参考图像,如果没有参考图像则显示编辑结果图像
|
||||
// 优先显示编辑结果图像,如果没有编辑结果图像则显示参考图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (edit.sourceAssets && edit.sourceAssets.length > 0) {
|
||||
// 优先使用编辑结果图像
|
||||
if (edit.outputAssets && edit.outputAssets.length > 0) {
|
||||
const asset = edit.outputAssets[0];
|
||||
if (asset.url) {
|
||||
const uploadedUrl = getUploadedImageUrl(edit, 0);
|
||||
imageUrl = uploadedUrl || asset.url;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有编辑结果图像,则使用参考图像
|
||||
if (!imageUrl && edit.sourceAssets && edit.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = edit.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
@@ -1028,12 +1122,6 @@ export const HistoryPanel: React.FC<{
|
||||
(edit.sourceAssets[0].url ? edit.sourceAssets[0].url : null);
|
||||
}
|
||||
|
||||
// 如果没有参考图像,则使用编辑结果图像
|
||||
if (!imageUrl) {
|
||||
imageUrl = getUploadedImageUrl(edit, 0) ||
|
||||
(edit.outputAssets && edit.outputAssets.length > 0 && edit.outputAssets[0].url ? edit.outputAssets[0].url : null);
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
return <img src={imageUrl} alt="编辑的变体" className="w-full h-full object-cover" />;
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,9 @@ export const ImageCanvas: React.FC = () => {
|
||||
const {
|
||||
canvasImage,
|
||||
canvasZoom,
|
||||
canvasPan,
|
||||
setCanvasZoom,
|
||||
setCanvasPan,
|
||||
brushStrokes,
|
||||
addBrushStroke,
|
||||
showMasks,
|
||||
@@ -48,132 +50,194 @@ export const ImageCanvas: React.FC = () => {
|
||||
|
||||
// 加载图像
|
||||
useEffect(() => {
|
||||
let img: HTMLImageElement | null = null;
|
||||
console.log('useEffect triggered, canvasImage:', canvasImage);
|
||||
|
||||
if (canvasImage) {
|
||||
console.log('开始加载图像,URL:', canvasImage);
|
||||
// 如果没有图像URL,直接返回
|
||||
if (!canvasImage) {
|
||||
console.log('没有图像需要加载');
|
||||
setImage(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let img: HTMLImageElement | null = null;
|
||||
let isCancelled = false;
|
||||
|
||||
console.log('开始加载图像,URL:', canvasImage);
|
||||
|
||||
img = new window.Image();
|
||||
|
||||
img.onload = () => {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
console.log('图像加载被取消');
|
||||
return;
|
||||
}
|
||||
|
||||
img = new window.Image();
|
||||
const isCancelled = false;
|
||||
console.log('图像加载成功,尺寸:', img.width, 'x', img.height);
|
||||
setImage(img);
|
||||
|
||||
img.onload = () => {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
console.log('图像加载被取消');
|
||||
return;
|
||||
}
|
||||
// 只在图像首次加载时自动适应画布
|
||||
if (!isCancelled && img) {
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const padding = isMobile ? 0.9 : 0.8;
|
||||
|
||||
console.log('图像加载成功,尺寸:', img.width, 'x', img.height);
|
||||
setImage(img);
|
||||
const scaleX = (stageSize.width * padding) / img.width;
|
||||
const scaleY = (stageSize.height * padding) / img.height;
|
||||
|
||||
// 只在图像首次加载时自动适应画布
|
||||
if (!isCancelled && img) {
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const padding = isMobile ? 0.9 : 0.8;
|
||||
const maxZoom = isMobile ? 0.3 : 0.8;
|
||||
const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
|
||||
|
||||
// 立即更新React状态以确保Konva Image组件使用正确的缩放值
|
||||
setCanvasZoom(optimalZoom);
|
||||
setCanvasPan({ x: 0, y: 0 });
|
||||
|
||||
// 使用setTimeout确保DOM已更新后再设置Stage
|
||||
setTimeout(() => {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleX = (stageSize.width * padding) / img.width;
|
||||
const scaleY = (stageSize.height * padding) / img.height;
|
||||
|
||||
const maxZoom = isMobile ? 0.3 : 0.8;
|
||||
const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
|
||||
|
||||
// 立即更新React状态以确保Konva Image组件使用正确的缩放值
|
||||
setCanvasZoom(optimalZoom);
|
||||
setCanvasPan({ x: 0, y: 0 });
|
||||
|
||||
// 使用setTimeout确保DOM已更新后再设置Stage
|
||||
setTimeout(() => {
|
||||
if (!isCancelled && img) {
|
||||
// 直接设置缩放,但保持Stage居中
|
||||
const stage = stageRef.current;
|
||||
if (stage) {
|
||||
stage.scale({ x: optimalZoom, y: optimalZoom });
|
||||
// 重置Stage位置以确保居中
|
||||
stage.position({ x: 0, y: 0 });
|
||||
stage.batchDraw();
|
||||
if (!isCancelled && img) {
|
||||
// 直接设置缩放,但保持Stage居中
|
||||
const stage = stageRef.current;
|
||||
if (stage) {
|
||||
stage.scale({ x: optimalZoom, y: optimalZoom });
|
||||
// 重置Stage位置以确保居中
|
||||
stage.position({ x: 0, y: 0 });
|
||||
stage.batchDraw();
|
||||
}
|
||||
|
||||
console.log('图像自动适应画布完成,缩放:', optimalZoom);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
img.onerror = (error) => {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('图像加载失败:', error);
|
||||
console.error('图像URL:', canvasImage);
|
||||
|
||||
// 检查是否是IndexedDB URL
|
||||
if (canvasImage.startsWith('indexeddb://')) {
|
||||
console.log('正在处理IndexedDB图像...');
|
||||
|
||||
// 从IndexedDB获取图像并创建Blob URL
|
||||
const imageId = canvasImage.replace('indexeddb://', '');
|
||||
import('../services/referenceImageService').then((module) => {
|
||||
const referenceImageService = module;
|
||||
referenceImageService.getReferenceImage(imageId)
|
||||
.then(blob => {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('图像自动适应画布完成,缩放:', optimalZoom);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
img.onerror = (error) => {
|
||||
if (!isCancelled) {
|
||||
console.error('图像加载失败:', error);
|
||||
console.error('图像URL:', canvasImage);
|
||||
if (blob) {
|
||||
const newUrl = URL.createObjectURL(blob);
|
||||
console.log('从IndexedDB创建新的Blob URL:', newUrl);
|
||||
// 更新canvasImage为新的URL
|
||||
import('../store/useAppStore').then((storeModule) => {
|
||||
const useAppStore = storeModule.useAppStore;
|
||||
// 检查是否已取消
|
||||
if (!isCancelled) {
|
||||
useAppStore.getState().setCanvasImage(newUrl);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('IndexedDB中未找到图像');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('从IndexedDB获取图像时出错:', err);
|
||||
});
|
||||
}).catch(err => {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是Blob URL
|
||||
if (canvasImage.startsWith('blob:')) {
|
||||
console.log('正在检查Blob URL是否有效...');
|
||||
console.error('导入referenceImageService时出错:', err);
|
||||
});
|
||||
}
|
||||
// 检查是否是Blob URL
|
||||
else if (canvasImage.startsWith('blob:')) {
|
||||
console.log('正在检查Blob URL是否有效...');
|
||||
|
||||
// 尝试从AppStore重新获取Blob并创建新的URL
|
||||
import('../store/useAppStore').then((module) => {
|
||||
const useAppStore = module.useAppStore;
|
||||
const blob = useAppStore.getState().getBlob(canvasImage);
|
||||
if (blob) {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查Blob URL是否仍然有效
|
||||
console.log('从AppStore找到Blob,尝试重新创建URL...');
|
||||
// 重新创建Blob URL并重试加载
|
||||
const newUrl = URL.createObjectURL(blob);
|
||||
console.log('创建新的Blob URL:', newUrl);
|
||||
// 更新canvasImage为新的URL
|
||||
useAppStore.getState().setCanvasImage(newUrl);
|
||||
} else {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('AppStore中未找到Blob');
|
||||
// 如果AppStore中也没有,尝试通过fetch检查URL
|
||||
fetch(canvasImage)
|
||||
.then(response => {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Blob URL无法访问:', response.status, response.statusText);
|
||||
// 尝试从AppStore重新获取Blob并创建新的URL
|
||||
import('../store/useAppStore').then((module) => {
|
||||
const useAppStore = module.useAppStore;
|
||||
const blob = useAppStore.getState().getBlob(canvasImage);
|
||||
if (blob) {
|
||||
console.log('从AppStore找到Blob,尝试重新创建URL...');
|
||||
// 重新创建Blob URL并重试加载
|
||||
const newUrl = URL.createObjectURL(blob);
|
||||
console.log('创建新的Blob URL:', newUrl);
|
||||
// 更新canvasImage为新的URL
|
||||
useAppStore.getState().setCanvasImage(newUrl);
|
||||
} else {
|
||||
console.error('AppStore中未找到Blob');
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('导入AppStore时出错:', err);
|
||||
});
|
||||
} else {
|
||||
console.log('Blob URL可以访问');
|
||||
console.log('Blob URL可以访问,但图像加载仍然失败');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('检查Blob URL时出错:', err);
|
||||
// 尝试从AppStore重新获取Blob
|
||||
import('../store/useAppStore').then((module) => {
|
||||
const useAppStore = module.useAppStore;
|
||||
const blob = useAppStore.getState().getBlob(canvasImage);
|
||||
if (blob) {
|
||||
console.log('从AppStore找到Blob,尝试重新创建URL...');
|
||||
// 重新创建Blob URL并重试加载
|
||||
const newUrl = URL.createObjectURL(blob);
|
||||
console.log('创建新的Blob URL:', newUrl);
|
||||
// 更新canvasImage为新的URL
|
||||
useAppStore.getState().setCanvasImage(newUrl);
|
||||
} else {
|
||||
console.error('AppStore中未找到Blob');
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('导入AppStore时出错:', err);
|
||||
});
|
||||
.catch(fetchErr => {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('检查Blob URL时出错:', fetchErr);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
img.src = canvasImage;
|
||||
} else {
|
||||
console.log('没有图像需要加载');
|
||||
// 当没有图像时,清理之前的图像对象
|
||||
if (image) {
|
||||
// 清理图像对象以释放内存
|
||||
image.onload = null;
|
||||
image.onerror = null;
|
||||
image.src = '';
|
||||
}).catch(err => {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('导入AppStore时出错:', err);
|
||||
});
|
||||
}
|
||||
setImage(null);
|
||||
}
|
||||
};
|
||||
|
||||
img.src = canvasImage;
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
console.log('清理图像加载资源');
|
||||
// 标记为已取消
|
||||
isCancelled = true;
|
||||
// 取消图像加载
|
||||
if (img) {
|
||||
img.onload = null;
|
||||
@@ -181,15 +245,8 @@ export const ImageCanvas: React.FC = () => {
|
||||
// 清理图像源以释放内存
|
||||
img.src = '';
|
||||
}
|
||||
|
||||
// 清理之前的图像对象
|
||||
if (image) {
|
||||
image.onload = null;
|
||||
image.onerror = null;
|
||||
image.src = '';
|
||||
}
|
||||
};
|
||||
}, [canvasImage, image, setCanvasZoom, stageSize.height, stageSize.width]); // 添加所有依赖项
|
||||
}, [canvasImage, setCanvasZoom, setCanvasPan, stageSize.height, stageSize.width]); // 移除image依赖项
|
||||
|
||||
// 处理舞台大小调整
|
||||
useEffect(() => {
|
||||
|
||||
@@ -16,7 +16,12 @@ const ImagePreview: React.FC<{
|
||||
index: number;
|
||||
selectedTool: 'generate' | 'edit' | 'mask';
|
||||
onRemove: () => void;
|
||||
}> = ({ image, index, onRemove }) => {
|
||||
onDragStart?: (e: React.DragEvent<HTMLDivElement>, index: number) => void;
|
||||
onDragOver?: (e: React.DragEvent<HTMLDivElement>, index: number) => void;
|
||||
onDragEnd?: (e: React.DragEvent<HTMLDivElement>) => void;
|
||||
onDrop?: (e: React.DragEvent<HTMLDivElement>, index: number) => void;
|
||||
isDragging?: boolean;
|
||||
}> = ({ image, index, onRemove, onDragStart, onDragOver, onDragEnd, onDrop, isDragging }) => {
|
||||
const [imageSrc, setImageSrc] = useState<string>(image);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
@@ -74,7 +79,20 @@ const ImagePreview: React.FC<{
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div
|
||||
className={cn("relative", isDragging ? "opacity-50" : "")}
|
||||
draggable
|
||||
onDragStart={(e) => onDragStart && onDragStart(e, index)}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
onDragOver && onDragOver(e, index);
|
||||
}}
|
||||
onDragEnd={(e) => onDragEnd && onDragEnd(e)}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
onDrop && onDrop(e, index);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={`参考图像 ${index + 1}`}
|
||||
@@ -111,6 +129,7 @@ export const PromptComposer: React.FC = () => {
|
||||
uploadedImages,
|
||||
addUploadedImage,
|
||||
removeUploadedImage,
|
||||
reorderUploadedImage,
|
||||
clearUploadedImages,
|
||||
editReferenceImages,
|
||||
addEditReferenceImage,
|
||||
@@ -130,6 +149,7 @@ export const PromptComposer: React.FC = () => {
|
||||
const [showClearConfirm, setShowClearConfirm] = useState(false);
|
||||
const [showHintsModal, setShowHintsModal] = useState(false);
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// 初始化参考图像数据库
|
||||
@@ -237,6 +257,13 @@ export const PromptComposer: React.FC = () => {
|
||||
// 创建一个特殊的URL来标识这是存储在IndexedDB中的图像
|
||||
const imageUrl = `indexeddb://${imageId}`;
|
||||
|
||||
// 同时创建一个可以直接在画布上显示的Blob URL
|
||||
const blob = await referenceImageService.getReferenceImage(imageId);
|
||||
let displayUrl = imageUrl; // 默认使用IndexedDB URL
|
||||
if (blob) {
|
||||
displayUrl = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
if (selectedTool === 'generate') {
|
||||
// 添加到参考图像(最多2张)
|
||||
if (uploadedImages.length < 2) {
|
||||
@@ -247,15 +274,15 @@ export const PromptComposer: React.FC = () => {
|
||||
if (editReferenceImages.length < 2) {
|
||||
addEditReferenceImage(imageUrl);
|
||||
}
|
||||
// 如果没有画布图像,则设置为画布图像
|
||||
// 如果没有画布图像,则设置为画布图像(使用可以直接显示的URL)
|
||||
if (!canvasImage) {
|
||||
setCanvasImage(imageUrl);
|
||||
setCanvasImage(displayUrl);
|
||||
}
|
||||
} else if (selectedTool === 'mask') {
|
||||
// 遮罩模式下,将图像添加为参考图像而不是清除现有图像
|
||||
// 只有在没有画布图像时才设置为画布图像
|
||||
// 只有在没有画布图像时才设置为画布图像(使用可以直接显示的URL)
|
||||
if (!canvasImage) {
|
||||
setCanvasImage(imageUrl);
|
||||
setCanvasImage(displayUrl);
|
||||
}
|
||||
// 不清除现有的上传图像,而是将新图像添加为参考图像(如果还有空间)
|
||||
if (uploadedImages.length < 2) {
|
||||
@@ -294,6 +321,31 @@ export const PromptComposer: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 拖拽排序处理函数
|
||||
const handleDragStart = (e: React.DragEvent<HTMLDivElement>, index: number) => {
|
||||
setDraggedIndex(index);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
// 在Firefox中需要设置dataTransfer数据
|
||||
e.dataTransfer.setData('text/plain', index.toString());
|
||||
};
|
||||
|
||||
const handleDragOverPreview = (e: React.DragEvent<HTMLDivElement>, index: number) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
};
|
||||
|
||||
const handleDropPreview = (e: React.DragEvent<HTMLDivElement>, index: number) => {
|
||||
e.preventDefault();
|
||||
if (draggedIndex !== null && draggedIndex !== index) {
|
||||
reorderUploadedImage(draggedIndex, index);
|
||||
}
|
||||
setDraggedIndex(null);
|
||||
};
|
||||
|
||||
const handleDragEnd = () => {
|
||||
setDraggedIndex(null);
|
||||
};
|
||||
|
||||
const handleClearSession = async () => {
|
||||
setCurrentPrompt('');
|
||||
clearUploadedImages();
|
||||
@@ -311,6 +363,9 @@ export const PromptComposer: React.FC = () => {
|
||||
} catch (error) {
|
||||
console.error('清空IndexedDB中的参考图像失败:', error);
|
||||
}
|
||||
|
||||
// 清理所有Blob URL
|
||||
useAppStore.getState().cleanupAllBlobUrls();
|
||||
};
|
||||
|
||||
const tools = [
|
||||
@@ -461,6 +516,11 @@ export const PromptComposer: React.FC = () => {
|
||||
index={index}
|
||||
selectedTool={selectedTool}
|
||||
onRemove={() => selectedTool === 'generate' ? removeUploadedImage(index) : removeEditReferenceImage(index)}
|
||||
onDragStart={selectedTool === 'generate' ? handleDragStart : undefined}
|
||||
onDragOver={selectedTool === 'generate' ? handleDragOverPreview : undefined}
|
||||
onDragEnd={selectedTool === 'generate' ? handleDragEnd : undefined}
|
||||
onDrop={selectedTool === 'generate' ? handleDropPreview : undefined}
|
||||
isDragging={selectedTool === 'generate' && draggedIndex === index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user