466 lines
13 KiB
TypeScript
466 lines
13 KiB
TypeScript
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;
|