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

347 lines
8.7 KiB
TypeScript

import { invoke } from '@tauri-apps/api/core';
import {
MaterialSearchRequest,
MaterialSearchResponse,
MaterialSearchResult,
GenerateSearchQueryRequest,
GenerateSearchQueryResponse,
MaterialSearchConfig,
OutfitRecommendation,
} from '../types/outfitRecommendation';
/**
* 素材库检索API服务
* 遵循 Tauri 开发规范的API服务设计原则
*/
export class MaterialSearchService {
/**
* 为穿搭方案生成智能检索条件
*/
static async generateSearchQuery(
recommendation: OutfitRecommendation,
options?: {
include_colors?: boolean;
include_styles?: boolean;
include_occasions?: boolean;
include_seasons?: boolean;
}
): Promise<GenerateSearchQueryResponse> {
try {
const request: GenerateSearchQueryRequest = {
recommendation,
options,
};
console.log('🔍 生成素材检索条件:', request);
const response = await invoke<GenerateSearchQueryResponse>(
'generate_material_search_query',
{ request }
);
console.log('✅ 检索条件生成成功:', response);
return response;
} catch (error) {
console.error('❌ 检索条件生成失败:', error);
throw new Error(`检索条件生成失败: ${error}`);
}
}
/**
* 执行素材库检索
*/
static async searchMaterials(
request: MaterialSearchRequest
): Promise<MaterialSearchResponse> {
try {
console.log('🔍 执行素材库检索:', request);
const response = await invoke<MaterialSearchResponse>(
'search_materials_for_outfit',
{ request }
);
console.log('✅ 素材检索成功:', response);
return response;
} catch (error) {
console.error('❌ 素材检索失败:', error);
throw new Error(`素材检索失败: ${error}`);
}
}
/**
* 快速素材检索(使用默认配置)
*/
static async quickSearch(
recommendationId: string,
query: string,
page: number = 1,
pageSize: number = 9
): Promise<MaterialSearchResponse> {
try {
console.log('🔍 快速素材检索:', { recommendationId, query, page, pageSize });
const response = await invoke<MaterialSearchResponse>(
'quick_material_search',
{
recommendationId,
query,
page,
pageSize,
}
);
console.log('✅ 快速检索成功:', response);
return response;
} catch (error) {
console.error('❌ 快速检索失败:', error);
throw new Error(`快速检索失败: ${error}`);
}
}
/**
* 执行分页检索
*/
static async searchWithPagination(
request: MaterialSearchRequest,
page: number,
pageSize: number = 9
): Promise<MaterialSearchResponse> {
try {
const paginatedRequest: MaterialSearchRequest = {
...request,
pagination: {
page,
page_size: pageSize,
},
};
return await this.searchMaterials(paginatedRequest);
} catch (error) {
console.error('Failed to perform paginated material search:', error);
throw new Error(`分页检索失败: ${error}`);
}
}
/**
* 为穿搭方案生成并执行检索
*/
static async generateAndSearch(
recommendation: OutfitRecommendation,
page: number = 1,
pageSize: number = 9,
options?: {
include_colors?: boolean;
include_styles?: boolean;
include_occasions?: boolean;
include_seasons?: boolean;
}
): Promise<{
queryResponse: GenerateSearchQueryResponse;
searchResponse: MaterialSearchResponse;
}> {
try {
// 1. 生成检索条件
const queryResponse = await this.generateSearchQuery(recommendation, options);
// 2. 执行检索
const searchRequest: MaterialSearchRequest = {
query: queryResponse.query,
recommendation_id: recommendation.id,
search_config: queryResponse.search_config,
pagination: {
page,
page_size: pageSize,
},
};
const searchResponse = await this.searchMaterials(searchRequest);
return {
queryResponse,
searchResponse,
};
} catch (error) {
console.error('Failed to generate and search materials:', error);
throw new Error(`生成并检索失败: ${error}`);
}
}
/**
* 获取检索统计信息
*/
static getSearchStats(response: MaterialSearchResponse): {
totalResults: number;
searchTime: number;
averageScore: number;
topCategories: string[];
} {
const totalResults = response.total_size;
const searchTime = response.search_time_ms;
// 计算平均评分
const averageScore = response.results.length > 0
? response.results.reduce((sum, result) => sum + result.relevance_score, 0) / response.results.length
: 0;
// 统计热门类别
const categoryCount: Record<string, number> = {};
response.results.forEach(result => {
result.products.forEach(product => {
categoryCount[product.category] = (categoryCount[product.category] || 0) + 1;
});
});
const topCategories = Object.entries(categoryCount)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
.map(([category]) => category);
return {
totalResults,
searchTime,
averageScore,
topCategories,
};
}
/**
* 格式化搜索时间
*/
static formatSearchTime(timeMs: number): string {
if (timeMs < 1000) {
return `${timeMs}ms`;
} else if (timeMs < 60000) {
return `${(timeMs / 1000).toFixed(1)}s`;
} else {
return `${Math.floor(timeMs / 60000)}m ${Math.floor((timeMs % 60000) / 1000)}s`;
}
}
/**
* 格式化相关性评分
*/
static formatRelevanceScore(score: number): string {
return `${(score * 100).toFixed(1)}%`;
}
/**
* 检查是否有更多结果
*/
static hasMoreResults(response: MaterialSearchResponse): boolean {
return response.current_page < response.total_pages;
}
/**
* 检查是否有上一页
*/
static hasPreviousResults(response: MaterialSearchResponse): boolean {
return response.current_page > 1;
}
/**
* 计算总页数
*/
static getTotalPages(totalSize: number, pageSize: number): number {
return Math.ceil(totalSize / pageSize);
}
/**
* 生成搜索摘要
*/
static generateSearchSummary(response: MaterialSearchResponse, query: string): string {
const { total_size, search_time_ms, results } = response;
const timeStr = this.formatSearchTime(search_time_ms);
if (total_size === 0) {
return `未找到与"${query}"相关的素材`;
}
const avgScore = results.length > 0
? results.reduce((sum, r) => sum + r.relevance_score, 0) / results.length
: 0;
return `找到 ${total_size} 个相关素材,用时 ${timeStr},平均相关度 ${this.formatRelevanceScore(avgScore)}`;
}
/**
* 获取页面范围信息
*/
static getPageRangeInfo(currentPage: number, pageSize: number, totalSize: number): {
start: number;
end: number;
total: number;
} {
const start = (currentPage - 1) * pageSize + 1;
const end = Math.min(currentPage * pageSize, totalSize);
return {
start,
end,
total: totalSize,
};
}
/**
* 创建默认搜索配置
*/
static createDefaultSearchConfig(): MaterialSearchConfig {
return {
relevance_threshold: 'HIGH',
categories: [],
environments: [],
color_filters: {},
design_styles: {},
max_results: 50,
};
}
/**
* 验证搜索请求
*/
static validateSearchRequest(request: MaterialSearchRequest): string[] {
const errors: string[] = [];
if (!request.query || request.query.trim().length === 0) {
errors.push('搜索查询不能为空');
}
if (!request.recommendation_id || request.recommendation_id.trim().length === 0) {
errors.push('穿搭方案ID不能为空');
}
if (request.pagination.page < 1) {
errors.push('页码必须大于0');
}
if (request.pagination.page_size < 1 || request.pagination.page_size > 100) {
errors.push('每页大小必须在1-100之间');
}
return errors;
}
/**
* 批量处理搜索结果
*/
static async batchProcessResults(
results: MaterialSearchResult[],
processor: (result: MaterialSearchResult) => Promise<MaterialSearchResult>
): Promise<MaterialSearchResult[]> {
try {
const processedResults = await Promise.all(
results.map(result => processor(result))
);
return processedResults;
} catch (error) {
console.error('Failed to batch process results:', error);
throw new Error(`批量处理失败: ${error}`);
}
}
}
/**
* 导出默认实例
*/
export default MaterialSearchService;