From 94e9f74b15abdd97e33ff0426d3c72e171f860ab Mon Sep 17 00:00:00 2001 From: imeepos Date: Mon, 21 Jul 2025 23:35:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E5=8A=9F=E8=83=BD=E5=92=8C=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 界面优化: - 修复页面头部信息重复问题,统一为时尚穿搭顾问 - 优化欢迎界面布局,移除重复标题 - 调整标签激活状态样式,使其更加subtle和优雅 标签系统增强: - 实现卡片标签与底部工具栏标签完美同步 - 修复标签选中状态的实时更新问题 - 添加气泡卡片展示所有标签详情 - 优化标签折叠功能,默认隐藏,一行布局 功能完善: - 修复输入框和发送按钮对齐问题 - 优化聊天输入框固定到底部 - 将弹框改为更优雅的气泡卡片消息 - 生成搜索和清空按钮移到外层,始终可见 导航优化: - 服装搭配导航直接链接到AI智能聊天 - 配置独立路由避免与便捷工具冲突 - 移除原有的服装搭配页面和路由 用户体验: - 标签选中状态双向同步更新 - 气泡卡片支持标签选择和实时反馈 - 优化加载状态显示和错误处理 - 完善的视觉反馈和交互动画 --- apps/desktop/src/App.tsx | 5 +- apps/desktop/src/components/ChatInterface.tsx | 541 +++++++++++------- apps/desktop/src/components/Navigation.tsx | 2 +- apps/desktop/src/pages/tools/ChatTool.tsx | 12 +- 4 files changed, 346 insertions(+), 214 deletions(-) diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 8257bc0..7748e55 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -15,7 +15,7 @@ import JsonParserTool from './pages/tools/JsonParserTool'; import DebugPanelTool from './pages/tools/DebugPanelTool'; import ChatTool from './pages/tools/ChatTool'; import ChatTestPage from './pages/tools/ChatTestPage'; -import OutfitMatch from './pages/OutfitMatch'; + import Navigation from './components/Navigation'; import { NotificationSystem, useNotifications } from './components/NotificationSystem'; import { useProjectStore } from './store/projectStore'; @@ -91,7 +91,8 @@ function App() { } /> } /> } /> - } /> + } /> + } /> } /> } /> diff --git a/apps/desktop/src/components/ChatInterface.tsx b/apps/desktop/src/components/ChatInterface.tsx index 7af075b..7d51227 100644 --- a/apps/desktop/src/components/ChatInterface.tsx +++ b/apps/desktop/src/components/ChatInterface.tsx @@ -10,7 +10,6 @@ import { Clock, CheckCircle, XCircle, - Terminal, Copy, ExternalLink, Tag, @@ -19,7 +18,7 @@ import { MapPin } from 'lucide-react'; import { queryRagGrounding } from '../services/ragGroundingService'; -import { RagGroundingQueryOptions, RagGroundingResponse } from '../types/ragGrounding'; +import { RagGroundingQueryOptions } from '../types/ragGrounding'; /** * 聊天消息接口 @@ -77,6 +76,10 @@ export const ChatInterface: React.FC = ({ const [showAnswers, setShowAnswers] = useState>({}); const [selectedTags, setSelectedTags] = useState([]); const [allTags, setAllTags] = useState([]); + const [isTagsExpanded, setIsTagsExpanded] = useState(false); + const [showTagBubble, setShowTagBubble] = useState(false); + const [bubbleTags, setBubbleTags] = useState<{categories: string[], environment: string[]}>({categories: [], environment: []}); + const [bubblePosition, setBubblePosition] = useState<{x: number, y: number}>({x: 0, y: 0}); const inputRef = useRef(null); const chatContainerRef = useRef(null); @@ -192,17 +195,37 @@ export const ChatInterface: React.FC = ({ {/* 分类标签 */} {imageData.categories.length > 0 && (
- {imageData.categories.slice(0, 4).map((category: string, catIndex: number) => ( - - - {category} - - ))} + {imageData.categories.slice(0, 4).map((category: string, catIndex: number) => { + const isSelected = selectedTags.includes(category); + return ( + + ); + })} {imageData.categories.length > 4 && ( - +{imageData.categories.length - 4} + )}
)} @@ -210,15 +233,27 @@ export const ChatInterface: React.FC = ({ {/* 环境标签 */} {imageData.environment_tags.length > 0 && (
- {imageData.environment_tags.slice(0, 3).map((tag: string, tagIndex: number) => ( - - - {tag} - - ))} + {imageData.environment_tags.slice(0, 3).map((tag: string, tagIndex: number) => { + const isSelected = selectedTags.includes(tag); + return ( + + ); + })}
)} @@ -253,7 +288,7 @@ export const ChatInterface: React.FC = ({ ); - }, [parseImageContent]); + }, [parseImageContent, selectedTags]); // 生成消息ID const generateMessageId = useCallback(() => { @@ -448,11 +483,11 @@ export const ChatInterface: React.FC = ({ const imageData = parseImageContent(source.content); if (imageData) { // 添加分类标签 - imageData.categories.forEach(tag => tagSet.add(tag)); + imageData.categories.forEach((tag: string) => tagSet.add(tag)); // 添加环境标签 - imageData.environment_tags.forEach(tag => tagSet.add(tag)); + imageData.environment_tags.forEach((tag: string) => tagSet.add(tag)); // 添加模特相关标签 - imageData.models.forEach(model => { + imageData.models.forEach((model: any) => { if (model.products) { model.products.forEach((product: any) => { if (product.category) { @@ -471,17 +506,44 @@ export const ChatInterface: React.FC = ({ // 当消息更新时,更新标签列表 useEffect(() => { - const tags = extractAllTags(); - setAllTags(tags); - }, [extractAllTags]); + const extractedTags = extractAllTags(); + // 合并提取的标签和已选择的标签,确保所有选中的标签都在列表中 + const combinedTags = [...new Set([...extractedTags, ...selectedTags])]; + setAllTags(combinedTags); + }, [extractAllTags, selectedTags]); // 切换标签选择状态 const toggleTag = useCallback((tag: string) => { - setSelectedTags(prev => - prev.includes(tag) + setSelectedTags(prev => { + const newTags = prev.includes(tag) ? prev.filter(t => t !== tag) - : [...prev, tag] - ); + : [...prev, tag]; + return newTags; + }); + }, []); + + // 切换标签展开状态 + const toggleTagsExpanded = useCallback(() => { + setIsTagsExpanded(prev => !prev); + }, []); + + // 显示标签详情气泡 + const showTagDetails = useCallback((imageData: any, event: React.MouseEvent) => { + const rect = event.currentTarget.getBoundingClientRect(); + setBubblePosition({ + x: rect.left + rect.width / 2, + y: rect.top - 10 + }); + setBubbleTags({ + categories: imageData.categories || [], + environment: imageData.environment_tags || [] + }); + setShowTagBubble(true); + }, []); + + // 关闭标签气泡 + const closeTagBubble = useCallback(() => { + setShowTagBubble(false); }, []); // 清空选中的标签 @@ -534,122 +596,31 @@ export const ChatInterface: React.FC = ({ const handleCopyMessage = useCallback(async (message: ChatMessage) => { try { await navigator.clipboard.writeText(message.content); - console.log('📋 已复制到剪贴板:', message.content); - - // 如果是AI回复,同时在控制台显示详细信息 - if (message.type === 'assistant' && message.metadata) { - console.group('📋 复制的AI回复详情'); - console.log('💬 回复内容:', message.content); - console.log('⏱️ 响应时间:', message.metadata.responseTime + 'ms'); - console.log('🤖 使用模型:', message.metadata.modelUsed); - console.log('🕐 回复时间:', message.timestamp.toLocaleString()); - - if (message.metadata.sources && message.metadata.sources.length > 0) { - console.log('📚 参考来源:'); - message.metadata.sources.forEach((source, index) => { - console.log(` ${index + 1}. ${source.title}`); - if (source.content) { - console.log(` 内容: ${JSON.stringify(source.content, null, 2)}`); - } - if (source.uri) { - console.log(` 链接: ${source.uri}`); - } - }); - } - console.groupEnd(); - } + console.log('✅ 已复制到剪贴板'); } catch (err) { console.error('❌ 复制失败:', err); } }, []); - // 在控制台显示消息详情 - const handleShowMessageDetails = useCallback((message: ChatMessage) => { - if (message.type === 'assistant') { - console.group('🔍 AI消息详情'); - console.log('💬 回复内容:', message.content); - console.log('🕐 时间戳:', message.timestamp.toLocaleString()); - console.log('📊 状态:', message.status); - if (message.metadata) { - console.log('⏱️ 响应时间:', message.metadata.responseTime + 'ms'); - console.log('🤖 使用模型:', message.metadata.modelUsed); - - if (message.metadata.sources && message.metadata.sources.length > 0) { - console.log('📚 参考来源 (' + message.metadata.sources.length + ' 条):'); - message.metadata.sources.forEach((source, index) => { - console.log(` 📖 ${index + 1}. ${source.title}`); - if (source.content) { - console.log(` 📄 内容: ${JSON.stringify(source.content, null, 2)}`); - } - if (source.uri) { - console.log(` 🔗 链接: ${source.uri}`); - } - }); - } - } - console.groupEnd(); - } else { - console.group('👤 用户消息详情'); - console.log('💬 消息内容:', message.content); - console.log('🕐 时间戳:', message.timestamp.toLocaleString()); - console.log('📊 状态:', message.status); - console.groupEnd(); - } - }, []); return ( -
- {/* 聊天头部 */} -
-
-
- -
-
-

