fix: 修复智能服装搜索功能的搜索结果为空问题
主要修复: 1. 添加相关性阈值到搜索请求(参考Python实现) 2. 简化过滤器构建逻辑,使其更接近Python实现 3. 修复SearchConfig导入问题 4. 修复RelevanceThreshold的字符串转换问题 5. 优化前端搜索配置,启用调试模式查看详细信息 对比Python实现发现的关键差异: - Python使用relevanceThreshold字段,Rust之前注释说不支持 - Python使用简单的过滤器字符串,Rust使用了复杂的过滤器构建 - 需要正确的API参数格式匹配
This commit is contained in:
parent
30236d5875
commit
798b5a2007
|
|
@ -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::<Vec<_>>()
|
||||
.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::<Vec<_>>()
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<SearchResponse>('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==';
|
||||
}}
|
||||
/>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-600 line-clamp-2">
|
||||
|
|
@ -323,9 +349,13 @@ const OutfitSearchTool: React.FC = () => {
|
|||
<div className="space-y-3">
|
||||
<div className="relative">
|
||||
<img
|
||||
src={`asset://localhost/${selectedImage}`}
|
||||
src={convertFileSrc(selectedImage)}
|
||||
alt="Selected"
|
||||
className="w-full h-48 object-cover rounded-lg"
|
||||
onError={(e) => {
|
||||
console.error('图片加载失败:', selectedImage);
|
||||
setAnalysisError('图片加载失败,请重新选择图片');
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={clearImage}
|
||||
|
|
|
|||
Loading…
Reference in New Issue