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'; import { useIndexedDBListener } from '../hooks/useIndexedDBListener'; 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>({}); // 使用自定义hook获取IndexedDB记录 const { generations: dbGenerations, edits: dbEdits, loading, error, refresh } = useIndexedDBListener(); // 筛选和搜索状态 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]); // 错误处理显示 if (error) { return (

历史记录和变体

加载历史记录时出错: {error}

); } // 筛选记录的函数 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.5 border border-gray-200 rounded-lg text-gray-600 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-yellow-400 card" placeholder="开始日期" /> setEndDate(e.target.value)} className="flex-1 text-xs p-1.5 border border-gray-200 rounded-lg text-gray-600 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-yellow-400 card" placeholder="结束日期" />
setSearchTerm(e.target.value)} className="flex-1 text-xs p-1.5 border border-gray-200 rounded-l-lg bg-gray-50 focus:outline-none focus:ring-1 focus:ring-yellow-400 card" 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}`, description: generation.prompt }); 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}`, description: edit.instruction }); 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}
))}
)}
{/* 生成详情 */}

详情

{(() => { 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} 个参考图像
{gen.sourceAssets.slice(0, 4).map((asset: any, index: number) => (
{ e.stopPropagation(); setPreviewModal({ open: true, imageUrl: asset.url, title: `参考图像 ${index + 1}`, description: `${asset.width} × ${asset.height}` }); }} > {`参考图像
))} {gen.sourceAssets.length > 4 && (
+{gen.sourceAssets.length - 4}
)}
)}
); } else if (selectedEdit) { const parentGen = filteredGenerations.find(g => g.id === selectedEdit.parentGenerationId) || 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}
{/* 显示原始生成的参考图像 */} {parentGen.sourceAssets && parentGen.sourceAssets.length > 0 && (
原始参考图像:
{parentGen.sourceAssets.slice(0, 4).map((asset: any, index: number) => (
{ e.stopPropagation(); setPreviewModal({ open: true, imageUrl: asset.url, title: `原始参考图像 ${index + 1}`, description: `${asset.width} × ${asset.height}` }); }} > {`原始参考图像
))} {parentGen.sourceAssets.length > 4 && (
+{parentGen.sourceAssets.length - 4}
)}
)}
)}
); } else { return (

选择一个记录以查看详细信息

); } })()}
{/* 操作 */}
{/* 图像预览模态框 */} setPreviewModal(prev => ({ ...prev, open }))} imageUrl={previewModal.imageUrl} title={previewModal.title} description={previewModal.description} /> {/* 悬浮预览 */} {hoveredImage && (
{hoveredImage.title}
预览 {/* 图像信息 */}
{imageDimensions && (
尺寸: {imageDimensions.width} × {imageDimensions.height}
)}
模式: {selectedTool}
)}
); };