AI 智能助手

-

基于 RAG 检索增强生成

-
-
- -
- {messages.length > 0 && ( - - )} -
-
- - 保留最新 {maxMessages} 条对话 -
-
- - 详细日志请查看浏览器控制台 -
-
-
-
- +
{/* 聊天消息区域 */}
0 ? '200px' : '120px' }} > {messages.length === 0 ? (
-
- +
+
-

时尚穿搭顾问

-

+

我是您的专属时尚顾问,基于丰富的女装穿搭知识库,为您提供个性化的搭配建议和时尚指导。

-
+
{[ '夏日清新穿搭推荐', '职场女性如何搭配?', @@ -756,18 +727,11 @@ export const ChatInterface: React.FC = ({
-
@@ -796,7 +760,9 @@ export const ChatInterface: React.FC = ({ : message.metadata.sources.slice(0, defaultDisplayCount); return displaySources.map((source, index) => - renderImageCard(source, index) +
+ {renderImageCard(source, index)} +
); })()}
@@ -856,64 +822,115 @@ export const ChatInterface: React.FC = ({
)} - {/* 标签选择区域 */} - {allTags.length > 0 && ( -
-
-
- - - 穿搭标签 ({selectedTags.length} 已选) - -
-
- {selectedTags.length > 0 && ( - - )} - {selectedTags.length > 0 && ( - - )} -
-
+ {/* 固定底部输入区域 */} +
+ {/* 标签选择区域 */} + {allTags.length > 0 && ( +
+ {/* 标签头部 - 始终显示 */} +
+
+ {/* 左侧:标签标题和已选标签 */} +
+ + + 穿搭标签 ({selectedTags.length} 已选) + - {/* 标签列表 */} -
- {allTags.map((tag, index) => { - const isSelected = selectedTags.includes(tag); - return ( - - ); - })} -
-
- )} +
- {/* 输入区域 */} -
-
+ {/* 右侧:操作按钮和展开按钮 */} +
+ {/* 操作按钮 */} + {selectedTags.length > 0 && ( + <> + + + + )} + + {/* 展开/折叠按钮 */} + +
+
+
+ + {/* 标签内容 - 可折叠 */} + {isTagsExpanded && ( +
+ {/* 标签列表 */} +
+ {allTags.map((tag, index) => { + const isSelected = selectedTags.includes(tag); + return ( + + ); + })} +
+
+ )} +
+ )} + + {/* 输入区域 */} +
+