mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-08-02 16:51:13 +08:00
Merge ba34ca5226b25fef489ee5ddb1242ce866f7da79 into d1e684c419b4f88fee62490b0c2b4fcf65469e59
This commit is contained in:
commit
43df0eea0c
@ -7,6 +7,7 @@ import { listPendingPermissions, useChatStore } from '../../stores/chatStore'
|
|||||||
import { useSessionStore } from '../../stores/sessionStore'
|
import { useSessionStore } from '../../stores/sessionStore'
|
||||||
import { useWorkspaceChatContextStore } from '../../stores/workspaceChatContextStore'
|
import { useWorkspaceChatContextStore } from '../../stores/workspaceChatContextStore'
|
||||||
import { SETTINGS_TAB_ID, useTabStore } from '../../stores/tabStore'
|
import { SETTINGS_TAB_ID, useTabStore } from '../../stores/tabStore'
|
||||||
|
import { MessageNavigation } from './MessageNavigation'
|
||||||
import { useTeamStore } from '../../stores/teamStore'
|
import { useTeamStore } from '../../stores/teamStore'
|
||||||
import { useUIStore } from '../../stores/uiStore'
|
import { useUIStore } from '../../stores/uiStore'
|
||||||
import { useTranslation } from '../../i18n'
|
import { useTranslation } from '../../i18n'
|
||||||
@ -529,6 +530,7 @@ function SelectableChatMessage({
|
|||||||
<div
|
<div
|
||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
data-chat-selectable-message={role}
|
data-chat-selectable-message={role}
|
||||||
|
data-message-id={messageId}
|
||||||
onPointerDown={(event) => {
|
onPointerDown={(event) => {
|
||||||
if (event.pointerType === 'mouse' && event.button !== 0) return
|
if (event.pointerType === 'mouse' && event.button !== 0) return
|
||||||
lastSelectionPointerRef.current = getSelectionPointer(event)
|
lastSelectionPointerRef.current = getSelectionPointer(event)
|
||||||
@ -1640,6 +1642,38 @@ export function MessageList({ sessionId, compact = false }: MessageListProps = {
|
|||||||
scrollToBottom('auto')
|
scrollToBottom('auto')
|
||||||
}, [scrollToBottom])
|
}, [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(() => {
|
useEffect(() => {
|
||||||
const content = scrollContentRef.current
|
const content = scrollContentRef.current
|
||||||
if (!content || typeof ResizeObserver === 'undefined') return
|
if (!content || typeof ResizeObserver === 'undefined') return
|
||||||
@ -2080,6 +2114,11 @@ export function MessageList({ sessionId, compact = false }: MessageListProps = {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<MessageNavigation
|
||||||
|
messages={messages}
|
||||||
|
onNavigate={handleNavigateToMessage}
|
||||||
|
/>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
open={Boolean(confirmTurnCard)}
|
open={Boolean(confirmTurnCard)}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
|
|||||||
66
desktop/src/components/chat/MessageNavigation.tsx
Normal file
66
desktop/src/components/chat/MessageNavigation.tsx
Normal file
@ -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 (
|
||||||
|
<div className="absolute right-0 top-0 bottom-0 w-[220px] overflow-y-auto py-4 pl-2 pr-4">
|
||||||
|
<div className="sticky top-0 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-container-low)] p-2 shadow-sm">
|
||||||
|
<div className="mb-2 px-2 text-[11px] font-semibold uppercase tracking-wider text-[var(--color-text-tertiary)]">
|
||||||
|
消息导航
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => onNavigate(item.id)}
|
||||||
|
className="group flex items-center gap-2 rounded-md px-2 py-1.5 text-left text-[12px] text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-surface-hover)]"
|
||||||
|
>
|
||||||
|
<User size={12} strokeWidth={2} className="shrink-0 text-[var(--color-text-tertiary)]" />
|
||||||
|
<span className="min-w-0 truncate">{item.label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user