diff --git a/packages/ui/src/components/chat/ChatContainer.tsx b/packages/ui/src/components/chat/ChatContainer.tsx index 4a5cf80b..cd202f93 100644 --- a/packages/ui/src/components/chat/ChatContainer.tsx +++ b/packages/ui/src/components/chat/ChatContainer.tsx @@ -14,6 +14,7 @@ import ScrollToBottomButton from './components/ScrollToBottomButton'; import { ScrollShadow } from '@/components/ui/ScrollShadow'; import { useChatScrollManager, type AnimationHandlers, type ContentChangeReason } from '@/hooks/useChatScrollManager'; import { useChatTimelineController } from './hooks/useChatTimelineController'; +import { TimelineDialog } from './TimelineDialog'; import { useChatTurnNavigation } from './hooks/useChatTurnNavigation'; import { useDeviceInfo } from '@/lib/device'; import { Button } from '@/components/ui/button'; @@ -342,6 +343,8 @@ export const ChatContainer: React.FC = () => { const isExpandedInput = useUIStore((state) => state.isExpandedInput); const stickyUserHeader = useUIStore((state) => state.stickyUserHeader); const chatRenderMode = useUIStore((state) => state.chatRenderMode); + const isTimelineDialogOpen = useUIStore((s) => s.isTimelineDialogOpen); + const setTimelineDialogOpen = useUIStore((s) => s.setTimelineDialogOpen); // Streaming state const streamingMessageId = useStreamingStore( @@ -958,19 +961,27 @@ export const ChatContainer: React.FC = () => {
- {!isDesktopExpandedInput && sessionMessages.length > 0 && ( - + {!isDesktopExpandedInput && sessionMessages.length > 0 && ( + )}
+ + ); }; diff --git a/packages/ui/src/components/chat/ChatInput.tsx b/packages/ui/src/components/chat/ChatInput.tsx index 54a2f420..25998fc3 100644 --- a/packages/ui/src/components/chat/ChatInput.tsx +++ b/packages/ui/src/components/chat/ChatInput.tsx @@ -799,6 +799,7 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo const inputSpellcheckEnabled = useUIStore((state) => state.inputSpellcheckEnabled); const isExpandedInput = useUIStore((state) => state.isExpandedInput); const setExpandedInput = useUIStore((state) => state.setExpandedInput); + const setTimelineDialogOpen = useUIStore((state) => state.setTimelineDialogOpen); const { git: runtimeGit } = useRuntimeAPIs(); const { currentTheme } = useThemeSystem(); const chatSearchDirectory = useChatSearchDirectory(); @@ -1494,6 +1495,10 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo scrollToBottom?.({ instant: true, force: true }); return; } + else if (commandName === 'timeline' && currentSessionId) { + setTimelineDialogOpen(true); + return; + } else if (commandName === 'compact' && currentSessionId) { try { await sessionActions.waitForConnectionOrThrow(); diff --git a/packages/ui/src/components/chat/CommandAutocomplete.tsx b/packages/ui/src/components/chat/CommandAutocomplete.tsx index 1430f6c7..0b9625fc 100644 --- a/packages/ui/src/components/chat/CommandAutocomplete.tsx +++ b/packages/ui/src/components/chat/CommandAutocomplete.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { RiCommandLine, RiFileLine, RiFlashlightLine, RiRefreshLine, RiScissorsLine, RiTerminalBoxLine, RiArrowGoBackLine, RiArrowGoForwardLine, RiSearchEyeLine } from '@remixicon/react'; +import { RiCommandLine, RiFileLine, RiFlashlightLine, RiRefreshLine, RiScissorsLine, RiTerminalBoxLine, RiArrowGoBackLine, RiArrowGoForwardLine, RiSearchEyeLine, RiTimeLine } from '@remixicon/react'; import { cn, fuzzyMatch } from '@/lib/utils'; import { useSessionUIStore } from '@/sync/session-ui-store'; import { useSessionMessages } from '@/sync/sync-context'; @@ -120,6 +120,7 @@ export const CommandAutocomplete = React.forwardRef; case 'redo': return ; + case 'timeline': + return ; case 'compact': return ; case 'review': diff --git a/packages/ui/src/components/chat/TimelineDialog.tsx b/packages/ui/src/components/chat/TimelineDialog.tsx index c492a3f3..6790b631 100644 --- a/packages/ui/src/components/chat/TimelineDialog.tsx +++ b/packages/ui/src/components/chat/TimelineDialog.tsx @@ -41,6 +41,8 @@ export const TimelineDialog: React.FC = ({ const [forkingMessageId, setForkingMessageId] = React.useState(null); const [searchQuery, setSearchQuery] = React.useState(''); + const [selectedIndex, setSelectedIndex] = React.useState(0); + const itemRefs = React.useRef<(HTMLDivElement | null)[]>([]); const formatRelativeTime = React.useCallback((timestamp: number): string => { const now = Date.now(); @@ -57,23 +59,79 @@ export const TimelineDialog: React.FC = ({ return new Date(timestamp).toLocaleDateString(); }, [t]); - // Filter user messages (reversed for newest first) + // Timeline actions are only valid for user messages. const userMessages = React.useMemo(() => { - const filtered = messages.filter(m => m.info.role === 'user'); - return filtered.reverse(); + return messages + .filter((message) => message.info.role === 'user') + .map((message, index) => ({ + message, + messageNumber: index + 1, + })) + .reverse(); }, [messages]); - // Filter by search query + // Filter by search query using all text parts in each user message. const filteredMessages = React.useMemo(() => { - if (!searchQuery.trim()) return userMessages; + const trimmedQuery = searchQuery.trim(); + if (!trimmedQuery) return userMessages; - const query = searchQuery.toLowerCase(); - return userMessages.filter((message) => { - const preview = getMessagePreview(message.parts).toLowerCase(); - return preview.includes(query); + const query = trimmedQuery.toLowerCase(); + return userMessages.filter(({ message }) => { + const fullText = getFullText(message.parts).toLowerCase(); + return fullText.includes(query); }); }, [userMessages, searchQuery]); + React.useEffect(() => { + setSelectedIndex(0); + }, [filteredMessages]); + + React.useEffect(() => { + itemRefs.current = itemRefs.current.slice(0, filteredMessages.length); + }, [filteredMessages.length]); + + React.useEffect(() => { + itemRefs.current[selectedIndex]?.scrollIntoView({ + block: 'nearest', + }); + }, [selectedIndex]); + + const navigateToMessage = React.useCallback(async (messageId: string) => { + const didNavigate = await onScrollToMessage?.(messageId); + if (didNavigate === false) { + return; + } + onOpenChange(false); + }, [onOpenChange, onScrollToMessage]); + + const handleSearchKeyDown = React.useCallback((event: React.KeyboardEvent) => { + const total = filteredMessages.length; + if (total === 0) { + return; + } + + if (event.key === 'ArrowDown') { + event.preventDefault(); + setSelectedIndex((current) => (current + 1) % total); + return; + } + + if (event.key === 'ArrowUp') { + event.preventDefault(); + setSelectedIndex((current) => (current - 1 + total) % total); + return; + } + + if (event.key === 'Enter') { + event.preventDefault(); + const safeIndex = ((selectedIndex % total) + total) % total; + const selected = filteredMessages[safeIndex]; + if (selected) { + void navigateToMessage(selected.message.info.id); + } + } + }, [filteredMessages, navigateToMessage, selectedIndex]); + // Handle fork with loading state and session refresh const handleFork = async (messageId: string) => { if (!currentSessionId) return; @@ -104,9 +162,11 @@ export const TimelineDialog: React.FC = ({
setSearchQuery(e.target.value)} + onKeyDown={handleSearchKeyDown} className="pl-9 w-full" />
@@ -117,34 +177,49 @@ export const TimelineDialog: React.FC = ({ {searchQuery ? t('chat.timeline.empty.search') : t('chat.timeline.empty.session')} ) : ( - filteredMessages.map((message) => { + filteredMessages.map(({ message, messageNumber }, index) => { const preview = getMessagePreview(message.parts); const timestamp = message.info.time.created; const relativeTime = formatRelativeTime(timestamp); - const messageNumber = userMessages.length - userMessages.indexOf(message); + const isSelected = index === selectedIndex; + + const snippet = searchQuery.trim() + ? getSearchSnippet(getFullText(message.parts), searchQuery) + : null; return (
{ - const didNavigate = await onScrollToMessage?.(message.info.id); - if (didNavigate === false) { - return; - } - onOpenChange(false); + ref={(element) => { + itemRefs.current[index] = element; }} + className={cn( + "group flex items-center gap-2 py-1.5 hover:bg-interactive-hover/30 rounded transition-colors cursor-pointer", + isSelected && "bg-interactive-selection text-interactive-selection-foreground" + )} + onClick={() => void navigateToMessage(message.info.id)} + onMouseEnter={() => setSelectedIndex(index)} > - + {messageNumber}. -

- {preview || t('chat.timeline.noTextContent')} - {preview && preview.length >= 80 && '…'} +

+ {snippet ?? (preview || t('chat.timeline.noTextContent'))} + {!snippet && preview && preview.length >= 80 && '…'}

- + {relativeTime} @@ -238,8 +313,26 @@ export const TimelineDialog: React.FC = ({ ); }; -function getMessagePreview(parts: Part[]): string { - const textPart = parts.find(p => p.type === 'text'); - if (!textPart || typeof textPart.text !== 'string') return ''; - return textPart.text.replace(/\n/g, ' ').slice(0, 80); +function getFullText(parts: Part[]): string { + return parts + .filter((p): p is Part & { type: 'text'; text: string } => p.type === 'text' && typeof p.text === 'string') + .map((p) => p.text) + .join('\n'); +} + +function getMessagePreview(parts: Part[]): string { + const full = getFullText(parts); + const singleLine = full.replace(/\n/g, ' '); + return singleLine.length > 80 ? singleLine.slice(0, 80) : singleLine; +} + +function getSearchSnippet(text: string, query: string, contextChars: number = 30): string | null { + const lowerText = text.toLowerCase(); + const lowerQuery = query.toLowerCase(); + const matchIndex = lowerText.indexOf(lowerQuery); + if (matchIndex === -1) return null; + + const start = Math.max(0, matchIndex - contextChars); + const end = Math.min(text.length, matchIndex + query.length + contextChars); + return `${start > 0 ? '…' : ''}${text.slice(start, end).replace(/\n/g, ' ')}${end < text.length ? '…' : ''}`; } diff --git a/packages/ui/src/hooks/useKeyboardShortcuts.ts b/packages/ui/src/hooks/useKeyboardShortcuts.ts index bb633088..187a5d29 100644 --- a/packages/ui/src/hooks/useKeyboardShortcuts.ts +++ b/packages/ui/src/hooks/useKeyboardShortcuts.ts @@ -30,6 +30,7 @@ export const useKeyboardShortcuts = () => { const setActiveMainTab = useUIStore((s) => s.setActiveMainTab); const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen); const setModelSelectorOpen = useUIStore((s) => s.setModelSelectorOpen); + const setTimelineDialogOpen = useUIStore((s) => s.setTimelineDialogOpen); const toggleExpandedInput = useUIStore((s) => s.toggleExpandedInput); const shortcutOverrides = useUIStore((s) => s.shortcutOverrides); const { themeMode, setThemeMode } = useThemeSystem(); @@ -61,6 +62,12 @@ export const useKeyboardShortcuts = () => { return; } + if (eventMatchesShortcut(e, combo('open_timeline_dialog'))) { + e.preventDefault(); + setTimelineDialogOpen(true); + return; + } + if (eventMatchesShortcut(e, combo('open_status'))) { e.preventDefault(); void showOpenCodeStatus(); @@ -430,6 +437,7 @@ export const useKeyboardShortcuts = () => { setActiveMainTab, setSettingsDialogOpen, setModelSelectorOpen, + setTimelineDialogOpen, toggleExpandedInput, setThemeMode, working, diff --git a/packages/ui/src/lib/i18n/messages/en.settings.ts b/packages/ui/src/lib/i18n/messages/en.settings.ts index ed8837d7..2d69d411 100644 --- a/packages/ui/src/lib/i18n/messages/en.settings.ts +++ b/packages/ui/src/lib/i18n/messages/en.settings.ts @@ -761,6 +761,7 @@ export const settingsDict = { 'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label': 'Cycle favorite model forward', 'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_backward.label': 'Cycle favorite model backward', 'settings.openchamber.keyboardShortcuts.action.expand_input.label': 'Expand input', + 'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': 'Open conversation timeline', 'settings.projects.sidebar.total': 'Total {count}', 'settings.projects.sidebar.actions.addProject': 'Add project', 'settings.projects.page.empty.noProjects': 'No projects available.', diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts index f61b0e0f..7602f560 100644 --- a/packages/ui/src/lib/i18n/messages/en.ts +++ b/packages/ui/src/lib/i18n/messages/en.ts @@ -1331,6 +1331,7 @@ export const dict = { 'chat.commandAutocomplete.command.initDescription': 'Create/update AGENTS.md file', 'chat.commandAutocomplete.command.undoDescription': 'Undo the last message', 'chat.commandAutocomplete.command.redoDescription': 'Redo previously undone messages', + 'chat.commandAutocomplete.command.timelineDescription': 'Open the conversation timeline', 'chat.commandAutocomplete.command.compactDescription': 'Compress session history using AI to reduce context size', 'chat.commandAutocomplete.command.summaryDescription': 'Non-destructive session summary. Optional topic hint after the command.', 'chat.commandAutocomplete.command.workspaceReviewDescription': 'Review current workspace changes for high-signal issues only.', diff --git a/packages/ui/src/lib/i18n/messages/es.settings.ts b/packages/ui/src/lib/i18n/messages/es.settings.ts index 88a27450..2f6686a3 100644 --- a/packages/ui/src/lib/i18n/messages/es.settings.ts +++ b/packages/ui/src/lib/i18n/messages/es.settings.ts @@ -761,6 +761,7 @@ export const settingsDict = { "settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label": "Siguiente modelo favorito", "settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_backward.label": "Modelo favorito anterior", "settings.openchamber.keyboardShortcuts.action.expand_input.label": "Expandir entrada", + "settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label": "Abrir línea de tiempo de conversación", "settings.projects.sidebar.total": "Total {count}", "settings.projects.sidebar.actions.addProject": "Añadir proyecto", "settings.projects.page.empty.noProjects": "No hay proyectos disponibles.", diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts index d8f35860..9e52bbfd 100644 --- a/packages/ui/src/lib/i18n/messages/es.ts +++ b/packages/ui/src/lib/i18n/messages/es.ts @@ -1297,6 +1297,7 @@ export const dict: Record = { "chat.commandAutocomplete.command.initDescription": "Crear/actualizar el archivo AGENTS.md", "chat.commandAutocomplete.command.undoDescription": "Deshacer el último mensaje", "chat.commandAutocomplete.command.redoDescription": "Rehacer mensajes previamente deshechos", + "chat.commandAutocomplete.command.timelineDescription": "Abrir la línea de tiempo de la conversación", "chat.commandAutocomplete.command.compactDescription": "Comprimir el historial de la sesión usando IA para reducir el tamaño del contexto", "chat.commandAutocomplete.command.summaryDescription": "Resumen no destructivo de la sesión. Pista opcional del tema después del comando.", "chat.commandAutocomplete.command.workspaceReviewDescription": "Revisar los cambios actuales del espacio de trabajo solo para problemas de alto impacto.", diff --git a/packages/ui/src/lib/i18n/messages/ko.settings.ts b/packages/ui/src/lib/i18n/messages/ko.settings.ts index 7fc8df21..38703777 100644 --- a/packages/ui/src/lib/i18n/messages/ko.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ko.settings.ts @@ -761,6 +761,7 @@ export const settingsDict = { 'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label': '즐겨찾기 모델 앞으로 순환', 'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_backward.label': '즐겨찾기 모델 뒤로 순환', 'settings.openchamber.keyboardShortcuts.action.expand_input.label': '입력 확장', + 'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': '대화 타임라인 열기', 'settings.projects.sidebar.total': '총 {count}개', 'settings.projects.sidebar.actions.addProject': '프로젝트 추가', 'settings.projects.page.empty.noProjects': '사용 가능한 프로젝트가 없습니다.', diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index 8fa81700..fd061ff3 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -1333,6 +1333,7 @@ export const dict: Record = { 'chat.commandAutocomplete.command.initDescription': 'AGENTS.md 파일 생성/업데이트', 'chat.commandAutocomplete.command.undoDescription': '마지막 메시지 실행 취소', 'chat.commandAutocomplete.command.redoDescription': '이전에 실행 취소한 메시지 다시 실행', + 'chat.commandAutocomplete.command.timelineDescription': '대화 타임라인 열기', 'chat.commandAutocomplete.command.compactDescription': 'AI로 세션 기록을 압축해 컨텍스트 크기를 줄입니다', 'chat.commandAutocomplete.command.summaryDescription': '세션 기록을 안전하게 요약합니다. 명령 뒤에 선택적으로 주제 힌트를 넣을 수 있습니다.', 'chat.commandAutocomplete.command.workspaceReviewDescription': '현재 워크스페이스 변경 사항에서 중요한 이슈만 리뷰합니다.', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts index e280bd8c..c57b4212 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts @@ -761,6 +761,7 @@ export const settingsDict = { "settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label": "Próximo modelo favorito", "settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_backward.label": "Modelo favorito anterior", "settings.openchamber.keyboardShortcuts.action.expand_input.label": "Expandir entrada", + "settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label": "Abrir linha do tempo da conversa", "settings.projects.sidebar.total": "Total {count}", "settings.projects.sidebar.actions.addProject": "Adicionar projeto", "settings.projects.page.empty.noProjects": "Não há projetos disponíveis.", diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index 3e400d79..ce4d969a 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -1297,6 +1297,7 @@ export const dict: Record = { "chat.commandAutocomplete.command.initDescription": "Criar/atualizar o arquivo AGENTS.md", "chat.commandAutocomplete.command.undoDescription": "Desfazer a última mensagem", "chat.commandAutocomplete.command.redoDescription": "Refazer mensagens desfeitas anteriormente", + "chat.commandAutocomplete.command.timelineDescription": "Abrir a linha do tempo da conversa", "chat.commandAutocomplete.command.compactDescription": "Comprimir o histórico da sessão usando IA para reduzir o tamanho do contexto", "chat.commandAutocomplete.command.summaryDescription": "Resumo não destrutivo da sessão. Dica opcional do tema após o comando.", "chat.commandAutocomplete.command.workspaceReviewDescription": "Revisar as alterações atuais do workspace apenas para problemas de alto impacto.", diff --git a/packages/ui/src/lib/i18n/messages/uk.settings.ts b/packages/ui/src/lib/i18n/messages/uk.settings.ts index 00b058c7..24e253b8 100644 --- a/packages/ui/src/lib/i18n/messages/uk.settings.ts +++ b/packages/ui/src/lib/i18n/messages/uk.settings.ts @@ -761,6 +761,7 @@ export const settingsDict = { "settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label": "Перемкнути улюблену модель вперед", "settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_backward.label": "Перемкнути улюблену модель назад", "settings.openchamber.keyboardShortcuts.action.expand_input.label": "Розгорнути введення", + "settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label": "Відкрити хронологію розмови", "settings.projects.sidebar.total": "Усього {count}", "settings.projects.sidebar.actions.addProject": "Додати проєкт", "settings.projects.page.empty.noProjects": "Немає доступних проєктів.", diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index f5a32f2f..2583ce6f 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -1297,6 +1297,7 @@ export const dict: Record = { "chat.commandAutocomplete.command.initDescription": "Створити або оновити файл AGENTS.md", "chat.commandAutocomplete.command.undoDescription": "Скасувати останнє повідомлення", "chat.commandAutocomplete.command.redoDescription": "Повторити раніше скасовані повідомлення", + "chat.commandAutocomplete.command.timelineDescription": "Відкрити хронологію розмови", "chat.commandAutocomplete.command.compactDescription": "Стиснути історію сесії за допомогою ШІ, щоб зменшити розмір контексту", "chat.commandAutocomplete.command.summaryDescription": "Неруйнівний підсумок сесії. Після команди можна додати тему.", "chat.commandAutocomplete.command.workspaceReviewDescription": "Перегляньте поточні зміни в робочому середовищі лише для проблем із сильним сигналом.", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts index b934be67..2ea0879c 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts @@ -761,6 +761,7 @@ export const settingsDict = { 'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_forward.label': '向前轮换收藏模型', 'settings.openchamber.keyboardShortcuts.action.cycle_favorite_model_backward.label': '向后轮换收藏模型', 'settings.openchamber.keyboardShortcuts.action.expand_input.label': '展开输入框', + 'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': '打开对话时间线', 'settings.projects.sidebar.total': '总计 {count}', 'settings.projects.sidebar.actions.addProject': '添加项目', 'settings.projects.page.empty.noProjects': '暂无项目。', diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index 5183712a..7ceb5c0a 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -1297,6 +1297,7 @@ export const dict: Record = { 'chat.commandAutocomplete.command.initDescription': '创建/更新 AGENTS.md 文件', 'chat.commandAutocomplete.command.undoDescription': '撤销上一条消息', 'chat.commandAutocomplete.command.redoDescription': '重做之前撤销的消息', + 'chat.commandAutocomplete.command.timelineDescription': '打开对话时间线', 'chat.commandAutocomplete.command.compactDescription': '使用 AI 压缩会话历史以减少上下文大小', 'chat.commandAutocomplete.command.summaryDescription': '非破坏性会话总结。命令后可选填主题提示。', 'chat.commandAutocomplete.command.workspaceReviewDescription': '仅审查当前工作区中高价值的问题。', diff --git a/packages/ui/src/lib/shortcuts.ts b/packages/ui/src/lib/shortcuts.ts index 2e925ade..1cfd764b 100644 --- a/packages/ui/src/lib/shortcuts.ts +++ b/packages/ui/src/lib/shortcuts.ts @@ -165,6 +165,13 @@ const SHORTCUT_ACTIONS: ReadonlyArray = [ description: 'Toggle the session sidebar', customizable: true, }, + { + id: 'open_timeline_dialog', + defaultCombo: 'mod+t', + label: 'Open conversation timeline', + description: 'Search and navigate within current conversation', + customizable: true, + }, { id: 'toggle_right_sidebar', defaultCombo: 'mod+b',