347 lines
8.7 KiB
TypeScript
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;
|