feat: 创建AI穿搭方案推荐小工具
- 新增独立的AI穿搭方案推荐工具页面 - 集成到便捷工具列表中,提供完整的工具体验 - 支持高级设置:风格选择、场合匹配、季节偏好等 - 实现结果导出和复制功能 - 优化用户界面和交互体验 - 添加使用提示和帮助信息 功能特点: - 简洁易用的输入界面 - 可折叠的高级设置选项 - 实时生成个性化穿搭方案 - 支持JSON格式导出结果 - 一键复制穿搭建议文本 - 响应式设计,适配不同屏幕尺寸
This commit is contained in:
parent
c4bb073507
commit
f1fd62b59b
|
|
@ -19,6 +19,7 @@ import ChatTestPage from './pages/tools/ChatTestPage';
|
||||||
import WatermarkTool from './pages/tools/WatermarkTool';
|
import WatermarkTool from './pages/tools/WatermarkTool';
|
||||||
import SimilaritySearchTool from './pages/tools/SimilaritySearchTool';
|
import SimilaritySearchTool from './pages/tools/SimilaritySearchTool';
|
||||||
import BatchThumbnailGenerator from './pages/tools/BatchThumbnailGenerator';
|
import BatchThumbnailGenerator from './pages/tools/BatchThumbnailGenerator';
|
||||||
|
import OutfitRecommendationTool from './pages/tools/OutfitRecommendationTool';
|
||||||
|
|
||||||
import Navigation from './components/Navigation';
|
import Navigation from './components/Navigation';
|
||||||
import { NotificationSystem, useNotifications } from './components/NotificationSystem';
|
import { NotificationSystem, useNotifications } from './components/NotificationSystem';
|
||||||
|
|
@ -121,6 +122,7 @@ function App() {
|
||||||
<Route path="/tools/watermark" element={<WatermarkTool />} />
|
<Route path="/tools/watermark" element={<WatermarkTool />} />
|
||||||
<Route path="/tools/similarity-search" element={<SimilaritySearchTool />} />
|
<Route path="/tools/similarity-search" element={<SimilaritySearchTool />} />
|
||||||
<Route path="/tools/batch-thumbnail-generator" element={<BatchThumbnailGenerator />} />
|
<Route path="/tools/batch-thumbnail-generator" element={<BatchThumbnailGenerator />} />
|
||||||
|
<Route path="/tools/outfit-recommendation" element={<OutfitRecommendationTool />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ import {
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
Droplets,
|
Droplets,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
Search
|
Search,
|
||||||
|
Sparkles
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Tool, ToolCategory, ToolStatus } from '../types/tool';
|
import { Tool, ToolCategory, ToolStatus } from '../types/tool';
|
||||||
|
|
||||||
|
|
@ -117,6 +118,21 @@ export const TOOLS_DATA: Tool[] = [
|
||||||
isPopular: true,
|
isPopular: true,
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
lastUpdated: '2024-01-25'
|
lastUpdated: '2024-01-25'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'outfit-recommendation',
|
||||||
|
name: 'AI穿搭方案推荐',
|
||||||
|
description: '基于TikTok视觉趋势的智能穿搭建议工具,提供个性化的时尚搭配方案',
|
||||||
|
longDescription: '专业的AI穿搭顾问工具,基于TikTok视觉趋势和时尚潮流,为用户生成个性化的穿搭方案。支持多种风格选择、场合匹配、色彩搭配建议,并提供TikTok优化建议和拍摄技巧,助力内容创作和时尚搭配。',
|
||||||
|
icon: Sparkles,
|
||||||
|
route: '/tools/outfit-recommendation',
|
||||||
|
category: ToolCategory.AI_TOOLS,
|
||||||
|
status: ToolStatus.STABLE,
|
||||||
|
tags: ['AI穿搭', '时尚搭配', 'TikTok', '个性化推荐', '视觉趋势'],
|
||||||
|
isNew: true,
|
||||||
|
isPopular: true,
|
||||||
|
version: '1.0.0',
|
||||||
|
lastUpdated: '2024-01-25'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,361 @@
|
||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Sparkles,
|
||||||
|
ArrowLeft,
|
||||||
|
Wand2,
|
||||||
|
Shirt,
|
||||||
|
Download,
|
||||||
|
Copy,
|
||||||
|
RefreshCw,
|
||||||
|
Settings,
|
||||||
|
Info
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import OutfitRecommendationService from '../../services/outfitRecommendationService';
|
||||||
|
import { OutfitRecommendation, STYLE_OPTIONS, OCCASION_OPTIONS, SEASON_OPTIONS } from '../../types/outfitRecommendation';
|
||||||
|
import OutfitRecommendationList from '../../components/outfit/OutfitRecommendationList';
|
||||||
|
import { CustomSelect } from '../../components/CustomSelect';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI穿搭方案推荐小工具
|
||||||
|
* 遵循 Tauri 开发规范和 UI/UX 设计标准
|
||||||
|
*/
|
||||||
|
const OutfitRecommendationTool: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
const [targetStyle, setTargetStyle] = useState<string>('');
|
||||||
|
const [occasions, setOccasions] = useState<string[]>([]);
|
||||||
|
const [season, setSeason] = useState<string>('');
|
||||||
|
const [colorPreferences, setColorPreferences] = useState<string[]>([]);
|
||||||
|
const [count, setCount] = useState(3);
|
||||||
|
|
||||||
|
const [recommendations, setRecommendations] = useState<OutfitRecommendation[]>([]);
|
||||||
|
const [isGenerating, setIsGenerating] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||||
|
|
||||||
|
// 返回工具列表
|
||||||
|
const handleBackToTools = useCallback(() => {
|
||||||
|
navigate('/tools');
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
// 生成穿搭方案
|
||||||
|
const handleGenerate = useCallback(async () => {
|
||||||
|
if (!query.trim()) {
|
||||||
|
setError('请输入穿搭关键词或描述');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsGenerating(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await OutfitRecommendationService.generateRecommendations({
|
||||||
|
query: query.trim(),
|
||||||
|
target_style: targetStyle || undefined,
|
||||||
|
occasions: occasions.length > 0 ? occasions : undefined,
|
||||||
|
season: season || undefined,
|
||||||
|
color_preferences: colorPreferences.length > 0 ? colorPreferences : undefined,
|
||||||
|
count,
|
||||||
|
});
|
||||||
|
|
||||||
|
setRecommendations(response.recommendations);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('穿搭方案生成失败:', err);
|
||||||
|
setError(err instanceof Error ? err.message : '穿搭方案生成失败');
|
||||||
|
} finally {
|
||||||
|
setIsGenerating(false);
|
||||||
|
}
|
||||||
|
}, [query, targetStyle, occasions, season, colorPreferences, count]);
|
||||||
|
|
||||||
|
// 重新生成
|
||||||
|
const handleRegenerate = useCallback(() => {
|
||||||
|
handleGenerate();
|
||||||
|
}, [handleGenerate]);
|
||||||
|
|
||||||
|
// 清空表单
|
||||||
|
const handleClear = useCallback(() => {
|
||||||
|
setQuery('');
|
||||||
|
setTargetStyle('');
|
||||||
|
setOccasions([]);
|
||||||
|
setSeason('');
|
||||||
|
setColorPreferences([]);
|
||||||
|
setCount(3);
|
||||||
|
setRecommendations([]);
|
||||||
|
setError(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 导出结果
|
||||||
|
const handleExport = useCallback(() => {
|
||||||
|
if (recommendations.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportData = {
|
||||||
|
query,
|
||||||
|
generated_at: new Date().toISOString(),
|
||||||
|
recommendations: recommendations.map(rec => ({
|
||||||
|
title: rec.title,
|
||||||
|
description: rec.description,
|
||||||
|
overall_style: rec.overall_style,
|
||||||
|
style_tags: rec.style_tags,
|
||||||
|
occasions: rec.occasions,
|
||||||
|
seasons: rec.seasons,
|
||||||
|
color_theme: rec.color_theme,
|
||||||
|
items: rec.items,
|
||||||
|
tiktok_tips: rec.tiktok_tips,
|
||||||
|
styling_tips: rec.styling_tips,
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `outfit-recommendations-${Date.now()}.json`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, [recommendations, query]);
|
||||||
|
|
||||||
|
// 复制结果
|
||||||
|
const handleCopy = useCallback(() => {
|
||||||
|
if (recommendations.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = recommendations.map(rec =>
|
||||||
|
`${rec.title}\n${rec.description}\n风格: ${rec.style_tags.join(', ')}\n\n`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
}, [recommendations]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-white">
|
||||||
|
{/* 顶部导航 */}
|
||||||
|
<div className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b border-gray-200">
|
||||||
|
<div className="max-w-7xl mx-auto px-6 py-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<button
|
||||||
|
onClick={handleBackToTools}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors duration-200"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4" />
|
||||||
|
<span className="text-sm font-medium">返回工具</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="h-6 w-px bg-gray-300"></div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-pink-500 rounded-xl flex items-center justify-center shadow-lg">
|
||||||
|
<Sparkles className="w-5 h-5 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl font-bold text-gray-900">AI穿搭方案推荐</h1>
|
||||||
|
<p className="text-sm text-gray-600">基于TikTok视觉趋势的智能穿搭建议</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{recommendations.length > 0 && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={handleCopy}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors duration-200"
|
||||||
|
title="复制结果"
|
||||||
|
>
|
||||||
|
<Copy className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleExport}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors duration-200"
|
||||||
|
title="导出结果"
|
||||||
|
>
|
||||||
|
<Download className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||||
|
className={`flex items-center gap-2 px-3 py-2 rounded-lg transition-colors duration-200 ${
|
||||||
|
showAdvanced
|
||||||
|
? 'text-primary-600 bg-primary-50'
|
||||||
|
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
|
||||||
|
}`}
|
||||||
|
title="高级设置"
|
||||||
|
>
|
||||||
|
<Settings className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 主要内容 */}
|
||||||
|
<div className="max-w-7xl mx-auto px-6 py-8">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
{/* 左侧输入区域 */}
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<div className="card p-6 sticky top-24">
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* 基础输入 */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-semibold text-gray-900 mb-3">
|
||||||
|
<Shirt className="w-4 h-4 inline mr-2" />
|
||||||
|
穿搭描述
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
placeholder="描述您想要的穿搭风格,如:休闲约会装、正式商务装、街头潮流风等..."
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent resize-none"
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 高级设置 */}
|
||||||
|
{showAdvanced && (
|
||||||
|
<div className="space-y-4 animate-fade-in">
|
||||||
|
<div className="border-t border-gray-200 pt-4">
|
||||||
|
<h3 className="text-sm font-semibold text-gray-900 mb-3">高级设置</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 目标风格 */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">目标风格</label>
|
||||||
|
<CustomSelect
|
||||||
|
value={targetStyle}
|
||||||
|
onChange={setTargetStyle}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: '不限制' },
|
||||||
|
...STYLE_OPTIONS.map(style => ({ value: style, label: style }))
|
||||||
|
]}
|
||||||
|
placeholder="选择风格"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 适合场合 */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">适合场合</label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
{OCCASION_OPTIONS.map(occasion => (
|
||||||
|
<label key={occasion} className="flex items-center gap-2 text-sm">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={occasions.includes(occasion)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setOccasions([...occasions, occasion]);
|
||||||
|
} else {
|
||||||
|
setOccasions(occasions.filter(o => o !== occasion));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
|
/>
|
||||||
|
{occasion}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 季节 */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">季节</label>
|
||||||
|
<CustomSelect
|
||||||
|
value={season}
|
||||||
|
onChange={setSeason}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: '不限制' },
|
||||||
|
...SEASON_OPTIONS.map(s => ({ value: s, label: s }))
|
||||||
|
]}
|
||||||
|
placeholder="选择季节"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 生成数量 */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">生成数量</label>
|
||||||
|
<select
|
||||||
|
value={count}
|
||||||
|
onChange={(e) => setCount(Number(e.target.value))}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||||
|
>
|
||||||
|
<option value={1}>1个方案</option>
|
||||||
|
<option value={2}>2个方案</option>
|
||||||
|
<option value={3}>3个方案</option>
|
||||||
|
<option value={5}>5个方案</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 操作按钮 */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<button
|
||||||
|
onClick={handleGenerate}
|
||||||
|
disabled={!query.trim() || isGenerating}
|
||||||
|
className="w-full btn btn-primary flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
{isGenerating ? (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="w-4 h-4 animate-spin" />
|
||||||
|
生成中...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Wand2 className="w-4 h-4" />
|
||||||
|
生成穿搭方案
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleClear}
|
||||||
|
className="w-full btn btn-outline"
|
||||||
|
>
|
||||||
|
清空重置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 使用提示 */}
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<Info className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
|
||||||
|
<div className="text-sm text-blue-800">
|
||||||
|
<p className="font-medium mb-1">使用提示</p>
|
||||||
|
<ul className="space-y-1 text-xs">
|
||||||
|
<li>• 描述越详细,生成的方案越精准</li>
|
||||||
|
<li>• 可以指定场合、风格、颜色偏好</li>
|
||||||
|
<li>• 每个方案都包含TikTok优化建议</li>
|
||||||
|
<li>• 支持导出和复制结果</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧结果区域 */}
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<OutfitRecommendationList
|
||||||
|
recommendations={recommendations}
|
||||||
|
isLoading={isGenerating}
|
||||||
|
error={error || undefined}
|
||||||
|
onRegenerate={handleRegenerate}
|
||||||
|
className="min-h-[600px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OutfitRecommendationTool;
|
||||||
Loading…
Reference in New Issue