mixvideo-v2/apps/desktop/src/services/intelligentSearchService.ts

350 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import OutfitSearchService from './outfitSearchService';
import {
SearchResponse,
OutfitAnalysisResult,
LLMQueryResponse,
SearchConfig,
DEFAULT_SEARCH_CONFIG,
} from '../types/outfitSearch';
/**
* 智能检索服务
* 整合图像分析、LLM问答和搜索功能提供统一的智能检索体验
* 遵循 Tauri 开发规范的服务层设计原则
*/
export class IntelligentSearchService {
/**
* 智能搜索模式枚举
*/
static readonly SearchMode = {
TEXT: 'text', // 文本搜索
IMAGE: 'image', // 图像分析搜索
LLM: 'llm', // LLM智能搜索
HYBRID: 'hybrid', // 混合搜索
} as const;
/**
* 执行智能搜索
* 根据输入类型自动选择最佳搜索策略
*/
static async executeIntelligentSearch(params: {
query?: string;
imagePath?: string;
llmQuery?: string;
config?: SearchConfig;
mode?: keyof typeof IntelligentSearchService.SearchMode;
}): Promise<{
searchResponse: SearchResponse;
analysisResult?: OutfitAnalysisResult;
llmResponse?: LLMQueryResponse;
searchMode: string;
suggestions: string[];
}> {
const { query, imagePath, llmQuery, config = DEFAULT_SEARCH_CONFIG, mode } = params;
// 自动检测搜索模式
const detectedMode = mode || this.detectSearchMode({ query, imagePath, llmQuery });
let searchResponse: SearchResponse;
let analysisResult: OutfitAnalysisResult | undefined;
let llmResponse: LLMQueryResponse | undefined;
let finalConfig = { ...config };
let suggestions: string[] = [];
switch (detectedMode) {
case this.SearchMode.IMAGE:
if (!imagePath) throw new Error('Image path is required for image search');
// 1. 分析图像
const imageAnalysis = await OutfitSearchService.analyzeOutfitImage({
image_path: imagePath,
image_name: imagePath.split('/').pop() || 'image',
});
analysisResult = imageAnalysis.result as OutfitAnalysisResult;
// 2. 基于分析结果生成搜索配置
finalConfig = await OutfitSearchService.generateSearchConfigFromAnalysis(analysisResult);
// 3. 执行搜索
searchResponse = await OutfitSearchService.searchSimilarOutfits({
query: analysisResult.style_description || 'model',
config: finalConfig,
page_size: 9,
page_offset: 0,
});
// 4. 生成搜索建议
suggestions = this.generateImageSearchSuggestions(analysisResult);
break;
case this.SearchMode.LLM:
if (!llmQuery) throw new Error('LLM query is required for LLM search');
// 1. 获取LLM回答
llmResponse = await OutfitSearchService.askLLMOutfitAdvice({
user_input: llmQuery,
});
// 2. 从LLM回答中提取搜索关键词
const extractedKeywords = this.extractKeywordsFromLLMResponse(llmResponse.answer);
// 3. 执行基于关键词的搜索
searchResponse = await OutfitSearchService.searchSimilarOutfits({
query: extractedKeywords.join(' ') || llmQuery,
config: finalConfig,
page_size: 9,
page_offset: 0,
});
// 4. 生成搜索建议
suggestions = extractedKeywords;
break;
case this.SearchMode.HYBRID:
// 混合搜索:结合多种输入
const hybridResults = await this.executeHybridSearch({
query,
imagePath,
llmQuery,
config: finalConfig,
});
searchResponse = hybridResults.searchResponse;
analysisResult = hybridResults.analysisResult;
llmResponse = hybridResults.llmResponse;
suggestions = hybridResults.suggestions;
break;
case this.SearchMode.TEXT:
default:
if (!query) throw new Error('Query is required for text search');
// 1. 生成搜索建议
suggestions = await OutfitSearchService.getSearchSuggestions(query);
// 2. 执行文本搜索
searchResponse = await OutfitSearchService.searchSimilarOutfits({
query,
config: finalConfig,
page_size: 9,
page_offset: 0,
});
break;
}
return {
searchResponse,
analysisResult,
llmResponse,
searchMode: detectedMode,
suggestions,
};
}
/**
* 自动检测搜索模式
*/
private static detectSearchMode(params: {
query?: string;
imagePath?: string;
llmQuery?: string;
}): string {
const { query, imagePath, llmQuery } = params;
// 如果有多个输入,使用混合模式
const inputCount = [query, imagePath, llmQuery].filter(Boolean).length;
if (inputCount > 1) {
return this.SearchMode.HYBRID;
}
// 单一输入模式检测
if (imagePath) return this.SearchMode.IMAGE;
if (llmQuery) return this.SearchMode.LLM;
if (query) return this.SearchMode.TEXT;
// 默认文本模式
return this.SearchMode.TEXT;
}
/**
* 执行混合搜索
*/
private static async executeHybridSearch(params: {
query?: string;
imagePath?: string;
llmQuery?: string;
config: SearchConfig;
}): Promise<{
searchResponse: SearchResponse;
analysisResult?: OutfitAnalysisResult;
llmResponse?: LLMQueryResponse;
suggestions: string[];
}> {
const { query, imagePath, llmQuery, config } = params;
const results: any = {};
const allSuggestions: string[] = [];
// 并行执行各种分析
const promises: Promise<any>[] = [];
if (imagePath) {
promises.push(
OutfitSearchService.analyzeOutfitImage({
image_path: imagePath,
image_name: imagePath.split('/').pop() || 'image',
}).then(result => {
results.analysisResult = result.result as OutfitAnalysisResult;
allSuggestions.push(...this.generateImageSearchSuggestions(results.analysisResult));
})
);
}
if (llmQuery) {
promises.push(
OutfitSearchService.askLLMOutfitAdvice({
user_input: llmQuery,
}).then(result => {
results.llmResponse = result;
const keywords = this.extractKeywordsFromLLMResponse(result.answer);
allSuggestions.push(...keywords);
})
);
}
if (query) {
promises.push(
OutfitSearchService.getSearchSuggestions(query).then(suggestions => {
allSuggestions.push(...suggestions);
})
);
}
// 等待所有分析完成
await Promise.all(promises);
// 合并搜索配置
let finalConfig = { ...config };
if (results.analysisResult) {
const imageConfig = await OutfitSearchService.generateSearchConfigFromAnalysis(results.analysisResult);
finalConfig = this.mergeSearchConfigs(finalConfig, imageConfig);
}
// 构建综合搜索查询
const searchQueries = [
query,
results.analysisResult?.style_description,
...allSuggestions.slice(0, 3), // 限制关键词数量
].filter(Boolean);
const finalQuery = searchQueries.join(' ').substring(0, 200); // 限制查询长度
// 执行最终搜索
const searchResponse = await OutfitSearchService.searchSimilarOutfits({
query: finalQuery || 'model',
config: finalConfig,
page_size: 9,
page_offset: 0,
});
return {
searchResponse,
analysisResult: results.analysisResult,
llmResponse: results.llmResponse,
suggestions: [...new Set(allSuggestions)].slice(0, 5), // 去重并限制数量
};
}
/**
* 从图像分析结果生成搜索建议
*/
private static generateImageSearchSuggestions(analysisResult: OutfitAnalysisResult): string[] {
const suggestions: string[] = [];
// 添加环境标签
if (analysisResult.environment_tags) {
suggestions.push(...analysisResult.environment_tags);
}
// 添加产品类别和设计风格
if (analysisResult.products) {
analysisResult.products.forEach(product => {
suggestions.push(product.category);
if (product.design_styles) {
suggestions.push(...product.design_styles);
}
});
}
return [...new Set(suggestions)].slice(0, 5);
}
/**
* 从LLM回答中提取关键词
*/
private static extractKeywordsFromLLMResponse(response: string): string[] {
const keywords: string[] = [];
const text = response.toLowerCase();
// 服装相关关键词模式
const patterns = [
/(?:推荐|建议|选择).*?([\u4e00-\u9fa5]+(?:装|衣|裤|鞋|包|帽))/g,
/(?:休闲|正式|运动|街头|优雅|可爱|性感|简约|复古|时尚)/g,
/(?:春|夏|秋|冬)(?:季|天)/g,
/(?:上班|约会|聚会|旅行|运动|居家)/g,
];
patterns.forEach(pattern => {
let match;
while ((match = pattern.exec(text)) !== null) {
if (match[1]) {
keywords.push(match[1]);
} else {
keywords.push(match[0]);
}
}
});
return [...new Set(keywords)].slice(0, 3);
}
/**
* 合并搜索配置
*/
private static mergeSearchConfigs(config1: SearchConfig, config2: SearchConfig): SearchConfig {
return {
relevance_threshold: config1.relevance_threshold,
environments: [...new Set([...config1.environments, ...config2.environments])],
categories: [...new Set([...config1.categories, ...config2.categories])],
color_filters: { ...config1.color_filters, ...config2.color_filters },
design_styles: {
...config1.design_styles,
...config2.design_styles,
},
max_keywords: Math.max(config1.max_keywords, config2.max_keywords),
debug_mode: config1.debug_mode || config2.debug_mode,
custom_filters: [...new Set([...config1.custom_filters, ...config2.custom_filters])],
query_enhancement_enabled: config1.query_enhancement_enabled && config2.query_enhancement_enabled,
color_thresholds: config1.color_thresholds,
};
}
/**
* 获取搜索历史和建议
*/
static async getSearchRecommendations(): Promise<{
popularKeywords: string[];
recentSearches: string[];
trendingStyles: string[];
}> {
// 这里可以从本地存储或服务器获取数据
return {
popularKeywords: ['休闲搭配', '正式风格', '运动装', '街头风', '优雅风格'],
recentSearches: [], // 从本地存储获取
trendingStyles: ['简约风', '复古风', '韩式风格', '日系风格', '欧美风'],
};
}
}
export default IntelligentSearchService;