diff --git a/src/esbuild.mjs b/src/esbuild.mjs index aabacfcee99..6089ac3306d 100644 --- a/src/esbuild.mjs +++ b/src/esbuild.mjs @@ -10,6 +10,24 @@ import { copyPaths, copyWasms, copyLocales, setupLocaleWatcher } from "@roo-code const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) +async function removeDirWithRetries(dirPath, retries = 5, retryDelayMs = 200) { + for (let attempt = 0; attempt <= retries; attempt++) { + try { + await fs.promises.rm(dirPath, { recursive: true, force: true }) + return + } catch (error) { + const isRetryable = error?.code === "ENOTEMPTY" || error?.code === "EBUSY" || error?.code === "EPERM" + const isLastAttempt = attempt === retries + + if (!isRetryable || isLastAttempt) { + throw error + } + + await new Promise((resolve) => globalThis.setTimeout(resolve, retryDelayMs * (attempt + 1))) + } + } +} + async function main() { const name = "extension" const production = process.argv.includes("--production") @@ -36,7 +54,7 @@ async function main() { if (fs.existsSync(distDir)) { console.log(`[${name}] Cleaning dist directory: ${distDir}`) - fs.rmSync(distDir, { recursive: true, force: true }) + await removeDirWithRetries(distDir) } /** diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 1f3f8f5a4f2..33c9acb2df2 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -124,6 +124,7 @@ interface ChatRowProps { isFollowUpAutoApprovalPaused?: boolean editable?: boolean hasCheckpoint?: boolean + onJumpToPreviousCheckpoint?: () => void } // eslint-disable-next-line @typescript-eslint/no-empty-object-type @@ -177,6 +178,7 @@ export const ChatRowContent = ({ onBatchFileResponse, isFollowUpAnswered, isFollowUpAutoApprovalPaused, + onJumpToPreviousCheckpoint, }: ChatRowContentProps) => { const { t, i18n } = useTranslation() @@ -1341,6 +1343,7 @@ export const ChatRowContent = ({ commitHash={message.text!} currentHash={currentCheckpoint} checkpoint={message.checkpoint} + onJumpToPreviousCheckpoint={onJumpToPreviousCheckpoint} /> ) case "condense_context": diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index fd0aca66cb7..7008dbe590d 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1261,6 +1261,23 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + const indices: number[] = [] + for (let i = 0; i < groupedMessages.length; i++) { + if (groupedMessages[i]?.say === "checkpoint_saved") { + indices.push(i) + } + } + return indices + }, [groupedMessages]) + + const hasLatestCheckpoint = checkpointIndices.length > 0 + const checkpointJumpCursorRef = useRef(null) + + useEffect(() => { + checkpointJumpCursorRef.current = null + }, [task?.ts, checkpointIndices]) + // Scroll lifecycle is managed by a dedicated hook to keep ChatView focused // on message handling and UI orchestration. const { @@ -1394,6 +1411,29 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + checkpointJumpCursorRef.current = null + handleScrollToBottomClick() + }, [handleScrollToBottomClick]) + + const handleScrollToLatestCheckpoint = useCallback(() => { + if (checkpointIndices.length === 0) { + return + } + + const previousCursor = checkpointJumpCursorRef.current + const nextCursor = previousCursor === null ? checkpointIndices.length - 1 : Math.max(0, previousCursor - 1) + const nextCheckpointIndex = checkpointIndices[nextCursor] + checkpointJumpCursorRef.current = nextCursor + + enterUserBrowsingHistory("keyboard-nav-up") + virtuosoRef.current?.scrollToIndex({ + index: nextCheckpointIndex, + align: "center", + behavior: "smooth", + }) + }, [checkpointIndices, enterUserBrowsingHistory]) + const itemContent = useCallback( (index: number, messageOrGroup: ClineMessage) => { const hasCheckpoint = modifiedMessages.some((message) => message.say === "checkpoint_saved") @@ -1430,6 +1470,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction ) }, @@ -1447,6 +1488,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction {showScrollToBottom ? ( - - - + <> + + + + {hasLatestCheckpoint && ( + + + + )} + ) : ( <> {primaryButtonText && ( diff --git a/webview-ui/src/components/chat/__tests__/ChatView.scroll-debug-repro.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatView.scroll-debug-repro.spec.tsx index c71df99f706..9ed1a3756e4 100644 --- a/webview-ui/src/components/chat/__tests__/ChatView.scroll-debug-repro.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ChatView.scroll-debug-repro.spec.tsx @@ -43,6 +43,11 @@ interface MockVirtuosoProps { interface VirtuosoHarnessState { scrollCalls: number + scrollToIndexArgs: Array<{ + index: number | "LAST" + align?: "end" | "start" | "center" + behavior?: "auto" | "smooth" + }> atBottomAfterCalls: number signalDelayMs: number emitFalseOnDataChange: boolean @@ -54,6 +59,7 @@ interface VirtuosoHarnessState { const harness = vi.hoisted(() => ({ scrollCalls: 0, + scrollToIndexArgs: [], atBottomAfterCalls: Number.POSITIVE_INFINITY, signalDelayMs: 20, emitFalseOnDataChange: true, @@ -152,8 +158,9 @@ vi.mock("react-virtuoso", () => { } useImperativeHandle(ref, () => ({ - scrollToIndex: () => { + scrollToIndex: (options) => { harness.scrollCalls += 1 + harness.scrollToIndexArgs.push(options) const reachedBottom = harness.scrollCalls >= harness.atBottomAfterCalls const timeoutId = window.setTimeout(() => { atBottomRef.current?.(reachedBottom) @@ -215,6 +222,23 @@ const buildMessages = (baseTs: number): ClineMessage[] => [ { type: "say", say: "text", ts: baseTs + 2, text: "row-2" }, ] +const buildMessagesWithCheckpoint = (baseTs: number): ClineMessage[] => [ + { type: "say", say: "text", ts: baseTs, text: "task" }, + { type: "say", say: "text", ts: baseTs + 1, text: "row-1" }, + { type: "say", say: "checkpoint_saved", ts: baseTs + 2, text: "checkpoint-1" }, + { type: "say", say: "text", ts: baseTs + 3, text: "row-2" }, +] + +const buildMessagesWithMultipleCheckpoints = (baseTs: number): ClineMessage[] => [ + { type: "say", say: "text", ts: baseTs, text: "task" }, + { type: "say", say: "checkpoint_saved", ts: baseTs + 1, text: "checkpoint-1" }, + { type: "say", say: "text", ts: baseTs + 2, text: "row-2" }, + { type: "say", say: "checkpoint_saved", ts: baseTs + 3, text: "checkpoint-2" }, + { type: "say", say: "text", ts: baseTs + 4, text: "row-4" }, + { type: "say", say: "checkpoint_saved", ts: baseTs + 5, text: "checkpoint-3" }, + { type: "say", say: "text", ts: baseTs + 6, text: "row-6" }, +] + const resolveFollowOutput = (isAtBottom: boolean): "auto" | false => { const followOutput = harness.followOutput if (typeof followOutput === "function") { @@ -254,19 +278,19 @@ const renderView = () => , ) -const hydrate = async (atBottomAfterCalls: number) => { +const hydrate = async (atBottomAfterCalls: number, clineMessages = buildMessages(Date.now() - 3_000)) => { harness.atBottomAfterCalls = atBottomAfterCalls renderView() await act(async () => { await Promise.resolve() }) await act(async () => { - postState(buildMessages(Date.now() - 3_000)) + postState(clineMessages) }) await waitFor(() => { const list = document.querySelector("[data-testid='virtuoso-item-list']") expect(list).toBeTruthy() - expect(list?.getAttribute("data-count")).toBe("2") + expect(list?.getAttribute("data-count")).toBe(String(Math.max(0, clineMessages.length - 1))) }) } @@ -317,9 +341,19 @@ const getScrollToBottomButton = (): HTMLButtonElement => { return button } +const getScrollToCheckpointButton = (): HTMLButtonElement => { + const button = document.querySelector("button[aria-label='chat:scrollToLatestCheckpoint']") + if (!(button instanceof HTMLButtonElement)) { + throw new Error("Expected scroll-to-checkpoint button") + } + + return button +} + describe("ChatView scroll behavior regression coverage", () => { beforeEach(() => { harness.scrollCalls = 0 + harness.scrollToIndexArgs = [] harness.atBottomAfterCalls = Number.POSITIVE_INFINITY harness.signalDelayMs = 20 harness.emitFalseOnDataChange = true @@ -503,4 +537,71 @@ describe("ChatView scroll behavior regression coverage", () => { }) await waitFor(() => expect(document.querySelector(".codicon-chevron-down")).toBeNull(), { timeout: 1_200 }) }) + + it("shows jump-to-checkpoint button and scrolls to latest checkpoint", async () => { + await hydrate(2, buildMessagesWithCheckpoint(Date.now() - 3_000)) + await waitForCalls(2) + await waitForCallsSettled() + + await act(async () => { + fireEvent.keyDown(window, { key: "PageUp" }) + }) + + await waitFor(() => expect(document.querySelector(".codicon-chevron-down")).toBeTruthy(), { + timeout: 1_200, + }) + + const checkpointButton = document.querySelector("button[aria-label='chat:scrollToLatestCheckpoint']") + expect(checkpointButton).toBeInstanceOf(HTMLButtonElement) + + const callsBeforeClick = harness.scrollCalls + + await act(async () => { + ;(checkpointButton as HTMLButtonElement).click() + }) + + expect(harness.scrollCalls).toBe(callsBeforeClick + 1) + expect(harness.scrollToIndexArgs.at(-1)).toMatchObject({ + index: 1, + align: "center", + behavior: "smooth", + }) + }) + + it("repeated checkpoint clicks step backward through previous checkpoints", async () => { + await hydrate(2, buildMessagesWithMultipleCheckpoints(Date.now() - 3_000)) + await waitForCalls(2) + await waitForCallsSettled() + + await act(async () => { + fireEvent.keyDown(window, { key: "PageUp" }) + }) + + await waitFor(() => expect(document.querySelector(".codicon-chevron-down")).toBeTruthy(), { + timeout: 1_200, + }) + + const checkpointButton = getScrollToCheckpointButton() + + await act(async () => { + ;(checkpointButton as HTMLButtonElement).click() + }) + expect(harness.scrollToIndexArgs.at(-1)).toMatchObject({ index: 4, align: "center", behavior: "smooth" }) + + await act(async () => { + ;(checkpointButton as HTMLButtonElement).click() + }) + expect(harness.scrollToIndexArgs.at(-1)).toMatchObject({ index: 2, align: "center", behavior: "smooth" }) + + await act(async () => { + ;(checkpointButton as HTMLButtonElement).click() + }) + expect(harness.scrollToIndexArgs.at(-1)).toMatchObject({ index: 0, align: "center", behavior: "smooth" }) + + // Once at the oldest checkpoint, additional clicks keep targeting it. + await act(async () => { + ;(checkpointButton as HTMLButtonElement).click() + }) + expect(harness.scrollToIndexArgs.at(-1)).toMatchObject({ index: 0, align: "center", behavior: "smooth" }) + }) }) diff --git a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx index 4895d05b3a0..3c15bc4d872 100644 --- a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx +++ b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx @@ -12,6 +12,7 @@ type CheckpointMenuBaseProps = { ts: number commitHash: string checkpoint: Checkpoint + onJumpToPreviousCheckpoint?: () => void } type CheckpointMenuControlledProps = { onOpenChange: (open: boolean) => void @@ -21,7 +22,13 @@ type CheckpointMenuUncontrolledProps = { } type CheckpointMenuProps = CheckpointMenuBaseProps & (CheckpointMenuControlledProps | CheckpointMenuUncontrolledProps) -export const CheckpointMenu = ({ ts, commitHash, checkpoint, onOpenChange }: CheckpointMenuProps) => { +export const CheckpointMenu = ({ + ts, + commitHash, + checkpoint, + onOpenChange, + onJumpToPreviousCheckpoint, +}: CheckpointMenuProps) => { const { t } = useTranslation() const [internalRestoreOpen, setInternalRestoreOpen] = useState(false) const [restoreConfirming, setRestoreConfirming] = useState(false) @@ -165,6 +172,16 @@ export const CheckpointMenu = ({ ts, commitHash, checkpoint, onOpenChange }: Che + + + setMoreOpen(open)} data-testid="more-popover"> diff --git a/webview-ui/src/components/chat/checkpoints/CheckpointSaved.tsx b/webview-ui/src/components/chat/checkpoints/CheckpointSaved.tsx index c70392e630c..170065d737d 100644 --- a/webview-ui/src/components/chat/checkpoints/CheckpointSaved.tsx +++ b/webview-ui/src/components/chat/checkpoints/CheckpointSaved.tsx @@ -11,9 +11,15 @@ type CheckpointSavedProps = { commitHash: string currentHash?: string checkpoint?: Record + onJumpToPreviousCheckpoint?: () => void } -export const CheckpointSaved = ({ checkpoint, currentHash, ...props }: CheckpointSavedProps) => { +export const CheckpointSaved = ({ + checkpoint, + currentHash, + onJumpToPreviousCheckpoint, + ...props +}: CheckpointSavedProps) => { const { t } = useTranslation() const isCurrent = currentHash === props.commitHash const [isPopoverOpen, setIsPopoverOpen] = useState(false) @@ -100,6 +106,7 @@ export const CheckpointSaved = ({ checkpoint, currentHash, ...props }: Checkpoin commitHash={props.commitHash} checkpoint={metadata} onOpenChange={handlePopoverOpenChange} + onJumpToPreviousCheckpoint={onJumpToPreviousCheckpoint} /> diff --git a/webview-ui/src/components/chat/checkpoints/__tests__/CheckpointSaved.spec.tsx b/webview-ui/src/components/chat/checkpoints/__tests__/CheckpointSaved.spec.tsx index d2b6d48a3ff..9a6a2bf301a 100644 --- a/webview-ui/src/components/chat/checkpoints/__tests__/CheckpointSaved.spec.tsx +++ b/webview-ui/src/components/chat/checkpoints/__tests__/CheckpointSaved.spec.tsx @@ -185,4 +185,21 @@ describe("CheckpointSaved popover visibility", () => { expect(getMenu().className).toContain("hidden") }) }) + + it("renders jump-to-previous-checkpoint control and triggers callback", async () => { + const onJumpToPreviousCheckpoint = vi.fn() + const { getByTestId, container } = render( + , + ) + + const getParentDiv = () => + container.querySelector("[class*='flex items-center justify-between']") as HTMLElement + + fireEvent.mouseEnter(getParentDiv()) + + const jumpButton = await waitFor(() => getByTestId("jump-previous-checkpoint-btn")) + await userEvent.click(jumpButton) + + expect(onJumpToPreviousCheckpoint).toHaveBeenCalledTimes(1) + }) }) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index a097d2d6b41..b4fa63796d9 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "Afegeix el missatge a la cua (s'enviarà quan acabi la tasca actual)", "scrollToBottom": "Desplaça't al final del xat", + "scrollToLatestCheckpoint": "Desplaça't al checkpoint anterior", "about": "Roo Code és tot un equip de desenvolupament d'IA al teu editor.", "docs": "Consulta els nostres documents per a més informació.", "onboarding": "La teva llista de tasques en aquest espai de treball està buida.", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 51bab02a5e7..da2f075978d 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "Nachricht zur Warteschlange hinzufügen (wird nach Abschluss der aktuellen Aufgabe gesendet)", "scrollToBottom": "Zum Chat-Ende scrollen", + "scrollToLatestCheckpoint": "Zum vorherigen Checkpoint scrollen", "about": "Roo Code ist ein ganzes KI-Entwicklerteam in deinem Editor.", "docs": "Schau in unsere Dokumentation, um mehr zu erfahren.", "onboarding": "Deine Aufgabenliste in diesem Arbeitsbereich ist leer.", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index c4deaab8bc3..8caf0e95147 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -124,6 +124,7 @@ "placeholder": "Edit your message..." }, "scrollToBottom": "Scroll to bottom of chat", + "scrollToLatestCheckpoint": "Scroll to previous checkpoint", "about": "Roo is a whole AI dev team in your editor", "docs": "Check our docs to get started", "onboarding": "What would you like to do?", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 15ddc98c875..b8ed12ac96d 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "Agregar mensaje a la cola (se enviará después de que termine la tarea actual)", "scrollToBottom": "Desplazarse al final del chat", + "scrollToLatestCheckpoint": "Desplazarse al checkpoint anterior", "about": "Roo Code es todo un equipo de desarrollo de IA en tu editor.", "docs": "Consulta nuestra documentación para saber más.", "onboarding": "Tu lista de tareas en este espacio de trabajo está vacía.", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index acb0c74df3e..e4a1f66f126 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "Ajouter le message à la file d'attente (sera envoyé après la fin de la tâche en cours)", "scrollToBottom": "Défiler jusqu'au bas du chat", + "scrollToLatestCheckpoint": "Défiler jusqu'au checkpoint précédent", "about": "Roo Code est une équipe complète de développeurs IA dans votre éditeur.", "docs": "Consultez notre documentation pour en savoir plus.", "onboarding": "Votre liste de tâches dans cet espace de travail est vide.", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 368a1808efb..f8fc354c4f1 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "संदेश को कतार में जोड़ें (वर्तमान कार्य पूरा होने के बाद भेजा जाएगा)", "scrollToBottom": "चैट के निचले हिस्से तक स्क्रॉल करें", + "scrollToLatestCheckpoint": "पिछले चेकपॉइंट तक स्क्रॉल करें", "about": "Roo Code आपके संपादक में एक पूरी AI देव टीम है।", "docs": "और जानने के लिए हमारे दस्तावेज़ देखें।", "onboarding": "इस कार्यक्षेत्र में आपकी कार्य सूची खाली है।", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 11394efb4c6..b63370a51e3 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -127,6 +127,7 @@ }, "enqueueMessage": "Tambahkan pesan ke antrean (akan dikirim setelah tugas saat ini selesai)", "scrollToBottom": "Gulir ke bawah chat", + "scrollToLatestCheckpoint": "Gulir ke checkpoint sebelumnya", "about": "Roo Code adalah seluruh tim pengembang AI di editor Anda.", "docs": "Lihat dokumentasi kami untuk mempelajari lebih lanjut.", "onboarding": "Daftar tugas Anda di ruang kerja ini kosong.", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index c4bb56b1226..af00a2f4000 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "Aggiungi il messaggio alla coda (sarà inviato dopo che l'attività corrente sarà terminata)", "scrollToBottom": "Scorri fino alla fine della chat", + "scrollToLatestCheckpoint": "Scorri al checkpoint precedente", "about": "Roo Code è un intero team di sviluppo AI nel tuo editor.", "docs": "Consulta la nostra documentazione per saperne di più.", "onboarding": "La tua lista di attività in questo spazio di lavoro è vuota.", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 16765b7a9cf..88ba4dd38e9 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "メッセージをキューに追加(現在のタスク完了後に送信されます)", "scrollToBottom": "チャットの最下部にスクロール", + "scrollToLatestCheckpoint": "前のチェックポイントまでスクロール", "about": "Roo Codeは、エディタに常駐するAI開発チームです。", "docs": "詳細については、ドキュメントをご確認ください。", "onboarding": "このワークスペースのタスクリストは空です。", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 600290267ef..a5dcf224c03 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "메시지를 대기열에 추가 (현재 작업 완료 후 전송)", "scrollToBottom": "채팅 하단으로 스크롤", + "scrollToLatestCheckpoint": "이전 체크포인트로 스크롤", "about": "Roo Code는 편집기 안에 있는 전체 AI 개발팀입니다.", "docs": "더 알아보려면 문서를 확인하세요.", "onboarding": "이 작업 공간의 작업 목록이 비어 있습니다.", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 1b8743b1fe9..f717865f3a7 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "Bericht aan de wachtrij toevoegen (wordt verzonden nadat de huidige taak is voltooid)", "scrollToBottom": "Scroll naar onderaan de chat", + "scrollToLatestCheckpoint": "Scroll naar het vorige checkpoint", "about": "Roo Code is een heel AI-ontwikkelteam in je editor.", "docs": "Bekijk onze documentatie voor meer informatie.", "onboarding": "Je takenlijst in deze werkruimte is leeg.", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 700570349f0..1b928ede341 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "Dodaj wiadomość do kolejki (zostanie wysłana po zakończeniu bieżącego zadania)", "scrollToBottom": "Przewiń do dołu czatu", + "scrollToLatestCheckpoint": "Przewiń do poprzedniego punktu kontrolnego", "about": "Roo Code to cały zespół deweloperów AI w Twoim edytorze.", "docs": "Sprawdź naszą dokumentację, aby dowiedzieć się więcej.", "onboarding": "Twoja lista zadań w tym obszarze roboczym jest pusta.", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index ae431dd02c0..54c44ffd04e 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "Adicionar mensagem à fila (será enviada após a conclusão da tarefa atual)", "scrollToBottom": "Rolar para o final do chat", + "scrollToLatestCheckpoint": "Rolar para o checkpoint anterior", "about": "Roo Code é uma equipe inteira de desenvolvimento de IA em seu editor.", "docs": "Confira nossa documentação para saber mais.", "onboarding": "Sua lista de tarefas neste espaço de trabalho está vazia.", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 0eb96d4ac17..ba0e9fdd53b 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "Добавить сообщение в очередь (будет отправлено после завершения текущей задачи)", "scrollToBottom": "Прокрутить чат вниз", + "scrollToLatestCheckpoint": "Прокрутить к предыдущему чекпоинту", "about": "Roo Code — это целая команда разработчиков ИИ в вашем редакторе.", "docs": "Ознакомьтесь с нашей документацией, чтобы узнать больше.", "onboarding": "Ваш список задач в этом рабочем пространстве пуст.", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index c11e8c3d79e..e3e1682ab8a 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "Mesajı kuyruğa ekle (mevcut görev tamamlandıktan sonra gönderilecek)", "scrollToBottom": "Sohbetin altına kaydır", + "scrollToLatestCheckpoint": "Önceki checkpoint'e kaydır", "about": "Roo Code, düzenleyicinizdeki bütün bir yapay zeka geliştirme ekibidir.", "docs": "Daha fazla bilgi için belgelerimize göz atın.", "onboarding": "Bu çalışma alanındaki görev listeniz boş.", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index a3ca51576cd..bbc6f9fe9d8 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "Thêm tin nhắn vào hàng đợi (sẽ gửi sau khi nhiệm vụ hiện tại hoàn tất)", "scrollToBottom": "Cuộn xuống cuối cuộc trò chuyện", + "scrollToLatestCheckpoint": "Cuộn đến checkpoint trước đó", "about": "Roo Code là một đội ngũ phát triển AI đầy đủ trong trình chỉnh sửa của bạn.", "docs": "Kiểm tra tài liệu của chúng tôi để tìm hiểu thêm.", "onboarding": "Danh sách nhiệm vụ của bạn trong không gian làm việc này đang trống.", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index a3149c87c13..d92cd5a54b1 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -97,6 +97,7 @@ }, "enqueueMessage": "将消息加入队列(当前任务完成后发送)", "scrollToBottom": "滚动到聊天底部", + "scrollToLatestCheckpoint": "滚动到上一个检查点", "about": "Roo Code 是您编辑器中的整个 AI 开发团队。", "docs": "查看我们的 文档 了解更多信息。", "onboarding": "此工作区中的任务列表为空。", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 3a37b0d329e..ef56f9622a3 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -124,6 +124,7 @@ "placeholder": "編輯訊息..." }, "scrollToBottom": "捲動至對話底部", + "scrollToLatestCheckpoint": "捲動至上一個檢查點", "about": "Roo 是編輯器中的完整 AI 開發團隊。", "docs": "請參閱 說明文件 開始使用。", "onboarding": "想要做什麼呢?",