import React, { useState, useEffect } from 'react'; import { useAppStore } from '../store/useAppStore'; import { Button } from './ui/Button'; import { History, Download, Image as ImageIcon } from 'lucide-react'; import { cn } from '../utils/cn'; import { ImagePreviewModal } from './ImagePreviewModal'; import * as indexedDBService from '../services/indexedDBService'; export const HistoryPanel: React.FC = () => { const { currentProject, canvasImage, selectedGenerationId, selectedEditId, selectGeneration, selectEdit, showHistory, setShowHistory, setCanvasImage, selectedTool } = useAppStore(); const { getBlob } = useAppStore.getState(); const [previewModal, setPreviewModal] = React.useState<{ open: boolean; imageUrl: string; title: string; description?: string; }>({ open: false, imageUrl: '', title: '', description: '' }); // 存储从Blob URL解码的图像数据 const [decodedImages, setDecodedImages] = useState>({}); // 存储从IndexedDB获取的完整记录 const [dbGenerations, setDbGenerations] = useState([]); const [dbEdits, setDbEdits] = useState([]); // 筛选和搜索状态 const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [searchTerm, setSearchTerm] = useState(''); // 悬浮预览状态 const [hoveredImage, setHoveredImage] = useState<{url: string, title: string} | null>(null); const [previewPosition, setPreviewPosition] = useState<{x: number, y: number}>({x: 0, y: 0}); const generations = currentProject?.generations || []; const edits = currentProject?.edits || []; // 获取上传后的图片链接 const getUploadedImageUrl = (generationOrEdit: any, index: number) => { 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_50`; } } return null; }; // 获取当前图像尺寸 const [imageDimensions, setImageDimensions] = React.useState<{ width: number; height: number } | null>(null); React.useEffect(() => { if (canvasImage) { const img = new Image(); img.onload = () => { setImageDimensions({ width: img.width, height: img.height }); }; img.src = canvasImage; } else { setImageDimensions(null); } }, [canvasImage]); // 当组件挂载时,从IndexedDB获取记录 useEffect(() => { const loadDBRecords = async () => { try { // 初始化数据库 await indexedDBService.initDB(); // 获取所有生成记录和编辑记录 const allGenerations = await indexedDBService.getAllGenerations(); const allEdits = await indexedDBService.getAllEdits(); setDbGenerations(allGenerations); setDbEdits(allEdits); } catch (err) { console.error('从IndexedDB加载记录失败:', err); } }; loadDBRecords(); }, []); // 当有新记录添加时,重新加载记录 useEffect(() => { // 监听store中的记录变化,如果有新记录则重新加载 const handleStorageChange = (e: StorageEvent) => { if (e.key === 'nano-banana-store') { // 重新加载记录 const loadDBRecords = async () => { try { const allGenerations = await indexedDBService.getAllGenerations(); const allEdits = await indexedDBService.getAllEdits(); setDbGenerations(allGenerations); setDbEdits(allEdits); } catch (err) { console.error('从IndexedDB重新加载记录失败:', err); } }; loadDBRecords(); } }; window.addEventListener('storage', handleStorageChange); return () => window.removeEventListener('storage', handleStorageChange); }, []); // 筛选记录的函数 const filterRecords = (records: any[], isGeneration: boolean) => { return records.filter(record => { // 日期筛选 const recordDate = new Date(record.timestamp); if (startDate && recordDate < new Date(startDate)) return false; if (endDate && recordDate > new Date(endDate)) return false; // 搜索词筛选 if (searchTerm) { if (isGeneration) { // 生成记录按提示词搜索 return record.prompt.toLowerCase().includes(searchTerm.toLowerCase()); } else { // 编辑记录按指令搜索 return record.instruction.toLowerCase().includes(searchTerm.toLowerCase()); } } return true; }); }; // 筛选后的记录 const filteredGenerations = filterRecords(dbGenerations, true); const filteredEdits = filterRecords(dbEdits, false); // 当项目变化时,解码Blob图像 useEffect(() => { const decodeBlobImages = async () => { const newDecodedImages: Record = {}; // 解码生成记录的输出图像 for (const gen of generations) { if (Array.isArray(gen.outputAssetsBlobUrls)) { for (const blobUrl of gen.outputAssetsBlobUrls) { if (!decodedImages[blobUrl] && blobUrl.startsWith('blob:')) { const blob = getBlob(blobUrl); if (blob) { const dataUrl = await new Promise((resolve) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result as string); reader.readAsDataURL(blob); }); newDecodedImages[blobUrl] = dataUrl; } } } } } // 解码编辑记录的输出图像 for (const edit of edits) { if (Array.isArray(edit.outputAssetsBlobUrls)) { for (const blobUrl of edit.outputAssetsBlobUrls) { if (!decodedImages[blobUrl] && blobUrl.startsWith('blob:')) { const blob = getBlob(blobUrl); if (blob) { const dataUrl = await new Promise((resolve) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result as string); reader.readAsDataURL(blob); }); newDecodedImages[blobUrl] = dataUrl; } } } } } if (Object.keys(newDecodedImages).length > 0) { setDecodedImages(prev => ({ ...prev, ...newDecodedImages })); } }; decodeBlobImages(); }, [generations, edits, getBlob, decodedImages]); if (!showHistory) { return (
); } return (
{/* 头部 */}

历史记录和变体

{/* 筛选和搜索控件 */}
setStartDate(e.target.value)} className="flex-1 text-xs p-1 border rounded" placeholder="开始日期" /> setEndDate(e.target.value)} className="flex-1 text-xs p-1 border rounded" placeholder="结束日期" />
setSearchTerm(e.target.value)} className="flex-1 text-xs p-1 border rounded-l" placeholder="搜索提示词或编辑指令..." />
{/* 变体网格 */}

