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) => (
+
+ ))}
+
+
+
+ )
+})