新增 历史记录中的生成详情显示参考图

This commit is contained in:
2025-09-15 22:30:39 +08:00
parent 7a5e5d77b0
commit a922a4e507
3 changed files with 169 additions and 60 deletions

View File

@@ -5,6 +5,7 @@ import { History, Download, Image as ImageIcon } from 'lucide-react';
import { cn } from '../utils/cn'; import { cn } from '../utils/cn';
import { ImagePreviewModal } from './ImagePreviewModal'; import { ImagePreviewModal } from './ImagePreviewModal';
import * as indexedDBService from '../services/indexedDBService'; import * as indexedDBService from '../services/indexedDBService';
import { useIndexedDBListener } from '../hooks/useIndexedDBListener';
export const HistoryPanel: React.FC = () => { export const HistoryPanel: React.FC = () => {
const { const {
@@ -37,9 +38,8 @@ export const HistoryPanel: React.FC = () => {
// 存储从Blob URL解码的图像数据 // 存储从Blob URL解码的图像数据
const [decodedImages, setDecodedImages] = useState<Record<string, string>>({}); const [decodedImages, setDecodedImages] = useState<Record<string, string>>({});
// 存储从IndexedDB获取的完整记录 // 使用自定义hook获取IndexedDB记录
const [dbGenerations, setDbGenerations] = useState<any[]>([]); const { generations: dbGenerations, edits: dbEdits, loading, error, refresh } = useIndexedDBListener();
const [dbEdits, setDbEdits] = useState<any[]>([]);
// 筛选和搜索状态 // 筛选和搜索状态
const [startDate, setStartDate] = useState<string>(''); const [startDate, setStartDate] = useState<string>('');
@@ -80,50 +80,39 @@ export const HistoryPanel: React.FC = () => {
} }
}, [canvasImage]); }, [canvasImage]);
// 当组件挂载时从IndexedDB获取记录 // 错误处理显示
useEffect(() => { if (error) {
const loadDBRecords = async () => { return (
try { <div className="w-80 bg-white border-l border-gray-200 p-6 flex flex-col h-full">
// 初始化数据库 <div className="flex items-center justify-between mb-6">
await indexedDBService.initDB(); <div className="flex items-center space-x-2">
<History className="h-5 w-5 text-gray-400" />
// 获取所有生成记录和编辑记录 <h3 className="text-sm font-medium text-gray-300"></h3>
const allGenerations = await indexedDBService.getAllGenerations(); </div>
const allEdits = await indexedDBService.getAllEdits(); <Button
variant="ghost"
setDbGenerations(allGenerations); size="icon"
setDbEdits(allEdits); onClick={() => setShowHistory(!showHistory)}
} catch (err) { className="h-6 w-6"
console.error('从IndexedDB加载记录失败:', err); title="隐藏历史面板"
>
×
</Button>
</div>
<div className="text-center py-8 text-red-500">
<p className="text-sm">: {error}</p>
<Button
variant="outline"
size="sm"
className="mt-2"
onClick={refresh}
>
</Button>
</div>
</div>
);
} }
};
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) => { const filterRecords = (records: any[], isGeneration: boolean) => {
@@ -233,16 +222,7 @@ export const HistoryPanel: React.FC = () => {
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
onClick={async () => { onClick={refresh}
try {
const allGenerations = await indexedDBService.getAllGenerations();
const allEdits = await indexedDBService.getAllEdits();
setDbGenerations(allGenerations);
setDbEdits(allEdits);
} catch (err) {
console.error('刷新历史记录失败:', err);
}
}}
className="h-6 w-6" className="h-6 w-6"
title="刷新历史记录" title="刷新历史记录"
> >
@@ -601,9 +581,37 @@ export const HistoryPanel: React.FC = () => {
{gen.sourceAssets && gen.sourceAssets.length > 0 && ( {gen.sourceAssets && gen.sourceAssets.length > 0 && (
<div> <div>
<h5 className="text-xs font-medium text-gray-500 mb-2"></h5> <h5 className="text-xs font-medium text-gray-500 mb-2"></h5>
<div className="text-xs text-gray-600"> <div className="text-xs text-gray-600 mb-2">
{gen.sourceAssets.length} {gen.sourceAssets.length}
</div> </div>
<div className="flex flex-wrap gap-2">
{gen.sourceAssets.slice(0, 4).map((asset: any, index: number) => (
<div
key={asset.id}
className="relative w-16 h-16 rounded border overflow-hidden cursor-pointer"
onClick={(e) => {
e.stopPropagation();
setPreviewModal({
open: true,
imageUrl: asset.url,
title: `参考图像 ${index + 1}`,
description: `${asset.width} × ${asset.height}`
});
}}
>
<img
src={asset.url}
alt={`参考图像 ${index + 1}`}
className="w-full h-full object-cover"
/>
</div>
))}
{gen.sourceAssets.length > 4 && (
<div className="w-16 h-16 rounded border flex items-center justify-center bg-gray-100 text-xs text-gray-500">
+{gen.sourceAssets.length - 4}
</div>
)}
</div>
</div> </div>
)} )}
</div> </div>
@@ -669,6 +677,42 @@ export const HistoryPanel: React.FC = () => {
<div className="text-xs text-gray-600"> <div className="text-xs text-gray-600">
基于: G{dbGenerations.findIndex(g => g.id === parentGen.id) + 1} 基于: G{dbGenerations.findIndex(g => g.id === parentGen.id) + 1}
</div> </div>
{/* 显示原始生成的参考图像 */}
{parentGen.sourceAssets && parentGen.sourceAssets.length > 0 && (
<div className="mt-2">
<div className="text-xs text-gray-600 mb-2">
:
</div>
<div className="flex flex-wrap gap-2">
{parentGen.sourceAssets.slice(0, 4).map((asset: any, index: number) => (
<div
key={asset.id}
className="relative w-16 h-16 rounded border overflow-hidden cursor-pointer"
onClick={(e) => {
e.stopPropagation();
setPreviewModal({
open: true,
imageUrl: asset.url,
title: `原始参考图像 ${index + 1}`,
description: `${asset.width} × ${asset.height}`
});
}}
>
<img
src={asset.url}
alt={`原始参考图像 ${index + 1}`}
className="w-full h-full object-cover"
/>
</div>
))}
{parentGen.sourceAssets.length > 4 && (
<div className="w-16 h-16 rounded border flex items-center justify-center bg-gray-100 text-xs text-gray-500">
+{parentGen.sourceAssets.length - 4}
</div>
)}
</div>
</div>
)}
</div> </div>
)} )}
</div> </div>

View File

@@ -47,11 +47,22 @@ export const useImageGeneration = () => {
const accessToken = import.meta.env.VITE_ACCESS_TOKEN || ''; const accessToken = import.meta.env.VITE_ACCESS_TOKEN || '';
let uploadResults: any[] | undefined; let uploadResults: any[] | undefined;
// 上传生成的图像 // 上传生成的图像和参考图像
if (accessToken) { if (accessToken) {
try { try {
// 上传生成的图像
const imageUrls = outputAssets.map(asset => asset.url); const imageUrls = outputAssets.map(asset => asset.url);
uploadResults = await uploadImages(imageUrls, accessToken); const outputUploadResults = await uploadImages(imageUrls, accessToken);
// 上传参考图像(如果存在)
let referenceUploadResults: any[] = [];
if (request.referenceImages && request.referenceImages.length > 0) {
const referenceUrls = request.referenceImages.map(img => `data:image/png;base64,${img}`);
referenceUploadResults = await uploadImages(referenceUrls, accessToken);
}
// 合并上传结果
uploadResults = [...outputUploadResults, ...referenceUploadResults];
// 检查上传结果 // 检查上传结果
const failedUploads = uploadResults.filter(r => !r.success); const failedUploads = uploadResults.filter(r => !r.success);

View File

@@ -0,0 +1,54 @@
import { useEffect, useState } from 'react';
import * as indexedDBService from '../services/indexedDBService';
export const useIndexedDBListener = () => {
const [generations, setGenerations] = useState<any[]>([]);
const [edits, setEdits] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadRecords = async () => {
try {
setLoading(true);
const allGenerations = await indexedDBService.getAllGenerations();
const allEdits = await indexedDBService.getAllEdits();
setGenerations(allGenerations);
setEdits(allEdits);
setError(null);
} catch (err) {
console.error('从IndexedDB加载记录失败:', err);
setError('加载历史记录失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
// 初始化数据库并加载记录
const initAndLoad = async () => {
try {
await indexedDBService.initDB();
await loadRecords();
} catch (err) {
console.error('初始化IndexedDB失败:', err);
setError('初始化数据库失败');
setLoading(false);
}
};
initAndLoad();
// 设置定时器定期检查新记录
const interval = setInterval(() => {
loadRecords();
}, 3000); // 每3秒检查一次
return () => clearInterval(interval);
}, []);
const refresh = () => {
loadRecords();
};
return { generations, edits, loading, error, refresh };
};