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_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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
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,
|
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'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
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