feat: 将穿搭图片生成功能改为Modal弹框形式
- 创建OutfitImageGenerationModal组件,使用Portal渲染到modal-root容器 - 修改ModelDetail页面,将穿搭生成选项卡改为按钮触发Modal - 更新ModelDetailTabs组件,为穿搭生成选项卡添加特殊处理 - 修复OutfitImageGallery和OutfitImageGenerator的图片路径处理 - 优化UI设计,使用渐变背景和美观的按钮界面 - 支持键盘ESC关闭和背景点击关闭Modal功能
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import {
|
||||
X,
|
||||
ChevronLeft,
|
||||
@@ -180,7 +181,12 @@ export const ModelImagePreviewModal: React.FC<ModelImagePreviewModalProps> = ({
|
||||
|
||||
const imageUrl = getImageSrc(currentPhoto.file_path);
|
||||
|
||||
return (
|
||||
// 获取modal-root容器
|
||||
const modalRoot = document.getElementById('modal-root');
|
||||
if (!modalRoot) return null;
|
||||
|
||||
// 使用Portal渲染到modal-root容器
|
||||
return createPortal(
|
||||
<div className={`fixed inset-0 z-50 flex items-center justify-center bg-black ${
|
||||
isFullscreen ? 'bg-opacity-100' : 'bg-opacity-75'
|
||||
}`}>
|
||||
@@ -435,7 +441,8 @@ export const ModelImagePreviewModal: React.FC<ModelImagePreviewModalProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
modalRoot
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -9,10 +9,14 @@ import {
|
||||
List,
|
||||
Search,
|
||||
Calendar,
|
||||
Sparkles
|
||||
Sparkles,
|
||||
Eye
|
||||
} from 'lucide-react';
|
||||
import { OutfitImageRecord, OutfitImageStatus } from '../types/outfitImage';
|
||||
import { DeleteConfirmDialog } from './DeleteConfirmDialog';
|
||||
import { getImageSrc } from '../utils/imagePathUtils';
|
||||
import { ImagePreviewModal } from './ImagePreviewModal';
|
||||
import { GroundingSource } from '../types/ragGrounding';
|
||||
|
||||
interface OutfitImageGalleryProps {
|
||||
records: OutfitImageRecord[];
|
||||
@@ -39,7 +43,16 @@ export const OutfitImageGallery: React.FC<OutfitImageGalleryProps> = ({
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('grid');
|
||||
const [filter, setFilter] = useState<FilterType>('all');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
|
||||
// 图片预览模态框状态
|
||||
const [previewModal, setPreviewModal] = useState<{
|
||||
isOpen: boolean;
|
||||
source: GroundingSource | null;
|
||||
}>({
|
||||
isOpen: false,
|
||||
source: null
|
||||
});
|
||||
|
||||
// 删除确认对话框状态
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<{
|
||||
show: boolean;
|
||||
@@ -88,6 +101,27 @@ export const OutfitImageGallery: React.FC<OutfitImageGalleryProps> = ({
|
||||
setDeleteConfirm({ show: false, record: null, deleting: false });
|
||||
}, []);
|
||||
|
||||
// 打开图片预览
|
||||
const openImagePreview = useCallback((imageUrl: string, title: string) => {
|
||||
const source: GroundingSource = {
|
||||
uri: imageUrl,
|
||||
title: title,
|
||||
content: { description: '穿搭生成图片' }
|
||||
};
|
||||
setPreviewModal({
|
||||
isOpen: true,
|
||||
source
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 关闭图片预览
|
||||
const closeImagePreview = useCallback(() => {
|
||||
setPreviewModal({
|
||||
isOpen: false,
|
||||
source: null
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getStatusIcon = (status: OutfitImageStatus) => {
|
||||
switch (status) {
|
||||
case OutfitImageStatus.Pending:
|
||||
@@ -298,12 +332,17 @@ export const OutfitImageGallery: React.FC<OutfitImageGalleryProps> = ({
|
||||
{record.status === OutfitImageStatus.Completed && record.outfit_images.length > 0 && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{record.outfit_images.slice(0, 4).map((image, index) => (
|
||||
<div key={image.id} className="aspect-square rounded-lg overflow-hidden">
|
||||
<div key={image.id} className="aspect-square rounded-lg overflow-hidden relative group cursor-pointer">
|
||||
<img
|
||||
src={image.image_url}
|
||||
src={getImageSrc(image.image_url)}
|
||||
alt={`穿搭图片 ${index + 1}`}
|
||||
className="w-full h-full object-cover hover:scale-105 transition-transform duration-200"
|
||||
onClick={() => openImagePreview(image.image_url, `穿搭图片 ${index + 1}`)}
|
||||
/>
|
||||
{/* 预览按钮覆盖层 */}
|
||||
<div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 transition-all duration-200 flex items-center justify-center">
|
||||
<Eye className="w-6 h-6 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-200" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{record.outfit_images.length > 4 && (
|
||||
@@ -381,9 +420,13 @@ export const OutfitImageGallery: React.FC<OutfitImageGalleryProps> = ({
|
||||
{record.status === OutfitImageStatus.Completed && record.outfit_images.length > 0 && (
|
||||
<div className="flex -space-x-2">
|
||||
{record.outfit_images.slice(0, 3).map((image, index) => (
|
||||
<div key={image.id} className="w-10 h-10 rounded-lg overflow-hidden border-2 border-white">
|
||||
<div
|
||||
key={image.id}
|
||||
className="w-10 h-10 rounded-lg overflow-hidden border-2 border-white cursor-pointer hover:scale-110 transition-transform duration-200"
|
||||
onClick={() => openImagePreview(image.image_url, `穿搭图片 ${index + 1}`)}
|
||||
>
|
||||
<img
|
||||
src={image.image_url}
|
||||
src={getImageSrc(image.image_url)}
|
||||
alt={`穿搭图片 ${index + 1}`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
@@ -416,6 +459,13 @@ export const OutfitImageGallery: React.FC<OutfitImageGalleryProps> = ({
|
||||
onCancel={cancelDelete}
|
||||
deleting={deleteConfirm.deleting}
|
||||
/>
|
||||
|
||||
{/* 图片预览模态框 */}
|
||||
<ImagePreviewModal
|
||||
isOpen={previewModal.isOpen}
|
||||
source={previewModal.source}
|
||||
onClose={closeImagePreview}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
146
apps/desktop/src/components/OutfitImageGenerationModal.tsx
Normal file
146
apps/desktop/src/components/OutfitImageGenerationModal.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { X, Sparkles } from 'lucide-react';
|
||||
import { Model } from '../types/model';
|
||||
import { OutfitImageRecord, OutfitImageGenerationRequest } from '../types/outfitImage';
|
||||
import { OutfitImageGenerator } from './OutfitImageGenerator';
|
||||
import { OutfitImageGallery } from './OutfitImageGallery';
|
||||
|
||||
interface OutfitImageGenerationModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
model: Model;
|
||||
outfitRecords: OutfitImageRecord[];
|
||||
onGenerate: (request: OutfitImageGenerationRequest) => Promise<void>;
|
||||
onDeleteRecord: (record: OutfitImageRecord) => Promise<void>;
|
||||
onRefreshRecords: () => Promise<void>;
|
||||
isGenerating?: boolean;
|
||||
recordsLoading?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 穿搭图片生成模态框组件
|
||||
* 使用Portal渲染到modal-root容器,避免复杂容器结构影响
|
||||
*/
|
||||
export const OutfitImageGenerationModal: React.FC<OutfitImageGenerationModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
model,
|
||||
outfitRecords,
|
||||
onGenerate,
|
||||
onDeleteRecord,
|
||||
onRefreshRecords,
|
||||
isGenerating = false,
|
||||
recordsLoading = false
|
||||
}) => {
|
||||
// 键盘事件处理
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
// 防止背景滚动
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
document.body.style.overflow = 'unset';
|
||||
};
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
// 获取modal-root容器
|
||||
const modalRoot = document.getElementById('modal-root');
|
||||
if (!modalRoot) return null;
|
||||
|
||||
// 使用Portal渲染到modal-root容器
|
||||
return createPortal(
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
|
||||
{/* 模态框背景 */}
|
||||
<div
|
||||
className="absolute inset-0 cursor-pointer"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* 模态框内容 */}
|
||||
<div className="relative bg-white rounded-2xl shadow-2xl max-w-7xl max-h-[95vh] w-full mx-4 flex flex-col overflow-hidden">
|
||||
{/* 头部 */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200 bg-gradient-to-r from-purple-50 to-pink-50">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-pink-500 rounded-xl flex items-center justify-center">
|
||||
<Sparkles className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">穿搭图片生成</h2>
|
||||
<p className="text-sm text-gray-600">为 {model.name} 生成AI穿搭效果图</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
title="关闭 (Esc)"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 主要内容区域 */}
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-full">
|
||||
{/* 穿搭图片生成器 */}
|
||||
<div className="space-y-6">
|
||||
<OutfitImageGenerator
|
||||
modelId={model.id}
|
||||
modelPhotos={model.photos}
|
||||
onGenerate={onGenerate}
|
||||
isGenerating={isGenerating}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 穿搭图片生成记录 */}
|
||||
<div className="space-y-6">
|
||||
<OutfitImageGallery
|
||||
records={outfitRecords}
|
||||
onDelete={onDeleteRecord}
|
||||
onRefresh={onRefreshRecords}
|
||||
loading={recordsLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部操作栏(可选) */}
|
||||
<div className="flex items-center justify-between p-6 border-t border-gray-200 bg-gray-50">
|
||||
<div className="text-sm text-gray-500">
|
||||
共 {outfitRecords.length} 条生成记录
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<button
|
||||
onClick={onRefreshRecords}
|
||||
className="px-4 py-2 text-gray-600 hover:text-gray-800 hover:bg-gray-200 rounded-lg transition-colors text-sm"
|
||||
>
|
||||
刷新记录
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-6 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors text-sm"
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
modalRoot
|
||||
);
|
||||
};
|
||||
|
||||
export default OutfitImageGenerationModal;
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { ModelPhoto, PhotoType } from '../types/model';
|
||||
import { OutfitImageGenerationRequest } from '../types/outfitImage';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { getImageSrc } from '../utils/imagePathUtils';
|
||||
|
||||
interface OutfitImageGeneratorProps {
|
||||
modelId: string;
|
||||
@@ -208,7 +208,7 @@ export const OutfitImageGenerator: React.FC<OutfitImageGeneratorProps> = ({
|
||||
onClick={() => setSelectedModelImageId(photo.id)}
|
||||
>
|
||||
<img
|
||||
src={convertFileSrc(photo.file_path)}
|
||||
src={getImageSrc(photo.file_path)}
|
||||
alt={photo.file_name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
@@ -280,7 +280,7 @@ export const OutfitImageGenerator: React.FC<OutfitImageGeneratorProps> = ({
|
||||
<div key={index} className="relative group">
|
||||
<div className="aspect-square rounded-lg overflow-hidden border border-gray-200">
|
||||
<img
|
||||
src={imagePath.startsWith('http') ? imagePath : `file://${imagePath}`}
|
||||
src={getImageSrc(imagePath)}
|
||||
alt={`商品图片 ${index + 1}`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
@@ -20,6 +20,7 @@ interface ModelDetailTabsProps {
|
||||
dynamics: ModelDynamic[];
|
||||
videoTasks: VideoGenerationTask[];
|
||||
outfitRecords: OutfitImageRecord[];
|
||||
onOpenOutfitModal?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,7 +33,8 @@ export const ModelDetailTabs: React.FC<ModelDetailTabsProps> = ({
|
||||
model,
|
||||
dynamics,
|
||||
videoTasks,
|
||||
outfitRecords
|
||||
outfitRecords,
|
||||
onOpenOutfitModal
|
||||
}) => {
|
||||
// 键盘快捷键支持
|
||||
useEffect(() => {
|
||||
@@ -120,28 +122,60 @@ export const ModelDetailTabs: React.FC<ModelDetailTabsProps> = ({
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200/50 mb-6">
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="flex space-x-8 px-6" aria-label="Tabs">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => onTabChange(tab.id)}
|
||||
title={`${tab.name} (${tab.shortcut})`}
|
||||
className={`${activeTab === tab.id
|
||||
? 'border-primary-500 text-primary-600 bg-primary-50'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 hover:bg-gray-50'
|
||||
} whitespace-nowrap py-4 px-4 border-b-2 font-medium text-sm transition-all duration-200 rounded-t-lg flex items-center gap-2 relative group`}
|
||||
>
|
||||
<span className="text-base">{tab.icon}</span>
|
||||
<span>{tab.name}</span>
|
||||
{tab.badge !== null && tab.badge > 0 && (
|
||||
<span className={`${activeTab === tab.id
|
||||
? 'bg-primary-500 text-white'
|
||||
: 'bg-gray-400 text-white group-hover:bg-gray-500'
|
||||
} text-xs px-2 py-0.5 rounded-full font-semibold transition-colors duration-200`}>
|
||||
{tab.badge}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
{tabs.map((tab) => {
|
||||
// 穿搭生成选项卡特殊处理 - 显示为按钮形式
|
||||
if (tab.id === 'outfits') {
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => {
|
||||
onTabChange(tab.id);
|
||||
onOpenOutfitModal?.();
|
||||
}}
|
||||
title={`${tab.name} (${tab.shortcut})`}
|
||||
className={`${activeTab === tab.id
|
||||
? 'border-purple-500 text-purple-600 bg-purple-50'
|
||||
: 'border-transparent text-gray-500 hover:text-purple-600 hover:border-purple-300 hover:bg-purple-50'
|
||||
} whitespace-nowrap py-4 px-4 border-b-2 font-medium text-sm transition-all duration-200 rounded-t-lg flex items-center gap-2 relative group`}
|
||||
>
|
||||
<span className="text-base">{tab.icon}</span>
|
||||
<span>{tab.name}</span>
|
||||
{tab.badge !== null && tab.badge > 0 && (
|
||||
<span className={`${activeTab === tab.id
|
||||
? 'bg-purple-500 text-white'
|
||||
: 'bg-gray-400 text-white group-hover:bg-purple-500'
|
||||
} text-xs px-2 py-0.5 rounded-full font-semibold transition-colors duration-200`}>
|
||||
{tab.badge}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// 其他选项卡正常处理
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => onTabChange(tab.id)}
|
||||
title={`${tab.name} (${tab.shortcut})`}
|
||||
className={`${activeTab === tab.id
|
||||
? 'border-primary-500 text-primary-600 bg-primary-50'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 hover:bg-gray-50'
|
||||
} whitespace-nowrap py-4 px-4 border-b-2 font-medium text-sm transition-all duration-200 rounded-t-lg flex items-center gap-2 relative group`}
|
||||
>
|
||||
<span className="text-base">{tab.icon}</span>
|
||||
<span>{tab.name}</span>
|
||||
{tab.badge !== null && tab.badge > 0 && (
|
||||
<span className={`${activeTab === tab.id
|
||||
? 'bg-primary-500 text-white'
|
||||
: 'bg-gray-400 text-white group-hover:bg-gray-500'
|
||||
} text-xs px-2 py-0.5 rounded-full font-semibold transition-colors duration-200`}>
|
||||
{tab.badge}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,8 +14,7 @@ import { DeleteConfirmDialog } from '../components/DeleteConfirmDialog';
|
||||
import { OutfitImageService } from '../services/outfitImageService';
|
||||
import { ModelDashboardStats, OutfitImageRecord, OutfitImageGenerationRequest } from '../types/outfitImage';
|
||||
import { ModelImageGallery } from '../components/ModelImageGallery';
|
||||
import { OutfitImageGenerator } from '../components/OutfitImageGenerator';
|
||||
import { OutfitImageGallery } from '../components/OutfitImageGallery';
|
||||
import { OutfitImageGenerationModal } from '../components/OutfitImageGenerationModal';
|
||||
import { ModelDetailHeader } from '../components/model-detail/ModelDetailHeader';
|
||||
import { ModelDetailTabs, TabId } from '../components/model-detail/ModelDetailTabs';
|
||||
import { ModelOverviewTab } from '../components/model-detail/ModelOverviewTab';
|
||||
@@ -50,6 +49,9 @@ const ModelDetail: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<TabId>('overview');
|
||||
const [dynamics] = useState<ModelDynamic[]>([]);
|
||||
|
||||
// 穿搭图片生成Modal状态
|
||||
const [showOutfitModal, setShowOutfitModal] = useState(false);
|
||||
|
||||
|
||||
const [deletePhotoConfirm, setDeletePhotoConfirm] = useState<{
|
||||
show: boolean;
|
||||
@@ -451,6 +453,7 @@ const ModelDetail: React.FC = () => {
|
||||
dynamics={dynamics}
|
||||
videoTasks={videoTasks}
|
||||
outfitRecords={outfitRecords}
|
||||
onOpenOutfitModal={() => setShowOutfitModal(false)}
|
||||
/>
|
||||
|
||||
{/* Tab内容区域 */}
|
||||
@@ -503,22 +506,31 @@ const ModelDetail: React.FC = () => {
|
||||
{/* 穿搭生成选项卡 */}
|
||||
{activeTab === 'outfits' && (
|
||||
<div className="animate-fade-in">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* 穿搭图片生成 */}
|
||||
<OutfitImageGenerator
|
||||
modelId={model.id}
|
||||
modelPhotos={model.photos}
|
||||
onGenerate={handleGenerateOutfitImages}
|
||||
isGenerating={generatingOutfit}
|
||||
/>
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-8">
|
||||
<div className="text-center">
|
||||
<div className="w-20 h-20 bg-gradient-to-br from-purple-500 to-pink-500 rounded-2xl flex items-center justify-center mx-auto mb-6">
|
||||
<span className="text-3xl">👗</span>
|
||||
</div>
|
||||
|
||||
{/* 穿搭图片生成记录 */}
|
||||
<OutfitImageGallery
|
||||
records={outfitRecords}
|
||||
onDelete={handleDeleteOutfitRecord}
|
||||
onRefresh={loadOutfitRecords}
|
||||
loading={outfitRecordsLoading}
|
||||
/>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">AI穿搭图片生成</h3>
|
||||
<p className="text-gray-600 mb-8 max-w-md mx-auto">
|
||||
为 {model.name} 生成专业的AI穿搭效果图,支持多种风格和商品搭配
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
onClick={() => setShowOutfitModal(true)}
|
||||
className="inline-flex items-center px-8 py-4 bg-gradient-to-r from-purple-500 to-pink-500 text-white font-semibold rounded-xl hover:from-purple-600 hover:to-pink-600 transition-all duration-200 shadow-lg hover:shadow-xl transform hover:scale-105"
|
||||
>
|
||||
<span className="text-xl mr-3">✨</span>
|
||||
开始生成穿搭图片
|
||||
</button>
|
||||
|
||||
<div className="text-sm text-gray-500">
|
||||
已有 {outfitRecords.length} 条生成记录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -544,6 +556,21 @@ const ModelDetail: React.FC = () => {
|
||||
onConfirm={confirmDeletePhoto}
|
||||
onCancel={cancelDeletePhoto}
|
||||
/>
|
||||
|
||||
{/* 穿搭图片生成Modal */}
|
||||
{model && (
|
||||
<OutfitImageGenerationModal
|
||||
isOpen={showOutfitModal}
|
||||
onClose={() => setShowOutfitModal(false)}
|
||||
model={model}
|
||||
outfitRecords={outfitRecords}
|
||||
onGenerate={handleGenerateOutfitImages}
|
||||
onDeleteRecord={handleDeleteOutfitRecord}
|
||||
onRefreshRecords={loadOutfitRecords}
|
||||
isGenerating={generatingOutfit}
|
||||
recordsLoading={outfitRecordsLoading}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user