重构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:
@@ -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) => {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
{/* 穿搭图片生成器 */}
|
||||
|
||||
Reference in New Issue
Block a user