You've already forked Nano-Banana-AI-Image-Editor
阶段性提交
This commit is contained in:
@@ -4,7 +4,6 @@ import { Button } from './ui/Button';
|
||||
import { History, Download, Image as ImageIcon, Trash2 } from 'lucide-react';
|
||||
import { cn } from '../utils/cn';
|
||||
import { ImagePreviewModal } from './ImagePreviewModal';
|
||||
import * as indexedDBService from '../services/indexedDBService';
|
||||
import { useIndexedDBListener } from '../hooks/useIndexedDBListener';
|
||||
import { DayPicker } from 'react-day-picker';
|
||||
import zhCN from 'react-day-picker/dist/locale/zh-CN';
|
||||
@@ -13,9 +12,8 @@ export const HistoryPanel: React.FC<{
|
||||
setHoveredImage: (image: {url: string, title: string, width?: number, height?: number} | null) => void,
|
||||
setPreviewPosition?: (position: {x: number, y: number} | null) => void
|
||||
}> = ({ setHoveredImage, setPreviewPosition }) => {
|
||||
const {
|
||||
const {
|
||||
currentProject,
|
||||
canvasImage,
|
||||
selectedGenerationId,
|
||||
selectedEditId,
|
||||
selectGeneration,
|
||||
@@ -23,7 +21,6 @@ export const HistoryPanel: React.FC<{
|
||||
showHistory,
|
||||
setShowHistory,
|
||||
setCanvasImage,
|
||||
selectedTool,
|
||||
removeGeneration,
|
||||
removeEdit
|
||||
} = useAppStore();
|
||||
@@ -68,18 +65,28 @@ export const HistoryPanel: React.FC<{
|
||||
const [startDate, setStartDate] = useState<string>(() => {
|
||||
// 初始化时默认显示今天的记录
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
return today.toISOString().split('T')[0];
|
||||
});
|
||||
const [endDate, setEndDate] = useState<string>(() => {
|
||||
// 初始化时默认显示今天的记录
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
return today.toISOString().split('T')[0];
|
||||
});
|
||||
const [searchTerm, setSearchTerm] = useState<string>('');
|
||||
const [showDatePicker, setShowDatePicker] = useState(false); // 控制日期选择器的显示
|
||||
const [dateRange, setDateRange] = useState<{ from: Date | undefined; to: Date | undefined }>({
|
||||
from: new Date(new Date().setHours(0, 0, 0, 0)),
|
||||
to: new Date(new Date().setHours(0, 0, 0, 0))
|
||||
from: (() => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
return today;
|
||||
})(),
|
||||
to: (() => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
return today;
|
||||
})()
|
||||
});
|
||||
|
||||
// 分页状态
|
||||
@@ -87,17 +94,18 @@ export const HistoryPanel: React.FC<{
|
||||
const itemsPerPage = 20; // 减少每页显示的项目数
|
||||
|
||||
// 获取当前图像尺寸
|
||||
const [imageDimensions, setImageDimensions] = React.useState<{ width: number; height: number } | null>(null);
|
||||
// const [imageDimensions, setImageDimensions] = React.useState<{ width: number; height: number } | null>(null);
|
||||
|
||||
// 当currentProject为空时,使用dbGenerations和dbEdits作为备选
|
||||
const displayGenerations = currentProject ? currentProject.generations : dbGenerations;
|
||||
const displayEdits = currentProject ? currentProject.edits : dbEdits;
|
||||
|
||||
// 筛选记录的函数
|
||||
const filterRecords = useCallback((records: any[], isGeneration: boolean) => {
|
||||
const filterRecords = useCallback((records: Array<{timestamp: number, prompt?: string, instruction?: string}>, isGeneration: boolean) => {
|
||||
return records.filter(record => {
|
||||
// 日期筛选 - 检查记录日期是否在筛选范围内
|
||||
const recordDate = new Date(record.timestamp);
|
||||
recordDate.setHours(0, 0, 0, 0); // 设置为当天的开始
|
||||
const recordDateStr = recordDate.toISOString().split('T')[0]; // 获取日期部分 YYYY-MM-DD
|
||||
|
||||
// 检查是否在日期范围内
|
||||
@@ -108,10 +116,10 @@ export const HistoryPanel: React.FC<{
|
||||
if (searchTerm) {
|
||||
if (isGeneration) {
|
||||
// 生成记录按提示词搜索
|
||||
return record.prompt.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
return record.prompt?.toLowerCase().includes(searchTerm.toLowerCase()) || false;
|
||||
} else {
|
||||
// 编辑记录按指令搜索
|
||||
return record.instruction.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
return record.instruction?.toLowerCase().includes(searchTerm.toLowerCase()) || false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,17 +131,17 @@ export const HistoryPanel: React.FC<{
|
||||
const filteredGenerations = useMemo(() => filterRecords(displayGenerations, true), [displayGenerations, filterRecords]);
|
||||
const filteredEdits = useMemo(() => filterRecords(displayEdits, false), [displayEdits, filterRecords]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (canvasImage) {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
setImageDimensions({ width: img.width, height: img.height });
|
||||
};
|
||||
img.src = canvasImage;
|
||||
} else {
|
||||
setImageDimensions(null);
|
||||
}
|
||||
}, [canvasImage]);
|
||||
// React.useEffect(() => {
|
||||
// if (canvasImage) {
|
||||
// const img = new Image();
|
||||
// img.onload = () => {
|
||||
// setImageDimensions({ width: img.width, height: img.height });
|
||||
// };
|
||||
// img.src = canvasImage;
|
||||
// } else {
|
||||
// setImageDimensions(null);
|
||||
// }
|
||||
// }, [canvasImage]);
|
||||
|
||||
// 当项目变化时,解码Blob图像
|
||||
useEffect(() => {
|
||||
@@ -196,8 +204,8 @@ export const HistoryPanel: React.FC<{
|
||||
if (generationOrEdit.uploadResults && generationOrEdit.uploadResults[index]) {
|
||||
const uploadResult = generationOrEdit.uploadResults[index];
|
||||
if (uploadResult.success && uploadResult.url) {
|
||||
// 添加参数以降低图片质量
|
||||
return `${uploadResult.url}?x-oss-process=image/quality,q_30`; // 降低质量到30%
|
||||
// 返回原始链接,不添加任何参数
|
||||
return uploadResult.url;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -469,6 +477,13 @@ export const HistoryPanel: React.FC<{
|
||||
selected={dateRange}
|
||||
onSelect={(range) => {
|
||||
if (range) {
|
||||
// 确保日期时间设置为当天的开始
|
||||
if (range.from) {
|
||||
range.from.setHours(0, 0, 0, 0);
|
||||
}
|
||||
if (range.to) {
|
||||
range.to.setHours(0, 0, 0, 0);
|
||||
}
|
||||
setDateRange(range);
|
||||
// 更新字符串格式的日期用于筛选
|
||||
if (range.from) {
|
||||
@@ -511,7 +526,8 @@ export const HistoryPanel: React.FC<{
|
||||
className="text-xs p-1.5 rounded-l-none h-7 border-gray-200 text-gray-600 hover:bg-gray-100 card"
|
||||
onClick={() => {
|
||||
// 重置为显示今天的记录
|
||||
const today = new Date(new Date().setHours(0, 0, 0, 0));
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const todayStr = today.toISOString().split('T')[0];
|
||||
setStartDate(todayStr);
|
||||
setEndDate(todayStr);
|
||||
@@ -581,7 +597,7 @@ export const HistoryPanel: React.FC<{
|
||||
sortedGenerations.some(gen => gen.id === record.id)
|
||||
);
|
||||
|
||||
return paginatedGenerations.map((generation, index) => {
|
||||
return paginatedGenerations.map((generation: {id: string, sourceAssets?: Array<{url: string}>, outputAssets?: Array<{url: string}>}) => {
|
||||
// 计算全局索引用于显示编号
|
||||
const globalIndex = allRecords.findIndex(record => record.id === generation.id);
|
||||
|
||||
@@ -596,23 +612,57 @@ export const HistoryPanel: React.FC<{
|
||||
)}
|
||||
onClick={() => {
|
||||
selectGeneration(generation.id);
|
||||
// 设置画布图像为第一个输出资产
|
||||
if (generation.outputAssets && generation.outputAssets.length > 0) {
|
||||
// 设置画布图像为参考图像,如果没有参考图像则使用第一个输出资产
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (generation.sourceAssets && generation.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = generation.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
const uploadedUrl = getUploadedImageUrl(generation, uploadResultIndex);
|
||||
if (uploadedUrl) {
|
||||
imageUrl = uploadedUrl;
|
||||
} else if (generation.sourceAssets[0].url) {
|
||||
imageUrl = generation.sourceAssets[0].url;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有参考图像,则使用生成结果图像
|
||||
if (!imageUrl && generation.outputAssets && generation.outputAssets.length > 0) {
|
||||
const asset = generation.outputAssets[0];
|
||||
if (asset.url) {
|
||||
setCanvasImage(asset.url);
|
||||
const uploadedUrl = getUploadedImageUrl(generation, 0);
|
||||
imageUrl = uploadedUrl || asset.url;
|
||||
}
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
// 设置当前悬停的记录
|
||||
setHoveredRecord({type: 'generation', id: generation.id});
|
||||
|
||||
// 优先使用上传后的远程链接,如果没有则使用原始链接
|
||||
let imageUrl = getUploadedImageUrl(generation, 0);
|
||||
if (!imageUrl && generation.outputAssets && generation.outputAssets.length > 0) {
|
||||
imageUrl = generation.outputAssets[0].url;
|
||||
}
|
||||
// 优先显示参考图像,如果没有参考图像则显示生成结果图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (generation.sourceAssets && generation.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = generation.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
imageUrl = getUploadedImageUrl(generation, uploadResultIndex) ||
|
||||
(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();
|
||||
@@ -719,9 +769,23 @@ export const HistoryPanel: React.FC<{
|
||||
}}
|
||||
>
|
||||
{(() => {
|
||||
// 优先使用上传后的远程链接
|
||||
const imageUrl = getUploadedImageUrl(generation, 0) ||
|
||||
(generation.outputAssets && generation.outputAssets.length > 0 && generation.outputAssets[0].url ? generation.outputAssets[0].url : null);
|
||||
// 优先显示参考图像,如果没有参考图像则显示生成结果图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (generation.sourceAssets && generation.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = generation.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
imageUrl = getUploadedImageUrl(generation, uploadResultIndex) ||
|
||||
(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" />;
|
||||
@@ -748,12 +812,34 @@ export const HistoryPanel: React.FC<{
|
||||
className="h-8 w-8 bg-white/90 hover:bg-white text-gray-700 rounded-full shadow-md"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// 下载图像
|
||||
const imageUrl = getUploadedImageUrl(generation, 0) ||
|
||||
(generation.outputAssets && generation.outputAssets.length > 0 && generation.outputAssets[0].url ? generation.outputAssets[0].url : null);
|
||||
// 下载图像 - 优先下载参考图像,如果没有则下载生成结果图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (generation.sourceAssets && generation.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = generation.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
imageUrl = getUploadedImageUrl(generation, uploadResultIndex) ||
|
||||
(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) {
|
||||
// 使用Promise来处理异步操作
|
||||
fetch(imageUrl)
|
||||
// 使用fetch获取图像数据并创建Blob URL以确保正确下载
|
||||
// 添加更多缓存控制头以绕过CDN缓存
|
||||
fetch(imageUrl, {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
})
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -767,6 +853,14 @@ export const HistoryPanel: React.FC<{
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('下载图像失败:', error);
|
||||
// 如果fetch失败,回退到直接使用a标签
|
||||
const link = document.createElement('a');
|
||||
link.href = imageUrl;
|
||||
link.download = `nano-banana-${Date.now()}.png`;
|
||||
link.target = '_blank';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
});
|
||||
}
|
||||
}}
|
||||
@@ -809,7 +903,7 @@ export const HistoryPanel: React.FC<{
|
||||
sortedEdits.some(edit => edit.id === record.id)
|
||||
);
|
||||
|
||||
return paginatedEdits.map((edit, index) => {
|
||||
return paginatedEdits.map((edit: {id: string, sourceAssets?: Array<{url: string}>, outputAssets?: Array<{url: string}>}) => {
|
||||
// 计算全局索引用于显示编号
|
||||
const globalIndex = allRecords.findIndex(record => record.id === edit.id);
|
||||
|
||||
@@ -825,22 +919,55 @@ export const HistoryPanel: React.FC<{
|
||||
onClick={() => {
|
||||
selectEdit(edit.id);
|
||||
selectGeneration(null);
|
||||
// 设置画布图像为第一个输出资产
|
||||
if (edit.outputAssets && edit.outputAssets.length > 0) {
|
||||
// 设置画布图像为参考图像,如果没有参考图像则使用第一个输出资产
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (edit.sourceAssets && edit.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = edit.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
const uploadedUrl = getUploadedImageUrl(edit, uploadResultIndex);
|
||||
if (uploadedUrl) {
|
||||
imageUrl = uploadedUrl;
|
||||
} else if (edit.sourceAssets[0].url) {
|
||||
imageUrl = edit.sourceAssets[0].url;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有参考图像,则使用编辑结果图像
|
||||
if (!imageUrl && edit.outputAssets && edit.outputAssets.length > 0) {
|
||||
const asset = edit.outputAssets[0];
|
||||
if (asset.url) {
|
||||
setCanvasImage(asset.url);
|
||||
const uploadedUrl = getUploadedImageUrl(edit, 0);
|
||||
imageUrl = uploadedUrl || asset.url;
|
||||
}
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
setCanvasImage(imageUrl);
|
||||
}
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
// 设置当前悬停的记录
|
||||
setHoveredRecord({type: 'edit', id: edit.id});
|
||||
|
||||
// 优先使用上传后的远程链接,如果没有则使用原始链接
|
||||
let imageUrl = getUploadedImageUrl(edit, 0);
|
||||
if (!imageUrl && edit.outputAssets && edit.outputAssets.length > 0) {
|
||||
imageUrl = edit.outputAssets[0].url;
|
||||
// 优先显示参考图像,如果没有参考图像则显示编辑结果图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (edit.sourceAssets && edit.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = edit.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
imageUrl = getUploadedImageUrl(edit, uploadResultIndex) ||
|
||||
(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) {
|
||||
@@ -889,9 +1016,23 @@ export const HistoryPanel: React.FC<{
|
||||
}}
|
||||
>
|
||||
{(() => {
|
||||
// 优先使用上传后的远程链接
|
||||
const imageUrl = getUploadedImageUrl(edit, 0) ||
|
||||
(edit.outputAssets && edit.outputAssets.length > 0 && edit.outputAssets[0].url ? edit.outputAssets[0].url : null);
|
||||
// 优先显示参考图像,如果没有参考图像则显示编辑结果图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (edit.sourceAssets && edit.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = edit.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
imageUrl = getUploadedImageUrl(edit, uploadResultIndex) ||
|
||||
(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" />;
|
||||
@@ -918,12 +1059,34 @@ export const HistoryPanel: React.FC<{
|
||||
className="h-8 w-8 bg-white/90 hover:bg-white text-gray-700 rounded-full shadow-md"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// 下载图像
|
||||
const imageUrl = getUploadedImageUrl(edit, 0) ||
|
||||
(edit.outputAssets && edit.outputAssets.length > 0 && edit.outputAssets[0].url ? edit.outputAssets[0].url : null);
|
||||
// 下载图像 - 优先下载参考图像,如果没有则下载编辑结果图像
|
||||
let imageUrl = null;
|
||||
|
||||
// 首先尝试获取参考图像
|
||||
if (edit.sourceAssets && edit.sourceAssets.length > 0) {
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = edit.outputAssets?.length || 0;
|
||||
const uploadResultIndex = outputAssetsCount; // 第一个参考图像
|
||||
imageUrl = getUploadedImageUrl(edit, uploadResultIndex) ||
|
||||
(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) {
|
||||
// 使用Promise来处理异步操作
|
||||
fetch(imageUrl)
|
||||
// 使用fetch获取图像数据并创建Blob URL以确保正确下载
|
||||
// 添加更多缓存控制头以绕过CDN缓存
|
||||
fetch(imageUrl, {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
})
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -937,6 +1100,14 @@ export const HistoryPanel: React.FC<{
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('下载图像失败:', error);
|
||||
// 如果fetch失败,回退到直接使用a标签
|
||||
const link = document.createElement('a');
|
||||
link.href = imageUrl;
|
||||
link.download = `nano-banana-${Date.now()}.png`;
|
||||
link.target = '_blank';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
});
|
||||
}
|
||||
}}
|
||||
@@ -1080,7 +1251,7 @@ export const HistoryPanel: React.FC<{
|
||||
{gen.outputAssets.length} 个生成结果
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{gen.outputAssets.slice(0, 4).map((asset: any, index: number) => {
|
||||
{gen.outputAssets.slice(0, 4).map((asset: {id: string, url?: string, blobUrl?: string, width: number, height: number}, index: number) => {
|
||||
// 获取上传后的远程链接(如果存在)
|
||||
const uploadedUrl = gen.uploadResults && gen.uploadResults[index] && gen.uploadResults[index].success
|
||||
? `${gen.uploadResults[index].url}?x-oss-process=image/quality,q_30`
|
||||
@@ -1169,20 +1340,14 @@ export const HistoryPanel: React.FC<{
|
||||
{gen.sourceAssets.length} 个参考图像
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{gen.sourceAssets.slice(0, 4).map((asset: any, index: number) => {
|
||||
// 获取上传后的远程链接(如果存在)
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
// 但由于gen可能是轻量级记录,我们需要从dbGenerations中获取完整的记录
|
||||
const fullGen = dbGenerations.find(item => item.id === gen.id) || gen;
|
||||
const outputAssetsCount = fullGen.outputAssets?.length || 0;
|
||||
|
||||
// 确保索引在有效范围内
|
||||
const uploadResultIndex = outputAssetsCount + index;
|
||||
const uploadedUrl = fullGen.uploadResults && fullGen.uploadResults[uploadResultIndex] && fullGen.uploadResults[uploadResultIndex].success
|
||||
? `${fullGen.uploadResults[uploadResultIndex].url}?x-oss-process=image/quality,q_30`
|
||||
{gen.sourceAssets.slice(0, 4).map((asset: {id: string, url?: string, blobUrl?: string, width: number, height: number}, index: number) => {
|
||||
// 优先使用上传后的远程链接,如果没有则使用asset中的URL
|
||||
// 参考图像在uploadResults中从索引1开始(图像2字段)
|
||||
const uploadResultIndex = 1 + index;
|
||||
const uploadedUrl = gen.uploadResults && gen.uploadResults[uploadResultIndex] && gen.uploadResults[uploadResultIndex].success
|
||||
? gen.uploadResults[uploadResultIndex].url
|
||||
: null;
|
||||
|
||||
// 如果没有上传的URL,则使用asset中的URL
|
||||
const displayUrl = uploadedUrl || asset.url || asset.blobUrl;
|
||||
|
||||
// 如果URL是blob:开头但已失效,尝试重新创建
|
||||
@@ -1320,10 +1485,10 @@ export const HistoryPanel: React.FC<{
|
||||
{selectedEdit.outputAssets.length} 个编辑结果
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{selectedEdit.outputAssets.slice(0, 4).map((asset: any, index: number) => {
|
||||
{selectedEdit.outputAssets.slice(0, 4).map((asset: {id: string, url?: string, blobUrl?: string, width: number, height: number}, index: number) => {
|
||||
// 获取上传后的远程链接(如果存在)
|
||||
const uploadedUrl = selectedEdit.uploadResults && selectedEdit.uploadResults[index] && selectedEdit.uploadResults[index].success
|
||||
? `${selectedEdit.uploadResults[index].url}?x-oss-process=image/quality,q_30`
|
||||
? selectedEdit.uploadResults[index].url
|
||||
: null;
|
||||
|
||||
// 如果没有上传的URL,则使用asset中的URL
|
||||
@@ -1401,6 +1566,96 @@ export const HistoryPanel: React.FC<{
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 编辑参考图像 */}
|
||||
{selectedEdit.sourceAssets && selectedEdit.sourceAssets.length > 0 && (
|
||||
<div className="pt-2 border-t border-gray-200">
|
||||
<h5 className="text-xs font-medium text-gray-500 mb-2">编辑参考图像</h5>
|
||||
<div className="text-xs text-gray-600 mb-2">
|
||||
{selectedEdit.sourceAssets.length} 个参考图像
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{selectedEdit.sourceAssets.slice(0, 4).map((asset: {id: string, url?: string, blobUrl?: string, width: number, height: number}, index: number) => {
|
||||
// 优先使用上传后的远程链接,如果没有则使用asset中的URL
|
||||
// 参考图像在uploadResults中从索引1开始(图像2字段)
|
||||
const uploadResultIndex = 1 + index;
|
||||
const uploadedUrl = selectedEdit.uploadResults && selectedEdit.uploadResults[uploadResultIndex] && selectedEdit.uploadResults[uploadResultIndex].success
|
||||
? selectedEdit.uploadResults[uploadResultIndex].url
|
||||
: null;
|
||||
|
||||
const displayUrl = uploadedUrl || asset.url || asset.blobUrl;
|
||||
|
||||
// 如果URL是blob:开头但已失效,尝试重新创建
|
||||
if (displayUrl && displayUrl.startsWith('blob:')) {
|
||||
// 检查blob是否仍然有效
|
||||
const img = new Image();
|
||||
img.onerror = () => {
|
||||
// Blob URL可能已失效,尝试重新创建
|
||||
import('../store/useAppStore').then((module) => {
|
||||
const useAppStore = module.useAppStore;
|
||||
const blob = useAppStore.getState().getBlob(displayUrl);
|
||||
if (blob) {
|
||||
const newUrl = URL.createObjectURL(blob);
|
||||
// 更新显示
|
||||
const imgElement = document.querySelector(`img[src="${displayUrl}"]`);
|
||||
if (imgElement) {
|
||||
imgElement.src = newUrl;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
img.src = displayUrl;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={asset.id}
|
||||
className="relative w-16 h-16 rounded border overflow-hidden cursor-pointer hover:ring-2 hover:ring-yellow-400"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setPreviewModal({
|
||||
open: true,
|
||||
imageUrl: displayUrl,
|
||||
title: `编辑参考图像 ${index + 1}`,
|
||||
description: `${asset.width} × ${asset.height}`
|
||||
});
|
||||
}}
|
||||
>
|
||||
{displayUrl ? (
|
||||
<img
|
||||
src={displayUrl}
|
||||
alt={`编辑参考图像 ${index + 1}`}
|
||||
className="w-full h-full object-cover"
|
||||
onError={(e) => {
|
||||
// 如果图像加载失败,尝试重新创建Blob URL
|
||||
if (displayUrl.startsWith('blob:')) {
|
||||
import('../store/useAppStore').then((module) => {
|
||||
const useAppStore = module.useAppStore;
|
||||
const blob = useAppStore.getState().getBlob(displayUrl);
|
||||
if (blob) {
|
||||
const newUrl = URL.createObjectURL(blob);
|
||||
(e.target as HTMLImageElement).src = newUrl;
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full bg-gray-100 flex items-center justify-center">
|
||||
<ImageIcon className="h-4 w-4 text-gray-400" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{selectedEdit.sourceAssets.length > 4 && (
|
||||
<div className="w-16 h-16 rounded border flex items-center justify-center bg-gray-100 text-xs text-gray-500">
|
||||
+{selectedEdit.sourceAssets.length - 4}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 原始生成参考 */}
|
||||
{parentGen && (
|
||||
<div className="pt-2 border-t border-gray-200">
|
||||
@@ -1415,18 +1670,14 @@ export const HistoryPanel: React.FC<{
|
||||
原始参考图像:
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{parentGen.sourceAssets.slice(0, 4).map((asset: any, index: number) => {
|
||||
// 获取上传后的远程链接(如果存在)
|
||||
// 参考图像在uploadResults中从索引outputAssets.length开始
|
||||
const outputAssetsCount = parentGen.outputAssets?.length || 0;
|
||||
|
||||
// 确保索引在有效范围内
|
||||
const uploadResultIndex = outputAssetsCount + index;
|
||||
{parentGen.sourceAssets.slice(0, 4).map((asset: {id: string, url?: string, blobUrl?: string, width: number, height: number}, index: number) => {
|
||||
// 优先使用上传后的远程链接,如果没有则使用asset中的URL
|
||||
// 参考图像在uploadResults中从索引1开始(图像2字段)
|
||||
const uploadResultIndex = 1 + index;
|
||||
const uploadedUrl = parentGen.uploadResults && parentGen.uploadResults[uploadResultIndex] && parentGen.uploadResults[uploadResultIndex].success
|
||||
? `${parentGen.uploadResults[uploadResultIndex].url}?x-oss-process=image/quality,q_30`
|
||||
? parentGen.uploadResults[uploadResultIndex].url
|
||||
: null;
|
||||
|
||||
// 如果没有上传的URL,则使用asset中的URL
|
||||
const displayUrl = uploadedUrl || asset.url || asset.blobUrl;
|
||||
|
||||
// 如果URL是blob:开头但已失效,尝试重新创建
|
||||
|
||||
Reference in New Issue
Block a user