diff --git a/apps/desktop/src-tauri/src/data/models/outfit_recommendation.rs b/apps/desktop/src-tauri/src/data/models/outfit_recommendation.rs index d293e6f..cbbacee 100644 --- a/apps/desktop/src-tauri/src/data/models/outfit_recommendation.rs +++ b/apps/desktop/src-tauri/src/data/models/outfit_recommendation.rs @@ -1,6 +1,47 @@ use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; +/// 分组策略 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GroupingStrategy { + /// 主要分组维度 + pub primary_dimension: String, + /// 分组原因说明 + pub reasoning: String, +} + +/// 方案质量评分 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OutfitQualityScore { + /// AI对该方案的信心度 (0-1) + pub ai_confidence_score: f32, + /// TikTok趋势匹配度 (0-1) + pub trend_score: f32, + /// 搭配versatility评分 (0-1) + pub versatility_score: f32, + /// 搭配难度等级 + pub difficulty_level: String, + /// 整体推荐度 (0-1) + pub overall_recommendation_score: f32, +} + +/// 穿搭方案分组 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OutfitRecommendationGroup { + /// 分组名称 + pub group: String, + /// 分组描述 + pub description: String, + /// 分组唯一标识符 + pub group_id: String, + /// 该分组的风格关键词 + pub style_keywords: Vec, + /// 是否可以加载更多方案 + pub can_load_more: bool, + /// 分组下的方案列表 + pub children: Vec, +} + /// 色彩信息 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ColorInfo { @@ -77,6 +118,8 @@ pub struct OutfitRecommendation { pub styling_tips: Vec, /// 创建时间 pub created_at: DateTime, + /// 方案质量评分 + pub quality_score: OutfitQualityScore, } /// 穿搭方案生成请求 @@ -99,14 +142,21 @@ pub struct OutfitRecommendationRequest { /// 穿搭方案生成响应 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OutfitRecommendationResponse { - /// 生成的穿搭方案列表 - pub recommendations: Vec, + /// 分组策略 + pub grouping_strategy: GroupingStrategy, + /// 生成的穿搭方案分组列表 + pub groups: Vec, /// 生成时间 (毫秒) pub generation_time_ms: u64, /// 生成时间戳 pub generated_at: DateTime, /// 使用的提示词 (调试用) pub prompt_used: Option, + + // 保持向后兼容性 + /// 生成的穿搭方案列表 (向后兼容) + #[serde(skip_serializing_if = "Vec::is_empty")] + pub recommendations: Vec, } /// 场景检索请求 (用于方案详情到场景检索的集成) diff --git a/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs b/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs index 93bb4de..0771d96 100644 --- a/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs +++ b/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs @@ -18,6 +18,13 @@ use crate::data::models::conversation::{ }; use crate::data::repositories::conversation_repository::ConversationRepository; +// 导入穿搭推荐相关模块 +use crate::data::models::outfit_recommendation::{ + OutfitRecommendation, OutfitRecommendationRequest, OutfitRecommendationResponse, + OutfitRecommendationGroup, GroupingStrategy, OutfitQualityScore, + ColorInfo, OutfitItem, SceneRecommendation +}; + // 导入穿搭方案推荐相关模块 use crate::data::models::outfit_recommendation::{ OutfitRecommendation, OutfitRecommendationRequest, OutfitRecommendationResponse, @@ -1738,17 +1745,27 @@ impl GeminiService { let raw_response = self.parse_gemini_response_content(&result)?; // 解析穿搭方案 - match self.parse_outfit_recommendations(&raw_response, request) { - Ok(recommendations) => { + match self.parse_outfit_recommendation_groups(&raw_response, request) { + Ok((grouping_strategy, groups)) => { let generation_time = start_time.elapsed().as_millis() as u64; - println!("✅ 穿搭方案生成完成,共生成 {} 个方案", recommendations.len()); + // 计算总方案数 + let total_recommendations: usize = groups.iter().map(|g| g.children.len()).sum(); + + println!("✅ 穿搭方案生成完成,共生成 {} 个分组,{} 个方案", groups.len(), total_recommendations); + + // 为向后兼容,将所有方案平铺到recommendations字段 + let all_recommendations: Vec = groups.iter() + .flat_map(|group| group.children.clone()) + .collect(); return Ok(OutfitRecommendationResponse { - recommendations, + grouping_strategy, + groups, generation_time_ms: generation_time, generated_at: chrono::Utc::now(), prompt_used: Some(prompt), + recommendations: all_recommendations, }); } Err(parse_error) => { @@ -1816,74 +1833,221 @@ impl GeminiService { prompt.push_str(&format!(" ## 输出要求 -请生成 {} 个不同风格的穿搭方案,以JSON格式返回,结构如下: +请根据用户需求智能分析,将 {} 个穿搭方案按照最合适的维度进行分组(如风格、场合、季节、色彩等),以JSON格式返回分组结构: ```json {{ - \"recommendations\": [ + \"grouping_strategy\": {{ + \"primary_dimension\": \"Style|Occasion|Season|Color|Budget|Complexity\", + \"reasoning\": \"选择此分组方式的原因说明\" + }}, + \"groups\": [ {{ - \"id\": \"outfit_001\", - \"title\": \"方案标题\", - \"description\": \"详细的穿搭描述,突出亮点和特色\", - \"overall_style\": \"整体风格\", - \"style_tags\": [\"风格标签1\", \"风格标签2\"], - \"occasions\": [\"适合场合1\", \"适合场合2\"], - \"seasons\": [\"适合季节\"], - \"items\": [ + \"group\": \"分组名称\", + \"description\": \"分组描述\", + \"group_id\": \"group_001\", + \"style_keywords\": [\"关键词1\", \"关键词2\"], + \"can_load_more\": true, + \"children\": [ {{ - \"category\": \"上装\", - \"description\": \"具体单品描述\", - \"primary_color\": {{ - \"name\": \"颜色名称\", - \"hex\": \"#FFFFFF\", - \"hsv\": [0.0, 0.0, 1.0] + \"id\": \"outfit_001\", + \"title\": \"方案标题\", + \"description\": \"详细的穿搭描述,突出亮点和特色\", + \"overall_style\": \"整体风格\", + \"style_tags\": [\"风格标签1\", \"风格标签2\"], + \"occasions\": [\"适合场合1\", \"适合场合2\"], + \"seasons\": [\"适合季节\"], + \"quality_score\": {{ + \"ai_confidence_score\": 0.9, + \"trend_score\": 0.8, + \"versatility_score\": 0.7, + \"difficulty_level\": \"Beginner|Intermediate|Advanced\", + \"overall_recommendation_score\": 0.85 }}, - \"secondary_color\": {{ - \"name\": \"次要颜色\", - \"hex\": \"#000000\", - \"hsv\": [0.0, 0.0, 0.0] - }}, - \"material\": \"材质\", - \"style_tags\": [\"单品风格标签\"] + \"items\": [ + {{ + \"category\": \"上装\", + \"description\": \"具体单品描述\", + \"primary_color\": {{ + \"name\": \"颜色名称\", + \"hex\": \"#FFFFFF\", + \"hsv\": [0.0, 0.0, 1.0] + }}, + \"secondary_color\": {{ + \"name\": \"次要颜色\", + \"hex\": \"#000000\", + \"hsv\": [0.0, 0.0, 0.0] + }}, + \"material\": \"材质\", + \"style_tags\": [\"单品风格标签\"] + }} + ], + \"color_theme\": \"色彩搭配主题\", + \"primary_colors\": [ + {{ + \"name\": \"主色调名称\", + \"hex\": \"#FFFFFF\", + \"hsv\": [0.0, 0.0, 1.0] + }} + ], + \"scene_recommendations\": [ + {{ + \"name\": \"推荐场景名称\", + \"description\": \"场景详细描述\", + \"scene_type\": \"室内或室外或特殊\", + \"time_of_day\": [\"时间段\"], + \"lighting\": \"光线条件描述\", + \"photography_tips\": [\"拍摄建议1\", \"拍摄建议2\"] + }} + ], + \"tiktok_tips\": [\"TikTok优化建议1\", \"TikTok优化建议2\"], + \"styling_tips\": [\"搭配要点1\", \"搭配要点2\"] }} - ], - \"color_theme\": \"色彩搭配主题\", - \"primary_colors\": [ - {{ - \"name\": \"主色调名称\", - \"hex\": \"#FFFFFF\", - \"hsv\": [0.0, 0.0, 1.0] - }} - ], - \"scene_recommendations\": [ - {{ - \"name\": \"推荐场景名称\", - \"description\": \"场景详细描述\", - \"scene_type\": \"室内或室外或特殊\", - \"time_of_day\": [\"时间段\"], - \"lighting\": \"光线条件描述\", - \"photography_tips\": [\"拍摄建议1\", \"拍摄建议2\"] - }} - ], - \"tiktok_tips\": [\"TikTok优化建议1\", \"TikTok优化建议2\"], - \"styling_tips\": [\"搭配要点1\", \"搭配要点2\"] + ] }} ] }} ``` 请确保: -1. 每个方案都有独特的风格定位 -2. 色彩搭配符合当前流行趋势 -3. 场景推荐具有强烈的视觉冲击力 -4. TikTok优化建议实用且具体 -5. 返回的是有效的JSON格式 +1. **智能分组**: 根据用户查询内容选择最合适的分组维度(风格/场合/季节/色彩等) +2. **分组均衡**: 每个分组包含2-4个方案,分组数量控制在2-4个 +3. **质量评分**: 为每个方案提供准确的质量评分(0-1范围) +4. **风格差异**: 同组内方案风格相近,不同组间有明显差异 +5. **关键词提取**: 为每个分组提供3-5个核心风格关键词 +6. **可扩展性**: 每个分组都支持后续获取更多同类方案 +7. **TikTok优化**: 所有建议都要考虑短视频平台特性 +8. **JSON格式**: 返回严格有效的JSON格式 ", request.count)); prompt } - /// 解析穿搭方案推荐响应 + /// 解析穿搭方案分组响应 + fn parse_outfit_recommendation_groups(&self, raw_response: &str, _request: &OutfitRecommendationRequest) -> Result<(GroupingStrategy, Vec)> { + println!("🔍 开始解析穿搭方案分组响应..."); + println!("📄 原始响应: {}", raw_response); + + // 尝试提取JSON部分 + let json_str = self.extract_json_from_response(raw_response)?; + + // 使用容错JSON解析器 + let config = ParserConfig { + max_text_length: 1024 * 1024, + enable_comments: true, + enable_unquoted_keys: true, + enable_trailing_commas: true, + timeout_ms: 30000, + recovery_strategies: vec![ + RecoveryStrategy::StandardJson, + RecoveryStrategy::ManualFix, + RecoveryStrategy::RegexExtract, + RecoveryStrategy::PartialParse, + ], + }; + + let mut parser = TolerantJsonParser::new(Some(config))?; + + match parser.parse(&json_str) { + Ok((parsed, _stats)) => { + // 解析分组策略 + let grouping_strategy = if let Some(strategy_obj) = parsed.get("grouping_strategy") { + GroupingStrategy { + primary_dimension: strategy_obj.get("primary_dimension") + .and_then(|v| v.as_str()) + .unwrap_or("Style") + .to_string(), + reasoning: strategy_obj.get("reasoning") + .and_then(|v| v.as_str()) + .unwrap_or("基于用户查询自动选择分组策略") + .to_string(), + } + } else { + GroupingStrategy { + primary_dimension: "Style".to_string(), + reasoning: "默认按风格分组".to_string(), + } + }; + + // 解析分组 + let mut groups = Vec::new(); + if let Some(groups_array) = parsed.get("groups").and_then(|v| v.as_array()) { + for (group_index, group_value) in groups_array.iter().enumerate() { + match self.parse_single_outfit_group(group_value, group_index) { + Ok(group) => groups.push(group), + Err(e) => { + println!("⚠️ 解析第{}个分组失败: {}", group_index + 1, e); + continue; + } + } + } + } + + if groups.is_empty() { + return Err(anyhow!("未能解析出任何有效的穿搭方案分组")); + } + + println!("✅ 成功解析 {} 个分组", groups.len()); + Ok((grouping_strategy, groups)) + } + Err(e) => { + println!("❌ JSON解析失败: {}", e); + Err(anyhow!("JSON解析失败: {}", e)) + } + } + } + + /// 解析单个穿搭方案分组 + fn parse_single_outfit_group(&self, value: &serde_json::Value, index: usize) -> Result { + let group = value.get("group") + .and_then(|v| v.as_str()) + .unwrap_or(&format!("分组{}", index + 1)) + .to_string(); + + let description = value.get("description") + .and_then(|v| v.as_str()) + .unwrap_or("精选穿搭方案") + .to_string(); + + let group_id = value.get("group_id") + .and_then(|v| v.as_str()) + .unwrap_or(&format!("group_{:03}", index + 1)) + .to_string(); + + let style_keywords = value.get("style_keywords") + .and_then(|v| v.as_array()) + .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect()) + .unwrap_or_default(); + + let can_load_more = value.get("can_load_more") + .and_then(|v| v.as_bool()) + .unwrap_or(true); + + // 解析子方案 + let mut children = Vec::new(); + if let Some(children_array) = value.get("children").and_then(|v| v.as_array()) { + for (child_index, child_value) in children_array.iter().enumerate() { + match self.parse_single_outfit_recommendation(child_value, child_index) { + Ok(recommendation) => children.push(recommendation), + Err(e) => { + println!("⚠️ 解析分组{}中第{}个方案失败: {}", group, child_index + 1, e); + continue; + } + } + } + } + + Ok(OutfitRecommendationGroup { + group, + description, + group_id, + style_keywords, + can_load_more, + children, + }) + } + + /// 解析穿搭方案推荐响应 (向后兼容) fn parse_outfit_recommendations(&self, raw_response: &str, request: &OutfitRecommendationRequest) -> Result> { println!("🔍 开始解析穿搭方案响应..."); println!("📄 原始响应: {}", raw_response); @@ -2022,6 +2186,37 @@ impl GeminiService { .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect()) .unwrap_or_default(); + // 解析质量评分 + let quality_score = if let Some(score_obj) = value.get("quality_score") { + OutfitQualityScore { + ai_confidence_score: score_obj.get("ai_confidence_score") + .and_then(|v| v.as_f64()) + .unwrap_or(0.8) as f32, + trend_score: score_obj.get("trend_score") + .and_then(|v| v.as_f64()) + .unwrap_or(0.7) as f32, + versatility_score: score_obj.get("versatility_score") + .and_then(|v| v.as_f64()) + .unwrap_or(0.6) as f32, + difficulty_level: score_obj.get("difficulty_level") + .and_then(|v| v.as_str()) + .unwrap_or("Intermediate") + .to_string(), + overall_recommendation_score: score_obj.get("overall_recommendation_score") + .and_then(|v| v.as_f64()) + .unwrap_or(0.7) as f32, + } + } else { + // 默认质量评分 + OutfitQualityScore { + ai_confidence_score: 0.8, + trend_score: 0.7, + versatility_score: 0.6, + difficulty_level: "Intermediate".to_string(), + overall_recommendation_score: 0.7, + } + }; + Ok(OutfitRecommendation { id, title, @@ -2037,6 +2232,7 @@ impl GeminiService { tiktok_tips, styling_tips, created_at: chrono::Utc::now(), + quality_score, }) } diff --git a/apps/desktop/src/components/outfit/OutfitRecommendationList.tsx b/apps/desktop/src/components/outfit/OutfitRecommendationList.tsx index 5a1fe05..ad5e303 100644 --- a/apps/desktop/src/components/outfit/OutfitRecommendationList.tsx +++ b/apps/desktop/src/components/outfit/OutfitRecommendationList.tsx @@ -15,6 +15,8 @@ import { EmptyState } from '../EmptyState'; * 遵循设计系统规范,提供统一的列表展示界面 */ export const OutfitRecommendationList: React.FC = ({ + groups, + groupingStrategy, recommendations, isLoading = false, error, @@ -22,6 +24,7 @@ export const OutfitRecommendationList: React.FC = onSceneSearch, onMaterialSearch, onRegenerate, + onLoadMoreForGroup, className = '', }) => { // 加载状态 @@ -131,6 +134,13 @@ export const OutfitRecommendationList: React.FC = ); } + // 确定显示模式:分组模式 vs 传统模式 + const hasGroups = groups && groups.length > 0; + const displayRecommendations = recommendations || []; + const totalRecommendations = hasGroups + ? groups.reduce((sum, group) => sum + group.children.length, 0) + : displayRecommendations.length; + // 正常状态 - 显示推荐列表 return (
@@ -145,8 +155,16 @@ export const OutfitRecommendationList: React.FC = AI穿搭推荐

