import { useEffect, useRef, type KeyboardEvent, type RefObject, } from 'react'; import { Button } from '@/components/ui/button'; const DIALOG_FOCUSABLE_SELECTOR = [ 'button:not([disabled])', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', '[tabindex]:not([tabindex="-1"])', ].join(','); function useModalDialogFocus( open: boolean, dialogRef: RefObject, initialFocusRef: RefObject, ) { const previousFocusRef = useRef(null); useEffect(() => { if (!open) return undefined; previousFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null; const dialog = dialogRef.current; (initialFocusRef.current ?? dialog?.querySelector(DIALOG_FOCUSABLE_SELECTOR)) ?.focus(); return () => { previousFocusRef.current?.focus(); previousFocusRef.current = null; }; }, [dialogRef, initialFocusRef, open]); } function handleModalKeyDown( event: KeyboardEvent, onCancel: () => void, ) { if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); onCancel(); return; } if (event.key !== 'Tab') return; const focusable = Array.from( event.currentTarget.querySelectorAll( DIALOG_FOCUSABLE_SELECTOR, ), ); if (focusable.length === 0) { event.preventDefault(); return; } const first = focusable[0]; const last = focusable.at(-1) ?? first; if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } } export interface ChatTimelineEntry { messageId: string; role: 'user' | 'assistant' | 'system' | 'toolresult'; label: string; preview: string; timestamp?: number; } export interface ChatCommandRenameDialogProps { open: boolean; title: string; onTitleChange: (title: string) => void; onSubmit: (title: string) => void; onCancel: () => void; } export function ChatCommandRenameDialog({ open, title, onTitleChange, onSubmit, onCancel, }: ChatCommandRenameDialogProps) { const dialogRef = useRef(null); const inputRef = useRef(null); useModalDialogFocus(open, dialogRef, inputRef); if (!open) return null; const trimmedTitle = title.trim(); return (
handleModalKeyDown(event, onCancel)} >
{ event.preventDefault(); if (trimmedTitle) onSubmit(trimmedTitle); }} >

重命名会话

onTitleChange(event.target.value)} />
); } export interface ChatCommandTimelineDialogProps { open: boolean; entries: readonly ChatTimelineEntry[]; onSelect: (messageId: string) => void; onCancel: () => void; } export function ChatCommandTimelineDialog({ open, entries, onSelect, onCancel, }: ChatCommandTimelineDialogProps) { const dialogRef = useRef(null); const firstEntryRef = useRef(null); useModalDialogFocus(open, dialogRef, firstEntryRef); if (!open) return null; const title = '消息时间线'; return (
handleModalKeyDown(event, onCancel)} >

{title}

{entries.map((entry) => ( ))}
); }