当前变体

{filteredGenerations.length + filteredEdits.length}/1000
{filteredGenerations.length === 0 && filteredEdits.length === 0 ? (
🖼️

暂无生成记录

) : (
{/* 显示生成记录 */} {[...filteredGenerations].sort((a, b) => b.timestamp - a.timestamp).slice(0, 50).map((generation, index) => (
{ selectGeneration(generation.id); // 设置画布图像为第一个输出资产 if (generation.outputAssets && generation.outputAssets.length > 0) { const asset = generation.outputAssets[0]; if (asset.url) { setCanvasImage(asset.url); } } }} onMouseEnter={(e) => { if (generation.outputAssets && generation.outputAssets.length > 0) { const asset = generation.outputAssets[0]; if (asset.url) { setHoveredImage({ url: asset.url, title: `生成记录 G${index + 1}: ${generation.prompt.substring(0, 50)}${generation.prompt.length > 50 ? '...' : ''}` }); setPreviewPosition({x: e.clientX, y: e.clientY}); } } }} onMouseMove={(e) => { // 调整预览位置以避免被遮挡 const previewWidth = 300; const previewHeight = 300; const offsetX = 10; const offsetY = 10; let x = e.clientX + offsetX; let y = e.clientY + offsetY; // 检查是否超出右边界 if (x + previewWidth > window.innerWidth) { x = window.innerWidth - previewWidth - 10; } // 检查是否超出下边界 if (y + previewHeight > window.innerHeight) { y = window.innerHeight - previewHeight - 10; } // 检查是否超出左边界 if (x < 0) { x = 10; } // 检查是否超出上边界 if (y < 0) { y = 10; } setPreviewPosition({x, y}); }} onMouseLeave={() => { setHoveredImage(null); }} > {generation.outputAssets && generation.outputAssets.length > 0 ? ( (() => { const asset = generation.outputAssets[0]; if (asset.url) { // 如果是base64数据URL,直接显示 if (asset.url.startsWith('data:')) { return 生成的变体; } // 如果是普通URL,直接显示 return 生成的变体; } else { return (
); } })() ) : (
)} {/* 变体编号 */}
G{index + 1}
))} {/* 显示编辑记录 */} {[...filteredEdits].sort((a, b) => b.timestamp - a.timestamp).slice(0, 50).map((edit, index) => (
{ selectEdit(edit.id); selectGeneration(null); // 设置画布图像为第一个输出资产 if (edit.outputAssets && edit.outputAssets.length > 0) { const asset = edit.outputAssets[0]; if (asset.url) { setCanvasImage(asset.url); } } }} onMouseEnter={(e) => { if (edit.outputAssets && edit.outputAssets.length > 0) { const asset = edit.outputAssets[0]; if (asset.url) { setHoveredImage({ url: asset.url, title: `编辑记录 E${index + 1}: ${edit.instruction.substring(0, 50)}${edit.instruction.length > 50 ? '...' : ''}` }); setPreviewPosition({x: e.clientX, y: e.clientY}); } } }} onMouseMove={(e) => { // 调整预览位置以避免被遮挡 const previewWidth = 300; const previewHeight = 300; const offsetX = 10; const offsetY = 10; let x = e.clientX + offsetX; let y = e.clientY + offsetY; // 检查是否超出右边界 if (x + previewWidth > window.innerWidth) { x = window.innerWidth - previewWidth - 10; } // 检查是否超出下边界 if (y + previewHeight > window.innerHeight) { y = window.innerHeight - previewHeight - 10; } // 检查是否超出左边界 if (x < 0) { x = 10; } // 检查是否超出上边界 if (y < 0) { y = 10; } setPreviewPosition({x, y}); }} onMouseLeave={() => { setHoveredImage(null); }} > {edit.outputAssets && edit.outputAssets.length > 0 ? ( (() => { const asset = edit.outputAssets[0]; if (asset.url) { // 如果是base64数据URL,直接显示 if (asset.url.startsWith('data:')) { return 编辑的变体; } // 如果是普通URL,直接显示 return 编辑的变体; } else { return (
); } })() ) : (
)} {/* 编辑标签 */}
E{index + 1}
))}
)}
{/* 当前图像信息 */} {(canvasImage || imageDimensions) && (

当前图像

{imageDimensions && (
尺寸: {imageDimensions.width} × {imageDimensions.height}
)}
模式: {selectedTool}
)} {/* 生成详情 */}

