diff --git a/src/app/chat/[id]/page.tsx b/src/app/chat/[id]/page.tsx index ca8d10a..6c87f07 100644 --- a/src/app/chat/[id]/page.tsx +++ b/src/app/chat/[id]/page.tsx @@ -10,6 +10,7 @@ import { MessageBubble } from '@/components/features/MessageBubble'; import { ChatHeaderInfo } from '@/components/features/ChatHeader'; import { SaveToNoteModal } from '@/components/features/SaveToNoteModal'; import { PromptOptimizer } from '@/components/features/PromptOptimizer'; +import { LinkPreviewModal } from '@/components/features/LinkPreviewModal'; import { cn } from '@/lib/utils'; import { useConversation, useConversations } from '@/hooks/useConversations'; import { useStreamChat, type ChatMessage } from '@/hooks/useStreamChat'; @@ -47,6 +48,10 @@ export default function ChatPage({ params }: PageProps) { const [noteModalOpen, setNoteModalOpen] = useState(false); const [noteContent, setNoteContent] = useState(''); + // 链接预览状态 + const [linkPreviewOpen, setLinkPreviewOpen] = useState(false); + const [linkPreviewUrl, setLinkPreviewUrl] = useState(null); + // 获取数据 const { conversation, loading: conversationLoading, error: conversationError } = useConversation(chatId); const { createConversation, updateConversation, deleteConversation } = useConversations(); @@ -342,6 +347,12 @@ export default function ChatPage({ params }: PageProps) { } }; + // 处理链接点击 - 在预览弹窗中打开 + const handleLinkClick = (url: string) => { + setLinkPreviewUrl(url); + setLinkPreviewOpen(true); + }; + // 转换模型格式 const modelOptions = models.map((m) => ({ id: m.modelId, @@ -568,12 +579,14 @@ export default function ChatPage({ params }: PageProps) { error={message.error} images={message.images} searchImages={message.searchImages} + searchVideos={message.searchVideos} uploadedImages={message.uploadedImages} uploadedDocuments={message.uploadedDocuments} usedTools={message.usedTools} pyodideStatus={message.pyodideStatus} onRegenerate={message.role === 'assistant' && !isStreaming ? handleRegenerate : undefined} onSaveToNote={message.role === 'assistant' && !isStreaming ? handleSaveToNote : undefined} + onLinkClick={handleLinkClick} conversationId={chatId} /> )) @@ -624,6 +637,13 @@ export default function ChatPage({ params }: PageProps) { conversationId={chatId} /> + {/* 链接预览弹窗 */} + setLinkPreviewOpen(false)} + /> + {/* 提示词优化工具浮动按钮 */} diff --git a/src/components/features/MessageBubble.tsx b/src/components/features/MessageBubble.tsx index 021fe33..d7a0349 100644 --- a/src/components/features/MessageBubble.tsx +++ b/src/components/features/MessageBubble.tsx @@ -8,6 +8,7 @@ import { Tooltip } from '@/components/ui/Tooltip'; import { MarkdownRenderer } from '@/components/markdown/MarkdownRenderer'; import { CodeExecutionResult, PyodideLoading } from '@/components/features/CodeExecutionResult'; import { SearchImagesGrid, type SearchImageItem } from '@/components/features/SearchImagesGrid'; +import { SearchVideosGrid, type SearchVideoItem } from '@/components/features/SearchVideosGrid'; import { ImageLightbox } from '@/components/ui/ImageLightbox'; import { DocumentPreview, type DocumentData } from '@/components/ui/DocumentPreview'; import { cn } from '@/lib/utils'; @@ -33,6 +34,8 @@ interface MessageBubbleProps { images?: string[]; /** 搜索到的图片(来自图片搜索工具) */ searchImages?: SearchImageItem[]; + /** 搜索到的视频(来自视频搜索工具) */ + searchVideos?: SearchVideoItem[]; /** 用户上传的图片(Base64 或 URL) */ uploadedImages?: string[]; /** 用户上传的文档 */ @@ -49,6 +52,8 @@ interface MessageBubbleProps { onRegenerate?: (messageId: string) => void; /** 保存到笔记回调(仅对 AI 消息有效),传入消息内容 */ onSaveToNote?: (content: string) => void; + /** 链接点击回调,用于在预览窗口中打开链接 */ + onLinkClick?: (url: string) => void; /** 对话 ID(用于关联笔记来源) */ conversationId?: string; } @@ -70,7 +75,7 @@ function getDocumentIcon(type: string) { return FileText; } -export function MessageBubble({ message, user, thinkingContent, isStreaming, error, images, searchImages, uploadedImages, uploadedDocuments, usedTools, pyodideStatus, onRegenerate, onSaveToNote, conversationId }: MessageBubbleProps) { +export function MessageBubble({ message, user, thinkingContent, isStreaming, error, images, searchImages, searchVideos, uploadedImages, uploadedDocuments, usedTools, pyodideStatus, onRegenerate, onSaveToNote, onLinkClick, conversationId }: MessageBubbleProps) { const isUser = message.role === 'user'; const [thinkingExpanded, setThinkingExpanded] = useState(false); const [copied, setCopied] = useState(false); @@ -284,11 +289,17 @@ export function MessageBubble({ message, user, thinkingContent, isStreaming, err )} + {/* 搜索到的视频(视频搜索工具结果)- 显示在图片下方 */} + {searchVideos && searchVideos.length > 0 && ( + + )} +
{message.content ? ( ) : isStreaming ? (