阶段性提交

This commit is contained in:
2025-09-21 14:43:59 +08:00
parent af2058f752
commit 690a530031
20 changed files with 1577 additions and 781 deletions

View File

@@ -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:开头但已失效,尝试重新创建