+
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);
+ }}
+ >

{
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
(null);
const fileInputRef = useRef(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, index: number) => {
+ setDraggedIndex(index);
+ e.dataTransfer.effectAllowed = 'move';
+ // 在Firefox中需要设置dataTransfer数据
+ e.dataTransfer.setData('text/plain', index.toString());
+ };
+
+ const handleDragOverPreview = (e: React.DragEvent, index: number) => {
+ e.preventDefault();
+ e.dataTransfer.dropEffect = 'move';
+ };
+
+ const handleDropPreview = (e: React.DragEvent, 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}
/>
))}
diff --git a/src/hooks/useImageGeneration.ts b/src/hooks/useImageGeneration.ts
index fbbac27..fea116e 100644
--- a/src/hooks/useImageGeneration.ts
+++ b/src/hooks/useImageGeneration.ts
@@ -208,7 +208,15 @@ export const useImageGeneration = () => {
};
addGeneration(generation);
- setCanvasImage(outputAssets[0].url);
+
+ // 调试日志:检查outputAssets
+ console.log('生成完成,outputAssets:', outputAssets);
+ if (outputAssets && outputAssets.length > 0) {
+ console.log('第一个输出资产URL:', outputAssets[0].url);
+ setCanvasImage(outputAssets[0].url);
+ } else {
+ console.error('生成完成但没有输出资产');
+ }
// 自动选择新生成的记录
const { selectGeneration } = useAppStore.getState();
diff --git a/src/store/useAppStore.ts b/src/store/useAppStore.ts
index afcf47c..9acfa7c 100644
--- a/src/store/useAppStore.ts
+++ b/src/store/useAppStore.ts
@@ -94,6 +94,7 @@ interface AppState {
addUploadedImage: (url: string) => void;
removeUploadedImage: (index: number) => void;
+ reorderUploadedImage: (fromIndex: number, toIndex: number) => void;
clearUploadedImages: () => void;
addEditReferenceImage: (url: string) => void;
@@ -194,6 +195,12 @@ export const useAppStore = create
()(
uploadedImages: state.uploadedImages.filter((_, i) => i !== index)
};
}),
+ reorderUploadedImage: (fromIndex, toIndex) => set((state) => {
+ const newUploadedImages = [...state.uploadedImages];
+ const [movedItem] = newUploadedImages.splice(fromIndex, 1);
+ newUploadedImages.splice(toIndex, 0, movedItem);
+ return { uploadedImages: newUploadedImages };
+ }),
clearUploadedImages: () => set((state) => {
// 删除所有存储在IndexedDB中的参考图像
state.uploadedImages.forEach(imageUrl => {
@@ -557,19 +564,27 @@ export const useAppStore = create()(
}),
// 释放指定的Blob URLs
- revokeBlobUrls: () => set((state) => {
- // 不再自动清理Blob URL,以确保参考图像不会被意外删除
- // 只有在用户明确请求清除会话时才清理
- console.log('Blob清理已禁用,参考图像将被永久保留');
- return state;
+ revokeBlobUrls: (urls: string[]) => set((state) => {
+ // 清理指定的Blob URL
+ const newBlobStore = new Map(state.blobStore);
+ urls.forEach(url => {
+ if (url.startsWith('blob:')) {
+ URL.revokeObjectURL(url);
+ newBlobStore.delete(url);
+ }
+ });
+ return { blobStore: newBlobStore };
}),
// 释放所有Blob URLs
cleanupAllBlobUrls: () => set((state) => {
- // 不再自动清理Blob URL,以确保参考图像不会被意外删除
- // 只有在用户明确请求清除会话时才清理
- console.log('Blob清理已禁用,参考图像将被永久保留');
- return state;
+ // 清理所有Blob URL
+ state.blobStore.forEach((blob, url) => {
+ if (url.startsWith('blob:')) {
+ URL.revokeObjectURL(url);
+ }
+ });
+ return { blobStore: new Map() };
}),
// 定期清理Blob URL