From 6d56f3c21d79e9fa674e08cea49b4e5f2370fe09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Thu, 30 Apr 2026 18:28:08 +0800 Subject: [PATCH] fix: remove legacy message rewind entry The chat surface now relies on the current-turn changes card for file rollback, so the older per-message hover rewind affordance was removed to avoid two competing rollback models. Constraint: Current-turn undo still depends on the existing checkpoint rewind API. Rejected: Keep both rewind entry points | duplicate rollback affordances make the product harder to explain and test. Confidence: high Scope-risk: moderate Directive: Prefer adding rollback behavior through the current-turn change card instead of restoring per-message hover rewind. Tested: bun test src/server/__tests__/sessions.test.ts Tested: cd desktop && bun run test Tested: cd desktop && bun run lint Tested: cd desktop && bun run build Not-tested: Browser E2E scripts were updated but not executed in this commit. --- desktop/scripts/e2e-rewind-agent-browser.sh | 6 +- .../e2e-rewind-complex-agent-browser.sh | 6 +- desktop/src/__tests__/pages.test.tsx | 10 +- .../components/chat/AskUserQuestion.test.tsx | 2 + .../chat/ComputerUsePermissionModal.test.tsx | 2 + .../components/chat/FileSearchMenu.test.tsx | 2 + .../src/components/chat/MessageActionBar.tsx | 35 +-- .../src/components/chat/MessageList.test.tsx | 98 ++----- desktop/src/components/chat/MessageList.tsx | 269 ------------------ desktop/src/components/chat/UserMessage.tsx | 6 +- .../src/components/chat/chatBlocks.test.tsx | 2 + .../components/shared/UpdateChecker.test.tsx | 2 + desktop/src/i18n/locales/en.ts | 15 - desktop/src/i18n/locales/zh.ts | 15 - 14 files changed, 49 insertions(+), 421 deletions(-) diff --git a/desktop/scripts/e2e-rewind-agent-browser.sh b/desktop/scripts/e2e-rewind-agent-browser.sh index 7b2ba1ab..3b8c6e66 100755 --- a/desktop/scripts/e2e-rewind-agent-browser.sh +++ b/desktop/scripts/e2e-rewind-agent-browser.sh @@ -137,10 +137,10 @@ fi ${AB} screenshot "${ARTIFACT_DIR}/03-after-edit.png" >/dev/null -${AB} eval "const button = [...document.querySelectorAll('button')].find((node) => node.getAttribute('aria-label') === 'Rewind to here'); if (!button) throw new Error('Rewind button not found'); button.click();" +${AB} find role button click --name "Undo current turn changes" ${AB} wait 1500 -${AB} screenshot "${ARTIFACT_DIR}/04-rewind-modal.png" >/dev/null -${AB} find role button click --name "Rewind here" +${AB} screenshot "${ARTIFACT_DIR}/04-undo-current-turn-confirm.png" >/dev/null +${AB} find role button click --name "Undo current turn" for _ in $(seq 1 120); do if grep -q "before-rewind" "${TARGET_FILE}" && ! grep -q "after-rewind" "${TARGET_FILE}"; then diff --git a/desktop/scripts/e2e-rewind-complex-agent-browser.sh b/desktop/scripts/e2e-rewind-complex-agent-browser.sh index 1739f0f0..265d8c02 100755 --- a/desktop/scripts/e2e-rewind-complex-agent-browser.sh +++ b/desktop/scripts/e2e-rewind-complex-agent-browser.sh @@ -171,10 +171,10 @@ wait_for_file_contains "${README_FILE}" "Second turn touched README" 60 wait_for_file_contains "${GENERATED_FILE}" "second-turn" 60 ${AB} screenshot "${ARTIFACT_DIR}/04-after-second-turn.png" >/dev/null -${AB} eval "const buttons = [...document.querySelectorAll('button')].filter((node) => node.getAttribute('aria-label') === 'Rewind to here'); if (buttons.length < 2) throw new Error('Expected at least two Rewind buttons, found ' + buttons.length); buttons[buttons.length - 1].click();" +${AB} find role button click --name "Undo current turn changes" ${AB} wait 1500 -${AB} screenshot "${ARTIFACT_DIR}/05-rewind-second-turn-modal.png" >/dev/null -${AB} find role button click --name "Rewind here" +${AB} screenshot "${ARTIFACT_DIR}/05-undo-second-turn-confirm.png" >/dev/null +${AB} find role button click --name "Undo current turn" for _ in $(seq 1 120); do if grep -q "turn-one" "${APP_FILE}" \ diff --git a/desktop/src/__tests__/pages.test.tsx b/desktop/src/__tests__/pages.test.tsx index a711ca3a..01beb273 100644 --- a/desktop/src/__tests__/pages.test.tsx +++ b/desktop/src/__tests__/pages.test.tsx @@ -1,4 +1,4 @@ -import { describe, it, expect, vi } from 'vitest' +import { beforeEach, describe, it, expect, vi } from 'vitest' import { fireEvent, render, screen } from '@testing-library/react' import '@testing-library/jest-dom' @@ -46,9 +46,14 @@ import { ToolInspection } from '../pages/ToolInspection' import { Sidebar } from '../components/layout/Sidebar' import { UserMessage } from '../components/chat/UserMessage' import { useChatStore } from '../stores/chatStore' +import { useSettingsStore } from '../stores/settingsStore' import { useSessionStore } from '../stores/sessionStore' import { useTabStore } from '../stores/tabStore' +beforeEach(() => { + useSettingsStore.setState({ locale: 'en' }) +}) + /** * Core rendering tests: content-only pages must render without crashing * and contain key structural elements from the prototype. @@ -576,7 +581,8 @@ describe('Design system compliance', () => { html.includes('C47A5A') || html.includes('8F482F') || html.includes('var(--color-brand)') || - html.includes('bg-[var(--color-brand)]'), + html.includes('bg-[var(--color-brand)]') || + html.includes('var(--gradient-btn-primary)'), ).toBe(true) unmount() } diff --git a/desktop/src/components/chat/AskUserQuestion.test.tsx b/desktop/src/components/chat/AskUserQuestion.test.tsx index 4a8686f1..b6d15d85 100644 --- a/desktop/src/components/chat/AskUserQuestion.test.tsx +++ b/desktop/src/components/chat/AskUserQuestion.test.tsx @@ -24,6 +24,7 @@ vi.mock('../../api/sessions', () => ({ import { AskUserQuestion } from './AskUserQuestion' import { useChatStore } from '../../stores/chatStore' +import { useSettingsStore } from '../../stores/settingsStore' import { useTabStore } from '../../stores/tabStore' const ACTIVE_TAB = 'active-tab' @@ -31,6 +32,7 @@ const ACTIVE_TAB = 'active-tab' describe('AskUserQuestion', () => { beforeEach(() => { sendMock.mockReset() + useSettingsStore.setState({ locale: 'en' }) useTabStore.setState({ activeTabId: ACTIVE_TAB, tabs: [{ sessionId: ACTIVE_TAB, title: 'Test', type: 'session', status: 'idle' }], diff --git a/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx b/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx index c8e372f2..409cef44 100644 --- a/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx +++ b/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx @@ -73,12 +73,14 @@ vi.mock('../../api/computerUse', () => ({ })) import { useChatStore } from '../../stores/chatStore' +import { useSettingsStore } from '../../stores/settingsStore' import { ComputerUsePermissionModal } from './ComputerUsePermissionModal' describe('ComputerUsePermissionModal', () => { beforeEach(() => { sendMock.mockReset() openSettingsMock.mockReset() + useSettingsStore.setState({ locale: 'en' }) useChatStore.setState({ sessions: {} }) }) diff --git a/desktop/src/components/chat/FileSearchMenu.test.tsx b/desktop/src/components/chat/FileSearchMenu.test.tsx index 45ba72c2..ba6cd561 100644 --- a/desktop/src/components/chat/FileSearchMenu.test.tsx +++ b/desktop/src/components/chat/FileSearchMenu.test.tsx @@ -4,6 +4,7 @@ import '@testing-library/jest-dom' import { FileSearchMenu } from './FileSearchMenu' import { ApiError } from '../../api/client' import { filesystemApi } from '../../api/filesystem' +import { useSettingsStore } from '../../stores/settingsStore' vi.mock('../../api/filesystem', () => ({ filesystemApi: { @@ -15,6 +16,7 @@ vi.mock('../../api/filesystem', () => ({ describe('FileSearchMenu', () => { beforeEach(() => { vi.clearAllMocks() + useSettingsStore.setState({ locale: 'en' }) }) it('shows an explicit error when directory browsing is denied', async () => { diff --git a/desktop/src/components/chat/MessageActionBar.tsx b/desktop/src/components/chat/MessageActionBar.tsx index 34070cae..3861bd40 100644 --- a/desktop/src/components/chat/MessageActionBar.tsx +++ b/desktop/src/components/chat/MessageActionBar.tsx @@ -3,22 +3,17 @@ import { CopyButton } from '../shared/CopyButton' type Props = { copyText?: string copyLabel: string - onRewind?: () => void - rewindLabel?: string align?: 'start' | 'end' } export function MessageActionBar({ copyText, copyLabel, - onRewind, - rewindLabel = 'Rewind to here', align = 'start', }: Props) { const hasCopy = Boolean(copyText?.trim()) - const hasRewind = Boolean(onRewind) - if (!hasCopy && !hasRewind) return null + if (!hasCopy) return null return (
- {hasRewind && ( - - )} - {hasCopy && ( - - )} +
) diff --git a/desktop/src/components/chat/MessageList.test.tsx b/desktop/src/components/chat/MessageList.test.tsx index af05d42a..c24749d2 100644 --- a/desktop/src/components/chat/MessageList.test.tsx +++ b/desktop/src/components/chat/MessageList.test.tsx @@ -563,7 +563,7 @@ describe('MessageList nested tool calls', () => { expect(assistantShell?.className).not.toContain('ml-10') }) - it('opens a rewind preview modal for user messages', async () => { + it('does not expose the old message-level rewind action', async () => { vi.spyOn(sessionsApi, 'rewind').mockResolvedValue({ target: { targetUserMessageId: 'user-1', @@ -575,11 +575,19 @@ describe('MessageList nested tool calls', () => { }, code: { available: true, - filesChanged: ['src/example.ts'], - insertions: 6, - deletions: 2, + filesChanged: ['src/App.tsx'], + insertions: 4, + deletions: 1, }, }) + vi.spyOn(sessionsApi, 'getWorkspaceStatus').mockResolvedValue({ + state: 'ok', + workDir: '/tmp/example-project', + repoName: 'example-project', + branch: null, + isGitRepo: false, + changedFiles: [], + }) useChatStore.setState({ sessions: { @@ -588,74 +596,15 @@ describe('MessageList nested tool calls', () => { { id: 'user-1', type: 'user_text', - content: '回到这一步重做', - timestamp: 1, - }, - ], - }), - }, - }) - - render() - - fireEvent.click(screen.getByRole('button', { name: 'Rewind to here' })) - - const dialog = await screen.findByRole('dialog') - expect(within(dialog).getByText('Rewind Conversation')).toBeTruthy() - expect(within(dialog).getByText('回到这一步重做')).toBeTruthy() - expect(within(dialog).getByText('src/example.ts')).toBeTruthy() - expect(sessionsApi.rewind).toHaveBeenCalledWith(ACTIVE_TAB, { - targetUserMessageId: 'user-1', - userMessageIndex: 0, - expectedContent: '回到这一步重做', - dryRun: true, - }) - }) - - it('confirms rewind with the selected message id and prompt guard', async () => { - vi.spyOn(sessionsApi, 'rewind').mockResolvedValue({ - target: { - targetUserMessageId: 'user-2', - userMessageIndex: 1, - userMessageCount: 2, - }, - conversation: { - messagesRemoved: 2, - }, - code: { - available: false, - filesChanged: [], - insertions: 0, - deletions: 0, - }, - }) - const reloadHistory = vi.fn().mockResolvedValue(undefined) - const queueComposerPrefill = vi.fn() - - useChatStore.setState({ - reloadHistory, - queueComposerPrefill, - sessions: { - [ACTIVE_TAB]: makeSessionState({ - messages: [ - { - id: 'user-1', - type: 'user_text', - content: '第一段', + content: '做一个页面', timestamp: 1, }, { id: 'assistant-1', type: 'assistant_text', - content: 'ok', + content: 'done', timestamp: 2, }, - { - id: 'user-2', - type: 'user_text', - content: '第二段', - timestamp: 3, - }, ], }), }, @@ -663,23 +612,8 @@ describe('MessageList nested tool calls', () => { render() - const buttons = screen.getAllByRole('button', { name: 'Rewind to here' }) - fireEvent.click(buttons[1]!) - const dialog = await screen.findByRole('dialog') - fireEvent.click(within(dialog).getByRole('button', { name: /Rewind here/ })) - - await waitFor(() => { - expect(sessionsApi.rewind).toHaveBeenLastCalledWith(ACTIVE_TAB, { - targetUserMessageId: 'user-2', - userMessageIndex: 1, - expectedContent: '第二段', - }) - }) - expect(reloadHistory).toHaveBeenCalledWith(ACTIVE_TAB) - expect(queueComposerPrefill).toHaveBeenCalledWith(ACTIVE_TAB, { - text: '第二段', - attachments: undefined, - }) + expect(await screen.findByRole('button', { name: 'Undo current turn changes' })).toBeTruthy() + expect(screen.queryByRole('button', { name: 'Rewind to here' })).toBeNull() }) it('shows a current-turn change card from checkpoint preview', async () => { diff --git a/desktop/src/components/chat/MessageList.tsx b/desktop/src/components/chat/MessageList.tsx index 278e12fe..f299a782 100644 --- a/desktop/src/components/chat/MessageList.tsx +++ b/desktop/src/components/chat/MessageList.tsx @@ -19,8 +19,6 @@ import { StreamingIndicator } from './StreamingIndicator' import { InlineTaskSummary } from './InlineTaskSummary' import { CurrentTurnChangeCard } from './CurrentTurnChangeCard' import type { AgentTaskNotification, UIMessage } from '../../types/chat' -import { Modal } from '../shared/Modal' -import { Button } from '../shared/Button' import { ConfirmDialog } from '../shared/ConfirmDialog' type ToolCall = Extract @@ -191,16 +189,6 @@ export function MessageList({ sessionId, compact = false }: MessageListProps = { const shouldAutoScrollRef = useRef(true) const lastSessionIdRef = useRef(resolvedSessionId) const t = useTranslation() - const [rewindTarget, setRewindTarget] = useState<{ - messageId: string - userMessageIndex: number - content: string - attachments?: Extract['attachments'] - } | null>(null) - const [rewindPreview, setRewindPreview] = useState(null) - const [rewindError, setRewindError] = useState(null) - const [isLoadingPreview, setIsLoadingPreview] = useState(false) - const [isExecutingRewind, setIsExecutingRewind] = useState(false) const [currentTurnPreview, setCurrentTurnPreview] = useState(null) const [currentTurnError, setCurrentTurnError] = useState(null) const [isLoadingCurrentTurnPreview, setIsLoadingCurrentTurnPreview] = useState(false) @@ -224,49 +212,6 @@ export function MessageList({ sessionId, compact = false }: MessageListProps = { bottomRef.current?.scrollIntoView?.({ behavior: 'smooth' }) }, [messages.length, resolvedSessionId, streamingText]) - useEffect(() => { - if (!resolvedSessionId || !rewindTarget) return - - let cancelled = false - setIsLoadingPreview(true) - setRewindPreview(null) - setRewindError(null) - - void sessionsApi - .rewind(resolvedSessionId, { - targetUserMessageId: rewindTarget.messageId, - userMessageIndex: rewindTarget.userMessageIndex, - expectedContent: rewindTarget.content, - dryRun: true, - }) - .then((preview) => { - if (!cancelled) { - setRewindPreview(preview) - } - }) - .catch((error) => { - if (cancelled) return - const message = - error instanceof ApiError - ? typeof error.body === 'object' && error.body && 'message' in error.body - ? String((error.body as { message: unknown }).message) - : error.message - : error instanceof Error - ? error.message - : String(error) - setRewindError(message) - }) - .finally(() => { - if (!cancelled) { - setIsLoadingPreview(false) - } - }) - - return () => { - cancelled = true - } - }, [resolvedSessionId, rewindTarget]) - const { toolResultMap, childToolCallsByParent, renderItems } = useMemo( () => buildRenderModel(messages), [messages], @@ -335,75 +280,6 @@ export function MessageList({ sessionId, compact = false }: MessageListProps = { } }, [chatState, isMemberSession, latestTurnTarget, resolvedSessionId]) - const closeRewindModal = useCallback(() => { - if (isExecutingRewind) return - setRewindTarget(null) - setRewindPreview(null) - setRewindError(null) - setIsLoadingPreview(false) - }, [isExecutingRewind]) - - const handleConfirmRewind = useCallback(async () => { - if (!resolvedSessionId || !rewindTarget || isExecutingRewind) return - - setIsExecutingRewind(true) - setRewindError(null) - - try { - if (chatState !== 'idle') { - stopGeneration(resolvedSessionId) - } - - const result = await sessionsApi.rewind(resolvedSessionId, { - targetUserMessageId: rewindTarget.messageId, - userMessageIndex: rewindTarget.userMessageIndex, - expectedContent: rewindTarget.content, - }) - - await reloadHistory(resolvedSessionId) - queueComposerPrefill(resolvedSessionId, { - text: rewindTarget.content, - attachments: rewindTarget.attachments, - }) - - addToast({ - type: 'success', - message: result.code.available - ? t('chat.rewindSuccessWithCode', { - count: result.conversation.messagesRemoved, - }) - : t('chat.rewindSuccessConversationOnly', { - count: result.conversation.messagesRemoved, - }), - }) - - setRewindTarget(null) - setRewindPreview(null) - } catch (error) { - const message = - error instanceof ApiError - ? typeof error.body === 'object' && error.body && 'message' in error.body - ? String((error.body as { message: unknown }).message) - : error.message - : error instanceof Error - ? error.message - : String(error) - setRewindError(message) - } finally { - setIsExecutingRewind(false) - } - }, [ - addToast, - chatState, - isExecutingRewind, - queueComposerPrefill, - reloadHistory, - resolvedSessionId, - rewindTarget, - stopGeneration, - t, - ]) - const handleUndoCurrentTurn = useCallback(async () => { if (!resolvedSessionId || !currentTurnPreview || isUndoingCurrentTurn) return @@ -467,8 +343,6 @@ export function MessageList({ sessionId, compact = false }: MessageListProps = { t, ]) - let visibleUserMessageIndex = -1 - return (
{ - setRewindTarget({ - messageId: message.id, - userMessageIndex, - content: message.content, - attachments: message.attachments, - }) - } - : undefined - } /> ) })} @@ -563,119 +420,6 @@ export function MessageList({ sessionId, compact = false }: MessageListProps = {
- - - - - } - > -
-
-
- {t('chat.rewindPromptLabel')} -
-
- {rewindTarget?.content || t('chat.rewindAttachmentOnly')} -
-
- - {isLoadingPreview && ( -
- {t('chat.rewindLoading')} -
- )} - - {!isLoadingPreview && rewindPreview && ( -
-
-
- history - {t('chat.rewindConversationCardTitle')} -
-

- {t('chat.rewindConversationCardBody', { - count: rewindPreview.conversation.messagesRemoved, - })} -

-
- -
-
- code - {t('chat.rewindCodeCardTitle')} -
- {rewindPreview.code.available ? ( -
-
{t('chat.rewindCodeFiles', { count: rewindPreview.code.filesChanged.length })}
-
{t('chat.rewindCodeInsertions', { count: rewindPreview.code.insertions })}
-
{t('chat.rewindCodeDeletions', { count: rewindPreview.code.deletions })}
-
- ) : ( -

- {rewindPreview.code.reason || t('chat.rewindCodeUnavailable')} -

- )} -
-
- )} - - {!isLoadingPreview && rewindPreview?.code.available && rewindPreview.code.filesChanged.length > 0 && ( -
-
- {t('chat.rewindFilesLabel')} -
-
- {rewindPreview.code.filesChanged.slice(0, 8).map((filePath) => ( - - {filePath} - - ))} - {rewindPreview.code.filesChanged.length > 8 && ( - - {t('chat.rewindFilesMore', { - count: rewindPreview.code.filesChanged.length - 8, - })} - - )} -
-
- )} - - {rewindError && ( -
- {rewindError} -
- )} -
-
- { @@ -700,18 +444,11 @@ export const MessageBlock = memo(function MessageBlock({ activeThinkingId, agentTaskNotifications, toolResult, - rewindableUserIndex, - onRequestRewind, }: { message: UIMessage activeThinkingId: string | null agentTaskNotifications: Record toolResult?: { content: unknown; isError: boolean } | null - rewindableUserIndex?: number | null - onRequestRewind?: ( - message: Extract, - userMessageIndex: number, - ) => void }) { const t = useTranslation() @@ -721,12 +458,6 @@ export const MessageBlock = memo(function MessageBlock({ onRequestRewind(message, rewindableUserIndex) - : undefined - } - rewindLabel={t('chat.rewindAction')} /> ) case 'assistant_text': diff --git a/desktop/src/components/chat/UserMessage.tsx b/desktop/src/components/chat/UserMessage.tsx index 729ed489..9162384e 100644 --- a/desktop/src/components/chat/UserMessage.tsx +++ b/desktop/src/components/chat/UserMessage.tsx @@ -5,11 +5,9 @@ import { MessageActionBar } from './MessageActionBar' type Props = { content: string attachments?: UIAttachment[] - onRewind?: () => void - rewindLabel?: string } -export function UserMessage({ content, attachments, onRewind, rewindLabel }: Props) { +export function UserMessage({ content, attachments }: Props) { const hasText = content.trim().length > 0 return ( @@ -35,8 +33,6 @@ export function UserMessage({ content, attachments, onRewind, rewindLabel }: Pro )} diff --git a/desktop/src/components/chat/chatBlocks.test.tsx b/desktop/src/components/chat/chatBlocks.test.tsx index 270144e2..de59a471 100644 --- a/desktop/src/components/chat/chatBlocks.test.tsx +++ b/desktop/src/components/chat/chatBlocks.test.tsx @@ -4,10 +4,12 @@ import { ThinkingBlock } from './ThinkingBlock' import { ToolCallBlock } from './ToolCallBlock' import { PermissionDialog } from './PermissionDialog' import { useChatStore } from '../../stores/chatStore' +import { useSettingsStore } from '../../stores/settingsStore' import { useTabStore } from '../../stores/tabStore' describe('chat blocks', () => { beforeEach(() => { + useSettingsStore.setState({ locale: 'en' }) useTabStore.setState({ activeTabId: 'active-tab', tabs: [{ sessionId: 'active-tab', title: 'Test', type: 'session' as const, status: 'idle' }] }) useChatStore.setState({ sessions: {} }) }) diff --git a/desktop/src/components/shared/UpdateChecker.test.tsx b/desktop/src/components/shared/UpdateChecker.test.tsx index da1b0c60..e29e2785 100644 --- a/desktop/src/components/shared/UpdateChecker.test.tsx +++ b/desktop/src/components/shared/UpdateChecker.test.tsx @@ -3,10 +3,12 @@ import { render, screen } from '@testing-library/react' import '@testing-library/jest-dom' import { UpdateChecker } from './UpdateChecker' +import { useSettingsStore } from '../../stores/settingsStore' import { useUpdateStore } from '../../stores/updateStore' describe('UpdateChecker', () => { beforeEach(() => { + useSettingsStore.setState({ locale: 'en' }) Object.defineProperty(window, '__TAURI__', { value: {}, configurable: true, diff --git a/desktop/src/i18n/locales/en.ts b/desktop/src/i18n/locales/en.ts index bd40377b..f19d0eda 100644 --- a/desktop/src/i18n/locales/en.ts +++ b/desktop/src/i18n/locales/en.ts @@ -617,21 +617,6 @@ export const en = { 'chat.select': 'select', 'chat.dismiss': 'dismiss', 'chat.stopTitle': 'Stop generation (Cmd+.)', - 'chat.rewindAction': 'Rewind to here', - 'chat.rewindModalTitle': 'Rewind Conversation', - 'chat.rewindConfirm': 'Rewind here', - 'chat.rewindPromptLabel': 'Selected prompt', - 'chat.rewindAttachmentOnly': 'This message only contains attachments.', - 'chat.rewindLoading': 'Inspecting file checkpoints for this turn...', - 'chat.rewindConversationCardTitle': 'Conversation rollback', - 'chat.rewindConversationCardBody': 'Remove this prompt and the {count} active messages that follow it, then reopen the composer at that point.', - 'chat.rewindCodeCardTitle': 'Code rollback', - 'chat.rewindCodeFiles': '{count} files will be restored', - 'chat.rewindCodeInsertions': '{count} insertions will be removed', - 'chat.rewindCodeDeletions': '{count} deletions will be restored', - 'chat.rewindCodeUnavailable': 'No file checkpoint is available for this prompt. The conversation can still be rewound.', - 'chat.rewindFilesLabel': 'Affected files', - 'chat.rewindFilesMore': '+{count} more', 'chat.rewindSuccessWithCode': 'Rewound {count} messages and restored tracked files.', 'chat.rewindSuccessConversationOnly': 'Rewound {count} messages. No file checkpoint was available for this turn.', 'chat.turnChangesCardLabel': 'Current turn changed files', diff --git a/desktop/src/i18n/locales/zh.ts b/desktop/src/i18n/locales/zh.ts index ad985b15..3e721154 100644 --- a/desktop/src/i18n/locales/zh.ts +++ b/desktop/src/i18n/locales/zh.ts @@ -619,21 +619,6 @@ export const zh: Record = { 'chat.select': '选择', 'chat.dismiss': '关闭', 'chat.stopTitle': '停止生成 (Cmd+.)', - 'chat.rewindAction': '回滚到这里', - 'chat.rewindModalTitle': '回滚对话', - 'chat.rewindConfirm': '执行回滚', - 'chat.rewindPromptLabel': '目标提示词', - 'chat.rewindAttachmentOnly': '这条消息只包含附件。', - 'chat.rewindLoading': '正在检查这一轮的文件检查点...', - 'chat.rewindConversationCardTitle': '对话回滚', - 'chat.rewindConversationCardBody': '移除这条提示词以及其后的 {count} 条活跃消息,并把输入框恢复到这个位置。', - 'chat.rewindCodeCardTitle': '代码回滚', - 'chat.rewindCodeFiles': '将恢复 {count} 个文件', - 'chat.rewindCodeInsertions': '将移除 {count} 行新增', - 'chat.rewindCodeDeletions': '将恢复 {count} 行删除', - 'chat.rewindCodeUnavailable': '这条提示词没有可用的文件检查点,仍然可以只回滚对话。', - 'chat.rewindFilesLabel': '受影响文件', - 'chat.rewindFilesMore': '另有 {count} 个', 'chat.rewindSuccessWithCode': '已回滚 {count} 条消息,并恢复相关文件。', 'chat.rewindSuccessConversationOnly': '已回滚 {count} 条消息。这一轮没有可用的文件检查点。', 'chat.turnChangesCardLabel': '当前轮次已更改文件',