UI优化内容: - 重新设计页面头部,使用优雅的渐变背景和现代化图标 - 优化标签导航,采用卡片式设计和平滑动画效果 - 美化搜索面板,改进输入框、筛选器和按钮的视觉设计 - 重构图片上传组件,添加拖拽区域样式和上传进度动画 - 优化搜索结果展示,使用网格布局和悬停效果 - 改进AI分析结果展示,采用卡片式布局和颜色编码 - 增强LLM聊天界面,现代化消息气泡和打字动画 响应式设计: - 实现移动优先的响应式布局 - 优化平板端和桌面端适配 - 修复1200px宽度下的左右布局显示问题 - 添加触摸友好的交互元素 用户体验提升: - 统一设计语言和视觉风格 - 添加流畅的页面切换和组件加载动画 - 优化加载状态、错误提示和空状态设计 - 改进信息层次和视觉可读性 技术改进: - 使用Tailwind CSS类替代内联样式 - 统一使用Lucide React图标库 - 完善CSS变量和设计令牌系统 - 添加兼容性变量支持旧的命名格式 符合promptx/frontend-developer规定的前端开发规范,确保界面美观、操作流畅、动画优美,符合用户操作习惯和大众审美习惯。
327 lines
8.4 KiB
TypeScript
327 lines
8.4 KiB
TypeScript
import { create } from 'zustand';
|
|
import {
|
|
OutfitSearchState,
|
|
SearchRequest,
|
|
DEFAULT_SEARCH_CONFIG,
|
|
} from '../types/outfitSearch';
|
|
import OutfitSearchService from '../services/outfitSearchService';
|
|
|
|
/**
|
|
* 服装搭配搜索状态管理
|
|
* 遵循 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: [],
|
|
});
|
|
}
|
|
},
|
|
|
|
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);
|
|
set({
|
|
isAnalyzing: false,
|
|
analysisError: error instanceof Error ? error.message : '图像分析失败',
|
|
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
|
|
);
|
|
|
|
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);
|
|
}
|
|
},
|
|
|
|
// 重置所有状态
|
|
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;
|