生成详情

{(() => { const gen = filteredGenerations.find(g => g.id === selectedGenerationId) || dbGenerations.find(g => g.id === selectedGenerationId); const selectedEdit = filteredEdits.find(e => e.id === selectedEditId) || dbEdits.find(e => e.id === selectedEditId); if (gen) { return (
提示:

{gen.prompt}

模型: {gen.modelVersion}
{gen.parameters.seed && (
种子: {gen.parameters.seed}
)}
时间: {new Date(gen.timestamp).toLocaleString()}
{/* 上传结果 */} {gen.uploadResults && gen.uploadResults.length > 0 && (
上传结果
{gen.uploadResults.map((result, index) => (
图像 {index + 1}: {result.success ? '成功' : '失败'}
{result.success && result.url && (
{result.url.split('/').pop()}
)} {result.error && (
{result.error}
)}
))}
)} {/* 参考图像信息 */} {gen.sourceAssets && gen.sourceAssets.length > 0 && (
参考图像
{gen.sourceAssets.length} 个参考图像
)}
); } else if (selectedEdit) { const parentGen = dbGenerations.find(g => g.id === selectedEdit.parentGenerationId); return (
编辑指令:

{selectedEdit.instruction}

类型: 图像编辑
创建时间: {new Date(selectedEdit.timestamp).toLocaleString()}
{selectedEdit.maskAssetId && (
遮罩: 已应用
)}
{/* 上传结果 */} {selectedEdit.uploadResults && selectedEdit.uploadResults.length > 0 && (
上传结果
{selectedEdit.uploadResults.map((result, index) => (
图像 {index + 1}: {result.success ? '成功' : '失败'}
{result.success && result.url && (
{result.url.split('/').pop()}
)} {result.error && (
{result.error}
)}
))}
)} {/* 原始生成参考 */} {parentGen && (
原始生成
基于: G{dbGenerations.findIndex(g => g.id === parentGen.id) + 1}
)}
); } else { return (

选择一个生成记录或编辑记录以查看详细信息

); } })()}
{/* 操作 */}
{/* 图像预览模态框 */} setPreviewModal(prev => ({ ...prev, open }))} imageUrl={previewModal.imageUrl} title={previewModal.title} description={previewModal.description} /> {/* 悬浮预览 */} {hoveredImage && (
{hoveredImage.title}
预览
)}
); };