feat: 创建简洁美观的AI服装分析展示组件

主要功能:
1. 新增SimpleAnalysisDisplay组件:
   - 简洁美观的分析结果展示
   - HSV颜色转十六进制显示
   - 产品分类图标和风格标签
   - 匹配度进度条可视化

2. 修改EnrichedAnalysisDemo页面:
   - 移除复杂的丰富分析功能
   - 直接展示原始AI分析结果
   - 使用convertFileSrc修复图片预览
   - 简化操作流程和界面

3. 展示内容包括:
   - 整体风格描述
   - 拍摄环境标签
   - 主色调色块展示
   - 服装单品详细分析
   - 颜色匹配度可视化

4. 界面优化:
   - 响应式网格布局
   - 渐变背景和圆角设计
   - 图标和色彩搭配
   - 清晰的信息层级

现在可以美观地展示真实的AI分析结果!
This commit is contained in:
imeepos
2025-07-25 14:40:48 +08:00
parent 7fb17ea16b
commit 8f88ace388
7 changed files with 1768 additions and 3 deletions

View File

@@ -291,6 +291,7 @@ pub fn run() {
commands::outfit_search_commands::get_default_search_config, commands::outfit_search_commands::get_default_search_config,
commands::outfit_search_commands::get_outfit_search_config, commands::outfit_search_commands::get_outfit_search_config,
commands::outfit_search_commands::generate_outfit_recommendations, 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::quick_similarity_search,
commands::similarity_search_commands::get_similarity_search_suggestions, commands::similarity_search_commands::get_similarity_search_suggestions,

View File

@@ -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 { fn build_simple_filters(config: &crate::data::models::outfit_search::SearchConfig) -> String {
let mut filters = Vec::new(); let mut filters = Vec::new();
// 优先使用类别过滤(最重要的过滤条件) // 优先使用类别过滤(最重要的过滤条件)
if !config.categories.is_empty() { 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)) .map(|cat| format!("\"{}\"", cat))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(","); .join(",");
@@ -952,6 +986,664 @@ fn build_simple_filters(config: &crate::data::models::outfit_search::SearchConfi
filters.join(" AND ") 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实现 /// 构建简化的查询字符串 - 参考Python实现
fn build_simple_query(base_query: &str, config: &crate::data::models::outfit_search::SearchConfig) -> String { fn build_simple_query(base_query: &str, config: &crate::data::models::outfit_search::SearchConfig) -> String {
if !config.query_enhancement_enabled { if !config.query_enhancement_enabled {

View File

@@ -22,6 +22,7 @@ import BatchThumbnailGenerator from './pages/tools/BatchThumbnailGenerator';
import OutfitRecommendationTool from './pages/tools/OutfitRecommendationTool'; import OutfitRecommendationTool from './pages/tools/OutfitRecommendationTool';
import AdvancedFilterTool from './pages/tools/AdvancedFilterTool'; import AdvancedFilterTool from './pages/tools/AdvancedFilterTool';
import OutfitSearchTool from './pages/tools/OutfitSearchTool'; import OutfitSearchTool from './pages/tools/OutfitSearchTool';
import { EnrichedAnalysisDemo } from './pages/tools/EnrichedAnalysisDemo';
import Navigation from './components/Navigation'; import Navigation from './components/Navigation';
import { NotificationSystem, useNotifications } from './components/NotificationSystem'; import { NotificationSystem, useNotifications } from './components/NotificationSystem';
@@ -127,6 +128,7 @@ function App() {
<Route path="/tools/outfit-recommendation" element={<OutfitRecommendationTool />} /> <Route path="/tools/outfit-recommendation" element={<OutfitRecommendationTool />} />
<Route path="/tools/outfit-search" element={<OutfitSearchTool />} /> <Route path="/tools/outfit-search" element={<OutfitSearchTool />} />
<Route path="/tools/advanced-filter-demo" element={<AdvancedFilterTool />} /> <Route path="/tools/advanced-filter-demo" element={<AdvancedFilterTool />} />
<Route path="/tools/enriched-analysis-demo" element={<EnrichedAnalysisDemo />} />
</Routes> </Routes>
</div> </div>
</main> </main>

View 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>
);
};

View 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>
);
};

View File

@@ -10,7 +10,8 @@ import {
ImageIcon, ImageIcon,
Search, Search,
Sparkles, Sparkles,
Filter Filter,
BarChart3
} from 'lucide-react'; } from 'lucide-react';
import { Tool, ToolCategory, ToolStatus } from '../types/tool'; import { Tool, ToolCategory, ToolStatus } from '../types/tool';
@@ -163,6 +164,21 @@ export const TOOLS_DATA: Tool[] = [
isPopular: true, isPopular: true,
version: '1.0.0', version: '1.0.0',
lastUpdated: '2024-01-26' 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'
} }
]; ];

View 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> JPGPNGWebPBMP</li>
<li> </li>
<li> AI模型</li>
</ul>
</div>
</div>
)}
</div>
</div>
);
};