重构ComfyUI集成架构:明确区分云端Modal和本地ComfyUI两种生成模式

- 函数重命名以提高可读性:
  - execute_comfyui_generation  execute_modal_cloud_generation
  - perform_outfit_image_generation  perform_local_comfyui_generation

- 更新生成类型标识:
  - 云端Modal模式:generation_type = 'modal_cloud'
  - 本地ComfyUI模式:generation_type = 'local_comfyui'

- 前端UI优化:
  - 将切换开关改为单选按钮组样式
  - 更新描述文本,明确两种模式的区别
  - Gallery中显示单选按钮样式的生成类型标签

- 架构清晰化:
  - 云端Modal模式:使用云端MidJourney API
  - 本地ComfyUI模式:使用用户配置的本地ComfyUI服务
  - 完全分离两种生成逻辑,避免混淆

- 修复类型推断错误,确保编译通过
This commit is contained in:
imeepos
2025-07-31 19:54:34 +08:00
parent c0994149d5
commit 548148e56a
7 changed files with 100 additions and 457 deletions

View File

@@ -1098,11 +1098,6 @@ impl ComfyUIService {
match upload_result {
Ok(result) => {
if result.success {
info!("图片上传成功: {} -> {}", filename, result.remote_url.as_deref().unwrap_or("N/A"));
} else {
warn!("图片上传失败: {} - {}", filename, result.error_message.as_deref().unwrap_or("未知错误"));
}
upload_results.push(result);
}
Err(e) => {

View File

@@ -149,7 +149,6 @@ impl OutfitPhotoGenerationService {
if let Some(remote_url) = upload_result.remote_url {
generation.result_image_urls.push(remote_url.clone());
successful_uploads += 1;
info!("图片上传成功: {}", remote_url);
}
} else {
warn!("图片上传失败: {}", upload_result.error_message.unwrap_or_default());
@@ -423,7 +422,6 @@ impl OutfitPhotoGenerationService {
if let Some(remote_url) = upload_result.remote_url {
generation.result_image_urls.push(remote_url.clone());
successful_uploads += 1;
info!("图片上传成功: {}", remote_url);
}
} else {
warn!("图片上传失败: {}", upload_result.error_message.unwrap_or_default());

View File

@@ -288,14 +288,10 @@ impl VideoGenerationService {
if let Some(remote_url) = upload_result.remote_url {
// 将S3 URL转换为可访问的CDN地址
let accessible_url = Self::convert_s3_to_cdn_url(&remote_url);
println!("✅ 图片上传成功S3 URL: {}", remote_url);
println!("🌐 转换为CDN URL: {}", accessible_url);
Ok(accessible_url)
} else if let Some(urn) = upload_result.urn {
// 将S3 URN转换为可访问的CDN地址
let accessible_url = Self::convert_s3_to_cdn_url(&urn);
println!("✅ 图片上传成功S3 URN: {}", urn);
println!("🌐 转换为CDN URL: {}", accessible_url);
Ok(accessible_url)
} else {
println!("⚠️ 上传成功但未返回URL或URN");

View File

@@ -252,7 +252,6 @@ impl ConnectionPool {
if connections.len() < self.config.max_connections {
match self.create_connection() {
Ok(new_conn) => {
println!("创建新的数据库连接,当前连接数: {}", connections.len() + 1);
return Ok(Some(new_conn));
},
Err(e) => {
@@ -263,7 +262,6 @@ impl ConnectionPool {
}
}
println!("连接池已满且无可用连接,当前连接数: {}", connections.len());
Ok(None)
}

View File

@@ -277,390 +277,15 @@ pub async fn execute_outfit_image_generation(
request: OutfitImageGenerationRequest,
) -> Result<OutfitImageGenerationResponse, String> {
info!("🎨 执行穿搭图片生成: {:?}", request);
let start_time = Utc::now();
// 检查是否使用 ComfyUI
// 检查是否使用 ComfyUI本地模式
if request.use_comfyui.unwrap_or(false) {
return execute_comfyui_generation(state, app_handle, request).await;
}
// 首先创建生成记录
let record_id = create_outfit_image_record(state.clone(), request.clone()).await?;
// 获取数据库和仓库
let database = state.get_database();
let outfit_repo = OutfitImageRepository::new(database.clone());
// 初始化错误处理服务
let error_service = ErrorHandlingService::new();
// 定义简单错误类型
#[derive(Debug)]
struct SimpleError(String);
impl std::fmt::Display for SimpleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for SimpleError {}
// 加载配置
let config = AppConfig::load();
// 检查 ComfyUI 是否启用
if !config.is_comfyui_enabled() {
let simple_error = SimpleError("ComfyUI 功能未启用".to_string());
let user_error = error_service.handle_error(&simple_error, None);
// 更新记录状态为失败
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.fail(user_error.message.clone());
let _ = outfit_repo.update_record(&record);
}
return Ok(OutfitImageGenerationResponse {
record_id,
generated_images: Vec::new(),
generation_time_ms: 0,
success: false,
error_message: Some(user_error.message),
generation_type: Some("standard".to_string()),
task_id: None,
});
}
// 创建 ComfyUI 服务
let comfyui_service = ComfyUIService::new(config.get_comfyui_settings().clone());
// 创建云上传服务
let cloud_upload_service = CloudUploadService::new();
// 检查 ComfyUI 连接
if !comfyui_service.check_connection().await.unwrap_or(false) {
let simple_error = SimpleError("ComfyUI 服务器连接失败".to_string());
let user_error = error_service.handle_error(&simple_error, None);
// 更新记录状态为失败
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.fail(user_error.message.clone());
let _ = outfit_repo.update_record(&record);
}
return Ok(OutfitImageGenerationResponse {
record_id,
generated_images: Vec::new(),
generation_time_ms: 0,
success: false,
error_message: Some(user_error.message),
generation_type: Some("standard".to_string()),
task_id: None,
});
}
// 获取模特图片URL使用model_image_id
let model_repo = ModelRepository::new(database.clone());
// 首先获取模特的所有照片,然后找到指定的照片
let model_image_local_path = match model_repo.get_photos(&request.model_id) {
Ok(photos) => {
// 查找指定ID的照片
if let Some(photo) = photos.iter().find(|p| p.id == request.model_image_id) {
photo.file_path.clone()
} else {
let simple_error = SimpleError(format!("模特图片不存在: {}", request.model_image_id));
let user_error = error_service.handle_error(&simple_error, None);
return Ok(OutfitImageGenerationResponse {
record_id,
generated_images: Vec::new(),
generation_time_ms: 0,
success: false,
error_message: Some(user_error.message),
generation_type: Some("standard".to_string()),
task_id: None,
});
}
}
Err(e) => {
let simple_error = SimpleError(format!("获取模特图片失败: {}", e));
let user_error = error_service.handle_error(&simple_error, None);
return Ok(OutfitImageGenerationResponse {
record_id,
generated_images: Vec::new(),
generation_time_ms: 0,
success: false,
error_message: Some(user_error.message),
generation_type: Some("standard".to_string()),
task_id: None,
});
}
};
// 上传模特图片到云端获取外网链接
let model_image_url = if model_image_local_path.starts_with("https://") {
// 如果已经是HTTPS URL直接使用
info!("📷 使用已有的云端模特图片URL: {}", model_image_local_path);
model_image_local_path
// 本地ComfyUI模式调用本地ComfyUI生成逻辑
let database = state.get_database();
return perform_local_comfyui_generation(database, app_handle, request).await;
} else {
// 如果是本地路径,上传到云存储
info!("📤 本地模特图片需要上传到云存储: {}", model_image_local_path);
match upload_image_to_cloud(&model_image_local_path, &cloud_upload_service).await {
Ok(url) => url,
Err(e) => {
let simple_error = SimpleError(format!("上传模特图片失败: {}", e));
let user_error = error_service.handle_error(&simple_error, None);
return Ok(OutfitImageGenerationResponse {
record_id,
generated_images: Vec::new(),
generation_time_ms: 0,
success: false,
error_message: Some(user_error.message),
generation_type: Some("standard".to_string()),
task_id: None,
});
}
}
};
// 更新记录状态为处理中
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.start_processing();
let _ = outfit_repo.update_record(&record);
info!("📝 更新穿搭图片生成记录状态为处理中");
// 云端Modal模式使用云端API
return execute_modal_cloud_generation(state, app_handle, request).await;
}
// 处理多个商品图片,为每个商品图片创建生成任务
let mut all_generated_images = Vec::new();
let mut has_errors = false;
let mut error_messages = Vec::new();
// 获取工作流文件路径
let workflow_path_str = match config.get_comfyui_settings().workflow_directory.as_ref() {
Some(path) if !path.is_empty() => path.clone(),
_ => {
let simple_error = SimpleError("请先在设置中选择ComfyUI工作流文件".to_string());
let user_error = error_service.handle_error(&simple_error, None);
return Ok(OutfitImageGenerationResponse {
record_id,
generated_images: Vec::new(),
generation_time_ms: 0,
success: false,
error_message: Some(user_error.message),
generation_type: Some("standard".to_string()),
task_id: None,
});
}
};
// 验证请求只包含一个商品图片(工作流模板设计为单商品)
if request.product_image_paths.len() != 1 {
let error_msg = format!(
"当前工作流模板只支持单个商品图片,但请求包含 {} 个商品图片。请为每个商品图片创建独立的生成任务。",
request.product_image_paths.len()
);
error!("{}", error_msg);
// 更新记录状态为失败
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.fail(error_msg.clone());
let _ = outfit_repo.update_record(&record);
}
return Ok(OutfitImageGenerationResponse {
record_id,
generated_images: Vec::new(),
generation_time_ms: 0,
success: false,
error_message: Some(error_msg),
generation_type: Some("standard".to_string()),
task_id: None,
});
}
let product_image_path = &request.product_image_paths[0];
info!("处理单个商品图片: {}", product_image_path);
// 上传商品图片到云端获取外网链接
let product_image_url = if product_image_path.starts_with("https://") {
// 如果已经是HTTPS URL直接使用
info!("📷 使用已有的云端商品图片URL: {}", product_image_path);
product_image_path.clone()
} else {
// 如果是本地路径,上传到云存储
info!("📤 本地商品图片需要上传到云存储: {}", product_image_path);
match upload_image_to_cloud(product_image_path, &cloud_upload_service).await {
Ok(url) => url,
Err(e) => {
has_errors = true;
let error_msg = format!("商品图片上传失败: {}", e);
error_messages.push(error_msg.clone());
error!("{}", error_msg);
// 更新记录状态为失败
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.fail(error_msg.clone());
let _ = outfit_repo.update_record(&record);
}
return Ok(OutfitImageGenerationResponse {
record_id,
generated_images: Vec::new(),
generation_time_ms: 0,
success: false,
error_message: Some(error_msg),
generation_type: Some("standard".to_string()),
task_id: None,
});
}
}
};
// 创建进度回调
let app_handle_clone = app_handle.clone();
let record_id_clone = record_id.clone();
let progress_callback = move |progress: WorkflowProgress| {
// 发送进度事件到前端
if let Err(e) = app_handle_clone.emit("outfit_generation_progress", &progress) {
warn!("发送进度事件失败: {}", e);
}
info!("生成进度 - 记录ID: {}, 进度: {}%", record_id_clone, progress.progress_percentage);
};
// 只有当用户明确提供了 prompt 时才进行替换,否则保持工作流原始设置
let prompt = match request.generation_prompt.as_ref() {
Some(p) if !p.trim().is_empty() => p.clone(),
_ => {
// 如果没有提供 prompt 或为空,使用空字符串,让工作流保持原始设置
info!("未提供生成提示词,将保持工作流模板的原始设置");
String::new()
}
};
// 执行 ComfyUI 工作流并自动上传结果
let remote_key_prefix = format!("outfit-images/{}", record_id);
match comfyui_service.execute_workflow_with_upload_indexed(
&cloud_upload_service,
&workflow_path_str,
&model_image_url,
&product_image_url,
&prompt,
None, // 负面提示词
Some(&remote_key_prefix),
progress_callback,
request.product_index, // 使用请求中的商品编号
).await {
Ok(upload_results) => {
// 处理上传结果
let mut successful_uploads = 0;
for upload_result in upload_results {
if upload_result.success {
if let Some(remote_url) = upload_result.remote_url {
all_generated_images.push(remote_url.clone());
successful_uploads += 1;
info!("图片上传成功: {}", remote_url);
}
} else {
warn!("图片上传失败: {}", upload_result.error_message.unwrap_or_default());
}
}
if successful_uploads == 0 {
has_errors = true;
error_messages.push("所有图片上传失败".to_string());
} else {
info!("商品图片生成成功,上传 {} 张图片", successful_uploads);
}
}
Err(e) => {
has_errors = true;
let error_msg = format!("商品图片生成异常: {}", e);
error_messages.push(error_msg.clone());
error!("{}", error_msg);
}
}
let end_time = Utc::now();
let total_duration = (end_time - start_time).num_milliseconds() as u64;
// 构建响应并更新数据库状态
let response = if all_generated_images.is_empty() {
let error_message = if error_messages.is_empty() {
"未生成任何图片".to_string()
} else {
error_messages.join("; ")
};
let simple_error = SimpleError(error_message.clone());
let user_error = error_service.handle_error(&simple_error, None);
// 更新记录状态为失败
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.fail(user_error.message.clone());
let _ = outfit_repo.update_record(&record);
info!("📝 更新穿搭图片生成记录状态为失败");
}
OutfitImageGenerationResponse {
record_id,
generated_images: Vec::new(),
generation_time_ms: total_duration,
success: false,
error_message: Some(user_error.message),
generation_type: Some("standard".to_string()),
task_id: None,
}
} else {
let success_message = if has_errors {
format!("部分生成成功,共生成 {} 张图片", all_generated_images.len())
} else {
format!("生成成功,共生成 {} 张图片", all_generated_images.len())
};
info!("{}", success_message);
// 转换S3 URL为CDN URL
let cdn_urls: Vec<String> = all_generated_images.iter()
.map(|url| convert_s3_to_cdn_url(url))
.collect();
// 更新记录状态为完成并创建OutfitImage记录
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.complete(cdn_urls.clone());
// 创建OutfitImage记录
for (index, image_url) in cdn_urls.iter().enumerate() {
let outfit_image = OutfitImage::new(
record_id.clone(),
image_url.clone(),
index as u32,
);
// 保存OutfitImage到数据库
if let Err(e) = outfit_repo.create_outfit_image(&outfit_image) {
warn!("创建OutfitImage记录失败: {}", e);
} else {
record.outfit_images.push(outfit_image);
}
}
let _ = outfit_repo.update_record(&record);
info!("📝 更新穿搭图片生成记录状态为完成,保存 {} 张图片", cdn_urls.len());
}
OutfitImageGenerationResponse {
record_id,
generated_images: cdn_urls,
generation_time_ms: total_duration,
success: true,
error_message: if has_errors { Some(error_messages.join("; ")) } else { None },
generation_type: Some("standard".to_string()),
task_id: None,
}
};
info!("✅ 穿搭图片生成完成,耗时: {}ms", total_duration);
Ok(response)
}
/// 执行单个穿搭图片生成任务(异步模式)
@@ -719,8 +344,8 @@ pub async fn execute_outfit_image_task(
let database = state.get_database();
tokio::spawn(async move {
// 直接调用生成逻辑不通过command函数
match perform_outfit_image_generation(database, app_handle_clone.clone(), request).await {
// 直接调用本地ComfyUI生成逻辑不通过command函数
match perform_local_comfyui_generation(database, app_handle_clone.clone(), request).await {
Ok(response) => {
info!("✅ 后台任务执行完成: {}", record_id_clone);
@@ -829,14 +454,10 @@ async fn upload_image_to_cloud(file_path: &str, cloud_service: &CloudUploadServi
if let Some(remote_url) = upload_result.remote_url {
// 将S3 URL转换为可访问的CDN地址
let accessible_url = convert_s3_to_cdn_url(&remote_url);
info!("✅ 图片上传成功S3 URL: {}", remote_url);
info!("🌐 转换为CDN URL: {}", accessible_url);
Ok(accessible_url)
} else if let Some(urn) = upload_result.urn {
// 将S3 URN转换为可访问的CDN地址
let accessible_url = convert_s3_to_cdn_url(&urn);
info!("✅ 图片上传成功S3 URN: {}", urn);
info!("🌐 转换为CDN URL: {}", accessible_url);
Ok(accessible_url)
} else {
warn!("⚠️ 上传成功但未返回URL或URN");
@@ -872,13 +493,13 @@ fn convert_s3_to_cdn_url(s3_url: &str) -> String {
}
}
/// 执行穿搭图片生成的核心逻辑(用于后台任务)
async fn perform_outfit_image_generation(
/// 执行本地ComfyUI生成的核心逻辑(用于后台任务)
async fn perform_local_comfyui_generation(
database: Arc<Database>,
app_handle: AppHandle,
request: OutfitImageGenerationRequest,
) -> Result<OutfitImageGenerationResponse, String> {
info!("🎨 后台执行穿搭图片生成: {:?}", request);
info!("🎨 后台执行本地ComfyUI穿搭图片生成: {:?}", request);
let start_time = Utc::now();
// 创建仓库实例
@@ -1078,6 +699,8 @@ async fn perform_outfit_image_generation(
generation_time_ms: 0,
success: false,
error_message: Some(user_error.message),
generation_type: Some("local_comfyui".to_string()),
task_id: None,
});
}
@@ -1115,6 +738,8 @@ async fn perform_outfit_image_generation(
generation_time_ms: 0,
success: false,
error_message: Some(user_error.message),
generation_type: Some("local_comfyui".to_string()),
task_id: None,
});
}
}
@@ -1194,7 +819,6 @@ async fn perform_outfit_image_generation(
if let Some(remote_url) = upload_result.remote_url {
all_generated_images.push(remote_url.clone());
successful_uploads += 1;
info!("图片上传成功: {}", remote_url);
}
} else {
warn!("图片上传失败: {}", upload_result.error_message.unwrap_or_default());
@@ -1243,6 +867,8 @@ async fn perform_outfit_image_generation(
generation_time_ms: total_duration,
success: false,
error_message: Some(user_error.message),
generation_type: Some("local_comfyui".to_string()),
task_id: None,
}
} else {
let success_message = if has_errors {
@@ -1288,6 +914,8 @@ async fn perform_outfit_image_generation(
generation_time_ms: total_duration,
success: true,
error_message: if has_errors { Some(error_messages.join("; ")) } else { None },
generation_type: Some("local_comfyui".to_string()),
task_id: None,
}
};
@@ -1359,13 +987,13 @@ impl Default for ApiConfig {
}
}
/// 执行 ComfyUI 生成
async fn execute_comfyui_generation(
/// 执行云端Modal生成
async fn execute_modal_cloud_generation(
state: State<'_, AppState>,
app_handle: AppHandle,
request: OutfitImageGenerationRequest,
) -> Result<OutfitImageGenerationResponse, String> {
info!("🎨 执行 ComfyUI 穿搭图片生成: {:?}", request);
info!("🎨 执行云端Modal穿搭图片生成: {:?}", request);
let start_time = Utc::now();
// 首先创建生成记录
@@ -1375,12 +1003,12 @@ async fn execute_comfyui_generation(
let database = state.get_database();
let outfit_repo = OutfitImageRepository::new(database.clone());
// 更新记录为 ComfyUI 生成类型
// 更新记录为云端Modal生成类型
if let Ok(Some(mut record)) = outfit_repo.get_record_by_id(&record_id) {
record.generation_type = Some("comfyui".to_string());
record.generation_type = Some("modal_cloud".to_string());
record.start_processing();
if let Err(e) = outfit_repo.update_record(&record) {
error!("更新记录为 ComfyUI 类型失败: {}", e);
error!("更新记录为云端Modal类型失败: {}", e);
}
}
@@ -1450,7 +1078,7 @@ async fn execute_comfyui_generation(
match status["status"].as_str() {
Some("completed") => {
let result_urls = status["result_urls"].as_array()
let result_urls: Vec<String> = status["result_urls"].as_array()
.map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect())
.unwrap_or_default();
@@ -1475,7 +1103,7 @@ async fn execute_comfyui_generation(
generation_time_ms: generation_time,
success: true,
error_message: None,
generation_type: Some("comfyui".to_string()),
generation_type: Some("modal_cloud".to_string()),
task_id: Some(task_id.to_string()),
});
}

View File

@@ -54,21 +54,25 @@ export const OutfitImageGallery: React.FC<OutfitImageGalleryProps> = ({
const [filter, setFilter] = useState<FilterType>('all');
const [searchQuery, setSearchQuery] = useState('');
// 渲染生成方式标识
// 渲染生成方式标识(单选按钮样式)
const renderGenerationType = (record: OutfitImageRecord) => {
if (record.generation_type === 'comfyui') {
if (record.generation_type === 'local_comfyui') {
return (
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800 border border-blue-200">
<span className="w-2 h-2 bg-blue-500 rounded-full mr-1"></span>
ComfyUI
</span>
<div className="inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-blue-50 border border-blue-200">
<div className="w-3 h-3 rounded-full border-2 border-blue-500 bg-blue-500 mr-2 flex items-center justify-center">
<div className="w-1.5 h-1.5 rounded-full bg-white"></div>
</div>
<span className="text-blue-700 font-semibold">ComfyUI</span>
</div>
);
} else if (record.generation_type === 'standard') {
} else if (record.generation_type === 'modal_cloud') {
return (
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-800 border border-purple-200">
<span className="w-2 h-2 bg-purple-500 rounded-full mr-1"></span>
</span>
<div className="inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-purple-50 border border-purple-200">
<div className="w-3 h-3 rounded-full border-2 border-purple-500 bg-purple-500 mr-2 flex items-center justify-center">
<div className="w-1.5 h-1.5 rounded-full bg-white"></div>
</div>
<span className="text-purple-700 font-semibold">Modal</span>
</div>
);
}
return null;

View File

@@ -26,7 +26,7 @@ export const OutfitImageGenerationModal: React.FC<OutfitImageGenerationModalProp
onBatchGenerate,
isGenerating = false
}) => {
// 新增状态
// 新增状态false = 云端Modal模式true = 本地ComfyUI模式
const [useComfyUI, setUseComfyUI] = useState(false);
// 创建新的处理函数
@@ -109,40 +109,64 @@ export const OutfitImageGenerationModal: React.FC<OutfitImageGenerationModalProp
{/* 主要内容区域 */}
<div className="flex-1 overflow-y-auto p-6">
<div className="max-w-2xl mx-auto">
{/* 生成方式选择开关 */}
<div className="flex items-center justify-between mb-6 p-4 bg-gradient-to-r from-gray-50 to-blue-50 rounded-xl border border-gray-200">
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${
useComfyUI ? 'bg-blue-500' : 'bg-purple-500'
}`}>
{useComfyUI ? (
<span className="text-white text-lg">🎨</span>
) : (
<span className="text-white text-lg"></span>
)}
</div>
<div>
<h3 className="text-sm font-semibold text-gray-900">
{useComfyUI ? 'ComfyUI 高质量生成' : '标准快速生成'}
</h3>
<p className="text-xs text-gray-600">
{useComfyUI ? '使用 ComfyUI 工作流,生成质量更高' : '使用标准算法,生成速度更快'}
</p>
</div>
</div>
<button
onClick={() => setUseComfyUI(!useComfyUI)}
className={`relative inline-flex h-7 w-12 items-center rounded-full transition-all duration-200 ${
useComfyUI ? 'bg-blue-500 shadow-lg' : 'bg-gray-300'
}`}
>
<span
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow-md transition-transform duration-200 ${
useComfyUI ? 'translate-x-6' : 'translate-x-1'
{/* 生成方式选择(单选按钮组) */}
<div className="mb-6 p-4 bg-gradient-to-r from-gray-50 to-blue-50 rounded-xl border border-gray-200">
<h3 className="text-sm font-semibold text-gray-900 mb-3"></h3>
<div className="flex gap-3">
{/* 云端Modal选项 */}
<button
onClick={() => setUseComfyUI(false)}
className={`flex-1 flex items-center gap-3 p-3 rounded-lg border-2 transition-all duration-200 ${
!useComfyUI
? 'border-purple-500 bg-purple-50 shadow-md'
: 'border-gray-200 bg-white hover:border-gray-300'
}`}
/>
</button>
>
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${
!useComfyUI ? 'border-purple-500 bg-purple-500' : 'border-gray-300'
}`}>
{!useComfyUI && <div className="w-2 h-2 rounded-full bg-white"></div>}
</div>
<div className="flex items-center gap-2">
<span className="text-lg"></span>
<div className="text-left">
<div className={`text-sm font-medium ${!useComfyUI ? 'text-purple-700' : 'text-gray-700'}`}>
Modal生成
</div>
<div className="text-xs text-gray-500">
使API
</div>
</div>
</div>
</button>
{/* 本地ComfyUI选项 */}
<button
onClick={() => setUseComfyUI(true)}
className={`flex-1 flex items-center gap-3 p-3 rounded-lg border-2 transition-all duration-200 ${
useComfyUI
? 'border-blue-500 bg-blue-50 shadow-md'
: 'border-gray-200 bg-white hover:border-gray-300'
}`}
>
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${
useComfyUI ? 'border-blue-500 bg-blue-500' : 'border-gray-300'
}`}>
{useComfyUI && <div className="w-2 h-2 rounded-full bg-white"></div>}
</div>
<div className="flex items-center gap-2">
<span className="text-lg">🎨</span>
<div className="text-left">
<div className={`text-sm font-medium ${useComfyUI ? 'text-blue-700' : 'text-gray-700'}`}>
ComfyUI生成
</div>
<div className="text-xs text-gray-500">
使ComfyUI服务host和端口
</div>
</div>
</div>
</button>
</div>
</div>
{/* 穿搭图片生成器 */}