mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat(chat): add previous checkpoint navigation controls and i18n (#12139)
Some checks are pending
Code QA Roo Code / check-translations (push) Waiting to run
Code QA Roo Code / knip (push) Waiting to run
Code QA Roo Code / compile (push) Waiting to run
Code QA Roo Code / platform-unit-test (ubuntu-latest) (push) Waiting to run
Code QA Roo Code / platform-unit-test (windows-latest) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Nightly Publish / publish-nightly (push) Waiting to run
Some checks are pending
Code QA Roo Code / check-translations (push) Waiting to run
Code QA Roo Code / knip (push) Waiting to run
Code QA Roo Code / compile (push) Waiting to run
Code QA Roo Code / platform-unit-test (ubuntu-latest) (push) Waiting to run
Code QA Roo Code / platform-unit-test (windows-latest) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Nightly Publish / publish-nightly (push) Waiting to run
This commit is contained in:
parent
3e202ebf5b
commit
2bb826039b
25 changed files with 251 additions and 15 deletions
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -1261,6 +1261,23 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
return result
|
||||
}, [isCondensing, visibleMessages])
|
||||
|
||||
const checkpointIndices = useMemo(() => {
|
||||
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<number | null>(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<ChatViewRef, ChatViewPro
|
|||
vscode.postMessage({ type: "cancelAutoApproval" })
|
||||
}, [])
|
||||
|
||||
const handleScrollToBottomAndResetCheckpointCursor = useCallback(() => {
|
||||
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<ChatViewRef, ChatViewPro
|
|||
})()
|
||||
}
|
||||
hasCheckpoint={hasCheckpoint}
|
||||
onJumpToPreviousCheckpoint={handleScrollToLatestCheckpoint}
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
|
@ -1447,6 +1488,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
isFollowUpAutoApprovalPaused,
|
||||
enableButtons,
|
||||
primaryButtonText,
|
||||
handleScrollToLatestCheckpoint,
|
||||
],
|
||||
)
|
||||
|
||||
|
|
@ -1641,14 +1683,27 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
showScrollToBottom ? "opacity-100" : enableButtons ? "opacity-100" : "opacity-50"
|
||||
}`}>
|
||||
{showScrollToBottom ? (
|
||||
<StandardTooltip content={t("chat:scrollToBottom")}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex-[2]"
|
||||
onClick={handleScrollToBottomClick}>
|
||||
<span className="codicon codicon-chevron-down"></span>
|
||||
</Button>
|
||||
</StandardTooltip>
|
||||
<>
|
||||
<StandardTooltip content={t("chat:scrollToBottom")}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={hasLatestCheckpoint ? "flex-1 mr-[6px]" : "flex-[2]"}
|
||||
onClick={handleScrollToBottomAndResetCheckpointCursor}>
|
||||
<span className="codicon codicon-chevron-down"></span>
|
||||
</Button>
|
||||
</StandardTooltip>
|
||||
{hasLatestCheckpoint && (
|
||||
<StandardTooltip content={t("chat:scrollToLatestCheckpoint")}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex-1 ml-[6px]"
|
||||
onClick={handleScrollToLatestCheckpoint}
|
||||
aria-label={t("chat:scrollToLatestCheckpoint")}>
|
||||
<span className="codicon codicon-history"></span>
|
||||
</Button>
|
||||
</StandardTooltip>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{primaryButtonText && (
|
||||
|
|
|
|||
|
|
@ -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<VirtuosoHarnessState>(() => ({
|
||||
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 = () =>
|
|||
</ExtensionStateContextProvider>,
|
||||
)
|
||||
|
||||
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" })
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
|||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<StandardTooltip content={t("chat:scrollToLatestCheckpoint")}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onJumpToPreviousCheckpoint}
|
||||
data-testid="jump-previous-checkpoint-btn"
|
||||
aria-label={t("chat:scrollToLatestCheckpoint")}>
|
||||
<span className="codicon codicon-chevron-up" />
|
||||
</Button>
|
||||
</StandardTooltip>
|
||||
<Popover open={moreOpen} onOpenChange={(open) => setMoreOpen(open)} data-testid="more-popover">
|
||||
<StandardTooltip content={t("chat:task.seeMore")}>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
|
|||
|
|
@ -11,9 +11,15 @@ type CheckpointSavedProps = {
|
|||
commitHash: string
|
||||
currentHash?: string
|
||||
checkpoint?: Record<string, unknown>
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<CheckpointSaved {...baseProps} onJumpToPreviousCheckpoint={onJumpToPreviousCheckpoint} />,
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ca/chat.json
generated
1
webview-ui/src/i18n/locales/ca/chat.json
generated
|
|
@ -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 <DocsLink>documents</DocsLink> per a més informació.",
|
||||
"onboarding": "La teva llista de tasques en aquest espai de treball està buida.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/de/chat.json
generated
1
webview-ui/src/i18n/locales/de/chat.json
generated
|
|
@ -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 <DocsLink>Dokumentation</DocsLink>, um mehr zu erfahren.",
|
||||
"onboarding": "Deine Aufgabenliste in diesem Arbeitsbereich ist leer.",
|
||||
|
|
|
|||
|
|
@ -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 <DocsLink>docs</DocsLink> to get started",
|
||||
"onboarding": "What would you like to do?",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/es/chat.json
generated
1
webview-ui/src/i18n/locales/es/chat.json
generated
|
|
@ -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 <DocsLink>documentación</DocsLink> para saber más.",
|
||||
"onboarding": "Tu lista de tareas en este espacio de trabajo está vacía.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/fr/chat.json
generated
1
webview-ui/src/i18n/locales/fr/chat.json
generated
|
|
@ -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 <DocsLink>documentation</DocsLink> pour en savoir plus.",
|
||||
"onboarding": "Votre liste de tâches dans cet espace de travail est vide.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/hi/chat.json
generated
1
webview-ui/src/i18n/locales/hi/chat.json
generated
|
|
@ -97,6 +97,7 @@
|
|||
},
|
||||
"enqueueMessage": "संदेश को कतार में जोड़ें (वर्तमान कार्य पूरा होने के बाद भेजा जाएगा)",
|
||||
"scrollToBottom": "चैट के निचले हिस्से तक स्क्रॉल करें",
|
||||
"scrollToLatestCheckpoint": "पिछले चेकपॉइंट तक स्क्रॉल करें",
|
||||
"about": "Roo Code आपके संपादक में एक पूरी AI देव टीम है।",
|
||||
"docs": "और जानने के लिए हमारे <DocsLink>दस्तावेज़</DocsLink> देखें।",
|
||||
"onboarding": "इस कार्यक्षेत्र में आपकी कार्य सूची खाली है।",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/id/chat.json
generated
1
webview-ui/src/i18n/locales/id/chat.json
generated
|
|
@ -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 <DocsLink>dokumentasi</DocsLink> kami untuk mempelajari lebih lanjut.",
|
||||
"onboarding": "Daftar tugas Anda di ruang kerja ini kosong.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/it/chat.json
generated
1
webview-ui/src/i18n/locales/it/chat.json
generated
|
|
@ -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 <DocsLink>documentazione</DocsLink> per saperne di più.",
|
||||
"onboarding": "La tua lista di attività in questo spazio di lavoro è vuota.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ja/chat.json
generated
1
webview-ui/src/i18n/locales/ja/chat.json
generated
|
|
@ -97,6 +97,7 @@
|
|||
},
|
||||
"enqueueMessage": "メッセージをキューに追加(現在のタスク完了後に送信されます)",
|
||||
"scrollToBottom": "チャットの最下部にスクロール",
|
||||
"scrollToLatestCheckpoint": "前のチェックポイントまでスクロール",
|
||||
"about": "Roo Codeは、エディタに常駐するAI開発チームです。",
|
||||
"docs": "詳細については、<DocsLink>ドキュメント</DocsLink>をご確認ください。",
|
||||
"onboarding": "このワークスペースのタスクリストは空です。",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ko/chat.json
generated
1
webview-ui/src/i18n/locales/ko/chat.json
generated
|
|
@ -97,6 +97,7 @@
|
|||
},
|
||||
"enqueueMessage": "메시지를 대기열에 추가 (현재 작업 완료 후 전송)",
|
||||
"scrollToBottom": "채팅 하단으로 스크롤",
|
||||
"scrollToLatestCheckpoint": "이전 체크포인트로 스크롤",
|
||||
"about": "Roo Code는 편집기 안에 있는 전체 AI 개발팀입니다.",
|
||||
"docs": "더 알아보려면 <DocsLink>문서</DocsLink>를 확인하세요.",
|
||||
"onboarding": "이 작업 공간의 작업 목록이 비어 있습니다.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/nl/chat.json
generated
1
webview-ui/src/i18n/locales/nl/chat.json
generated
|
|
@ -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 <DocsLink>documentatie</DocsLink> voor meer informatie.",
|
||||
"onboarding": "Je takenlijst in deze werkruimte is leeg.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/pl/chat.json
generated
1
webview-ui/src/i18n/locales/pl/chat.json
generated
|
|
@ -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ą <DocsLink>dokumentację</DocsLink>, aby dowiedzieć się więcej.",
|
||||
"onboarding": "Twoja lista zadań w tym obszarze roboczym jest pusta.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
1
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
|
|
@ -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 <DocsLink>documentação</DocsLink> para saber mais.",
|
||||
"onboarding": "Sua lista de tarefas neste espaço de trabalho está vazia.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/ru/chat.json
generated
1
webview-ui/src/i18n/locales/ru/chat.json
generated
|
|
@ -97,6 +97,7 @@
|
|||
},
|
||||
"enqueueMessage": "Добавить сообщение в очередь (будет отправлено после завершения текущей задачи)",
|
||||
"scrollToBottom": "Прокрутить чат вниз",
|
||||
"scrollToLatestCheckpoint": "Прокрутить к предыдущему чекпоинту",
|
||||
"about": "Roo Code — это целая команда разработчиков ИИ в вашем редакторе.",
|
||||
"docs": "Ознакомьтесь с нашей <DocsLink>документацией</DocsLink>, чтобы узнать больше.",
|
||||
"onboarding": "Ваш список задач в этом рабочем пространстве пуст.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/tr/chat.json
generated
1
webview-ui/src/i18n/locales/tr/chat.json
generated
|
|
@ -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 <DocsLink>belgelerimize</DocsLink> göz atın.",
|
||||
"onboarding": "Bu çalışma alanındaki görev listeniz boş.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/vi/chat.json
generated
1
webview-ui/src/i18n/locales/vi/chat.json
generated
|
|
@ -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 <DocsLink>tài liệu</DocsLink> 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.",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
1
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
|
|
@ -97,6 +97,7 @@
|
|||
},
|
||||
"enqueueMessage": "将消息加入队列(当前任务完成后发送)",
|
||||
"scrollToBottom": "滚动到聊天底部",
|
||||
"scrollToLatestCheckpoint": "滚动到上一个检查点",
|
||||
"about": "Roo Code 是您编辑器中的整个 AI 开发团队。",
|
||||
"docs": "查看我们的 <DocsLink>文档</DocsLink> 了解更多信息。",
|
||||
"onboarding": "此工作区中的任务列表为空。",
|
||||
|
|
|
|||
1
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
1
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
|
|
@ -124,6 +124,7 @@
|
|||
"placeholder": "編輯訊息..."
|
||||
},
|
||||
"scrollToBottom": "捲動至對話底部",
|
||||
"scrollToLatestCheckpoint": "捲動至上一個檢查點",
|
||||
"about": "Roo 是編輯器中的完整 AI 開發團隊。",
|
||||
"docs": "請參閱 <DocsLink>說明文件</DocsLink> 開始使用。",
|
||||
"onboarding": "想要做什麼呢?",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue