diff --git a/apps/desktop/src-tauri/src/presentation/commands/outfit_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/outfit_commands.rs index 482ddfe..1dc60be 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/outfit_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/outfit_commands.rs @@ -5,6 +5,7 @@ use tauri::{command, State}; use std::sync::Arc; +use serde_json::Value; use crate::app_state::AppState; use crate::business::services::outfit_analysis_service::OutfitAnalysisService; @@ -146,8 +147,92 @@ pub async fn create_outfit_items_from_analysis( project_id: String, analysis_id: String, ) -> Result, String> { - // 这是一个简化的实现,实际应该从分析结果中提取产品信息 - Err("功能开发中".to_string()) + let database = state.get_database(); + let analysis_service = create_analysis_service(database.clone())?; + let item_service = create_item_service(database)?; + + // 1. 获取分析结果 + let analysis = analysis_service.get_analysis_by_id(&analysis_id).await + .map_err(|e| format!("获取分析结果失败: {}", e))? + .ok_or("分析结果不存在")?; + + // 2. 检查分析状态 + if analysis.analysis_status != crate::data::models::outfit_analysis::AnalysisStatus::Completed { + return Err("分析尚未完成,无法创建服装单品".to_string()); + } + + // 3. 解析分析结果 + let analysis_result = analysis.analysis_result + .ok_or("分析结果为空")?; + + // 4. 从分析结果中提取产品信息 + let products = extract_products_from_image_analysis(&analysis_result); + + if products.is_empty() { + return Err("分析结果中没有找到服装单品".to_string()); + } + + // 5. 为每个产品创建服装单品 + let mut created_items = Vec::new(); + for (index, product) in products.iter().enumerate() { + // 转换类别字符串为枚举 + let category = match product.category.as_str() { + "上衣" | "T恤" | "衬衫" | "毛衣" | "背心" => crate::data::models::outfit_item::OutfitCategory::Top, + "下装" | "裤子" | "短裤" | "裙子" => crate::data::models::outfit_item::OutfitCategory::Bottom, + "外套" | "夹克" | "大衣" => crate::data::models::outfit_item::OutfitCategory::Outerwear, + "连衣裙" => crate::data::models::outfit_item::OutfitCategory::Dress, + "鞋子" | "运动鞋" | "高跟鞋" | "靴子" => crate::data::models::outfit_item::OutfitCategory::Footwear, + "包包" | "手提包" | "背包" | "配饰" | "帽子" | "围巾" | "手表" => crate::data::models::outfit_item::OutfitCategory::Accessory, + _ => crate::data::models::outfit_item::OutfitCategory::Other, + }; + + // 转换颜色信息 + let color_primary = if let Some(color_value) = &product.color_pattern { + parse_color_from_value(color_value).unwrap_or_else(|| { + crate::data::models::outfit_analysis::ColorHSV::new(0.0, 0.0, 0.5) + }) + } else { + crate::data::models::outfit_analysis::ColorHSV::new(0.0, 0.0, 0.5) + }; + + let request = CreateOutfitItemRequest { + project_id: project_id.clone(), + analysis_id: Some(analysis_id.clone()), + name: product.name.clone(), + category, + brand: None, // AI分析通常不能识别品牌 + model: None, + color_primary, + color_secondary: None, + styles: vec![], // 暂时为空,可以后续扩展 + design_elements: product.design_styles.clone().unwrap_or_default(), + size: None, // AI分析通常不能识别尺寸 + material: None, + price: None, // AI分析不包含价格信息 + purchase_date: None, + image_urls: vec![analysis.image_path.clone()], + tags: product.tags.clone().unwrap_or_default(), + notes: Some(format!("从AI分析结果自动创建 (分析ID: {})", analysis_id)), + }; + + match item_service.create_item(request).await { + Ok(item) => { + created_items.push(item); + println!("✅ 成功创建服装单品 {}: {}", index + 1, product.name); + } + Err(e) => { + println!("❌ 创建服装单品失败 {}: {} - {}", index + 1, product.name, e); + // 继续创建其他单品,不因为一个失败而停止 + } + } + } + + if created_items.is_empty() { + return Err("没有成功创建任何服装单品".to_string()); + } + + println!("🎉 成功从分析结果创建了 {} 个服装单品", created_items.len()); + Ok(created_items) } /// 获取服装单品列表 @@ -331,3 +416,159 @@ pub async fn save_outfit_image( // 返回文件路径 Ok(file_path.to_string_lossy().to_string()) } + +/// 从ImageAnalysisResult中提取产品信息 +fn extract_products_from_image_analysis(analysis_result: &crate::data::models::outfit_analysis::ImageAnalysisResult) -> Vec { + analysis_result.products.iter().map(|product| { + ProductInfo { + name: format!("{}", product.category), + category: product.category.clone(), + description: Some(product.description.clone()), + color_pattern: Some(serde_json::to_value(&product.color_pattern).unwrap_or(serde_json::Value::Null)), + design_styles: Some(product.design_styles.clone()), + tags: Some(vec![product.category.clone()]), + } + }).collect() +} + +/// 从分析结果中提取产品信息(备用方法) +fn extract_products_from_analysis(analysis_result: &Value) -> anyhow::Result> { + let mut products = Vec::new(); + + // 尝试从不同的结构中提取产品信息 + if let Some(products_array) = analysis_result.get("products").and_then(|v| v.as_array()) { + // 标准格式:{ "products": [...] } + for (index, product) in products_array.iter().enumerate() { + if let Some(product_info) = parse_product_from_json(product, index)? { + products.push(product_info); + } + } + } else if let Some(items_array) = analysis_result.get("items").and_then(|v| v.as_array()) { + // 备选格式:{ "items": [...] } + for (index, item) in items_array.iter().enumerate() { + if let Some(product_info) = parse_product_from_json(item, index)? { + products.push(product_info); + } + } + } else if analysis_result.is_array() { + // 直接是数组格式:[...] + if let Some(array) = analysis_result.as_array() { + for (index, item) in array.iter().enumerate() { + if let Some(product_info) = parse_product_from_json(item, index)? { + products.push(product_info); + } + } + } + } else { + // 尝试解析单个产品对象 + if let Some(product_info) = parse_product_from_json(analysis_result, 0)? { + products.push(product_info); + } + } + + Ok(products) +} + +/// 从JSON对象解析单个产品信息 +fn parse_product_from_json(json: &Value, index: usize) -> anyhow::Result> { + // 提取类别信息 + let category = json.get("category") + .and_then(|v| v.as_str()) + .or_else(|| json.get("type").and_then(|v| v.as_str())) + .or_else(|| json.get("clothing_type").and_then(|v| v.as_str())) + .unwrap_or("未分类") + .to_string(); + + // 如果没有有效的类别,跳过这个产品 + if category == "未分类" || category.is_empty() { + return Ok(None); + } + + // 提取名称 + let name = json.get("name") + .and_then(|v| v.as_str()) + .or_else(|| json.get("description").and_then(|v| v.as_str())) + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("{} {}", category, index + 1)); + + // 提取描述 + let description = json.get("description") + .and_then(|v| v.as_str()) + .or_else(|| json.get("details").and_then(|v| v.as_str())) + .map(|s| s.to_string()); + + // 提取颜色信息 + let color_pattern = json.get("color_pattern") + .or_else(|| json.get("colors")) + .or_else(|| json.get("color")) + .cloned(); + + // 提取设计风格 + let design_styles = json.get("design_styles") + .or_else(|| json.get("styles")) + .or_else(|| json.get("style")) + .and_then(|v| { + if let Some(array) = v.as_array() { + Some(array.iter() + .filter_map(|s| s.as_str()) + .map(|s| s.to_string()) + .collect()) + } else if let Some(s) = v.as_str() { + Some(vec![s.to_string()]) + } else { + None + } + }); + + // 提取标签 + let tags = json.get("tags") + .and_then(|v| { + if let Some(array) = v.as_array() { + Some(array.iter() + .filter_map(|s| s.as_str()) + .map(|s| s.to_string()) + .collect()) + } else if let Some(s) = v.as_str() { + Some(vec![s.to_string()]) + } else { + None + } + }); + + Ok(Some(ProductInfo { + name, + category, + description, + color_pattern, + design_styles, + tags, + })) +} + +/// 产品信息结构体 +#[derive(Debug, Clone)] +struct ProductInfo { + name: String, + category: String, + description: Option, + color_pattern: Option, + design_styles: Option>, + tags: Option>, +} + +/// 从JSON值解析颜色信息 +fn parse_color_from_value(value: &Value) -> Option { + if let Some(obj) = value.as_object() { + let hue = obj.get("hue")?.as_f64().unwrap_or(0.0); + let saturation = obj.get("saturation")?.as_f64().unwrap_or(0.0); + let value = obj.get("value")?.as_f64().unwrap_or(0.5); + + Some(crate::data::models::outfit_analysis::ColorHSV::new( + hue.clamp(0.0, 1.0), + saturation.clamp(0.0, 1.0), + value.clamp(0.0, 1.0) + )) + } else { + None + } +}