diff --git a/desktop/src/components/chat/MessageList.tsx b/desktop/src/components/chat/MessageList.tsx index fca217cd..b785fa2a 100644 --- a/desktop/src/components/chat/MessageList.tsx +++ b/desktop/src/components/chat/MessageList.tsx @@ -7,6 +7,7 @@ import { useChatStore } from '../../stores/chatStore' import { useSessionStore } from '../../stores/sessionStore' import { useWorkspaceChatContextStore } from '../../stores/workspaceChatContextStore' import { SETTINGS_TAB_ID, useTabStore } from '../../stores/tabStore' +import { MessageNavigation } from './MessageNavigation' import { useTeamStore } from '../../stores/teamStore' import { useUIStore } from '../../stores/uiStore' import { useTranslation } from '../../i18n' @@ -505,6 +506,7 @@ function SelectableChatMessage({
{ if (event.pointerType === 'mouse' && event.button !== 0) return lastSelectionPointerRef.current = getSelectionPointer(event) @@ -1615,6 +1617,38 @@ export function MessageList({ sessionId, compact = false }: MessageListProps = { scrollToBottom('auto') }, [scrollToBottom]) + const handleNavigateToMessage = useCallback((messageId: string) => { + const scrollToElement = () => { + const el = document.querySelector(`[data-message-id="${messageId}"]`) + if (el) { + el.scrollIntoView({ behavior: 'smooth', block: 'center' }) + return true + } + return false + } + + if (scrollToElement()) return + + const container = scrollContainerRef.current + if (!container) return + + const messageIndex = messages.findIndex((m) => m.id === messageId) + if (messageIndex === -1) return + + const totalMessages = messages.length + if (totalMessages === 0) return + + const scrollHeight = container.scrollHeight - container.clientHeight + const estimatedPosition = (messageIndex / totalMessages) * scrollHeight + container.scrollTop = Math.max(0, estimatedPosition - container.clientHeight / 3) + + requestAnimationFrame(() => { + requestAnimationFrame(() => { + scrollToElement() + }) + }) + }, [messages]) + useEffect(() => { const content = scrollContentRef.current if (!content || typeof ResizeObserver === 'undefined') return @@ -2046,6 +2080,11 @@ export function MessageList({ sessionId, compact = false }: MessageListProps = { )} + + { diff --git a/desktop/src/components/chat/MessageNavigation.tsx b/desktop/src/components/chat/MessageNavigation.tsx new file mode 100644 index 00000000..09685cd6 --- /dev/null +++ b/desktop/src/components/chat/MessageNavigation.tsx @@ -0,0 +1,66 @@ +import { memo, useMemo } from 'react' +import { User } from 'lucide-react' +import type { UIMessage } from '../../types/chat' + +type MessageNavigationProps = { + messages: UIMessage[] + onNavigate: (messageId: string) => void +} + +type NavigationItem = { + id: string + label: string +} + +function getNavigationLabel(message: UIMessage): string { + if (message.type === 'user_text') { + const text = message.content.trim() + return text.length > 30 ? text.slice(0, 30) + '...' : text || '用户消息' + } + return '消息' +} + +export const MessageNavigation = memo(function MessageNavigation({ + messages, + onNavigate, +}: MessageNavigationProps) { + const navItems = useMemo(() => { + const items: NavigationItem[] = [] + for (const message of messages) { + if (message.type === 'user_text') { + items.push({ + id: message.id, + label: getNavigationLabel(message), + }) + } + } + return items + }, [messages]) + + if (navItems.length === 0) { + return null + } + + return ( +
+
+
+ 消息导航 +
+
+ {navItems.map((item) => ( + + ))} +
+
+
+ ) +})