feat: 实现AI穿搭方案智能分组功能

- 修改提示词让AI直接返回分组结构
- 添加GroupingStrategy和OutfitQualityScore数据结构
- 支持按风格、场合、季节等维度智能分组
- 为每个方案添加质量评分系统
- 前端支持分组展示和获取更多同类方案
- 保持向后兼容性

主要变更:
- 后端: 更新提示词和解析逻辑支持分组JSON结构
- 前端: OutfitRecommendationList支持分组显示
- 类型: 新增分组相关TypeScript接口
- 功能: 每个分组支持'获取更多'按钮扩展方案
This commit is contained in:
imeepos 2025-07-28 11:26:13 +08:00
parent 9a764d60dc
commit d33f7fbc7f
5 changed files with 526 additions and 78 deletions

View File

@ -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<String>,
/// 是否可以加载更多方案
pub can_load_more: bool,
/// 分组下的方案列表
pub children: Vec<OutfitRecommendation>,
}
/// 色彩信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColorInfo {
@ -77,6 +118,8 @@ pub struct OutfitRecommendation {
pub styling_tips: Vec<String>,
/// 创建时间
pub created_at: DateTime<Utc>,
/// 方案质量评分
pub quality_score: OutfitQualityScore,
}
/// 穿搭方案生成请求
@ -99,14 +142,21 @@ pub struct OutfitRecommendationRequest {
/// 穿搭方案生成响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutfitRecommendationResponse {
/// 生成的穿搭方案列表
pub recommendations: Vec<OutfitRecommendation>,
/// 分组策略
pub grouping_strategy: GroupingStrategy,
/// 生成的穿搭方案分组列表
pub groups: Vec<OutfitRecommendationGroup>,
/// 生成时间 (毫秒)
pub generation_time_ms: u64,
/// 生成时间戳
pub generated_at: DateTime<Utc>,
/// 使用的提示词 (调试用)
pub prompt_used: Option<String>,
// 保持向后兼容性
/// 生成的穿搭方案列表 (向后兼容)
#[serde(skip_serializing_if = "Vec::is_empty")]
pub recommendations: Vec<OutfitRecommendation>,
}
/// 场景检索请求 (用于方案详情到场景检索的集成)

View File

@ -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<OutfitRecommendation> = 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-42-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<OutfitRecommendationGroup>)> {
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<OutfitRecommendationGroup> {
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<Vec<OutfitRecommendation>> {
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,
})
}

View File

@ -15,6 +15,8 @@ import { EmptyState } from '../EmptyState';
*
*/
export const OutfitRecommendationList: React.FC<OutfitRecommendationListProps> = ({
groups,
groupingStrategy,
recommendations,
isLoading = false,
error,
@ -22,6 +24,7 @@ export const OutfitRecommendationList: React.FC<OutfitRecommendationListProps> =
onSceneSearch,
onMaterialSearch,
onRegenerate,
onLoadMoreForGroup,
className = '',
}) => {
// 加载状态
@ -131,6 +134,13 @@ export const OutfitRecommendationList: React.FC<OutfitRecommendationListProps> =
);
}
// 确定显示模式:分组模式 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 (
<div className={`space-y-6 ${className}`}>
@ -145,8 +155,16 @@ export const OutfitRecommendationList: React.FC<OutfitRecommendationListProps> =
AI穿搭推荐
</h3>
<p className="text-sm text-medium-emphasis">
{recommendations.length} 穿
{hasGroups
? `共为您生成 ${groups.length} 个分组,${totalRecommendations} 个个性化穿搭方案`
: `共为您生成 ${totalRecommendations} 个个性化穿搭方案`
}
</p>
{groupingStrategy && (
<p className="text-xs text-low-emphasis mt-1">
{groupingStrategy.reasoning}
</p>
)}
</div>
</div>
@ -161,21 +179,81 @@ export const OutfitRecommendationList: React.FC<OutfitRecommendationListProps> =
)}
</div>
{/* 推荐卡片网格 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{recommendations.map((recommendation, index) => (
<OutfitRecommendationCard
key={recommendation.id || index}
recommendation={recommendation}
onSelect={onRecommendationSelect}
onSceneSearch={onSceneSearch}
onMaterialSearch={onMaterialSearch}
showDetails={true}
compact={false}
className="animate-fade-in-up"
/>
))}
</div>
{/* 分组模式显示 */}
{hasGroups ? (
<div className="space-y-8">
{groups.map((group, groupIndex) => (
<div key={group.group_id || groupIndex} className="space-y-4">
{/* 分组标题 */}
<div className="flex items-center justify-between">
<div>
<h4 className="text-lg font-semibold text-high-emphasis">
{group.group}
</h4>
<p className="text-sm text-medium-emphasis">
{group.description}
</p>
{group.style_keywords.length > 0 && (
<div className="flex flex-wrap gap-1 mt-2">
{group.style_keywords.map((keyword, index) => (
<span
key={index}
className="px-2 py-1 text-xs bg-primary-50 text-primary-600 rounded-full"
>
{keyword}
</span>
))}
</div>
)}
</div>
{/* 获取更多按钮 */}
{group.can_load_more && onLoadMoreForGroup && (
<button
onClick={() => onLoadMoreForGroup(group.group_id, group.style_keywords)}
className="flex items-center gap-2 px-3 py-2 text-sm text-primary-600 hover:text-primary-700 hover:bg-primary-50 rounded-lg transition-all duration-200"
>
<ShoppingBag className="w-4 h-4" />
<span></span>
</button>
)}
</div>
{/* 分组内的方案卡片 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{group.children.map((recommendation, index) => (
<OutfitRecommendationCard
key={recommendation.id || `${groupIndex}-${index}`}
recommendation={recommendation}
onSelect={onRecommendationSelect}
onSceneSearch={onSceneSearch}
onMaterialSearch={onMaterialSearch}
showDetails={true}
compact={false}
className="animate-fade-in-up"
/>
))}
</div>
</div>
))}
</div>
) : (
/* 传统模式显示 */
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{displayRecommendations.map((recommendation, index) => (
<OutfitRecommendationCard
key={recommendation.id || index}
recommendation={recommendation}
onSelect={onRecommendationSelect}
onSceneSearch={onSceneSearch}
onMaterialSearch={onMaterialSearch}
showDetails={true}
compact={false}
className="animate-fade-in-up"
/>
))}
</div>
)}
{/* 底部提示 */}
<div className="card p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200">

View File

@ -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<string[]>([]);
const [count, setCount] = useState(3);
// 分组相关状态
const [groups, setGroups] = useState<OutfitRecommendationGroup[]>([]);
const [groupingStrategy, setGroupingStrategy] = useState<GroupingStrategy | null>(null);
const [recommendations, setRecommendations] = useState<OutfitRecommendation[]>([]);
const [isGenerating, setIsGenerating] = useState(false);
const [error, setError] = useState<string | null>(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 = () => {
{/* 右侧结果区域 */}
<div className="lg:col-span-3">
<OutfitRecommendationList
groups={groups}
groupingStrategy={groupingStrategy || undefined}
recommendations={recommendations}
isLoading={isGenerating}
error={error || undefined}
onRegenerate={handleRegenerate}
onMaterialSearch={handleMaterialSearch}
onLoadMoreForGroup={handleLoadMoreForGroup}
className="min-h-[600px]"
/>
</div>

View File

@ -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;
}