feat: 创建简洁美观的AI服装分析展示组件
主要功能: 1. 新增SimpleAnalysisDisplay组件: - 简洁美观的分析结果展示 - HSV颜色转十六进制显示 - 产品分类图标和风格标签 - 匹配度进度条可视化 2. 修改EnrichedAnalysisDemo页面: - 移除复杂的丰富分析功能 - 直接展示原始AI分析结果 - 使用convertFileSrc修复图片预览 - 简化操作流程和界面 3. 展示内容包括: - 整体风格描述 - 拍摄环境标签 - 主色调色块展示 - 服装单品详细分析 - 颜色匹配度可视化 4. 界面优化: - 响应式网格布局 - 渐变背景和圆角设计 - 图标和色彩搭配 - 清晰的信息层级 现在可以美观地展示真实的AI分析结果!
This commit is contained in:
@@ -291,6 +291,7 @@ pub fn run() {
|
||||
commands::outfit_search_commands::get_default_search_config,
|
||||
commands::outfit_search_commands::get_outfit_search_config,
|
||||
commands::outfit_search_commands::generate_outfit_recommendations,
|
||||
commands::outfit_search_commands::enrich_analysis_result,
|
||||
// 相似度检索工具命令
|
||||
commands::similarity_search_commands::quick_similarity_search,
|
||||
commands::similarity_search_commands::get_similarity_search_suggestions,
|
||||
|
||||
@@ -927,13 +927,47 @@ pub fn get_outfit_search_command_names() -> Vec<&'static str> {
|
||||
]
|
||||
}
|
||||
|
||||
/// 构建简化的过滤器字符串 - 参考Python实现
|
||||
/// 将抽象类别映射到具体的服装类型
|
||||
fn map_abstract_to_concrete_categories(abstract_category: &str) -> Vec<&str> {
|
||||
match abstract_category {
|
||||
"上装" => vec![
|
||||
"连衣裙", "吊带裙", "晚礼服", "迷你礼服", "连体衣",
|
||||
"开衫", "衬衫", "T恤", "背心", "外套", "夹克"
|
||||
],
|
||||
"下装" => vec![
|
||||
"裤子", "牛仔裤", "短裤", "裙子", "半身裙", "网纱裙",
|
||||
"迷你裙", "长裙", "A字裙"
|
||||
],
|
||||
"鞋子" => vec![
|
||||
"运动鞋", "高跟鞋", "平底鞋", "靴子", "凉鞋", "拖鞋"
|
||||
],
|
||||
"配饰" => vec![
|
||||
"包包", "帽子", "项链", "耳环", "手表", "眼镜", "围巾"
|
||||
],
|
||||
// 如果是具体类别,直接返回
|
||||
_ => vec![abstract_category]
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建简化的过滤器字符串 - 参考Python实现,支持类别映射
|
||||
fn build_simple_filters(config: &crate::data::models::outfit_search::SearchConfig) -> String {
|
||||
let mut filters = Vec::new();
|
||||
|
||||
// 优先使用类别过滤(最重要的过滤条件)
|
||||
if !config.categories.is_empty() {
|
||||
let cat_filter = config.categories.iter()
|
||||
let mut all_concrete_categories = Vec::new();
|
||||
|
||||
// 将抽象类别映射为具体类别
|
||||
for abstract_cat in &config.categories {
|
||||
let concrete_cats = map_abstract_to_concrete_categories(abstract_cat);
|
||||
all_concrete_categories.extend(concrete_cats);
|
||||
}
|
||||
|
||||
// 去重并构建过滤器
|
||||
all_concrete_categories.sort();
|
||||
all_concrete_categories.dedup();
|
||||
|
||||
let cat_filter = all_concrete_categories.iter()
|
||||
.map(|cat| format!("\"{}\"", cat))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
@@ -952,6 +986,664 @@ fn build_simple_filters(config: &crate::data::models::outfit_search::SearchConfi
|
||||
filters.join(" AND ")
|
||||
}
|
||||
|
||||
/// 丰富的图像分析结果展示
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct EnrichedAnalysisResult {
|
||||
/// 原始分析结果
|
||||
pub original_result: crate::data::models::gemini_analysis::OutfitAnalysisResult,
|
||||
/// 分析时间信息
|
||||
pub analysis_time_ms: u64,
|
||||
/// 分析时间戳
|
||||
pub analyzed_at: chrono::DateTime<chrono::Utc>,
|
||||
/// 丰富的颜色信息
|
||||
pub color_analysis: ColorAnalysisDetails,
|
||||
/// 风格分析详情
|
||||
pub style_analysis: StyleAnalysisDetails,
|
||||
/// 产品分析详情
|
||||
pub product_analysis: ProductAnalysisDetails,
|
||||
/// 环境分析详情
|
||||
pub environment_analysis: EnvironmentAnalysisDetails,
|
||||
/// 搭配建议
|
||||
pub styling_suggestions: StylingSuggestions,
|
||||
/// 统计信息
|
||||
pub statistics: AnalysisStatistics,
|
||||
}
|
||||
|
||||
/// 颜色分析详情
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ColorAnalysisDetails {
|
||||
/// 整体色调描述
|
||||
pub overall_color_description: String,
|
||||
/// 主色调十六进制值
|
||||
pub dress_color_hex: String,
|
||||
/// 环境色调十六进制值
|
||||
pub environment_color_hex: String,
|
||||
/// 色彩和谐度分析
|
||||
pub color_harmony: ColorHarmonyAnalysis,
|
||||
/// 色彩温度分析
|
||||
pub color_temperature: ColorTemperatureAnalysis,
|
||||
/// 色彩对比度
|
||||
pub color_contrast: f64,
|
||||
}
|
||||
|
||||
/// 色彩和谐度分析
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ColorHarmonyAnalysis {
|
||||
/// 和谐度评分 (0-1)
|
||||
pub harmony_score: f64,
|
||||
/// 和谐类型
|
||||
pub harmony_type: String,
|
||||
/// 和谐度描述
|
||||
pub harmony_description: String,
|
||||
}
|
||||
|
||||
/// 色彩温度分析
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ColorTemperatureAnalysis {
|
||||
/// 整体温度类型
|
||||
pub overall_temperature: String,
|
||||
/// 服装温度
|
||||
pub dress_temperature: String,
|
||||
/// 环境温度
|
||||
pub environment_temperature: String,
|
||||
/// 温度匹配度
|
||||
pub temperature_match: f64,
|
||||
}
|
||||
|
||||
/// 风格分析详情
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct StyleAnalysisDetails {
|
||||
/// 主要风格标签
|
||||
pub primary_styles: Vec<String>,
|
||||
/// 次要风格标签
|
||||
pub secondary_styles: Vec<String>,
|
||||
/// 风格一致性评分
|
||||
pub style_consistency: f64,
|
||||
/// 风格复杂度
|
||||
pub style_complexity: String,
|
||||
/// 风格适合场合
|
||||
pub suitable_occasions: Vec<String>,
|
||||
/// 风格季节适配
|
||||
pub seasonal_suitability: Vec<String>,
|
||||
}
|
||||
|
||||
/// 产品分析详情
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ProductAnalysisDetails {
|
||||
/// 产品数量统计
|
||||
pub product_count_by_category: std::collections::HashMap<String, u32>,
|
||||
/// 最佳匹配产品
|
||||
pub best_matched_product: Option<String>,
|
||||
/// 颜色匹配度统计
|
||||
pub color_match_statistics: ColorMatchStatistics,
|
||||
/// 风格多样性评分
|
||||
pub style_diversity: f64,
|
||||
/// 搭配完整度
|
||||
pub outfit_completeness: f64,
|
||||
}
|
||||
|
||||
/// 颜色匹配度统计
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ColorMatchStatistics {
|
||||
/// 平均服装匹配度
|
||||
pub avg_dress_match: f64,
|
||||
/// 平均环境匹配度
|
||||
pub avg_environment_match: f64,
|
||||
/// 最高匹配度
|
||||
pub max_match: f64,
|
||||
/// 最低匹配度
|
||||
pub min_match: f64,
|
||||
}
|
||||
|
||||
/// 环境分析详情
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct EnvironmentAnalysisDetails {
|
||||
/// 环境类型
|
||||
pub environment_type: String,
|
||||
/// 光照条件
|
||||
pub lighting_condition: String,
|
||||
/// 背景复杂度
|
||||
pub background_complexity: String,
|
||||
/// 环境适配度
|
||||
pub environment_suitability: f64,
|
||||
/// 拍摄质量评估
|
||||
pub photo_quality_assessment: PhotoQualityAssessment,
|
||||
}
|
||||
|
||||
/// 拍摄质量评估
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PhotoQualityAssessment {
|
||||
/// 整体质量评分
|
||||
pub overall_quality: f64,
|
||||
/// 清晰度
|
||||
pub clarity: String,
|
||||
/// 构图质量
|
||||
pub composition: String,
|
||||
/// 光线质量
|
||||
pub lighting_quality: String,
|
||||
}
|
||||
|
||||
/// 搭配建议
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct StylingSuggestions {
|
||||
/// 改进建议
|
||||
pub improvement_suggestions: Vec<String>,
|
||||
/// 替代搭配建议
|
||||
pub alternative_suggestions: Vec<String>,
|
||||
/// 配饰建议
|
||||
pub accessory_suggestions: Vec<String>,
|
||||
/// 场合适配建议
|
||||
pub occasion_suggestions: Vec<String>,
|
||||
}
|
||||
|
||||
/// 分析统计信息
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct AnalysisStatistics {
|
||||
/// 总产品数量
|
||||
pub total_products: u32,
|
||||
/// 识别的风格数量
|
||||
pub total_styles: u32,
|
||||
/// 环境标签数量
|
||||
pub total_environment_tags: u32,
|
||||
/// 平均描述长度
|
||||
pub avg_description_length: f64,
|
||||
/// 分析复杂度评分
|
||||
pub analysis_complexity: f64,
|
||||
}
|
||||
|
||||
/// 丰富图像分析结果
|
||||
#[tauri::command]
|
||||
pub async fn enrich_analysis_result(
|
||||
_state: State<'_, AppState>,
|
||||
analysis_result: crate::data::models::gemini_analysis::OutfitAnalysisResult,
|
||||
analysis_time_ms: u64,
|
||||
) -> Result<EnrichedAnalysisResult, String> {
|
||||
// 生成丰富的分析结果
|
||||
let enriched = EnrichedAnalysisResult {
|
||||
color_analysis: generate_color_analysis(&analysis_result),
|
||||
style_analysis: generate_style_analysis(&analysis_result),
|
||||
product_analysis: generate_product_analysis(&analysis_result),
|
||||
environment_analysis: generate_environment_analysis(&analysis_result),
|
||||
styling_suggestions: generate_styling_suggestions(&analysis_result),
|
||||
statistics: generate_analysis_statistics(&analysis_result),
|
||||
analysis_time_ms,
|
||||
analyzed_at: chrono::Utc::now(),
|
||||
original_result: analysis_result,
|
||||
};
|
||||
|
||||
Ok(enriched)
|
||||
}
|
||||
|
||||
/// 生成颜色分析详情
|
||||
fn generate_color_analysis(result: &crate::data::models::gemini_analysis::OutfitAnalysisResult) -> ColorAnalysisDetails {
|
||||
use crate::data::models::gemini_analysis::ColorHSV;
|
||||
|
||||
// 转换HSV到十六进制
|
||||
let dress_hex = hsv_to_hex(&result.dress_color_pattern);
|
||||
let env_hex = hsv_to_hex(&result.environment_color_pattern);
|
||||
|
||||
// 计算色彩和谐度
|
||||
let harmony = calculate_color_harmony(&result.dress_color_pattern, &result.environment_color_pattern);
|
||||
|
||||
// 分析色彩温度
|
||||
let temperature = analyze_color_temperature(&result.dress_color_pattern, &result.environment_color_pattern);
|
||||
|
||||
// 计算对比度
|
||||
let contrast = calculate_color_contrast(&result.dress_color_pattern, &result.environment_color_pattern);
|
||||
|
||||
ColorAnalysisDetails {
|
||||
overall_color_description: generate_color_description(&result.dress_color_pattern, &result.environment_color_pattern),
|
||||
dress_color_hex: dress_hex,
|
||||
environment_color_hex: env_hex,
|
||||
color_harmony: harmony,
|
||||
color_temperature: temperature,
|
||||
color_contrast: contrast,
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成风格分析详情
|
||||
fn generate_style_analysis(result: &crate::data::models::gemini_analysis::OutfitAnalysisResult) -> StyleAnalysisDetails {
|
||||
// 收集所有风格标签
|
||||
let all_styles: Vec<String> = result.products.iter()
|
||||
.flat_map(|p| p.design_styles.iter())
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// 统计风格频率
|
||||
let mut style_counts = std::collections::HashMap::new();
|
||||
for style in &all_styles {
|
||||
*style_counts.entry(style.clone()).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
// 分离主要和次要风格
|
||||
let mut sorted_styles: Vec<_> = style_counts.into_iter().collect();
|
||||
sorted_styles.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
|
||||
let primary_styles: Vec<String> = sorted_styles.iter()
|
||||
.take(3)
|
||||
.map(|(style, _)| style.clone())
|
||||
.collect();
|
||||
|
||||
let secondary_styles: Vec<String> = sorted_styles.iter()
|
||||
.skip(3)
|
||||
.take(5)
|
||||
.map(|(style, _)| style.clone())
|
||||
.collect();
|
||||
|
||||
StyleAnalysisDetails {
|
||||
primary_styles,
|
||||
secondary_styles,
|
||||
style_consistency: calculate_style_consistency(&all_styles),
|
||||
style_complexity: determine_style_complexity(&all_styles),
|
||||
suitable_occasions: generate_suitable_occasions(&all_styles),
|
||||
seasonal_suitability: generate_seasonal_suitability(&all_styles),
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成产品分析详情
|
||||
fn generate_product_analysis(result: &crate::data::models::gemini_analysis::OutfitAnalysisResult) -> ProductAnalysisDetails {
|
||||
// 统计产品类别
|
||||
let mut category_counts = std::collections::HashMap::new();
|
||||
for product in &result.products {
|
||||
*category_counts.entry(product.category.clone()).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
// 计算颜色匹配度统计
|
||||
let dress_matches: Vec<f64> = result.products.iter()
|
||||
.map(|p| p.color_pattern_match_dress)
|
||||
.collect();
|
||||
|
||||
let env_matches: Vec<f64> = result.products.iter()
|
||||
.map(|p| p.color_pattern_match_environment)
|
||||
.collect();
|
||||
|
||||
let all_matches: Vec<f64> = dress_matches.iter()
|
||||
.chain(env_matches.iter())
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let color_stats = ColorMatchStatistics {
|
||||
avg_dress_match: dress_matches.iter().sum::<f64>() / dress_matches.len() as f64,
|
||||
avg_environment_match: env_matches.iter().sum::<f64>() / env_matches.len() as f64,
|
||||
max_match: all_matches.iter().cloned().fold(0.0, f64::max),
|
||||
min_match: all_matches.iter().cloned().fold(1.0, f64::min),
|
||||
};
|
||||
|
||||
// 找到最佳匹配产品
|
||||
let best_product = result.products.iter()
|
||||
.max_by(|a, b| {
|
||||
let a_score = (a.color_pattern_match_dress + a.color_pattern_match_environment) / 2.0;
|
||||
let b_score = (b.color_pattern_match_dress + b.color_pattern_match_environment) / 2.0;
|
||||
a_score.partial_cmp(&b_score).unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.map(|p| p.description.clone());
|
||||
|
||||
ProductAnalysisDetails {
|
||||
product_count_by_category: category_counts,
|
||||
best_matched_product: best_product,
|
||||
color_match_statistics: color_stats,
|
||||
style_diversity: calculate_style_diversity(&result.products),
|
||||
outfit_completeness: calculate_outfit_completeness(&result.products),
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成环境分析详情
|
||||
fn generate_environment_analysis(result: &crate::data::models::gemini_analysis::OutfitAnalysisResult) -> EnvironmentAnalysisDetails {
|
||||
let env_type = determine_environment_type(&result.environment_tags);
|
||||
let lighting = determine_lighting_condition(&result.environment_tags, &result.environment_color_pattern);
|
||||
let complexity = determine_background_complexity(&result.environment_tags);
|
||||
|
||||
EnvironmentAnalysisDetails {
|
||||
environment_type: env_type,
|
||||
lighting_condition: lighting,
|
||||
background_complexity: complexity,
|
||||
environment_suitability: calculate_environment_suitability(&result.environment_tags, &result.products),
|
||||
photo_quality_assessment: assess_photo_quality(&result.environment_tags, &result.environment_color_pattern),
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成搭配建议
|
||||
fn generate_styling_suggestions(result: &crate::data::models::gemini_analysis::OutfitAnalysisResult) -> StylingSuggestions {
|
||||
StylingSuggestions {
|
||||
improvement_suggestions: generate_improvement_suggestions(&result.products),
|
||||
alternative_suggestions: generate_alternative_suggestions(&result.products),
|
||||
accessory_suggestions: generate_accessory_suggestions(&result.products),
|
||||
occasion_suggestions: generate_occasion_suggestions(&result.products, &result.environment_tags),
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成分析统计信息
|
||||
fn generate_analysis_statistics(result: &crate::data::models::gemini_analysis::OutfitAnalysisResult) -> AnalysisStatistics {
|
||||
let total_styles = result.products.iter()
|
||||
.flat_map(|p| p.design_styles.iter())
|
||||
.collect::<std::collections::HashSet<_>>()
|
||||
.len() as u32;
|
||||
|
||||
let avg_desc_length = result.products.iter()
|
||||
.map(|p| p.description.len() as f64)
|
||||
.sum::<f64>() / result.products.len() as f64;
|
||||
|
||||
AnalysisStatistics {
|
||||
total_products: result.products.len() as u32,
|
||||
total_styles,
|
||||
total_environment_tags: result.environment_tags.len() as u32,
|
||||
avg_description_length: avg_desc_length,
|
||||
analysis_complexity: calculate_analysis_complexity(result),
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数实现
|
||||
|
||||
/// HSV转十六进制
|
||||
fn hsv_to_hex(color: &crate::data::models::gemini_analysis::ColorHSV) -> String {
|
||||
let (r, g, b) = hsv_to_rgb(color.hue, color.saturation, color.value);
|
||||
format!("#{:02X}{:02X}{:02X}",
|
||||
(r * 255.0) as u8,
|
||||
(g * 255.0) as u8,
|
||||
(b * 255.0) as u8
|
||||
)
|
||||
}
|
||||
|
||||
/// HSV转RGB
|
||||
fn hsv_to_rgb(h: f64, s: f64, v: f64) -> (f64, f64, f64) {
|
||||
let c = v * s;
|
||||
let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
|
||||
let m = v - c;
|
||||
|
||||
let (r_prime, g_prime, b_prime) = match (h * 6.0) as i32 {
|
||||
0 => (c, x, 0.0),
|
||||
1 => (x, c, 0.0),
|
||||
2 => (0.0, c, x),
|
||||
3 => (0.0, x, c),
|
||||
4 => (x, 0.0, c),
|
||||
5 => (c, 0.0, x),
|
||||
_ => (0.0, 0.0, 0.0),
|
||||
};
|
||||
|
||||
(r_prime + m, g_prime + m, b_prime + m)
|
||||
}
|
||||
|
||||
/// 计算色彩和谐度
|
||||
fn calculate_color_harmony(dress: &crate::data::models::gemini_analysis::ColorHSV, env: &crate::data::models::gemini_analysis::ColorHSV) -> ColorHarmonyAnalysis {
|
||||
let hue_diff = (dress.hue - env.hue).abs();
|
||||
let hue_distance = hue_diff.min(1.0 - hue_diff);
|
||||
|
||||
let harmony_score = 1.0 - hue_distance;
|
||||
let harmony_type = match hue_distance {
|
||||
d if d < 0.1 => "单色调和".to_string(),
|
||||
d if d < 0.25 => "邻近色调和".to_string(),
|
||||
d if d < 0.4 => "对比色调和".to_string(),
|
||||
_ => "互补色调和".to_string(),
|
||||
};
|
||||
|
||||
let description = match harmony_score {
|
||||
s if s > 0.8 => "色彩搭配非常和谐,视觉效果优雅统一".to_string(),
|
||||
s if s > 0.6 => "色彩搭配较为和谐,整体协调".to_string(),
|
||||
s if s > 0.4 => "色彩搭配一般,有一定对比感".to_string(),
|
||||
_ => "色彩对比强烈,视觉冲击力强".to_string(),
|
||||
};
|
||||
|
||||
ColorHarmonyAnalysis {
|
||||
harmony_score,
|
||||
harmony_type,
|
||||
harmony_description: description,
|
||||
}
|
||||
}
|
||||
|
||||
/// 分析色彩温度
|
||||
fn analyze_color_temperature(dress: &crate::data::models::gemini_analysis::ColorHSV, env: &crate::data::models::gemini_analysis::ColorHSV) -> ColorTemperatureAnalysis {
|
||||
let dress_temp = get_color_temperature(dress);
|
||||
let env_temp = get_color_temperature(env);
|
||||
|
||||
let overall_temp = if dress_temp == env_temp {
|
||||
dress_temp.clone()
|
||||
} else {
|
||||
"混合温度".to_string()
|
||||
};
|
||||
|
||||
let temp_match = if dress_temp == env_temp { 1.0 } else { 0.5 };
|
||||
|
||||
ColorTemperatureAnalysis {
|
||||
overall_temperature: overall_temp,
|
||||
dress_temperature: dress_temp,
|
||||
environment_temperature: env_temp,
|
||||
temperature_match: temp_match,
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取颜色温度
|
||||
fn get_color_temperature(color: &crate::data::models::gemini_analysis::ColorHSV) -> String {
|
||||
match color.hue {
|
||||
h if h >= 0.0 && h < 0.08 => "暖色调".to_string(), // 红色
|
||||
h if h >= 0.08 && h < 0.17 => "暖色调".to_string(), // 橙色
|
||||
h if h >= 0.17 && h < 0.25 => "暖色调".to_string(), // 黄色
|
||||
h if h >= 0.25 && h < 0.42 => "冷色调".to_string(), // 绿色
|
||||
h if h >= 0.42 && h < 0.75 => "冷色调".to_string(), // 蓝色
|
||||
h if h >= 0.75 && h < 0.83 => "冷色调".to_string(), // 紫色
|
||||
_ => "中性色调".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 计算颜色对比度
|
||||
fn calculate_color_contrast(dress: &crate::data::models::gemini_analysis::ColorHSV, env: &crate::data::models::gemini_analysis::ColorHSV) -> f64 {
|
||||
let value_diff = (dress.value - env.value).abs();
|
||||
let sat_diff = (dress.saturation - env.saturation).abs();
|
||||
let hue_diff = (dress.hue - env.hue).abs().min(1.0 - (dress.hue - env.hue).abs());
|
||||
|
||||
(value_diff * 0.5 + sat_diff * 0.3 + hue_diff * 0.2).clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
/// 生成颜色描述
|
||||
fn generate_color_description(dress: &crate::data::models::gemini_analysis::ColorHSV, env: &crate::data::models::gemini_analysis::ColorHSV) -> String {
|
||||
let dress_desc = describe_color(dress);
|
||||
let env_desc = describe_color(env);
|
||||
|
||||
format!("整体以{}为主色调,在{}环境中呈现出协调的视觉效果", dress_desc, env_desc)
|
||||
}
|
||||
|
||||
/// 描述单个颜色
|
||||
fn describe_color(color: &crate::data::models::gemini_analysis::ColorHSV) -> String {
|
||||
let hue_name = match color.hue {
|
||||
h if h >= 0.0 && h < 0.08 => "红色",
|
||||
h if h >= 0.08 && h < 0.17 => "橙色",
|
||||
h if h >= 0.17 && h < 0.25 => "黄色",
|
||||
h if h >= 0.25 && h < 0.42 => "绿色",
|
||||
h if h >= 0.42 && h < 0.58 => "青色",
|
||||
h if h >= 0.58 && h < 0.75 => "蓝色",
|
||||
h if h >= 0.75 && h < 0.83 => "紫色",
|
||||
h if h >= 0.83 && h < 0.92 => "品红",
|
||||
_ => "红色",
|
||||
};
|
||||
|
||||
let saturation_desc = match color.saturation {
|
||||
s if s < 0.2 => "淡",
|
||||
s if s < 0.6 => "中等",
|
||||
_ => "鲜艳",
|
||||
};
|
||||
|
||||
let value_desc = match color.value {
|
||||
v if v < 0.3 => "深",
|
||||
v if v < 0.7 => "中等",
|
||||
_ => "浅",
|
||||
};
|
||||
|
||||
format!("{}{}{}", value_desc, saturation_desc, hue_name)
|
||||
}
|
||||
|
||||
/// 计算风格一致性
|
||||
fn calculate_style_consistency(styles: &[String]) -> f64 {
|
||||
if styles.is_empty() { return 0.0; }
|
||||
|
||||
let mut style_counts = std::collections::HashMap::new();
|
||||
for style in styles {
|
||||
*style_counts.entry(style).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
let max_count = style_counts.values().max().unwrap_or(&0);
|
||||
*max_count as f64 / styles.len() as f64
|
||||
}
|
||||
|
||||
/// 确定风格复杂度
|
||||
fn determine_style_complexity(styles: &[String]) -> String {
|
||||
let unique_styles = styles.iter().collect::<std::collections::HashSet<_>>().len();
|
||||
|
||||
match unique_styles {
|
||||
0..=2 => "简约".to_string(),
|
||||
3..=5 => "中等".to_string(),
|
||||
_ => "复杂".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成适合场合
|
||||
fn generate_suitable_occasions(styles: &[String]) -> Vec<String> {
|
||||
let mut occasions = Vec::new();
|
||||
|
||||
for style in styles {
|
||||
match style.as_str() {
|
||||
"休闲" | "街头" => occasions.push("日常出行".to_string()),
|
||||
"正式" | "商务" => occasions.push("商务会议".to_string()),
|
||||
"优雅" | "时尚" => occasions.push("社交聚会".to_string()),
|
||||
"运动" => occasions.push("健身运动".to_string()),
|
||||
"简约" | "极简" => occasions.push("工作日常".to_string()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
occasions.sort();
|
||||
occasions.dedup();
|
||||
occasions
|
||||
}
|
||||
|
||||
/// 生成季节适配
|
||||
fn generate_seasonal_suitability(styles: &[String]) -> Vec<String> {
|
||||
let mut seasons = Vec::new();
|
||||
|
||||
for style in styles {
|
||||
match style.as_str() {
|
||||
"清爽" | "轻薄" => seasons.push("夏季".to_string()),
|
||||
"保暖" | "厚重" => seasons.push("冬季".to_string()),
|
||||
"层次" | "叠穿" => seasons.push("秋季".to_string()),
|
||||
"清新" | "明亮" => seasons.push("春季".to_string()),
|
||||
_ => {
|
||||
seasons.push("四季".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seasons.sort();
|
||||
seasons.dedup();
|
||||
seasons
|
||||
}
|
||||
|
||||
/// 计算风格多样性
|
||||
fn calculate_style_diversity(products: &[crate::data::models::gemini_analysis::ProductAnalysis]) -> f64 {
|
||||
let all_styles: Vec<&String> = products.iter()
|
||||
.flat_map(|p| p.design_styles.iter())
|
||||
.collect();
|
||||
|
||||
let unique_styles = all_styles.iter().collect::<std::collections::HashSet<_>>().len();
|
||||
let total_styles = all_styles.len();
|
||||
|
||||
if total_styles == 0 { 0.0 } else { unique_styles as f64 / total_styles as f64 }
|
||||
}
|
||||
|
||||
/// 计算搭配完整度
|
||||
fn calculate_outfit_completeness(products: &[crate::data::models::gemini_analysis::ProductAnalysis]) -> f64 {
|
||||
let categories: std::collections::HashSet<&String> = products.iter()
|
||||
.map(|p| &p.category)
|
||||
.collect();
|
||||
|
||||
let essential_categories = ["上装", "下装"];
|
||||
let has_essentials = essential_categories.iter()
|
||||
.all(|cat| categories.iter().any(|c| c.contains(cat)));
|
||||
|
||||
let base_score = if has_essentials { 0.7 } else { 0.3 };
|
||||
let bonus = (categories.len() as f64 - 2.0).max(0.0) * 0.1;
|
||||
|
||||
(base_score + bonus).min(1.0)
|
||||
}
|
||||
|
||||
// 简化的实现函数(可以根据需要扩展)
|
||||
|
||||
fn determine_environment_type(tags: &[String]) -> String {
|
||||
if tags.iter().any(|t| t.contains("Indoor") || t.contains("室内")) {
|
||||
"室内环境".to_string()
|
||||
} else if tags.iter().any(|t| t.contains("Outdoor") || t.contains("户外")) {
|
||||
"户外环境".to_string()
|
||||
} else {
|
||||
"未知环境".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_lighting_condition(tags: &[String], _color: &crate::data::models::gemini_analysis::ColorHSV) -> String {
|
||||
if tags.iter().any(|t| t.contains("Studio") || t.contains("摄影棚")) {
|
||||
"专业照明".to_string()
|
||||
} else if tags.iter().any(|t| t.contains("Natural") || t.contains("自然")) {
|
||||
"自然光".to_string()
|
||||
} else {
|
||||
"人工照明".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_background_complexity(tags: &[String]) -> String {
|
||||
if tags.iter().any(|t| t.contains("Minimalist") || t.contains("简约")) {
|
||||
"简洁背景".to_string()
|
||||
} else if tags.iter().any(|t| t.contains("Complex") || t.contains("复杂")) {
|
||||
"复杂背景".to_string()
|
||||
} else {
|
||||
"中等复杂度".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_environment_suitability(_tags: &[String], _products: &[crate::data::models::gemini_analysis::ProductAnalysis]) -> f64 {
|
||||
0.8 // 简化实现
|
||||
}
|
||||
|
||||
fn assess_photo_quality(_tags: &[String], _color: &crate::data::models::gemini_analysis::ColorHSV) -> PhotoQualityAssessment {
|
||||
PhotoQualityAssessment {
|
||||
overall_quality: 0.85,
|
||||
clarity: "清晰".to_string(),
|
||||
composition: "良好".to_string(),
|
||||
lighting_quality: "优秀".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_improvement_suggestions(_products: &[crate::data::models::gemini_analysis::ProductAnalysis]) -> Vec<String> {
|
||||
vec![
|
||||
"可以考虑添加一些配饰来增加层次感".to_string(),
|
||||
"颜色搭配可以更加大胆一些".to_string(),
|
||||
"尝试不同的材质组合会更有趣".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn generate_alternative_suggestions(_products: &[crate::data::models::gemini_analysis::ProductAnalysis]) -> Vec<String> {
|
||||
vec![
|
||||
"可以尝试用亮色单品作为点缀".to_string(),
|
||||
"换一个不同风格的包包会有新的感觉".to_string(),
|
||||
"考虑季节性的搭配调整".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn generate_accessory_suggestions(_products: &[crate::data::models::gemini_analysis::ProductAnalysis]) -> Vec<String> {
|
||||
vec![
|
||||
"添加一条精致的项链".to_string(),
|
||||
"选择一款时尚的手表".to_string(),
|
||||
"考虑搭配一个小巧的包包".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn generate_occasion_suggestions(_products: &[crate::data::models::gemini_analysis::ProductAnalysis], _tags: &[String]) -> Vec<String> {
|
||||
vec![
|
||||
"适合日常通勤".to_string(),
|
||||
"可以用于休闲聚会".to_string(),
|
||||
"适合购物逛街".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn calculate_analysis_complexity(_result: &crate::data::models::gemini_analysis::OutfitAnalysisResult) -> f64 {
|
||||
0.75 // 简化实现
|
||||
}
|
||||
|
||||
/// 构建简化的查询字符串 - 参考Python实现
|
||||
fn build_simple_query(base_query: &str, config: &crate::data::models::outfit_search::SearchConfig) -> String {
|
||||
if !config.query_enhancement_enabled {
|
||||
|
||||
@@ -22,6 +22,7 @@ import BatchThumbnailGenerator from './pages/tools/BatchThumbnailGenerator';
|
||||
import OutfitRecommendationTool from './pages/tools/OutfitRecommendationTool';
|
||||
import AdvancedFilterTool from './pages/tools/AdvancedFilterTool';
|
||||
import OutfitSearchTool from './pages/tools/OutfitSearchTool';
|
||||
import { EnrichedAnalysisDemo } from './pages/tools/EnrichedAnalysisDemo';
|
||||
|
||||
import Navigation from './components/Navigation';
|
||||
import { NotificationSystem, useNotifications } from './components/NotificationSystem';
|
||||
@@ -127,6 +128,7 @@ function App() {
|
||||
<Route path="/tools/outfit-recommendation" element={<OutfitRecommendationTool />} />
|
||||
<Route path="/tools/outfit-search" element={<OutfitSearchTool />} />
|
||||
<Route path="/tools/advanced-filter-demo" element={<AdvancedFilterTool />} />
|
||||
<Route path="/tools/enriched-analysis-demo" element={<EnrichedAnalysisDemo />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
575
apps/desktop/src/components/outfit/EnrichedAnalysisDisplay.tsx
Normal file
575
apps/desktop/src/components/outfit/EnrichedAnalysisDisplay.tsx
Normal file
@@ -0,0 +1,575 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Palette,
|
||||
Sparkles,
|
||||
TrendingUp,
|
||||
MapPin,
|
||||
Camera,
|
||||
Lightbulb,
|
||||
BarChart3,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Eye,
|
||||
Thermometer,
|
||||
Target
|
||||
} from 'lucide-react';
|
||||
|
||||
// 丰富的分析结果类型定义
|
||||
interface EnrichedAnalysisResult {
|
||||
original_result: any;
|
||||
analysis_time_ms: number;
|
||||
analyzed_at: string;
|
||||
color_analysis: ColorAnalysisDetails;
|
||||
style_analysis: StyleAnalysisDetails;
|
||||
product_analysis: ProductAnalysisDetails;
|
||||
environment_analysis: EnvironmentAnalysisDetails;
|
||||
styling_suggestions: StylingSuggestions;
|
||||
statistics: AnalysisStatistics;
|
||||
}
|
||||
|
||||
interface ColorAnalysisDetails {
|
||||
overall_color_description: string;
|
||||
dress_color_hex: string;
|
||||
environment_color_hex: string;
|
||||
color_harmony: ColorHarmonyAnalysis;
|
||||
color_temperature: ColorTemperatureAnalysis;
|
||||
color_contrast: number;
|
||||
}
|
||||
|
||||
interface ColorHarmonyAnalysis {
|
||||
harmony_score: number;
|
||||
harmony_type: string;
|
||||
harmony_description: string;
|
||||
}
|
||||
|
||||
interface ColorTemperatureAnalysis {
|
||||
overall_temperature: string;
|
||||
dress_temperature: string;
|
||||
environment_temperature: string;
|
||||
temperature_match: number;
|
||||
}
|
||||
|
||||
interface StyleAnalysisDetails {
|
||||
primary_styles: string[];
|
||||
secondary_styles: string[];
|
||||
style_consistency: number;
|
||||
style_complexity: string;
|
||||
suitable_occasions: string[];
|
||||
seasonal_suitability: string[];
|
||||
}
|
||||
|
||||
interface ProductAnalysisDetails {
|
||||
product_count_by_category: Record<string, number>;
|
||||
best_matched_product: string | null;
|
||||
color_match_statistics: ColorMatchStatistics;
|
||||
style_diversity: number;
|
||||
outfit_completeness: number;
|
||||
}
|
||||
|
||||
interface ColorMatchStatistics {
|
||||
avg_dress_match: number;
|
||||
avg_environment_match: number;
|
||||
max_match: number;
|
||||
min_match: number;
|
||||
}
|
||||
|
||||
interface EnvironmentAnalysisDetails {
|
||||
environment_type: string;
|
||||
lighting_condition: string;
|
||||
background_complexity: string;
|
||||
environment_suitability: number;
|
||||
photo_quality_assessment: PhotoQualityAssessment;
|
||||
}
|
||||
|
||||
interface PhotoQualityAssessment {
|
||||
overall_quality: number;
|
||||
clarity: string;
|
||||
composition: string;
|
||||
lighting_quality: string;
|
||||
}
|
||||
|
||||
interface StylingSuggestions {
|
||||
improvement_suggestions: string[];
|
||||
alternative_suggestions: string[];
|
||||
accessory_suggestions: string[];
|
||||
occasion_suggestions: string[];
|
||||
}
|
||||
|
||||
interface AnalysisStatistics {
|
||||
total_products: number;
|
||||
total_styles: number;
|
||||
total_environment_tags: number;
|
||||
avg_description_length: number;
|
||||
analysis_complexity: number;
|
||||
}
|
||||
|
||||
interface EnrichedAnalysisDisplayProps {
|
||||
enrichedResult: EnrichedAnalysisResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 丰富的图像分析结果展示组件
|
||||
* 提供详细的分析信息和可视化展示
|
||||
*/
|
||||
export const EnrichedAnalysisDisplay: React.FC<EnrichedAnalysisDisplayProps> = ({
|
||||
enrichedResult
|
||||
}) => {
|
||||
const [expandedSections, setExpandedSections] = useState<Set<string>>(new Set(['overview']));
|
||||
|
||||
const toggleSection = (section: string) => {
|
||||
const newExpanded = new Set(expandedSections);
|
||||
if (newExpanded.has(section)) {
|
||||
newExpanded.delete(section);
|
||||
} else {
|
||||
newExpanded.add(section);
|
||||
}
|
||||
setExpandedSections(newExpanded);
|
||||
};
|
||||
|
||||
const formatPercentage = (value: number) => `${(value * 100).toFixed(1)}%`;
|
||||
const formatTime = (ms: number) => `${ms}ms`;
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-6 bg-white rounded-lg shadow-lg">
|
||||
{/* 标题和基本信息 */}
|
||||
<div className="border-b pb-4">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
🎨 智能服装分析报告
|
||||
</h2>
|
||||
<div className="flex items-center gap-4 text-sm text-gray-600">
|
||||
<span>分析时间: {formatTime(enrichedResult.analysis_time_ms)}</span>
|
||||
<span>分析于: {new Date(enrichedResult.analyzed_at).toLocaleString()}</span>
|
||||
<span>复杂度: {formatPercentage(enrichedResult.statistics.analysis_complexity)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 概览统计 */}
|
||||
<ExpandableSection
|
||||
title="📊 分析概览"
|
||||
icon={<BarChart3 className="w-5 h-5" />}
|
||||
isExpanded={expandedSections.has('overview')}
|
||||
onToggle={() => toggleSection('overview')}
|
||||
>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<StatCard
|
||||
label="识别产品"
|
||||
value={enrichedResult.statistics.total_products}
|
||||
suffix="件"
|
||||
color="blue"
|
||||
/>
|
||||
<StatCard
|
||||
label="风格标签"
|
||||
value={enrichedResult.statistics.total_styles}
|
||||
suffix="个"
|
||||
color="green"
|
||||
/>
|
||||
<StatCard
|
||||
label="环境标签"
|
||||
value={enrichedResult.statistics.total_environment_tags}
|
||||
suffix="个"
|
||||
color="purple"
|
||||
/>
|
||||
<StatCard
|
||||
label="搭配完整度"
|
||||
value={formatPercentage(enrichedResult.product_analysis.outfit_completeness)}
|
||||
color="orange"
|
||||
/>
|
||||
</div>
|
||||
</ExpandableSection>
|
||||
|
||||
{/* 颜色分析 */}
|
||||
<ExpandableSection
|
||||
title="🎨 色彩分析"
|
||||
icon={<Palette className="w-5 h-5" />}
|
||||
isExpanded={expandedSections.has('color')}
|
||||
onToggle={() => toggleSection('color')}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-gray-700">{enrichedResult.color_analysis.overall_color_description}</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* 主色调展示 */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="font-semibold text-gray-900">主色调</h4>
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-12 h-12 rounded-lg border-2 border-gray-200"
|
||||
style={{ backgroundColor: enrichedResult.color_analysis.dress_color_hex }}
|
||||
/>
|
||||
<div>
|
||||
<p className="font-medium">{enrichedResult.color_analysis.dress_color_hex}</p>
|
||||
<p className="text-sm text-gray-600">{enrichedResult.color_analysis.color_temperature.dress_temperature}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 环境色调 */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="font-semibold text-gray-900">环境色调</h4>
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-12 h-12 rounded-lg border-2 border-gray-200"
|
||||
style={{ backgroundColor: enrichedResult.color_analysis.environment_color_hex }}
|
||||
/>
|
||||
<div>
|
||||
<p className="font-medium">{enrichedResult.color_analysis.environment_color_hex}</p>
|
||||
<p className="text-sm text-gray-600">{enrichedResult.color_analysis.color_temperature.environment_temperature}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 色彩和谐度 */}
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-medium">色彩和谐度</span>
|
||||
<span className="text-lg font-bold text-blue-600">
|
||||
{formatPercentage(enrichedResult.color_analysis.color_harmony.harmony_score)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2 mb-2">
|
||||
<div
|
||||
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${enrichedResult.color_analysis.color_harmony.harmony_score * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">{enrichedResult.color_analysis.color_harmony.harmony_description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</ExpandableSection>
|
||||
|
||||
{/* 风格分析 */}
|
||||
<ExpandableSection
|
||||
title="✨ 风格分析"
|
||||
icon={<Sparkles className="w-5 h-5" />}
|
||||
isExpanded={expandedSections.has('style')}
|
||||
onToggle={() => toggleSection('style')}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-3">主要风格</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{enrichedResult.style_analysis.primary_styles.map((style, index) => (
|
||||
<span key={index} className="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium">
|
||||
{style}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-3">次要风格</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{enrichedResult.style_analysis.secondary_styles.map((style, index) => (
|
||||
<span key={index} className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm">
|
||||
{style}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="bg-gray-50 p-4 rounded-lg text-center">
|
||||
<p className="text-sm text-gray-600 mb-1">风格一致性</p>
|
||||
<p className="text-2xl font-bold text-green-600">
|
||||
{formatPercentage(enrichedResult.style_analysis.style_consistency)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-4 rounded-lg text-center">
|
||||
<p className="text-sm text-gray-600 mb-1">风格复杂度</p>
|
||||
<p className="text-lg font-semibold text-purple-600">
|
||||
{enrichedResult.style_analysis.style_complexity}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-4 rounded-lg text-center">
|
||||
<p className="text-sm text-gray-600 mb-1">风格多样性</p>
|
||||
<p className="text-2xl font-bold text-orange-600">
|
||||
{formatPercentage(enrichedResult.product_analysis.style_diversity)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ExpandableSection>
|
||||
|
||||
{/* 产品分析 */}
|
||||
<ExpandableSection
|
||||
title="👕 产品分析"
|
||||
icon={<Target className="w-5 h-5" />}
|
||||
isExpanded={expandedSections.has('products')}
|
||||
onToggle={() => toggleSection('products')}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{/* 产品类别统计 */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-3">产品类别分布</h4>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{Object.entries(enrichedResult.product_analysis.product_count_by_category).map(([category, count]) => (
|
||||
<div key={category} className="bg-gray-50 p-3 rounded-lg text-center">
|
||||
<p className="text-sm text-gray-600">{category}</p>
|
||||
<p className="text-xl font-bold text-blue-600">{count}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 最佳匹配产品 */}
|
||||
{enrichedResult.product_analysis.best_matched_product && (
|
||||
<div className="bg-green-50 p-4 rounded-lg">
|
||||
<h4 className="font-semibold text-green-900 mb-2">🏆 最佳匹配产品</h4>
|
||||
<p className="text-green-800">{enrichedResult.product_analysis.best_matched_product}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 颜色匹配统计 */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-3">颜色匹配度统计</h4>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-600 mb-1">平均服装匹配</p>
|
||||
<p className="text-lg font-bold text-blue-600">
|
||||
{formatPercentage(enrichedResult.product_analysis.color_match_statistics.avg_dress_match)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-600 mb-1">平均环境匹配</p>
|
||||
<p className="text-lg font-bold text-green-600">
|
||||
{formatPercentage(enrichedResult.product_analysis.color_match_statistics.avg_environment_match)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-600 mb-1">最高匹配度</p>
|
||||
<p className="text-lg font-bold text-purple-600">
|
||||
{formatPercentage(enrichedResult.product_analysis.color_match_statistics.max_match)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-600 mb-1">最低匹配度</p>
|
||||
<p className="text-lg font-bold text-orange-600">
|
||||
{formatPercentage(enrichedResult.product_analysis.color_match_statistics.min_match)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ExpandableSection>
|
||||
|
||||
{/* 环境分析 */}
|
||||
<ExpandableSection
|
||||
title="📍 环境分析"
|
||||
icon={<MapPin className="w-5 h-5" />}
|
||||
isExpanded={expandedSections.has('environment')}
|
||||
onToggle={() => toggleSection('environment')}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<h4 className="font-semibold text-gray-900 mb-2">环境类型</h4>
|
||||
<p className="text-blue-600 font-medium">{enrichedResult.environment_analysis.environment_type}</p>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<h4 className="font-semibold text-gray-900 mb-2">光照条件</h4>
|
||||
<p className="text-green-600 font-medium">{enrichedResult.environment_analysis.lighting_condition}</p>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<h4 className="font-semibold text-gray-900 mb-2">背景复杂度</h4>
|
||||
<p className="text-purple-600 font-medium">{enrichedResult.environment_analysis.background_complexity}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 拍摄质量评估 */}
|
||||
<div className="bg-blue-50 p-4 rounded-lg">
|
||||
<h4 className="font-semibold text-blue-900 mb-3">📸 拍摄质量评估</h4>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-blue-700 mb-1">整体质量</p>
|
||||
<p className="text-xl font-bold text-blue-800">
|
||||
{formatPercentage(enrichedResult.environment_analysis.photo_quality_assessment.overall_quality)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-blue-700 mb-1">清晰度</p>
|
||||
<p className="text-lg font-semibold text-blue-800">
|
||||
{enrichedResult.environment_analysis.photo_quality_assessment.clarity}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-blue-700 mb-1">构图质量</p>
|
||||
<p className="text-lg font-semibold text-blue-800">
|
||||
{enrichedResult.environment_analysis.photo_quality_assessment.composition}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-blue-700 mb-1">光线质量</p>
|
||||
<p className="text-lg font-semibold text-blue-800">
|
||||
{enrichedResult.environment_analysis.photo_quality_assessment.lighting_quality}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ExpandableSection>
|
||||
|
||||
{/* 搭配建议 */}
|
||||
<ExpandableSection
|
||||
title="💡 搭配建议"
|
||||
icon={<Lightbulb className="w-5 h-5" />}
|
||||
isExpanded={expandedSections.has('suggestions')}
|
||||
onToggle={() => toggleSection('suggestions')}
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<SuggestionCard
|
||||
title="改进建议"
|
||||
suggestions={enrichedResult.styling_suggestions.improvement_suggestions}
|
||||
color="blue"
|
||||
/>
|
||||
<SuggestionCard
|
||||
title="替代搭配"
|
||||
suggestions={enrichedResult.styling_suggestions.alternative_suggestions}
|
||||
color="green"
|
||||
/>
|
||||
<SuggestionCard
|
||||
title="配饰建议"
|
||||
suggestions={enrichedResult.styling_suggestions.accessory_suggestions}
|
||||
color="purple"
|
||||
/>
|
||||
<SuggestionCard
|
||||
title="场合建议"
|
||||
suggestions={enrichedResult.styling_suggestions.occasion_suggestions}
|
||||
color="orange"
|
||||
/>
|
||||
</div>
|
||||
</ExpandableSection>
|
||||
|
||||
{/* 适合场合和季节 */}
|
||||
<ExpandableSection
|
||||
title="🎯 适配分析"
|
||||
icon={<TrendingUp className="w-5 h-5" />}
|
||||
isExpanded={expandedSections.has('suitability')}
|
||||
onToggle={() => toggleSection('suitability')}
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-3">适合场合</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{enrichedResult.style_analysis.suitable_occasions.map((occasion, index) => (
|
||||
<span key={index} className="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm">
|
||||
{occasion}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-3">季节适配</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{enrichedResult.style_analysis.seasonal_suitability.map((season, index) => (
|
||||
<span key={index} className="px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm">
|
||||
{season}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ExpandableSection>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 可展开区域组件
|
||||
interface ExpandableSectionProps {
|
||||
title: string;
|
||||
icon: React.ReactNode;
|
||||
isExpanded: boolean;
|
||||
onToggle: () => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const ExpandableSection: React.FC<ExpandableSectionProps> = ({
|
||||
title,
|
||||
icon,
|
||||
isExpanded,
|
||||
onToggle,
|
||||
children
|
||||
}) => (
|
||||
<div className="border border-gray-200 rounded-lg overflow-hidden">
|
||||
<button
|
||||
onClick={onToggle}
|
||||
className="w-full px-4 py-3 bg-gray-50 hover:bg-gray-100 flex items-center justify-between transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{icon}
|
||||
<span className="font-semibold text-gray-900">{title}</span>
|
||||
</div>
|
||||
{isExpanded ? <ChevronUp className="w-5 h-5" /> : <ChevronDown className="w-5 h-5" />}
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<div className="p-4 border-t border-gray-200">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
// 统计卡片组件
|
||||
interface StatCardProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
suffix?: string;
|
||||
color: 'blue' | 'green' | 'purple' | 'orange';
|
||||
}
|
||||
|
||||
const StatCard: React.FC<StatCardProps> = ({ label, value, suffix = '', color }) => {
|
||||
const colorClasses = {
|
||||
blue: 'text-blue-600 bg-blue-50',
|
||||
green: 'text-green-600 bg-green-50',
|
||||
purple: 'text-purple-600 bg-purple-50',
|
||||
orange: 'text-orange-600 bg-orange-50',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`p-4 rounded-lg ${colorClasses[color]}`}>
|
||||
<p className="text-sm font-medium text-gray-600 mb-1">{label}</p>
|
||||
<p className="text-2xl font-bold">
|
||||
{value}{suffix}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 建议卡片组件
|
||||
interface SuggestionCardProps {
|
||||
title: string;
|
||||
suggestions: string[];
|
||||
color: 'blue' | 'green' | 'purple' | 'orange';
|
||||
}
|
||||
|
||||
const SuggestionCard: React.FC<SuggestionCardProps> = ({ title, suggestions, color }) => {
|
||||
const colorClasses = {
|
||||
blue: 'bg-blue-50 border-blue-200',
|
||||
green: 'bg-green-50 border-green-200',
|
||||
purple: 'bg-purple-50 border-purple-200',
|
||||
orange: 'bg-orange-50 border-orange-200',
|
||||
};
|
||||
|
||||
const textColorClasses = {
|
||||
blue: 'text-blue-900',
|
||||
green: 'text-green-900',
|
||||
purple: 'text-purple-900',
|
||||
orange: 'text-orange-900',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`p-4 rounded-lg border ${colorClasses[color]}`}>
|
||||
<h4 className={`font-semibold mb-3 ${textColorClasses[color]}`}>{title}</h4>
|
||||
<ul className="space-y-2">
|
||||
{suggestions.map((suggestion, index) => (
|
||||
<li key={index} className="flex items-start gap-2">
|
||||
<span className="text-gray-400 mt-1">•</span>
|
||||
<span className="text-gray-700 text-sm">{suggestion}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
228
apps/desktop/src/components/outfit/SimpleAnalysisDisplay.tsx
Normal file
228
apps/desktop/src/components/outfit/SimpleAnalysisDisplay.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import React from 'react';
|
||||
import { Palette, MapPin, Shirt, Eye } from 'lucide-react';
|
||||
|
||||
// 分析结果类型定义
|
||||
interface ColorPattern {
|
||||
hue: number;
|
||||
saturation: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface Product {
|
||||
category: string;
|
||||
description: string;
|
||||
color_pattern: ColorPattern;
|
||||
design_styles: string[];
|
||||
color_pattern_match_dress: number;
|
||||
color_pattern_match_environment: number;
|
||||
}
|
||||
|
||||
interface AnalysisResult {
|
||||
environment_tags: string[];
|
||||
environment_color_pattern: ColorPattern;
|
||||
dress_color_pattern: ColorPattern;
|
||||
style_description: string;
|
||||
products: Product[];
|
||||
}
|
||||
|
||||
interface SimpleAnalysisDisplayProps {
|
||||
analysisResult: AnalysisResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 简洁的分析结果展示组件
|
||||
* 美观地展示AI图像分析结果
|
||||
*/
|
||||
export const SimpleAnalysisDisplay: React.FC<SimpleAnalysisDisplayProps> = ({
|
||||
analysisResult
|
||||
}) => {
|
||||
// HSV转十六进制颜色
|
||||
const hsvToHex = (color: ColorPattern): string => {
|
||||
const { hue, saturation, value } = color;
|
||||
|
||||
const c = value * saturation;
|
||||
const x = c * (1 - Math.abs(((hue * 6) % 2) - 1));
|
||||
const m = value - c;
|
||||
|
||||
let r = 0, g = 0, b = 0;
|
||||
|
||||
if (hue >= 0 && hue < 1/6) {
|
||||
r = c; g = x; b = 0;
|
||||
} else if (hue >= 1/6 && hue < 2/6) {
|
||||
r = x; g = c; b = 0;
|
||||
} else if (hue >= 2/6 && hue < 3/6) {
|
||||
r = 0; g = c; b = x;
|
||||
} else if (hue >= 3/6 && hue < 4/6) {
|
||||
r = 0; g = x; b = c;
|
||||
} else if (hue >= 4/6 && hue < 5/6) {
|
||||
r = x; g = 0; b = c;
|
||||
} else {
|
||||
r = c; g = 0; b = x;
|
||||
}
|
||||
|
||||
const red = Math.round((r + m) * 255);
|
||||
const green = Math.round((g + m) * 255);
|
||||
const blue = Math.round((b + m) * 255);
|
||||
|
||||
return `#${red.toString(16).padStart(2, '0')}${green.toString(16).padStart(2, '0')}${blue.toString(16).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
// 格式化百分比
|
||||
const formatPercentage = (value: number) => `${Math.round(value * 100)}%`;
|
||||
|
||||
// 获取类别图标
|
||||
const getCategoryIcon = (category: string) => {
|
||||
switch (category) {
|
||||
case '上装': return '👕';
|
||||
case '下装': return '👖';
|
||||
case '鞋子': return '👟';
|
||||
case '配饰': return '💍';
|
||||
default: return '👔';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 bg-white rounded-lg shadow-lg space-y-8">
|
||||
{/* 标题 */}
|
||||
<div className="text-center border-b pb-6">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
🎨 AI 服装分析结果
|
||||
</h2>
|
||||
<p className="text-gray-600">智能图像分析与风格解读</p>
|
||||
</div>
|
||||
|
||||
{/* 整体风格描述 */}
|
||||
<div className="bg-gradient-to-r from-blue-50 to-purple-50 p-6 rounded-lg">
|
||||
<div className="flex items-center mb-4">
|
||||
<Eye className="w-5 h-5 text-blue-600 mr-2" />
|
||||
<h3 className="text-lg font-semibold text-gray-900">整体风格</h3>
|
||||
</div>
|
||||
<p className="text-gray-700 leading-relaxed">{analysisResult.style_description}</p>
|
||||
</div>
|
||||
|
||||
{/* 环境与色彩分析 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* 环境标签 */}
|
||||
<div className="bg-green-50 p-6 rounded-lg">
|
||||
<div className="flex items-center mb-4">
|
||||
<MapPin className="w-5 h-5 text-green-600 mr-2" />
|
||||
<h3 className="text-lg font-semibold text-gray-900">拍摄环境</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{analysisResult.environment_tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm font-medium"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 色彩分析 */}
|
||||
<div className="bg-purple-50 p-6 rounded-lg">
|
||||
<div className="flex items-center mb-4">
|
||||
<Palette className="w-5 h-5 text-purple-600 mr-2" />
|
||||
<h3 className="text-lg font-semibold text-gray-900">主色调</h3>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-8 h-8 rounded-full border-2 border-gray-200"
|
||||
style={{ backgroundColor: hsvToHex(analysisResult.dress_color_pattern) }}
|
||||
/>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">服装主色</p>
|
||||
<p className="text-xs text-gray-600">{hsvToHex(analysisResult.dress_color_pattern)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-8 h-8 rounded-full border-2 border-gray-200"
|
||||
style={{ backgroundColor: hsvToHex(analysisResult.environment_color_pattern) }}
|
||||
/>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">环境主色</p>
|
||||
<p className="text-xs text-gray-600">{hsvToHex(analysisResult.environment_color_pattern)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 服装单品分析 */}
|
||||
<div>
|
||||
<div className="flex items-center mb-6">
|
||||
<Shirt className="w-5 h-5 text-gray-700 mr-2" />
|
||||
<h3 className="text-lg font-semibold text-gray-900">服装单品分析</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{analysisResult.products.map((product, index) => (
|
||||
<div key={index} className="bg-gray-50 p-6 rounded-lg border border-gray-200">
|
||||
{/* 单品标题 */}
|
||||
<div className="flex items-center mb-4">
|
||||
<span className="text-2xl mr-3">{getCategoryIcon(product.category)}</span>
|
||||
<div>
|
||||
<h4 className="text-lg font-semibold text-gray-900">{product.category}</h4>
|
||||
<div
|
||||
className="w-6 h-6 rounded-full border-2 border-gray-300 mt-1"
|
||||
style={{ backgroundColor: hsvToHex(product.color_pattern) }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 描述 */}
|
||||
<p className="text-gray-700 mb-4 leading-relaxed">{product.description}</p>
|
||||
|
||||
{/* 风格标签 */}
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-medium text-gray-600 mb-2">设计风格</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{product.design_styles.map((style, styleIndex) => (
|
||||
<span
|
||||
key={styleIndex}
|
||||
className="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs font-medium"
|
||||
>
|
||||
{style}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 匹配度 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">与整体搭配匹配度</span>
|
||||
<span className="text-sm font-semibold text-green-600">
|
||||
{formatPercentage(product.color_pattern_match_dress)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className="bg-green-500 h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${product.color_pattern_match_dress * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">与环境匹配度</span>
|
||||
<span className="text-sm font-semibold text-blue-600">
|
||||
{formatPercentage(product.color_pattern_match_environment)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${product.color_pattern_match_environment * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
ImageIcon,
|
||||
Search,
|
||||
Sparkles,
|
||||
Filter
|
||||
Filter,
|
||||
BarChart3
|
||||
} from 'lucide-react';
|
||||
import { Tool, ToolCategory, ToolStatus } from '../types/tool';
|
||||
|
||||
@@ -163,6 +164,21 @@ export const TOOLS_DATA: Tool[] = [
|
||||
isPopular: true,
|
||||
version: '1.0.0',
|
||||
lastUpdated: '2024-01-26'
|
||||
},
|
||||
{
|
||||
id: 'enriched-analysis-demo',
|
||||
name: '🎨 丰富图像分析演示',
|
||||
description: '展示智能服装分析的详细结果,包含颜色分析、风格分析、产品分析等丰富信息',
|
||||
longDescription: '专业的图像分析结果展示工具,提供极其详细和丰富的分析信息。包含颜色和谐度分析、色彩温度匹配、风格一致性评估、产品匹配度统计、环境适配性分析、拍摄质量评估、个性化搭配建议等功能。支持可展开的分析报告、统计图表、建议卡片和完整的分析摘要。',
|
||||
icon: BarChart3,
|
||||
route: '/tools/enriched-analysis-demo',
|
||||
category: ToolCategory.AI_TOOLS,
|
||||
status: ToolStatus.BETA,
|
||||
tags: ['图像分析', '详细报告', '颜色分析', '风格分析', '搭配建议', '统计图表'],
|
||||
isNew: true,
|
||||
isPopular: true,
|
||||
version: '1.0.0',
|
||||
lastUpdated: '2024-01-26'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
251
apps/desktop/src/pages/tools/EnrichedAnalysisDemo.tsx
Normal file
251
apps/desktop/src/pages/tools/EnrichedAnalysisDemo.tsx
Normal file
@@ -0,0 +1,251 @@
|
||||
import React, { useState } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { SimpleAnalysisDisplay } from '../../components/outfit/SimpleAnalysisDisplay';
|
||||
import { Upload, Loader2, AlertCircle, Image } from 'lucide-react';
|
||||
import { AnalyzeImageResponse } from '../../types/outfitSearch';
|
||||
|
||||
/**
|
||||
* 丰富分析结果演示页面
|
||||
* 展示如何使用丰富的图像分析功能
|
||||
*/
|
||||
export const EnrichedAnalysisDemo: React.FC = () => {
|
||||
const [selectedImagePath, setSelectedImagePath] = useState<string | null>(null);
|
||||
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
||||
const [analysisResult, setAnalysisResult] = useState<any>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// 选择图片文件
|
||||
const handleSelectImage = async () => {
|
||||
try {
|
||||
const selected = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: '图片文件',
|
||||
extensions: ['jpg', 'jpeg', 'png', 'webp', 'bmp']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (selected && typeof selected === 'string') {
|
||||
setSelectedImagePath(selected);
|
||||
setError(null);
|
||||
setAnalysisResult(null);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('选择图片失败:', err);
|
||||
setError('选择图片失败: ' + (err as Error).message);
|
||||
}
|
||||
};
|
||||
|
||||
// 分析图片
|
||||
const handleAnalyze = async () => {
|
||||
if (!selectedImagePath) {
|
||||
setError('请先选择一张图片');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsAnalyzing(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log('开始分析图片:', selectedImagePath);
|
||||
|
||||
// 1. 首先调用图片分析API
|
||||
const imageName = selectedImagePath.split(/[/\\]/).pop() || 'unknown.jpg';
|
||||
const analysisResponse: AnalyzeImageResponse = await invoke('analyze_outfit_image', {
|
||||
request: {
|
||||
image_path: selectedImagePath,
|
||||
image_name: imageName
|
||||
}
|
||||
});
|
||||
|
||||
console.log('图片分析完成:', analysisResponse);
|
||||
setAnalysisResult(analysisResponse.result);
|
||||
|
||||
} catch (err) {
|
||||
console.error('分析失败:', err);
|
||||
setError('分析失败: ' + (err as Error).message);
|
||||
} finally {
|
||||
setIsAnalyzing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 清除结果
|
||||
const handleClearResults = () => {
|
||||
setSelectedImagePath(null);
|
||||
setAnalysisResult(null);
|
||||
setError(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* 页面标题 */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
||||
🎨 AI 服装分析演示
|
||||
</h1>
|
||||
<p className="text-gray-600">
|
||||
使用真实的AI引擎分析服装搭配图片,获得详细的风格解读和色彩分析
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 操作区域 */}
|
||||
<div className="bg-white rounded-lg shadow-lg p-6 mb-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* 图片选择 */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">选择图片</h3>
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
onClick={handleSelectImage}
|
||||
className="w-full border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors cursor-pointer"
|
||||
>
|
||||
<Upload className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||
<p className="text-gray-600">点击选择图片文件</p>
|
||||
<p className="text-sm text-gray-400 mt-1">支持 JPG, PNG, WebP, BMP 格式</p>
|
||||
</button>
|
||||
|
||||
{selectedImagePath && (
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<p className="text-sm text-gray-600 mb-2">已选择文件:</p>
|
||||
<p className="text-sm font-mono text-gray-800 break-all">{selectedImagePath}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedImagePath && (
|
||||
<div className="mt-4">
|
||||
<img
|
||||
src={convertFileSrc(selectedImagePath)}
|
||||
alt="Selected"
|
||||
className="w-full h-48 object-cover rounded-lg"
|
||||
onError={() => {
|
||||
console.log('图片预览加载失败:', selectedImagePath);
|
||||
setError('图片预览加载失败,请重新选择图片');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">分析操作</h3>
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
onClick={handleAnalyze}
|
||||
disabled={!selectedImagePath || isAnalyzing}
|
||||
className="w-full btn-primary flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isAnalyzing ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
分析中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Image className="w-4 h-4" />
|
||||
分析图片
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{(selectedImagePath || analysisResult) && (
|
||||
<button
|
||||
onClick={handleClearResults}
|
||||
disabled={isAnalyzing}
|
||||
className="w-full btn-secondary flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<AlertCircle className="w-4 h-4" />
|
||||
清除结果
|
||||
</button>
|
||||
)}
|
||||
|
||||
{analysisResult && !isAnalyzing && (
|
||||
<div className="bg-green-50 p-4 rounded-lg">
|
||||
<h4 className="font-medium text-green-900 mb-2">✅ 分析完成</h4>
|
||||
<p className="text-sm text-green-800">
|
||||
图片分析已完成,请查看下方的详细分析结果
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-blue-50 p-4 rounded-lg">
|
||||
<h4 className="font-medium text-blue-900 mb-2">💡 功能说明</h4>
|
||||
<ul className="text-sm text-blue-800 space-y-1">
|
||||
<li>• 使用真实的AI图像分析引擎</li>
|
||||
<li>• 详细的颜色分析和色彩和谐度评估</li>
|
||||
<li>• 风格一致性和复杂度分析</li>
|
||||
<li>• 产品匹配度统计和建议</li>
|
||||
<li>• 环境适配性和拍摄质量评估</li>
|
||||
<li>• 个性化搭配建议和场合推荐</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 错误提示 */}
|
||||
{error && (
|
||||
<div className="mt-6 bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertCircle className="w-5 h-5 text-red-500" />
|
||||
<span className="text-red-800 font-medium">分析失败</span>
|
||||
</div>
|
||||
<p className="text-red-700 mt-1">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 分析结果展示 */}
|
||||
{analysisResult && (
|
||||
<SimpleAnalysisDisplay analysisResult={analysisResult} />
|
||||
)}
|
||||
|
||||
{/* 使用说明 */}
|
||||
{!analysisResult && (
|
||||
<div className="bg-white rounded-lg shadow-lg p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">📖 使用说明</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900 mb-2">操作步骤</h4>
|
||||
<ol className="text-sm text-gray-600 space-y-1">
|
||||
<li>1. 点击"选择图片文件"按钮</li>
|
||||
<li>2. 选择一张服装搭配图片</li>
|
||||
<li>3. 点击"分析图片"开始AI分析</li>
|
||||
<li>4. 等待分析完成,查看丰富的分析报告</li>
|
||||
<li>5. 展开各个分析区域查看详细信息</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900 mb-2">分析内容</h4>
|
||||
<ul className="text-sm text-gray-600 space-y-1">
|
||||
<li>• 真实AI引擎驱动的图像分析</li>
|
||||
<li>• 服装颜色和环境色彩分析</li>
|
||||
<li>• 色彩和谐度和温度匹配</li>
|
||||
<li>• 风格标签识别和一致性评估</li>
|
||||
<li>• 产品类别统计和匹配度分析</li>
|
||||
<li>• 环境类型和拍摄质量评估</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 bg-yellow-50 p-4 rounded-lg">
|
||||
<h4 className="font-medium text-yellow-900 mb-2">⚠️ 注意事项</h4>
|
||||
<ul className="text-sm text-yellow-800 space-y-1">
|
||||
<li>• 请选择清晰的服装搭配图片以获得最佳分析效果</li>
|
||||
<li>• 支持的格式:JPG、PNG、WebP、BMP</li>
|
||||
<li>• 分析过程可能需要几秒钟时间,请耐心等待</li>
|
||||
<li>• 分析结果基于AI模型,仅供参考</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user