- 共为您生成 {recommendations.length} 个个性化穿搭方案 + {hasGroups + ? `共为您生成 ${groups.length} 个分组,${totalRecommendations} 个个性化穿搭方案` + : `共为您生成 ${totalRecommendations} 个个性化穿搭方案` + }

+ {groupingStrategy && ( +

+ {groupingStrategy.reasoning} +

+ )}
@@ -161,21 +179,81 @@ export const OutfitRecommendationList: React.FC = )} - {/* 推荐卡片网格 */} -
- {recommendations.map((recommendation, index) => ( - - ))} -
+ {/* 分组模式显示 */} + {hasGroups ? ( +
+ {groups.map((group, groupIndex) => ( +
+ {/* 分组标题 */} +
+
+

+ {group.group} +

+

+ {group.description} +

+ {group.style_keywords.length > 0 && ( +
+ {group.style_keywords.map((keyword, index) => ( + + {keyword} + + ))} +
+ )} +
+ + {/* 获取更多按钮 */} + {group.can_load_more && onLoadMoreForGroup && ( + + )} +
+ + {/* 分组内的方案卡片 */} +
+ {group.children.map((recommendation, index) => ( + + ))} +
+
+ ))} +
+ ) : ( + /* 传统模式显示 */ +
+ {displayRecommendations.map((recommendation, index) => ( + + ))} +
+ )} {/* 底部提示 */}
diff --git a/apps/desktop/src/pages/tools/OutfitRecommendationTool.tsx b/apps/desktop/src/pages/tools/OutfitRecommendationTool.tsx index 2d60a2f..e5b2da6 100644 --- a/apps/desktop/src/pages/tools/OutfitRecommendationTool.tsx +++ b/apps/desktop/src/pages/tools/OutfitRecommendationTool.tsx @@ -10,7 +10,14 @@ import { Info } from 'lucide-react'; import OutfitRecommendationService from '../../services/outfitRecommendationService'; -import { OutfitRecommendation, STYLE_OPTIONS, OCCASION_OPTIONS, SEASON_OPTIONS } from '../../types/outfitRecommendation'; +import { + OutfitRecommendation, + OutfitRecommendationGroup, + GroupingStrategy, + STYLE_OPTIONS, + OCCASION_OPTIONS, + SEASON_OPTIONS +} from '../../types/outfitRecommendation'; import OutfitRecommendationList from '../../components/outfit/OutfitRecommendationList'; import { CustomSelect } from '../../components/CustomSelect'; import { MaterialSearchPanel, MaterialDetailModal } from '../../components/material'; @@ -28,6 +35,9 @@ const OutfitRecommendationTool: React.FC = () => { const [colorPreferences, setColorPreferences] = useState([]); const [count, setCount] = useState(3); + // 分组相关状态 + const [groups, setGroups] = useState([]); + const [groupingStrategy, setGroupingStrategy] = useState(null); const [recommendations, setRecommendations] = useState([]); const [isGenerating, setIsGenerating] = useState(false); const [error, setError] = useState(null); @@ -61,7 +71,18 @@ const OutfitRecommendationTool: React.FC = () => { count, }); - setRecommendations(response.recommendations); + // 更新分组和方案数据 + if (response.groups && response.groups.length > 0) { + setGroups(response.groups); + setGroupingStrategy(response.grouping_strategy); + // 为向后兼容,也设置recommendations + setRecommendations(response.recommendations || []); + } else { + // 向后兼容模式 + setGroups([]); + setGroupingStrategy(null); + setRecommendations(response.recommendations || []); + } } catch (err) { console.error('穿搭方案生成失败:', err); setError(err instanceof Error ? err.message : '穿搭方案生成失败'); @@ -75,6 +96,65 @@ const OutfitRecommendationTool: React.FC = () => { handleGenerate(); }, [handleGenerate]); + // 获取更多同类方案 + const handleLoadMoreForGroup = useCallback(async (groupId: string, styleKeywords: string[]) => { + try { + console.log(`🎨 获取分组 ${groupId} 的更多方案,关键词:`, styleKeywords); + + // 构建增强的查询 + const enhancedQuery = `${query} ${styleKeywords.join(' ')}`; + + const response = await OutfitRecommendationService.generateRecommendations({ + query: enhancedQuery, + target_style: targetStyle || undefined, + occasions: occasions.length > 0 ? occasions : undefined, + season: season || undefined, + color_preferences: colorPreferences.length > 0 ? colorPreferences : undefined, + count: 3, // 每次获取3个新方案 + }); + + // 找到对应的分组并添加新方案 + if (response.groups && response.groups.length > 0) { + // 如果返回了分组,合并到现有分组中 + setGroups(prevGroups => { + return prevGroups.map(group => { + if (group.group_id === groupId) { + // 找到匹配的新分组并合并方案 + const newGroup = response.groups.find(g => + g.style_keywords.some(keyword => + styleKeywords.includes(keyword) + ) + ); + if (newGroup) { + return { + ...group, + children: [...group.children, ...newGroup.children] + }; + } + } + return group; + }); + }); + } else if (response.recommendations) { + // 向后兼容:直接添加到对应分组 + setGroups(prevGroups => { + return prevGroups.map(group => { + if (group.group_id === groupId) { + return { + ...group, + children: [...group.children, ...response.recommendations] + }; + } + return group; + }); + }); + } + } catch (err) { + console.error('获取更多方案失败:', err); + setError(err instanceof Error ? err.message : '获取更多方案失败'); + } + }, [query, targetStyle, occasions, season, colorPreferences]); + // 清空表单 const handleClear = useCallback(() => { setQuery(''); @@ -83,6 +163,8 @@ const OutfitRecommendationTool: React.FC = () => { setSeason(''); setColorPreferences([]); setCount(3); + setGroups([]); + setGroupingStrategy(null); setRecommendations([]); setError(null); }, []); @@ -362,11 +444,14 @@ const OutfitRecommendationTool: React.FC = () => { {/* 右侧结果区域 */}
diff --git a/apps/desktop/src/types/outfitRecommendation.ts b/apps/desktop/src/types/outfitRecommendation.ts index cbe1e7c..e8ab422 100644 --- a/apps/desktop/src/types/outfitRecommendation.ts +++ b/apps/desktop/src/types/outfitRecommendation.ts @@ -3,6 +3,31 @@ * 遵循 Tauri 开发规范的类型安全设计 */ +// 分组策略 +export interface GroupingStrategy { + primary_dimension: string; + reasoning: string; +} + +// 方案质量评分 +export interface OutfitQualityScore { + ai_confidence_score: number; + trend_score: number; + versatility_score: number; + difficulty_level: string; + overall_recommendation_score: number; +} + +// 穿搭方案分组 +export interface OutfitRecommendationGroup { + group: string; + description: string; + group_id: string; + style_keywords: string[]; + can_load_more: boolean; + children: OutfitRecommendation[]; +} + // 色彩信息 export interface ColorInfo { /** 色彩名称 */ @@ -75,6 +100,8 @@ export interface OutfitRecommendation { styling_tips: string[]; /** 创建时间 */ created_at: string; + /** 方案质量评分 */ + quality_score: OutfitQualityScore; } // 穿搭方案生成请求 @@ -95,14 +122,20 @@ export interface OutfitRecommendationRequest { // 穿搭方案生成响应 export interface OutfitRecommendationResponse { - /** 生成的穿搭方案列表 */ - recommendations: OutfitRecommendation[]; + /** 分组策略 */ + grouping_strategy: GroupingStrategy; + /** 生成的穿搭方案分组列表 */ + groups: OutfitRecommendationGroup[]; /** 生成时间 (毫秒) */ generation_time_ms: number; /** 生成时间戳 */ generated_at: string; /** 使用的提示词 (调试用) */ prompt_used?: string; + + // 保持向后兼容性 + /** 生成的穿搭方案列表 (向后兼容) */ + recommendations?: OutfitRecommendation[]; } // 场景检索请求 (用于方案详情到场景检索的集成) @@ -151,8 +184,12 @@ export interface OutfitRecommendationCardProps { // 穿搭方案列表组件属性 export interface OutfitRecommendationListProps { - /** 穿搭方案列表 */ - recommendations: OutfitRecommendation[]; + /** 穿搭方案分组列表 */ + groups?: OutfitRecommendationGroup[]; + /** 分组策略 */ + groupingStrategy?: GroupingStrategy; + /** 穿搭方案列表 (向后兼容) */ + recommendations?: OutfitRecommendation[]; /** 是否正在加载 */ isLoading?: boolean; /** 错误信息 */ @@ -165,6 +202,8 @@ export interface OutfitRecommendationListProps { onMaterialSearch?: (recommendation: OutfitRecommendation) => void; /** 重新生成事件 */ onRegenerate?: () => void; + /** 获取更多同类方案事件 */ + onLoadMoreForGroup?: (groupId: string, styleKeywords: string[]) => void; /** 自定义样式类名 */ className?: string; }