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::gemini_analysis::{AnalyzeImageRequest, AnalyzeImageResponse};
|
||||||
use crate::data::models::outfit_search::{
|
use crate::data::models::outfit_search::{
|
||||||
LLMQueryRequest, LLMQueryResponse, OutfitSearchGlobalConfig, ProductInfo, SearchFilterBuilder,
|
LLMQueryRequest, LLMQueryResponse, OutfitSearchGlobalConfig, ProductInfo, SearchFilterBuilder,
|
||||||
SearchRequest, SearchResponse, SearchResult,
|
SearchRequest, SearchResponse, SearchResult, SearchConfig,
|
||||||
};
|
};
|
||||||
use crate::data::models::outfit_recommendation::{
|
use crate::data::models::outfit_recommendation::{
|
||||||
OutfitRecommendationRequest, OutfitRecommendationResponse,
|
OutfitRecommendationRequest, OutfitRecommendationResponse,
|
||||||
|
|
@ -465,15 +465,11 @@ async fn execute_vertex_ai_search(
|
||||||
eprintln!("搜索配置摘要: {}", config_summary);
|
eprintln!("搜索配置摘要: {}", config_summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 构建搜索过滤器 - 使用增强的过滤器构建器
|
// 5. 构建简化的搜索过滤器 - 参考Python实现
|
||||||
let search_filter = SearchFilterBuilder::build_filters(&request.config);
|
let search_filter = build_simple_filters(&request.config);
|
||||||
|
|
||||||
// 6. 构建增强查询字符串 - 参考 Python 实现
|
// 6. 构建简化的查询字符串 - 参考 Python 实现
|
||||||
let enhanced_query = if request.config.query_enhancement_enabled {
|
let enhanced_query = build_simple_query(&request.query, &request.config);
|
||||||
SearchFilterBuilder::build_enhanced_query(&request.query, &request.config)
|
|
||||||
} else {
|
|
||||||
request.query.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 调试输出
|
// 调试输出
|
||||||
if request.config.debug_mode {
|
if request.config.debug_mode {
|
||||||
|
|
@ -489,7 +485,15 @@ async fn execute_vertex_ai_search(
|
||||||
"offset": request.page_offset
|
"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!({
|
payload["relevanceScoreSpec"] = serde_json::json!({
|
||||||
"returnRelevanceScore": true
|
"returnRelevanceScore": true
|
||||||
});
|
});
|
||||||
|
|
@ -895,3 +899,61 @@ pub fn get_outfit_search_command_names() -> Vec<&'static str> {
|
||||||
"get_outfit_search_config",
|
"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';
|
} from 'lucide-react';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { open } from '@tauri-apps/plugin-dialog';
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
|
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||||
import {
|
import {
|
||||||
OutfitAnalysisResult,
|
OutfitAnalysisResult,
|
||||||
SearchRequest,
|
SearchRequest,
|
||||||
|
|
@ -121,17 +122,38 @@ const OutfitSearchTool: React.FC = () => {
|
||||||
setSearchError(null);
|
setSearchError(null);
|
||||||
|
|
||||||
try {
|
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 = {
|
const searchRequest: SearchRequest = {
|
||||||
query: 'outfit search',
|
query: 'model fashion outfit',
|
||||||
config: searchConfig,
|
config: simpleConfig,
|
||||||
page_size: 9,
|
page_size: 9,
|
||||||
page_offset: (currentPage - 1) * 9
|
page_offset: (currentPage - 1) * 9
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log('发送搜索请求:', searchRequest);
|
||||||
|
|
||||||
const response = await invoke<SearchResponse>('search_similar_outfits', {
|
const response = await invoke<SearchResponse>('search_similar_outfits', {
|
||||||
request: searchRequest
|
request: searchRequest
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('搜索响应:', response);
|
||||||
setSearchResults(response);
|
setSearchResults(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to search outfits:', error);
|
console.error('Failed to search outfits:', error);
|
||||||
|
|
@ -240,6 +262,10 @@ const OutfitSearchTool: React.FC = () => {
|
||||||
src={result.image_url}
|
src={result.image_url}
|
||||||
alt="Outfit"
|
alt="Outfit"
|
||||||
className="w-full h-48 object-cover rounded-lg mb-3"
|
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">
|
<div className="space-y-2">
|
||||||
<p className="text-sm text-gray-600 line-clamp-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="space-y-3">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<img
|
<img
|
||||||
src={`asset://localhost/${selectedImage}`}
|
src={convertFileSrc(selectedImage)}
|
||||||
alt="Selected"
|
alt="Selected"
|
||||||
className="w-full h-48 object-cover rounded-lg"
|
className="w-full h-48 object-cover rounded-lg"
|
||||||
|
onError={(e) => {
|
||||||
|
console.error('图片加载失败:', selectedImage);
|
||||||
|
setAnalysisError('图片加载失败,请重新选择图片');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={clearImage}
|
onClick={clearImage}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue