diff --git a/src/components/features/ChatInput.tsx b/src/components/features/ChatInput.tsx index 27352a6..7a61433 100644 --- a/src/components/features/ChatInput.tsx +++ b/src/components/features/ChatInput.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState } from 'react'; -import { Plus, Clock, ArrowUp } from 'lucide-react'; +import { Plus, ArrowUp } from 'lucide-react'; import { ModelSelector } from './ModelSelector'; import { ToolsDropdown } from './ToolsDropdown'; import { cn } from '@/lib/utils'; @@ -40,7 +40,9 @@ export function ChatInput({ }; const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey) { + // 检查是否正在使用输入法组合(如中文输入法选词时按回车) + // isComposing 为 true 时,表示用户正在用输入法选词,此时不应发送消息 + if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) { e.preventDefault(); handleSend(); } @@ -85,14 +87,6 @@ export function ChatInput({ onToolToggle={onToolToggle} onEnableAllToggle={onEnableAllTools} /> - - {/* 历史记录 */} - {/* 右侧按钮 */} diff --git a/src/components/features/MessageBubble.tsx b/src/components/features/MessageBubble.tsx index 395b8d4..9d922d5 100644 --- a/src/components/features/MessageBubble.tsx +++ b/src/components/features/MessageBubble.tsx @@ -1,47 +1,35 @@ 'use client'; -import { Copy, ThumbsUp, ThumbsDown, RefreshCw } from 'lucide-react'; +import { useState } from 'react'; +import { Copy, ThumbsUp, ThumbsDown, RefreshCw, ChevronDown, ChevronUp, Brain, Loader2, AlertCircle, Check } from 'lucide-react'; import { Avatar } from '@/components/ui/Avatar'; import { AILogo } from '@/components/ui/AILogo'; +import { MarkdownRenderer } from '@/components/markdown/MarkdownRenderer'; import { cn } from '@/lib/utils'; import type { Message, User } from '@/types'; interface MessageBubbleProps { message: Message; user?: User; + thinkingContent?: string; + isStreaming?: boolean; + error?: string; } -export function MessageBubble({ message, user }: MessageBubbleProps) { +export function MessageBubble({ message, user, thinkingContent, isStreaming, error }: MessageBubbleProps) { const isUser = message.role === 'user'; + const [thinkingExpanded, setThinkingExpanded] = useState(false); + const [copied, setCopied] = useState(false); - // 简单的 Markdown 渲染 - const renderContent = (content: string) => { - // 处理代码块 - const parts = content.split(/(`[^`]+`)/g); - return parts.map((part, index) => { - if (part.startsWith('`') && part.endsWith('`')) { - return ( - - {part.slice(1, -1)} - - ); - } - // 处理粗体 - const boldParts = part.split(/(\*\*[^*]+\*\*)/g); - return boldParts.map((boldPart, boldIndex) => { - if (boldPart.startsWith('**') && boldPart.endsWith('**')) { - return ( - - {boldPart.slice(2, -2)} - - ); - } - return {boldPart}; - }); - }); + // 复制消息内容 + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(message.content); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (error) { + console.error('Failed to copy:', error); + } }; if (isUser) { @@ -66,55 +54,78 @@ export function MessageBubble({ message, user }: MessageBubbleProps) { {/* 消息内容 */}
-
-
- {message.content.split('\n\n').map((paragraph, index) => { - // 处理列表 - if (paragraph.startsWith('- ') || paragraph.startsWith('* ')) { - const items = paragraph.split('\n'); - return ( -
    - {items.map((item, i) => ( -
  • - {renderContent(item.replace(/^[-*]\s/, ''))} -
  • - ))} -
- ); - } - // 处理有序列表 - if (/^\d+\.\s/.test(paragraph)) { - const items = paragraph.split('\n'); - return ( -
    - {items.map((item, i) => ( -
  1. - {renderContent(item.replace(/^\d+\.\s/, ''))} -
  2. - ))} -
- ); - } - return ( -

- {renderContent(paragraph)} -

- ); - })} + {/* 思考内容 */} + {thinkingContent && ( +
+ + {thinkingExpanded && ( +
+
+                  {thinkingContent}
+                
+
+ )} +
+ )} + + {/* 错误提示 */} + {error && ( +
+ +
{error}
+
+ )} + + {/* 主要内容 */} +
+
+ {message.content ? ( + + ) : isStreaming ? ( +
+ + 正在思考... +
+ ) : null}
- {/* 操作按钮 */} -
- - - - -
+ {/* 流式状态指示器 */} + {isStreaming && message.content && ( +
+ + 生成中... +
+ )} - {/* 免责声明 */} -
- cchcode can make mistakes -
+ {/* 操作按钮(非流式状态下显示) */} + {!isStreaming && message.content && ( + <> +
+ + + + +
+ + {/* 免责声明 */} +
+ cchcode can make mistakes +
+ + )}