diff --git a/apps/desktop/src-tauri/src/presentation/commands/outfit_search_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/outfit_search_commands.rs index 9088409..85c9e98 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/outfit_search_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/outfit_search_commands.rs @@ -5,7 +5,7 @@ use crate::app_state::AppState; use crate::data::models::gemini_analysis::{AnalyzeImageRequest, AnalyzeImageResponse}; use crate::data::models::outfit_search::{ LLMQueryRequest, LLMQueryResponse, OutfitSearchGlobalConfig, ProductInfo, SearchFilterBuilder, - SearchRequest, SearchResponse, SearchResult, + SearchRequest, SearchResponse, SearchResult, SearchConfig, }; use crate::data::models::outfit_recommendation::{ OutfitRecommendationRequest, OutfitRecommendationResponse, @@ -465,15 +465,11 @@ async fn execute_vertex_ai_search( eprintln!("搜索配置摘要: {}", config_summary); } - // 5. 构建搜索过滤器 - 使用增强的过滤器构建器 - let search_filter = SearchFilterBuilder::build_filters(&request.config); + // 5. 构建简化的搜索过滤器 - 参考Python实现 + let search_filter = build_simple_filters(&request.config); - // 6. 构建增强查询字符串 - 参考 Python 实现 - let enhanced_query = if request.config.query_enhancement_enabled { - SearchFilterBuilder::build_enhanced_query(&request.query, &request.config) - } else { - request.query.clone() - }; + // 6. 构建简化的查询字符串 - 参考 Python 实现 + let enhanced_query = build_simple_query(&request.query, &request.config); // 调试输出 if request.config.debug_mode { @@ -489,7 +485,15 @@ async fn execute_vertex_ai_search( "offset": request.page_offset }); - // 添加相关性评分规范(但不设置阈值,因为API不支持) + // 添加相关性阈值和评分规范(参考Python实现) + let threshold_str = match request.config.relevance_threshold { + crate::data::models::outfit_search::RelevanceThreshold::Lowest => "LOWEST", + crate::data::models::outfit_search::RelevanceThreshold::Low => "LOW", + crate::data::models::outfit_search::RelevanceThreshold::Medium => "MEDIUM", + crate::data::models::outfit_search::RelevanceThreshold::High => "HIGH", + crate::data::models::outfit_search::RelevanceThreshold::Unspecified => "RELEVANCE_THRESHOLD_UNSPECIFIED", + }; + payload["relevanceThreshold"] = serde_json::Value::String(threshold_str.to_string()); payload["relevanceScoreSpec"] = serde_json::json!({ "returnRelevanceScore": true }); @@ -895,3 +899,61 @@ pub fn get_outfit_search_command_names() -> Vec<&'static str> { "get_outfit_search_config", ] } + +/// 构建简化的过滤器字符串 - 参考Python实现 +fn build_simple_filters(config: &SearchConfig) -> String { + let mut filters = Vec::new(); + + // 环境标签过滤 + if !config.environments.is_empty() { + let env_filter = config.environments.iter() + .map(|env| format!("\"{}\"", env)) + .collect::>() + .join(","); + filters.push(format!("environment_tags: ANY({})", env_filter)); + } + + // 类别过滤 + if !config.categories.is_empty() { + let cat_filter = config.categories.iter() + .map(|cat| format!("\"{}\"", cat)) + .collect::>() + .join(","); + filters.push(format!("products.category: ANY({})", cat_filter)); + } + + filters.join(" AND ") +} + +/// 构建简化的查询字符串 - 参考Python实现 +fn build_simple_query(base_query: &str, config: &SearchConfig) -> String { + if !config.query_enhancement_enabled { + return base_query.to_string(); + } + + let mut keywords = Vec::new(); + + // 添加环境关键词 + keywords.extend(config.environments.clone()); + + // 添加设计风格关键词 + for styles in config.design_styles.values() { + keywords.extend(styles.clone()); + } + + // 限制关键词数量 + if keywords.len() > config.max_keywords { + keywords.truncate(config.max_keywords); + } + + if keywords.is_empty() { + base_query.to_string() + } else { + let keywords_str = keywords.join(" "); + if base_query.trim().is_empty() { + format!("model {}", keywords_str) + } else { + format!("{} {}", base_query.trim(), keywords_str) + } + } +} diff --git a/apps/desktop/src/pages/tools/OutfitSearchTool.tsx b/apps/desktop/src/pages/tools/OutfitSearchTool.tsx index d18573c..5bae38f 100644 --- a/apps/desktop/src/pages/tools/OutfitSearchTool.tsx +++ b/apps/desktop/src/pages/tools/OutfitSearchTool.tsx @@ -15,6 +15,7 @@ import { } from 'lucide-react'; import { invoke } from '@tauri-apps/api/core'; import { open } from '@tauri-apps/plugin-dialog'; +import { convertFileSrc } from '@tauri-apps/api/core'; import { OutfitAnalysisResult, SearchRequest, @@ -121,17 +122,38 @@ const OutfitSearchTool: React.FC = () => { setSearchError(null); try { + // 构建简化的搜索配置 + const simpleConfig: SearchConfig = { + relevance_threshold: 'HIGH' as any, + environments: searchConfig.environments || [], + categories: searchConfig.categories || [], + color_filters: {}, + design_styles: {}, + max_keywords: 10, + debug_mode: true, // 启用调试模式查看详细信息 + custom_filters: [], + query_enhancement_enabled: true, + color_thresholds: { + default_hue_threshold: 0.05, + default_saturation_threshold: 0.05, + default_value_threshold: 0.2 + } + }; + const searchRequest: SearchRequest = { - query: 'outfit search', - config: searchConfig, + query: 'model fashion outfit', + config: simpleConfig, page_size: 9, page_offset: (currentPage - 1) * 9 }; + console.log('发送搜索请求:', searchRequest); + const response = await invoke('search_similar_outfits', { request: searchRequest }); + console.log('搜索响应:', response); setSearchResults(response); } catch (error) { console.error('Failed to search outfits:', error); @@ -240,6 +262,10 @@ const OutfitSearchTool: React.FC = () => { src={result.image_url} alt="Outfit" className="w-full h-48 object-cover rounded-lg mb-3" + onError={(e) => { + console.error('搜索结果图片加载失败:', result.image_url); + e.currentTarget.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik0xMDAgNzBMMTMwIDEwMEgxMTBWMTMwSDkwVjEwMEg3MEwxMDAgNzBaIiBmaWxsPSIjOUI5QkEwIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iMTUwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjOUI5QkEwIiBmb250LXNpemU9IjEyIj7lm77niYfliKDpmaTlpLHotKU8L3RleHQ+Cjwvc3ZnPg=='; + }} />

@@ -323,9 +349,13 @@ const OutfitSearchTool: React.FC = () => {

Selected { + console.error('图片加载失败:', selectedImage); + setAnalysisError('图片加载失败,请重新选择图片'); + }} />