mixvideo-v2/apps/desktop/src/store/outfitSearchStore.ts

466 lines
13 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 { create } from 'zustand';
import {
OutfitSearchState,
SearchRequest,
DEFAULT_SEARCH_CONFIG,
} from '../types/outfitSearch';
import OutfitSearchService from '../services/outfitSearchService';
import IntelligentSearchService from '../services/intelligentSearchService';
/**
* 服装搭配搜索状态管理
* 遵循 Tauri 开发规范的状态管理模式
*/
export const useOutfitSearchStore = create<OutfitSearchState>((set, _get) => ({
// 搜索状态
searchConfig: DEFAULT_SEARCH_CONFIG,
searchResults: [],
isSearching: false,
searchError: null,
// 分析状态
analysisResult: null,
isAnalyzing: false,
analysisError: null,
// LLM问答状态
llmResponse: null,
isAsking: false,
llmError: null,
// UI状态
selectedImage: null,
showAdvancedFilters: false,
currentPage: 1,
// 历史记录
searchHistory: [],
// 操作方法
updateSearchConfig: (config) => {
set((state) => ({
searchConfig: { ...state.searchConfig, ...config },
}));
},
executeSearch: async (request) => {
set({ isSearching: true, searchError: null });
try {
const response = await OutfitSearchService.searchSimilarOutfits(request);
set({
searchResults: response.results,
isSearching: false,
currentPage: Math.floor(request.page_offset / request.page_size) + 1,
});
// 添加到搜索历史
const historyItem = {
id: Date.now().toString(),
query: request.query,
config: request.config,
results_count: response.total_size,
search_time_ms: response.search_time_ms,
created_at: new Date().toISOString(),
};
set((state) => ({
searchHistory: [historyItem, ...state.searchHistory.slice(0, 9)], // 保留最近10条
}));
} catch (error) {
console.error('Search failed:', error);
set({
isSearching: false,
searchError: error instanceof Error ? error.message : '搜索失败',
searchResults: [],
});
}
},
// 智能搜索
executeIntelligentSearch: async (params: {
query?: string;
imagePath?: string;
llmQuery?: string;
mode?: 'TEXT' | 'IMAGE' | 'LLM' | 'HYBRID';
}) => {
set({ isSearching: true, searchError: null });
try {
const result = await IntelligentSearchService.executeIntelligentSearch({
...params,
config: useOutfitSearchStore.getState().searchConfig,
});
set({
searchResults: result.searchResponse.results,
isSearching: false,
currentPage: 1,
});
// 如果有分析结果,更新分析状态
if (result.analysisResult) {
set({
analysisResult: result.analysisResult,
});
}
// 如果有LLM响应更新LLM状态
if (result.llmResponse) {
set({
llmResponse: result.llmResponse,
});
}
// 添加到搜索历史
const historyItem = {
id: Date.now().toString(),
query: params.query || params.llmQuery || '智能搜索',
config: useOutfitSearchStore.getState().searchConfig,
results_count: result.searchResponse.total_size,
search_time_ms: result.searchResponse.search_time_ms,
created_at: new Date().toISOString(),
};
set((state) => ({
searchHistory: [historyItem, ...state.searchHistory.slice(0, 9)],
}));
return result;
} catch (error) {
console.error('Intelligent search failed:', error);
set({
searchError: error instanceof Error ? error.message : '智能搜索失败',
isSearching: false,
});
throw error;
}
},
analyzeImage: async (request) => {
set({ isAnalyzing: true, analysisError: null });
try {
const response = await OutfitSearchService.analyzeOutfitImage(request);
set({
analysisResult: response.result,
isAnalyzing: false,
});
// 基于分析结果生成搜索配置
try {
const generatedConfig = await OutfitSearchService.generateSearchConfigFromAnalysis(
response.result
);
set((state) => ({
searchConfig: { ...state.searchConfig, ...generatedConfig },
}));
} catch (configError) {
console.warn('Failed to generate search config from analysis:', configError);
}
} catch (error) {
console.error('Image analysis failed:', error);
// 提供更友好的错误信息
let errorMessage = '图像分析失败';
if (error instanceof Error) {
if (error.message.includes('无法从Gemini响应中提取有效的JSON数据')) {
errorMessage = 'AI分析服务返回了不完整的数据请重试或尝试其他图片';
} else if (error.message.includes('网络')) {
errorMessage = '网络连接问题,请检查网络后重试';
} else {
errorMessage = error.message;
}
}
set({
isAnalyzing: false,
analysisError: errorMessage,
analysisResult: null,
});
}
},
askLLM: async (request) => {
set({ isAsking: true, llmError: null });
try {
const response = await OutfitSearchService.askLLMOutfitAdvice(request);
set({
llmResponse: response,
isAsking: false,
});
} catch (error) {
console.error('LLM query failed:', error);
set({
isAsking: false,
llmError: error instanceof Error ? error.message : 'LLM问答失败',
llmResponse: null,
});
}
},
clearResults: () => {
set({
searchResults: [],
searchError: null,
currentPage: 1,
});
},
clearErrors: () => {
set({
searchError: null,
analysisError: null,
llmError: null,
});
},
setSelectedImage: (imagePath) => {
set({ selectedImage: imagePath });
// 如果清除了图像,也清除分析结果
if (!imagePath) {
set({ analysisResult: null, analysisError: null });
}
},
toggleAdvancedFilters: () => {
set((state) => ({
showAdvancedFilters: !state.showAdvancedFilters,
}));
},
loadSearchHistory: async () => {
// 这里可以从本地存储或数据库加载搜索历史
// 暂时使用空实现
try {
// const history = await loadSearchHistoryFromStorage();
// set({ searchHistory: history });
} catch (error) {
console.error('Failed to load search history:', error);
}
},
}));
/**
* 搜索相关的辅助函数
*/
export const useOutfitSearchActions = () => {
const store = useOutfitSearchStore();
return {
// 快速搜索
quickSearch: async (query: string) => {
const request: SearchRequest = {
query,
config: store.searchConfig,
page_size: 9,
page_offset: 0,
};
await store.executeSearch(request);
},
// 分页搜索
searchWithPagination: async (page: number, pageSize: number = 9) => {
const request: SearchRequest = {
query: store.searchHistory[0]?.query || 'model',
config: store.searchConfig,
page_size: pageSize,
page_offset: (page - 1) * pageSize,
};
await store.executeSearch(request);
},
// 基于分析结果搜索
searchFromAnalysis: async () => {
if (!store.analysisResult) return;
try {
const generatedConfig = await OutfitSearchService.generateSearchConfigFromAnalysis(
store.analysisResult
);
// 更新搜索配置以反映分析结果
store.updateSearchConfig(generatedConfig);
const request: SearchRequest = {
query: store.analysisResult.style_description || 'model',
config: generatedConfig,
page_size: 9,
page_offset: 0,
};
await store.executeSearch(request);
} catch (error) {
console.error('Failed to search from analysis:', error);
}
},
// 将分析结果应用到筛选条件
applyAnalysisToFilters: () => {
if (!store.analysisResult) return;
const analysisResult = store.analysisResult;
const newConfig = { ...store.searchConfig };
// 提取类别信息
if (analysisResult.products && analysisResult.products.length > 0) {
const categories = analysisResult.products.map(p => p.category);
newConfig.categories = [...new Set(categories)]; // 去重
// 设置颜色过滤器
const colorFilters: Record<string, any> = {};
analysisResult.products.forEach(product => {
if (product.color_pattern) {
colorFilters[product.category] = {
enabled: true,
color: product.color_pattern,
hue_threshold: 0.05,
saturation_threshold: 0.05,
value_threshold: 0.20,
};
}
});
newConfig.color_filters = colorFilters;
// 设置设计风格
const designStyles: Record<string, string[]> = {};
analysisResult.products.forEach(product => {
if (product.design_styles && product.design_styles.length > 0) {
designStyles[product.category] = product.design_styles;
}
});
newConfig.design_styles = designStyles;
} else {
// 如果没有具体产品信息,使用颜色信息创建通用筛选器
if (analysisResult.dress_color_pattern) {
newConfig.color_filters = {
'general': {
enabled: true,
color: analysisResult.dress_color_pattern,
hue_threshold: 0.1,
saturation_threshold: 0.1,
value_threshold: 0.2,
}
};
}
}
// 设置环境标签过滤掉Unknown
if (analysisResult.environment_tags && analysisResult.environment_tags.length > 0) {
const validTags = analysisResult.environment_tags.filter(tag => tag !== 'Unknown');
if (validTags.length > 0) {
newConfig.environments = validTags;
}
}
// 更新配置
store.updateSearchConfig(newConfig);
},
// 重置所有状态
resetAll: () => {
store.clearResults();
store.clearErrors();
store.setSelectedImage(null);
store.updateSearchConfig(DEFAULT_SEARCH_CONFIG);
useOutfitSearchStore.setState({
analysisResult: null,
llmResponse: null,
showAdvancedFilters: false,
currentPage: 1,
});
},
// 导出搜索配置
exportSearchConfig: () => {
return JSON.stringify(store.searchConfig, null, 2);
},
// 导入搜索配置
importSearchConfig: (configJson: string) => {
try {
const config = JSON.parse(configJson);
store.updateSearchConfig(config);
return true;
} catch (error) {
console.error('Failed to import search config:', error);
return false;
}
},
// 获取搜索统计
getSearchStats: () => {
const { searchHistory, searchResults } = store;
return {
totalSearches: searchHistory.length,
averageResultsCount: searchHistory.length > 0
? searchHistory.reduce((sum, item) => sum + item.results_count, 0) / searchHistory.length
: 0,
averageSearchTime: searchHistory.length > 0
? searchHistory.reduce((sum, item) => sum + item.search_time_ms, 0) / searchHistory.length
: 0,
currentResultsCount: searchResults.length,
};
},
};
};
/**
* 选择器函数
*/
export const useOutfitSearchSelectors = () => {
return {
// 获取当前搜索状态
useSearchState: () => useOutfitSearchStore((state) => ({
isSearching: state.isSearching,
searchError: state.searchError,
results: state.searchResults,
currentPage: state.currentPage,
})),
// 获取分析状态
useAnalysisState: () => useOutfitSearchStore((state) => ({
isAnalyzing: state.isAnalyzing,
analysisError: state.analysisError,
result: state.analysisResult,
selectedImage: state.selectedImage,
})),
// 获取LLM状态
useLLMState: () => useOutfitSearchStore((state) => ({
isAsking: state.isAsking,
llmError: state.llmError,
response: state.llmResponse,
})),
// 获取UI状态
useUIState: () => useOutfitSearchStore((state) => ({
showAdvancedFilters: state.showAdvancedFilters,
searchConfig: state.searchConfig,
})),
// 检查是否有活动筛选
useHasActiveFilters: () => useOutfitSearchStore((state) => {
const { searchConfig } = state;
return (
searchConfig.categories.length > 0 ||
searchConfig.environments.length > 0 ||
Object.values(searchConfig.color_filters).some(f => f.enabled) ||
Object.values(searchConfig.design_styles).some(styles => styles.length > 0)
);
}),
};
};
export default useOutfitSearchStore;