mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-11 22:51:26 +00:00
Add a way to save screenshots from the browser tool (#9963)
* Add a way to save screenshots from the browser tool * fix: use cross-platform paths in BrowserSession screenshot tests * fix: validate screenshot paths to prevent filesystem escape --------- Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
parent
1898848d95
commit
721b02e58c
31 changed files with 579 additions and 26 deletions
|
|
@ -23,11 +23,12 @@ export interface Size {
|
|||
}
|
||||
|
||||
export interface BrowserActionParams {
|
||||
action: "launch" | "click" | "hover" | "type" | "scroll_down" | "scroll_up" | "resize" | "close"
|
||||
action: "launch" | "click" | "hover" | "type" | "scroll_down" | "scroll_up" | "resize" | "close" | "screenshot"
|
||||
url?: string
|
||||
coordinate?: Coordinate
|
||||
size?: Size
|
||||
text?: string
|
||||
path?: string
|
||||
}
|
||||
|
||||
export interface GenerateImageParams {
|
||||
|
|
|
|||
|
|
@ -406,6 +406,7 @@ export class NativeToolCallParser {
|
|||
coordinate: partialArgs.coordinate,
|
||||
size: partialArgs.size,
|
||||
text: partialArgs.text,
|
||||
path: partialArgs.path,
|
||||
}
|
||||
}
|
||||
break
|
||||
|
|
@ -645,6 +646,7 @@ export class NativeToolCallParser {
|
|||
coordinate: args.coordinate,
|
||||
size: args.size,
|
||||
text: args.text,
|
||||
path: args.path,
|
||||
} as NativeArgsFor<TName>
|
||||
}
|
||||
break
|
||||
|
|
|
|||
|
|
@ -247,6 +247,10 @@ Parameters:
|
|||
- Use with the `size` parameter to specify the new size.
|
||||
* scroll_down: Scroll down the page by one page height.
|
||||
* scroll_up: Scroll up the page by one page height.
|
||||
* screenshot: Take a screenshot and save it to a file.
|
||||
- Use with the `path` parameter to specify the destination file path.
|
||||
- Supported formats: .png, .jpeg, .webp
|
||||
- Example: `<action>screenshot</action>` with `<path>screenshots/result.png</path>`
|
||||
* close: Close the Puppeteer-controlled browser instance. This **must always be the final browser action**.
|
||||
- Example: `<action>close</action>`
|
||||
- url: (optional) Use this for providing the URL for the `launch` action.
|
||||
|
|
@ -264,6 +268,9 @@ Parameters:
|
|||
* Example: <size>1280,720</size>
|
||||
- text: (optional) Use this for providing the text for the `type` action.
|
||||
* Example: <text>Hello, world!</text>
|
||||
- path: (optional) File path for the `screenshot` action. Path is relative to the workspace.
|
||||
* Supported formats: .png, .jpeg, .webp
|
||||
* Example: <path>screenshots/my-screenshot.png</path>
|
||||
Usage:
|
||||
<browser_action>
|
||||
<action>Action to perform (e.g., launch, click, type, press, scroll_down, scroll_up, close)</action>
|
||||
|
|
@ -284,6 +291,12 @@ Example: Requesting to click on the element at coordinates 450,300 on a 1024x768
|
|||
<coordinate>450,300@1024x768</coordinate>
|
||||
</browser_action>
|
||||
|
||||
Example: Taking a screenshot and saving it to a file
|
||||
<browser_action>
|
||||
<action>screenshot</action>
|
||||
<path>screenshots/result.png</path>
|
||||
</browser_action>
|
||||
|
||||
## ask_followup_question
|
||||
Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ Parameters:
|
|||
- Use with the \`size\` parameter to specify the new size.
|
||||
* scroll_down: Scroll down the page by one page height.
|
||||
* scroll_up: Scroll up the page by one page height.
|
||||
* screenshot: Take a screenshot and save it to a file.
|
||||
- Use with the \`path\` parameter to specify the destination file path.
|
||||
- Supported formats: .png, .jpeg, .webp
|
||||
- Example: \`<action>screenshot</action>\` with \`<path>screenshots/result.png</path>\`
|
||||
* close: Close the Puppeteer-controlled browser instance. This **must always be the final browser action**.
|
||||
- Example: \`<action>close</action>\`
|
||||
- url: (optional) Use this for providing the URL for the \`launch\` action.
|
||||
|
|
@ -56,6 +60,9 @@ Parameters:
|
|||
* Example: <size>1280,720</size>
|
||||
- text: (optional) Use this for providing the text for the \`type\` action.
|
||||
* Example: <text>Hello, world!</text>
|
||||
- path: (optional) File path for the \`screenshot\` action. Path is relative to the workspace.
|
||||
* Supported formats: .png, .jpeg, .webp
|
||||
* Example: <path>screenshots/my-screenshot.png</path>
|
||||
Usage:
|
||||
<browser_action>
|
||||
<action>Action to perform (e.g., launch, click, type, press, scroll_down, scroll_up, close)</action>
|
||||
|
|
@ -74,5 +81,11 @@ Example: Requesting to click on the element at coordinates 450,300 on a 1024x768
|
|||
<browser_action>
|
||||
<action>click</action>
|
||||
<coordinate>450,300@1024x768</coordinate>
|
||||
</browser_action>
|
||||
|
||||
Example: Taking a screenshot and saving it to a file
|
||||
<browser_action>
|
||||
<action>screenshot</action>
|
||||
<path>screenshots/result.png</path>
|
||||
</browser_action>`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ const SIZE_PARAMETER_DESCRIPTION = `Viewport dimensions for the resize action in
|
|||
|
||||
const TEXT_PARAMETER_DESCRIPTION = `Text to type when performing the type action, or key name to press when performing the press action (e.g., 'Enter', 'Tab', 'Escape')`
|
||||
|
||||
const PATH_PARAMETER_DESCRIPTION = `File path where the screenshot should be saved (relative to workspace). Required for screenshot action. Supports .png, .jpeg, and .webp extensions. Example: 'screenshots/result.png'`
|
||||
|
||||
export default {
|
||||
type: "function",
|
||||
function: {
|
||||
|
|
@ -33,7 +35,18 @@ export default {
|
|||
action: {
|
||||
type: "string",
|
||||
description: ACTION_PARAMETER_DESCRIPTION,
|
||||
enum: ["launch", "click", "hover", "type", "press", "scroll_down", "scroll_up", "resize", "close"],
|
||||
enum: [
|
||||
"launch",
|
||||
"click",
|
||||
"hover",
|
||||
"type",
|
||||
"press",
|
||||
"scroll_down",
|
||||
"scroll_up",
|
||||
"resize",
|
||||
"close",
|
||||
"screenshot",
|
||||
],
|
||||
},
|
||||
url: {
|
||||
type: ["string", "null"],
|
||||
|
|
@ -51,6 +64,10 @@ export default {
|
|||
type: ["string", "null"],
|
||||
description: TEXT_PARAMETER_DESCRIPTION,
|
||||
},
|
||||
path: {
|
||||
type: ["string", "null"],
|
||||
description: PATH_PARAMETER_DESCRIPTION,
|
||||
},
|
||||
},
|
||||
required: ["action"],
|
||||
additionalProperties: false,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export async function browserActionTool(
|
|||
const coordinate: string | undefined = block.params.coordinate
|
||||
const text: string | undefined = block.params.text
|
||||
const size: string | undefined = block.params.size
|
||||
const filePath: string | undefined = block.params.path
|
||||
|
||||
if (!action || !browserActions.includes(action)) {
|
||||
// checking for action to ensure it is complete and valid
|
||||
|
|
@ -155,6 +156,17 @@ export async function browserActionTool(
|
|||
}
|
||||
}
|
||||
|
||||
if (action === "screenshot") {
|
||||
if (!filePath) {
|
||||
cline.consecutiveMistakeCount++
|
||||
cline.recordToolError("browser_action")
|
||||
cline.didToolFailInCurrentTurn = true
|
||||
pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "path"))
|
||||
// Do not close the browser on parameter validation errors
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cline.consecutiveMistakeCount = 0
|
||||
|
||||
// Prepare say payload; include executedCoordinate for pointer actions
|
||||
|
|
@ -191,6 +203,9 @@ export async function browserActionTool(
|
|||
case "resize":
|
||||
browserActionResult = await cline.browserSession.resize(size!)
|
||||
break
|
||||
case "screenshot":
|
||||
browserActionResult = await cline.browserSession.saveScreenshot(filePath!, cline.cwd)
|
||||
break
|
||||
case "close":
|
||||
browserActionResult = await cline.browserSession.closeBrowser()
|
||||
break
|
||||
|
|
@ -205,12 +220,16 @@ export async function browserActionTool(
|
|||
case "press":
|
||||
case "scroll_down":
|
||||
case "scroll_up":
|
||||
case "resize": {
|
||||
case "resize":
|
||||
case "screenshot": {
|
||||
await cline.say("browser_action_result", JSON.stringify(browserActionResult))
|
||||
|
||||
const images = browserActionResult?.screenshot ? [browserActionResult.screenshot] : []
|
||||
|
||||
let messageText = `The browser action has been executed.`
|
||||
let messageText =
|
||||
action === "screenshot"
|
||||
? `Screenshot saved to: ${filePath}`
|
||||
: `The browser action has been executed.`
|
||||
|
||||
messageText += `\n\n**CRITICAL**: When providing click/hover coordinates:`
|
||||
messageText += `\n1. Screenshot dimensions != Browser viewport dimensions`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
// Test screenshot action functionality in browser actions
|
||||
import { describe, it, expect } from "vitest"
|
||||
import { browserActions } from "../../../shared/ExtensionMessage"
|
||||
|
||||
describe("Browser Action Screenshot", () => {
|
||||
describe("browserActions array", () => {
|
||||
it("should include screenshot action", () => {
|
||||
expect(browserActions).toContain("screenshot")
|
||||
})
|
||||
|
||||
it("should have screenshot as a valid browser action type", () => {
|
||||
const allActions = [
|
||||
"launch",
|
||||
"click",
|
||||
"hover",
|
||||
"type",
|
||||
"press",
|
||||
"scroll_down",
|
||||
"scroll_up",
|
||||
"resize",
|
||||
"close",
|
||||
"screenshot",
|
||||
]
|
||||
expect(browserActions).toEqual(allActions)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -756,6 +756,55 @@ export class BrowserSession {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines image type from file extension
|
||||
*/
|
||||
private getImageTypeFromPath(filePath: string): "png" | "jpeg" | "webp" {
|
||||
const ext = path.extname(filePath).toLowerCase()
|
||||
if (ext === ".jpg" || ext === ".jpeg") return "jpeg"
|
||||
if (ext === ".webp") return "webp"
|
||||
return "png"
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a screenshot and saves it to the specified file path.
|
||||
* @param filePath - The destination file path (relative to workspace)
|
||||
* @param cwd - Current working directory for resolving relative paths
|
||||
* @returns BrowserActionResult with screenshot data and saved file path
|
||||
* @throws Error if the resolved path escapes the workspace directory
|
||||
*/
|
||||
async saveScreenshot(filePath: string, cwd: string): Promise<BrowserActionResult> {
|
||||
// Always resolve the path against the workspace root
|
||||
const normalizedCwd = path.resolve(cwd)
|
||||
const fullPath = path.resolve(cwd, filePath)
|
||||
|
||||
// Validate that the resolved path stays within the workspace (before calling doAction)
|
||||
if (!fullPath.startsWith(normalizedCwd + path.sep) && fullPath !== normalizedCwd) {
|
||||
throw new Error(
|
||||
`Screenshot path "${filePath}" resolves to "${fullPath}" which is outside the workspace "${normalizedCwd}". ` +
|
||||
`Paths must be relative to the workspace and cannot escape it.`,
|
||||
)
|
||||
}
|
||||
|
||||
return this.doAction(async (page) => {
|
||||
// Ensure directory exists
|
||||
await fs.mkdir(path.dirname(fullPath), { recursive: true })
|
||||
|
||||
// Determine image type from extension
|
||||
const imageType = this.getImageTypeFromPath(filePath)
|
||||
|
||||
// Take screenshot directly to file (more efficient than base64 for file saving)
|
||||
await page.screenshot({
|
||||
path: fullPath,
|
||||
type: imageType,
|
||||
quality:
|
||||
imageType === "png"
|
||||
? undefined
|
||||
: ((this.context.globalState.get("screenshotQuality") as number | undefined) ?? 75),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a cursor indicator on the page at the specified position
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// npx vitest services/browser/__tests__/BrowserSession.spec.ts
|
||||
|
||||
import * as path from "path"
|
||||
import { BrowserSession } from "../BrowserSession"
|
||||
import { discoverChromeHostUrl, tryChromeHostUrl } from "../browserDiscovery"
|
||||
|
||||
|
|
@ -395,6 +396,179 @@ describe("cursor visualization", () => {
|
|||
expect(result.currentMousePosition).toBeUndefined()
|
||||
})
|
||||
|
||||
describe("saveScreenshot", () => {
|
||||
// Use a cross-platform workspace path for testing
|
||||
const testWorkspace = path.resolve("/workspace")
|
||||
|
||||
it("should save screenshot to specified path with png format", async () => {
|
||||
const mockFs = await import("fs/promises")
|
||||
const page: any = {
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"),
|
||||
url: vi.fn().mockReturnValue("https://example.com"),
|
||||
viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }),
|
||||
evaluate: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
|
||||
const mockCtx: any = {
|
||||
globalState: { get: vi.fn(), update: vi.fn() },
|
||||
globalStorageUri: { fsPath: "/mock/global/storage/path" },
|
||||
extensionUri: { fsPath: "/mock/extension/path" },
|
||||
}
|
||||
const session = new BrowserSession(mockCtx)
|
||||
;(session as any).page = page
|
||||
|
||||
await session.saveScreenshot("screenshots/test.png", testWorkspace)
|
||||
|
||||
expect(mockFs.mkdir).toHaveBeenCalledWith(path.join(testWorkspace, "screenshots"), { recursive: true })
|
||||
expect(page.screenshot).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
path: path.join(testWorkspace, "screenshots", "test.png"),
|
||||
type: "png",
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should save screenshot with jpeg format for .jpg extension", async () => {
|
||||
const page: any = {
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"),
|
||||
url: vi.fn().mockReturnValue("https://example.com"),
|
||||
viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }),
|
||||
evaluate: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
|
||||
const mockCtx: any = {
|
||||
globalState: { get: vi.fn().mockReturnValue(80), update: vi.fn() },
|
||||
globalStorageUri: { fsPath: "/mock/global/storage/path" },
|
||||
extensionUri: { fsPath: "/mock/extension/path" },
|
||||
}
|
||||
const session = new BrowserSession(mockCtx)
|
||||
;(session as any).page = page
|
||||
|
||||
await session.saveScreenshot("screenshots/test.jpg", testWorkspace)
|
||||
|
||||
expect(page.screenshot).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
path: path.join(testWorkspace, "screenshots", "test.jpg"),
|
||||
type: "jpeg",
|
||||
quality: 80,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should save screenshot with webp format", async () => {
|
||||
const page: any = {
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"),
|
||||
url: vi.fn().mockReturnValue("https://example.com"),
|
||||
viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }),
|
||||
evaluate: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
|
||||
const mockCtx: any = {
|
||||
globalState: { get: vi.fn().mockReturnValue(75), update: vi.fn() },
|
||||
globalStorageUri: { fsPath: "/mock/global/storage/path" },
|
||||
extensionUri: { fsPath: "/mock/extension/path" },
|
||||
}
|
||||
const session = new BrowserSession(mockCtx)
|
||||
;(session as any).page = page
|
||||
|
||||
await session.saveScreenshot("test.webp", testWorkspace)
|
||||
|
||||
expect(page.screenshot).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
path: path.join(testWorkspace, "test.webp"),
|
||||
type: "webp",
|
||||
quality: 75,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should reject absolute file paths outside workspace", async () => {
|
||||
// Create a cross-platform absolute path for testing
|
||||
const absolutePath = path.resolve("/absolute/path/screenshot.png")
|
||||
const page: any = {
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"),
|
||||
url: vi.fn().mockReturnValue("https://example.com"),
|
||||
viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }),
|
||||
evaluate: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
|
||||
const mockCtx: any = {
|
||||
globalState: { get: vi.fn(), update: vi.fn() },
|
||||
globalStorageUri: { fsPath: "/mock/global/storage/path" },
|
||||
extensionUri: { fsPath: "/mock/extension/path" },
|
||||
}
|
||||
const session = new BrowserSession(mockCtx)
|
||||
;(session as any).page = page
|
||||
|
||||
await expect(session.saveScreenshot(absolutePath, testWorkspace)).rejects.toThrow(/outside the workspace/)
|
||||
|
||||
expect(page.screenshot).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should reject paths with .. that escape the workspace", async () => {
|
||||
const page: any = {
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"),
|
||||
url: vi.fn().mockReturnValue("https://example.com"),
|
||||
viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }),
|
||||
evaluate: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
|
||||
const mockCtx: any = {
|
||||
globalState: { get: vi.fn(), update: vi.fn() },
|
||||
globalStorageUri: { fsPath: "/mock/global/storage/path" },
|
||||
extensionUri: { fsPath: "/mock/extension/path" },
|
||||
}
|
||||
const session = new BrowserSession(mockCtx)
|
||||
;(session as any).page = page
|
||||
|
||||
await expect(session.saveScreenshot("../../etc/passwd", testWorkspace)).rejects.toThrow(
|
||||
/outside the workspace/,
|
||||
)
|
||||
|
||||
expect(page.screenshot).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should allow paths with .. that stay within workspace", async () => {
|
||||
const mockFs = await import("fs/promises")
|
||||
const page: any = {
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"),
|
||||
url: vi.fn().mockReturnValue("https://example.com"),
|
||||
viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }),
|
||||
evaluate: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
|
||||
const mockCtx: any = {
|
||||
globalState: { get: vi.fn(), update: vi.fn() },
|
||||
globalStorageUri: { fsPath: "/mock/global/storage/path" },
|
||||
extensionUri: { fsPath: "/mock/extension/path" },
|
||||
}
|
||||
const session = new BrowserSession(mockCtx)
|
||||
;(session as any).page = page
|
||||
|
||||
// Path like "subdir/../screenshot.png" should resolve to "screenshot.png" within workspace
|
||||
await session.saveScreenshot("subdir/../screenshot.png", testWorkspace)
|
||||
|
||||
expect(page.screenshot).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
path: path.join(testWorkspace, "screenshot.png"),
|
||||
type: "png",
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getViewportSize", () => {
|
||||
it("falls back to configured viewport when no page or last viewport is available", () => {
|
||||
const localCtx: any = {
|
||||
|
|
|
|||
|
|
@ -432,6 +432,7 @@ export const browserActions = [
|
|||
"scroll_up",
|
||||
"resize",
|
||||
"close",
|
||||
"screenshot",
|
||||
] as const
|
||||
|
||||
export type BrowserAction = (typeof browserActions)[number]
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ export interface ListCodeDefinitionNamesToolUse extends ToolUse<"list_code_defin
|
|||
|
||||
export interface BrowserActionToolUse extends ToolUse<"browser_action"> {
|
||||
name: "browser_action"
|
||||
params: Partial<Pick<Record<ToolParamName, string>, "action" | "url" | "coordinate" | "text" | "size">>
|
||||
params: Partial<Pick<Record<ToolParamName, string>, "action" | "url" | "coordinate" | "text" | "size" | "path">>
|
||||
}
|
||||
|
||||
export interface UseMcpToolToolUse extends ToolUse<"use_mcp_tool"> {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
Play,
|
||||
Check,
|
||||
Maximize2,
|
||||
Camera,
|
||||
} from "lucide-react"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
|
@ -41,6 +42,8 @@ const getActionIcon = (action: string) => {
|
|||
return <Check className="w-3.5 h-3.5 opacity-70" />
|
||||
case "resize":
|
||||
return <Maximize2 className="w-3.5 h-3.5 opacity-70" />
|
||||
case "screenshot":
|
||||
return <Camera className="w-3.5 h-3.5 opacity-70" />
|
||||
case "hover":
|
||||
default:
|
||||
return <Pointer className="w-3.5 h-3.5 opacity-70" />
|
||||
|
|
@ -77,7 +80,7 @@ const BrowserActionRow = memo(({ message, nextMessage, actionIndex, totalActions
|
|||
|
||||
// Format action display text
|
||||
const actionText = useMemo(() => {
|
||||
if (!browserAction) return "Browser action"
|
||||
if (!browserAction) return t("chat:browser.actions.title")
|
||||
|
||||
// Helper to scale coordinates from screenshot dimensions to viewport dimensions
|
||||
// Matches the backend's scaleCoordinate function logic
|
||||
|
|
@ -86,27 +89,33 @@ const BrowserActionRow = memo(({ message, nextMessage, actionIndex, totalActions
|
|||
|
||||
switch (browserAction.action) {
|
||||
case "launch":
|
||||
return `Launched browser`
|
||||
return t("chat:browser.actions.launched")
|
||||
case "click":
|
||||
return `Clicked at: ${browserAction.executedCoordinate || getViewportCoordinate(browserAction.coordinate)}`
|
||||
return t("chat:browser.actions.clicked", {
|
||||
coordinate: browserAction.executedCoordinate || getViewportCoordinate(browserAction.coordinate),
|
||||
})
|
||||
case "type":
|
||||
return `Typed: ${browserAction.text}`
|
||||
return t("chat:browser.actions.typed", { text: browserAction.text })
|
||||
case "press":
|
||||
return `Pressed key: ${prettyKey(browserAction.text)}`
|
||||
return t("chat:browser.actions.pressed", { key: prettyKey(browserAction.text) })
|
||||
case "hover":
|
||||
return `Hovered at: ${browserAction.executedCoordinate || getViewportCoordinate(browserAction.coordinate)}`
|
||||
return t("chat:browser.actions.hovered", {
|
||||
coordinate: browserAction.executedCoordinate || getViewportCoordinate(browserAction.coordinate),
|
||||
})
|
||||
case "scroll_down":
|
||||
return "Scrolled down"
|
||||
return t("chat:browser.actions.scrolledDown")
|
||||
case "scroll_up":
|
||||
return "Scrolled up"
|
||||
return t("chat:browser.actions.scrolledUp")
|
||||
case "resize":
|
||||
return `Resized to: ${browserAction.size?.split(/[x,]/).join(" x ")}`
|
||||
return t("chat:browser.actions.resized", { size: browserAction.size?.split(/[x,]/).join(" x ") })
|
||||
case "screenshot":
|
||||
return t("chat:browser.actions.screenshotSaved")
|
||||
case "close":
|
||||
return "Closed browser"
|
||||
return t("chat:browser.actions.closed")
|
||||
default:
|
||||
return browserAction.action
|
||||
}
|
||||
}, [browserAction, viewportDimensions])
|
||||
}, [browserAction, viewportDimensions, t])
|
||||
|
||||
// Auto-open Browser Session panel when:
|
||||
// 1. This is a "launch" action (new browser session) - always opens and navigates to launch
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { memo, useEffect, useMemo, useRef, useState } from "react"
|
||||
import deepEqual from "fast-deep-equal"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import type { TFunction } from "i18next"
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { BrowserAction, BrowserActionResult, ClineSayBrowserAction } from "@roo/ExtensionMessage"
|
||||
|
|
@ -30,9 +31,11 @@ import {
|
|||
ChevronsRight,
|
||||
ExternalLink,
|
||||
Copy,
|
||||
Camera,
|
||||
} from "lucide-react"
|
||||
|
||||
const getBrowserActionText = (
|
||||
t: TFunction,
|
||||
action: BrowserAction,
|
||||
executedCoordinate?: string,
|
||||
coordinate?: string,
|
||||
|
|
@ -48,23 +51,29 @@ const getBrowserActionText = (
|
|||
|
||||
switch (action) {
|
||||
case "launch":
|
||||
return `Launched browser`
|
||||
return t("chat:browser.actions.launched")
|
||||
case "click":
|
||||
return `Clicked at: ${executedCoordinate || getViewportCoordinate(coordinate)}`
|
||||
return t("chat:browser.actions.clicked", {
|
||||
coordinate: executedCoordinate || getViewportCoordinate(coordinate),
|
||||
})
|
||||
case "type":
|
||||
return `Typed: ${text}`
|
||||
return t("chat:browser.actions.typed", { text })
|
||||
case "press":
|
||||
return `Pressed key: ${prettyKey(text)}`
|
||||
return t("chat:browser.actions.pressed", { key: prettyKey(text) })
|
||||
case "scroll_down":
|
||||
return "Scrolled down"
|
||||
return t("chat:browser.actions.scrolledDown")
|
||||
case "scroll_up":
|
||||
return "Scrolled up"
|
||||
return t("chat:browser.actions.scrolledUp")
|
||||
case "hover":
|
||||
return `Hovered at: ${executedCoordinate || getViewportCoordinate(coordinate)}`
|
||||
return t("chat:browser.actions.hovered", {
|
||||
coordinate: executedCoordinate || getViewportCoordinate(coordinate),
|
||||
})
|
||||
case "resize":
|
||||
return `Resized to: ${size?.split(/[x,]/).join(" x ")}`
|
||||
return t("chat:browser.actions.resized", { size: size?.split(/[x,]/).join(" x ") })
|
||||
case "screenshot":
|
||||
return t("chat:browser.actions.screenshotSaved")
|
||||
case "close":
|
||||
return "Closed browser"
|
||||
return t("chat:browser.actions.closed")
|
||||
default:
|
||||
return action
|
||||
}
|
||||
|
|
@ -87,6 +96,8 @@ const getActionIcon = (action: BrowserAction) => {
|
|||
return <Check className="w-4 h-4 opacity-80" />
|
||||
case "resize":
|
||||
return <Maximize2 className="w-4 h-4 opacity-80" />
|
||||
case "screenshot":
|
||||
return <Camera className="w-4 h-4 opacity-80" />
|
||||
case "hover":
|
||||
default:
|
||||
return <Pointer className="w-4 h-4 opacity-80" />
|
||||
|
|
@ -557,6 +568,7 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
|||
{getActionIcon(action.action)}
|
||||
<span>
|
||||
{getBrowserActionText(
|
||||
t,
|
||||
action.action,
|
||||
action.executedCoordinate,
|
||||
action.coordinate,
|
||||
|
|
@ -572,7 +584,7 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
|||
return (
|
||||
<>
|
||||
{getActionIcon("launch" as any)}
|
||||
<span>{getBrowserActionText("launch", undefined, initialUrl, undefined)}</span>
|
||||
<span>{getBrowserActionText(t, "launch", undefined, initialUrl, undefined)}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/ca/chat.json
generated
12
webview-ui/src/i18n/locales/ca/chat.json
generated
|
|
@ -341,13 +341,25 @@
|
|||
"sessionStarted": "Sessió de navegador iniciada",
|
||||
"actions": {
|
||||
"title": "Acció del navegador: ",
|
||||
"launched": "Navegador iniciat",
|
||||
"launch": "Iniciar navegador a {{url}}",
|
||||
"clicked": "Fet clic ({{coordinate}})",
|
||||
"click": "Clic ({{coordinate}})",
|
||||
"typed": "Escrit: {{text}}",
|
||||
"type": "Escriure \"{{text}}\"",
|
||||
"pressed": "Premut: {{key}}",
|
||||
"press": "Prem {{key}}",
|
||||
"scrolledDown": "Desplaçat avall",
|
||||
"scrollDown": "Desplaçar avall",
|
||||
"scrolledUp": "Desplaçat amunt",
|
||||
"scrollUp": "Desplaçar amunt",
|
||||
"hovered": "Planat sobre ({{coordinate}})",
|
||||
"hover": "Plana sobre ({{coordinate}})",
|
||||
"resized": "Mida canviada a: {{size}}",
|
||||
"resize": "Canvia la mida a {{size}}",
|
||||
"screenshotSaved": "Captura de pantalla guardada",
|
||||
"screenshot": "Guarda la captura de pantalla a {{path}}",
|
||||
"closed": "Navegador tancat",
|
||||
"close": "Tancar navegador"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/de/chat.json
generated
12
webview-ui/src/i18n/locales/de/chat.json
generated
|
|
@ -341,13 +341,25 @@
|
|||
"sessionStarted": "Browser-Sitzung gestartet",
|
||||
"actions": {
|
||||
"title": "Browser-Aktion: ",
|
||||
"launched": "Browser gestartet",
|
||||
"launch": "Browser starten auf {{url}}",
|
||||
"clicked": "Geklickt auf: {{coordinate}}",
|
||||
"click": "Klicken ({{coordinate}})",
|
||||
"typed": "Eingegeben: {{text}}",
|
||||
"type": "Eingeben \"{{text}}\"",
|
||||
"pressed": "{{key}} gedrückt",
|
||||
"press": "{{key}} drücken",
|
||||
"scrolledDown": "Nach unten gescrollt",
|
||||
"scrollDown": "Nach unten scrollen",
|
||||
"scrolledUp": "Nach oben gescrollt",
|
||||
"scrollUp": "Nach oben scrollen",
|
||||
"hovered": "Gehovered auf: {{coordinate}}",
|
||||
"hover": "Hover ({{coordinate}})",
|
||||
"resized": "Größe geändert auf: {{size}}",
|
||||
"resize": "Größe ändern auf {{size}}",
|
||||
"screenshotSaved": "Screenshot gespeichert",
|
||||
"screenshot": "Screenshot speichern unter {{path}}",
|
||||
"closed": "Browser geschlossen",
|
||||
"close": "Browser schließen"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -356,13 +356,25 @@
|
|||
"sessionStarted": "Browser Session Started",
|
||||
"actions": {
|
||||
"title": "Browser Action: ",
|
||||
"launched": "Launched browser",
|
||||
"launch": "Launch browser at {{url}}",
|
||||
"clicked": "Clicked at: {{coordinate}}",
|
||||
"click": "Click ({{coordinate}})",
|
||||
"typed": "Typed: {{text}}",
|
||||
"type": "Type \"{{text}}\"",
|
||||
"pressed": "Pressed key: {{key}}",
|
||||
"press": "Press {{key}}",
|
||||
"scrolledDown": "Scrolled down",
|
||||
"scrollDown": "Scroll down",
|
||||
"scrolledUp": "Scrolled up",
|
||||
"scrollUp": "Scroll up",
|
||||
"hovered": "Hovered at: {{coordinate}}",
|
||||
"hover": "Hover ({{coordinate}})",
|
||||
"resized": "Resized to: {{size}}",
|
||||
"resize": "Resize to {{size}}",
|
||||
"screenshotSaved": "Screenshot saved",
|
||||
"screenshot": "Save screenshot to {{path}}",
|
||||
"closed": "Closed browser",
|
||||
"close": "Close browser"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/es/chat.json
generated
12
webview-ui/src/i18n/locales/es/chat.json
generated
|
|
@ -341,13 +341,25 @@
|
|||
"sessionStarted": "Sesión de navegador iniciada",
|
||||
"actions": {
|
||||
"title": "Acción del navegador: ",
|
||||
"launched": "Navegador iniciado",
|
||||
"launch": "Iniciar navegador en {{url}}",
|
||||
"clicked": "Clic en: {{coordinate}}",
|
||||
"click": "Clic ({{coordinate}})",
|
||||
"typed": "Escrito: {{text}}",
|
||||
"type": "Escribir \"{{text}}\"",
|
||||
"pressed": "Pulsado: {{key}}",
|
||||
"press": "Pulsar {{key}}",
|
||||
"scrolledDown": "Desplazado hacia abajo",
|
||||
"scrollDown": "Desplazar hacia abajo",
|
||||
"scrolledUp": "Desplazado hacia arriba",
|
||||
"scrollUp": "Desplazar hacia arriba",
|
||||
"hovered": "Flotado en: {{coordinate}}",
|
||||
"hover": "Flotar ({{coordinate}})",
|
||||
"resized": "Tamaño cambiado a: {{size}}",
|
||||
"resize": "Cambiar tamaño a {{size}}",
|
||||
"screenshotSaved": "Captura de pantalla guardada",
|
||||
"screenshot": "Guardar captura de pantalla en {{path}}",
|
||||
"closed": "Navegador cerrado",
|
||||
"close": "Cerrar navegador"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/fr/chat.json
generated
12
webview-ui/src/i18n/locales/fr/chat.json
generated
|
|
@ -341,13 +341,25 @@
|
|||
"sessionStarted": "Session de navigateur démarrée",
|
||||
"actions": {
|
||||
"title": "Action du navigateur : ",
|
||||
"launched": "Navigateur lancé",
|
||||
"launch": "Lancer le navigateur sur {{url}}",
|
||||
"clicked": "Cliqué sur : {{coordinate}}",
|
||||
"click": "Cliquer ({{coordinate}})",
|
||||
"typed": "Saisi : {{text}}",
|
||||
"type": "Saisir \"{{text}}\"",
|
||||
"pressed": "Appuyé sur : {{key}}",
|
||||
"press": "Appuyer sur {{key}}",
|
||||
"scrolledDown": "Défilé vers le bas",
|
||||
"scrollDown": "Défiler vers le bas",
|
||||
"scrolledUp": "Défilé vers le haut",
|
||||
"scrollUp": "Défiler vers le haut",
|
||||
"hovered": "Survolé : {{coordinate}}",
|
||||
"hover": "Survoler ({{coordinate}})",
|
||||
"resized": "Redimensionné à : {{size}}",
|
||||
"resize": "Redimensionner à {{size}}",
|
||||
"screenshotSaved": "Capture d'écran enregistrée",
|
||||
"screenshot": "Enregistrer une capture d'écran dans {{path}}",
|
||||
"closed": "Navigateur fermé",
|
||||
"close": "Fermer le navigateur"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/hi/chat.json
generated
12
webview-ui/src/i18n/locales/hi/chat.json
generated
|
|
@ -341,13 +341,25 @@
|
|||
"sessionStarted": "ब्राउज़र सत्र शुरू हुआ",
|
||||
"actions": {
|
||||
"title": "ब्राउज़र क्रिया: ",
|
||||
"launched": "ब्राउज़र लॉन्च हुआ",
|
||||
"launch": "{{url}} पर ब्राउज़र लॉन्च करें",
|
||||
"clicked": "क्लिक किया गया ({{coordinate}})",
|
||||
"click": "क्लिक करें ({{coordinate}})",
|
||||
"typed": "टाइप किया गया: {{text}}",
|
||||
"type": "टाइप करें \"{{text}}\"",
|
||||
"pressed": "दबाया गया: {{key}}",
|
||||
"press": "{{key}} दबाएँ",
|
||||
"scrolledDown": "नीचे स्क्रॉल किया गया",
|
||||
"scrollDown": "नीचे स्क्रॉल करें",
|
||||
"scrolledUp": "ऊपर स्क्रॉल किया गया",
|
||||
"scrollUp": "ऊपर स्क्रॉल करें",
|
||||
"hovered": "होवर किया गया ({{coordinate}})",
|
||||
"hover": "होवर करें ({{coordinate}})",
|
||||
"resized": "आकार बदला गया: {{size}}",
|
||||
"resize": "आकार बदलें {{size}}",
|
||||
"screenshotSaved": "स्क्रीनशॉट सहेजा गया",
|
||||
"screenshot": "स्क्रीनशॉट को {{path}} में सहेजें",
|
||||
"closed": "ब्राउज़र बंद किया गया",
|
||||
"close": "ब्राउज़र बंद करें"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/id/chat.json
generated
12
webview-ui/src/i18n/locales/id/chat.json
generated
|
|
@ -362,13 +362,25 @@
|
|||
"sessionStarted": "Sesi Browser Dimulai",
|
||||
"actions": {
|
||||
"title": "Aksi Browser: ",
|
||||
"launched": "Browser diluncurkan",
|
||||
"launch": "Luncurkan browser di {{url}}",
|
||||
"clicked": "Diklik ({{coordinate}})",
|
||||
"click": "Klik ({{coordinate}})",
|
||||
"typed": "Diketik: {{text}}",
|
||||
"type": "Ketik \"{{text}}\"",
|
||||
"pressed": "Ditekan: {{key}}",
|
||||
"press": "Tekan {{key}}",
|
||||
"scrolledDown": "Digulir ke bawah",
|
||||
"scrollDown": "Gulir ke bawah",
|
||||
"scrolledUp": "Digulir ke atas",
|
||||
"scrollUp": "Gulir ke atas",
|
||||
"hovered": "Diarahkan ({{coordinate}})",
|
||||
"hover": "Arahkan ({{coordinate}})",
|
||||
"resized": "Ukuran diubah ke: {{size}}",
|
||||
"resize": "Ubah ukuran ke {{size}}",
|
||||
"screenshotSaved": "Screenshot disimpan",
|
||||
"screenshot": "Simpan screenshot ke {{path}}",
|
||||
"closed": "Browser ditutup",
|
||||
"close": "Tutup browser"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/it/chat.json
generated
12
webview-ui/src/i18n/locales/it/chat.json
generated
|
|
@ -341,13 +341,25 @@
|
|||
"sessionStarted": "Sessione browser avviata",
|
||||
"actions": {
|
||||
"title": "Azione browser: ",
|
||||
"launched": "Browser avviato",
|
||||
"launch": "Avvia browser su {{url}}",
|
||||
"clicked": "Cliccato su: {{coordinate}}",
|
||||
"click": "Clic ({{coordinate}})",
|
||||
"typed": "Digitato: {{text}}",
|
||||
"type": "Digita \"{{text}}\"",
|
||||
"pressed": "Premuto: {{key}}",
|
||||
"press": "Premi {{key}}",
|
||||
"scrolledDown": "Scorso verso il basso",
|
||||
"scrollDown": "Scorri verso il basso",
|
||||
"scrolledUp": "Scorso verso l'alto",
|
||||
"scrollUp": "Scorri verso l'alto",
|
||||
"hovered": "Passato il mouse su: {{coordinate}}",
|
||||
"hover": "Passa il mouse ({{coordinate}})",
|
||||
"resized": "Ridimensionato a: {{size}}",
|
||||
"resize": "Ridimensiona a {{size}}",
|
||||
"screenshotSaved": "Screenshot salvato",
|
||||
"screenshot": "Salva screenshot in {{path}}",
|
||||
"closed": "Browser chiuso",
|
||||
"close": "Chiudi browser"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/ja/chat.json
generated
12
webview-ui/src/i18n/locales/ja/chat.json
generated
|
|
@ -341,13 +341,25 @@
|
|||
"sessionStarted": "ブラウザセッション開始",
|
||||
"actions": {
|
||||
"title": "ブラウザ操作: ",
|
||||
"launched": "ブラウザを起動しました",
|
||||
"launch": "{{url}} でブラウザを起動",
|
||||
"clicked": "クリック: {{coordinate}}",
|
||||
"click": "クリック ({{coordinate}})",
|
||||
"typed": "入力: {{text}}",
|
||||
"type": "入力 \"{{text}}\"",
|
||||
"pressed": "{{key}}を押しました",
|
||||
"press": "{{key}}を押す",
|
||||
"scrolledDown": "下にスクロールしました",
|
||||
"scrollDown": "下にスクロール",
|
||||
"scrolledUp": "上にスクロールしました",
|
||||
"scrollUp": "上にスクロール",
|
||||
"hovered": "ホバー: {{coordinate}}",
|
||||
"hover": "ホバー ({{coordinate}})",
|
||||
"resized": "サイズを変更しました: {{size}}",
|
||||
"resize": "サイズを {{size}} に変更",
|
||||
"screenshotSaved": "スクリーンショットを保存しました",
|
||||
"screenshot": "スクリーンショットを {{path}} に保存",
|
||||
"closed": "ブラウザを閉じました",
|
||||
"close": "ブラウザを閉じる"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/ko/chat.json
generated
12
webview-ui/src/i18n/locales/ko/chat.json
generated
|
|
@ -341,13 +341,25 @@
|
|||
"sessionStarted": "브라우저 세션 시작됨",
|
||||
"actions": {
|
||||
"title": "브라우저 작업: ",
|
||||
"launched": "브라우저 실행됨",
|
||||
"launch": "{{url}}에서 브라우저 실행",
|
||||
"clicked": "클릭함: {{coordinate}}",
|
||||
"click": "클릭 ({{coordinate}})",
|
||||
"typed": "입력함: {{text}}",
|
||||
"type": "입력 \"{{text}}\"",
|
||||
"pressed": "{{key}} 누름",
|
||||
"press": "{{key}} 누르기",
|
||||
"scrolledDown": "아래로 스크롤됨",
|
||||
"scrollDown": "아래로 스크롤",
|
||||
"scrolledUp": "위로 스크롤됨",
|
||||
"scrollUp": "위로 스크롤",
|
||||
"hovered": "가리킴: {{coordinate}}",
|
||||
"hover": "가리키기 ({{coordinate}})",
|
||||
"resized": "크기 조정됨: {{size}}",
|
||||
"resize": "크기를 {{size}}로 조정",
|
||||
"screenshotSaved": "스크린샷 저장됨",
|
||||
"screenshot": "스크린샷을 {{path}}에 저장",
|
||||
"closed": "브라우저 닫음",
|
||||
"close": "브라우저 닫기"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/nl/chat.json
generated
12
webview-ui/src/i18n/locales/nl/chat.json
generated
|
|
@ -341,13 +341,25 @@
|
|||
"sessionStarted": "Browsersessie gestart",
|
||||
"actions": {
|
||||
"title": "Browseractie: ",
|
||||
"launched": "Browser gestart",
|
||||
"launch": "Browser starten op {{url}}",
|
||||
"clicked": "Geklikt ({{coordinate}})",
|
||||
"click": "Klik ({{coordinate}})",
|
||||
"typed": "Getypt: {{text}}",
|
||||
"type": "Typ \"{{text}}\"",
|
||||
"pressed": "Ingedrukt: {{key}}",
|
||||
"press": "Druk op {{key}}",
|
||||
"scrolledDown": "Naar beneden gescrolld",
|
||||
"scrollDown": "Scroll naar beneden",
|
||||
"scrolledUp": "Naar boven gescrolld",
|
||||
"scrollUp": "Scroll naar boven",
|
||||
"hovered": "Zweefde ({{coordinate}})",
|
||||
"hover": "Zweven ({{coordinate}})",
|
||||
"resized": "Grootte gewijzigd naar: {{size}}",
|
||||
"resize": "Grootte wijzigen naar {{size}}",
|
||||
"screenshotSaved": "Schermopname opgeslagen",
|
||||
"screenshot": "Schermopname opslaan naar {{path}}",
|
||||
"closed": "Browser gesloten",
|
||||
"close": "Browser sluiten"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/pl/chat.json
generated
12
webview-ui/src/i18n/locales/pl/chat.json
generated
|
|
@ -341,13 +341,25 @@
|
|||
"sessionStarted": "Sesja przeglądarki rozpoczęta",
|
||||
"actions": {
|
||||
"title": "Akcja przeglądarki: ",
|
||||
"launched": "Przeglądarka uruchomiona",
|
||||
"launch": "Uruchom przeglądarkę na {{url}}",
|
||||
"clicked": "Kliknięto ({{coordinate}})",
|
||||
"click": "Kliknij ({{coordinate}})",
|
||||
"typed": "Wpisano: {{text}}",
|
||||
"type": "Wpisz \"{{text}}\"",
|
||||
"pressed": "Naciśnięto: {{key}}",
|
||||
"press": "Naciśnij {{key}}",
|
||||
"scrolledDown": "Przewinięto w dół",
|
||||
"scrollDown": "Przewiń w dół",
|
||||
"scrolledUp": "Przewinięto w górę",
|
||||
"scrollUp": "Przewiń w górę",
|
||||
"hovered": "Najechano ({{coordinate}})",
|
||||
"hover": "Najedź ({{coordinate}})",
|
||||
"resized": "Zmieniono rozmiar na: {{size}}",
|
||||
"resize": "Zmień rozmiar na {{size}}",
|
||||
"screenshotSaved": "Zrzut ekranu zapisany",
|
||||
"screenshot": "Zapisz zrzut ekranu do {{path}}",
|
||||
"closed": "Przeglądarka zamknięta",
|
||||
"close": "Zamknij przeglądarkę"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
12
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
|
|
@ -341,13 +341,25 @@
|
|||
"sessionStarted": "Sessão do navegador iniciada",
|
||||
"actions": {
|
||||
"title": "Ação do navegador: ",
|
||||
"launched": "Navegador iniciado",
|
||||
"launch": "Iniciar navegador em {{url}}",
|
||||
"clicked": "Clicado em: {{coordinate}}",
|
||||
"click": "Clique ({{coordinate}})",
|
||||
"typed": "Digitado: {{text}}",
|
||||
"type": "Digitar \"{{text}}\"",
|
||||
"pressed": "Pressionado: {{key}}",
|
||||
"press": "Pressione {{key}}",
|
||||
"scrolledDown": "Rolado para baixo",
|
||||
"scrollDown": "Rolar para baixo",
|
||||
"scrolledUp": "Rolado para cima",
|
||||
"scrollUp": "Rolar para cima",
|
||||
"hovered": "Pairado em: {{coordinate}}",
|
||||
"hover": "Pairar ({{coordinate}})",
|
||||
"resized": "Redimensionado para: {{size}}",
|
||||
"resize": "Redimensionar para {{size}}",
|
||||
"screenshotSaved": "Captura de tela salva",
|
||||
"screenshot": "Salvar captura de tela em {{path}}",
|
||||
"closed": "Navegador fechado",
|
||||
"close": "Fechar navegador"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/ru/chat.json
generated
12
webview-ui/src/i18n/locales/ru/chat.json
generated
|
|
@ -342,13 +342,25 @@
|
|||
"sessionStarted": "Сессия браузера запущена",
|
||||
"actions": {
|
||||
"title": "Действие браузера: ",
|
||||
"launched": "Браузер открыт",
|
||||
"launch": "Открыть браузер по адресу {{url}}",
|
||||
"clicked": "Клик в: {{coordinate}}",
|
||||
"click": "Клик ({{coordinate}})",
|
||||
"typed": "Введено: {{text}}",
|
||||
"type": "Ввести \"{{text}}\"",
|
||||
"pressed": "Нажато: {{key}}",
|
||||
"press": "Нажать {{key}}",
|
||||
"scrolledDown": "Прокручено вниз",
|
||||
"scrollDown": "Прокрутить вниз",
|
||||
"scrolledUp": "Прокручено вверх",
|
||||
"scrollUp": "Прокрутить вверх",
|
||||
"hovered": "Наведено в: {{coordinate}}",
|
||||
"hover": "Навести ({{coordinate}})",
|
||||
"resized": "Размер изменен на: {{size}}",
|
||||
"resize": "Изменить размер на {{size}}",
|
||||
"screenshotSaved": "Снимок экрана сохранён",
|
||||
"screenshot": "Сохранить снимок экрана в {{path}}",
|
||||
"closed": "Браузер закрыт",
|
||||
"close": "Закрыть браузер"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/tr/chat.json
generated
12
webview-ui/src/i18n/locales/tr/chat.json
generated
|
|
@ -342,13 +342,25 @@
|
|||
"sessionStarted": "Tarayıcı Oturumu Başlatıldı",
|
||||
"actions": {
|
||||
"title": "Tarayıcı Eylemi: ",
|
||||
"launched": "Tarayıcı başlatıldı",
|
||||
"launch": "{{url}} adresinde tarayıcı başlat",
|
||||
"clicked": "Tıklandı ({{coordinate}})",
|
||||
"click": "Tıkla ({{coordinate}})",
|
||||
"typed": "Yazıldı: {{text}}",
|
||||
"type": "Yaz \"{{text}}\"",
|
||||
"pressed": "Basıldı: {{key}}",
|
||||
"press": "{{key}} tuşuna bas",
|
||||
"scrolledDown": "Aşağı kaydırıldı",
|
||||
"scrollDown": "Aşağı kaydır",
|
||||
"scrolledUp": "Yukarı kaydırıldı",
|
||||
"scrollUp": "Yukarı kaydır",
|
||||
"hovered": "Üzerine gelinildi ({{coordinate}})",
|
||||
"hover": "Üzerine gel ({{coordinate}})",
|
||||
"resized": "Boyut değiştirildi: {{size}}",
|
||||
"resize": "Boyutu değiştir {{size}}",
|
||||
"screenshotSaved": "Ekran görüntüsü kaydedildi",
|
||||
"screenshot": "Ekran görüntüsünü {{path}} yoluna kaydet",
|
||||
"closed": "Tarayıcı kapatıldı",
|
||||
"close": "Tarayıcıyı kapat"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/vi/chat.json
generated
12
webview-ui/src/i18n/locales/vi/chat.json
generated
|
|
@ -342,13 +342,25 @@
|
|||
"sessionStarted": "Phiên trình duyệt đã bắt đầu",
|
||||
"actions": {
|
||||
"title": "Hành động trình duyệt: ",
|
||||
"launched": "Trình duyệt đã khởi chạy",
|
||||
"launch": "Khởi chạy trình duyệt tại {{url}}",
|
||||
"clicked": "Đã nhấp ({{coordinate}})",
|
||||
"click": "Nhấp ({{coordinate}})",
|
||||
"typed": "Đã gõ: {{text}}",
|
||||
"type": "Gõ \"{{text}}\"",
|
||||
"pressed": "Đã nhấn: {{key}}",
|
||||
"press": "Nhấn {{key}}",
|
||||
"scrolledDown": "Đã cuộn xuống",
|
||||
"scrollDown": "Cuộn xuống",
|
||||
"scrolledUp": "Đã cuộn lên",
|
||||
"scrollUp": "Cuộn lên",
|
||||
"hovered": "Đã di chuột ({{coordinate}})",
|
||||
"hover": "Di chuột ({{coordinate}})",
|
||||
"resized": "Đã thay đổi kích thước: {{size}}",
|
||||
"resize": "Thay đổi kích thước thành {{size}}",
|
||||
"screenshotSaved": "Ảnh chụp màn hình đã được lưu",
|
||||
"screenshot": "Lưu ảnh chụp màn hình vào {{path}}",
|
||||
"closed": "Trình duyệt đã đóng",
|
||||
"close": "Đóng trình duyệt"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
12
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
|
|
@ -342,13 +342,25 @@
|
|||
"sessionStarted": "浏览器会话已启动",
|
||||
"actions": {
|
||||
"title": "浏览器操作: ",
|
||||
"launched": "浏览器已启动",
|
||||
"launch": "访问 {{url}}",
|
||||
"clicked": "点击位置: {{coordinate}}",
|
||||
"click": "点击 ({{coordinate}})",
|
||||
"typed": "输入: {{text}}",
|
||||
"type": "输入 \"{{text}}\"",
|
||||
"pressed": "已按 {{key}}",
|
||||
"press": "按 {{key}}",
|
||||
"scrolledDown": "已向下滚动",
|
||||
"scrollDown": "向下滚动",
|
||||
"scrolledUp": "已向上滚动",
|
||||
"scrollUp": "向上滚动",
|
||||
"hovered": "悬停位置: {{coordinate}}",
|
||||
"hover": "悬停 ({{coordinate}})",
|
||||
"resized": "大小已更改: {{size}}",
|
||||
"resize": "调整大小为 {{size}}",
|
||||
"screenshotSaved": "截图已保存",
|
||||
"screenshot": "将截图保存到 {{path}}",
|
||||
"closed": "浏览器已关闭",
|
||||
"close": "关闭浏览器"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
12
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
12
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
|
|
@ -360,13 +360,25 @@
|
|||
"sessionStarted": "瀏覽器工作階段已啟動",
|
||||
"actions": {
|
||||
"title": "瀏覽器動作:",
|
||||
"launched": "瀏覽器已啟動",
|
||||
"launch": "在 {{url}} 啟動瀏覽器",
|
||||
"clicked": "點選位置: {{coordinate}}",
|
||||
"click": "點選 ({{coordinate}})",
|
||||
"typed": "輸入: {{text}}",
|
||||
"type": "輸入「{{text}}」",
|
||||
"pressed": "已按下 {{key}}",
|
||||
"press": "按下 {{key}}",
|
||||
"scrolledDown": "已向下捲動",
|
||||
"scrollDown": "向下捲動",
|
||||
"scrolledUp": "已向上捲動",
|
||||
"scrollUp": "向上捲動",
|
||||
"hovered": "懸停位置: {{coordinate}}",
|
||||
"hover": "懸停 ({{coordinate}})",
|
||||
"resized": "大小已變更: {{size}}",
|
||||
"resize": "調整大小為 {{size}}",
|
||||
"screenshotSaved": "螢幕擷圖已儲存",
|
||||
"screenshot": "將螢幕擷圖儲存至 {{path}}",
|
||||
"closed": "瀏覽器已關閉",
|
||||
"close": "關閉瀏覽器"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue