feat(browser): rework element annotation and picker toolbar
Element annotation is now free-text: dropped the Fix/Change/Question/ Approve intent pills and the intent field, so the user just says what they want. The card uses the shared Button and is titled "Annotate element". Toolbar: the element picker and DevTools are no longer dev-gated and work in prod; DevTools uses a wrench icon, and the old clipboard "grab" is now a Camera "Screenshot element" tool. Each toolbar button highlights only its own mode, and the copy toast reflects whether a screenshot image was actually captured.
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
|||||||
} from "lucide-react-native";
|
} from "lucide-react-native";
|
||||||
import { withUnistyles } from "react-native-unistyles";
|
import { withUnistyles } from "react-native-unistyles";
|
||||||
import type { AgentAttachment } from "@getpaseo/protocol/messages";
|
import type { AgentAttachment } from "@getpaseo/protocol/messages";
|
||||||
import type { BrowserAnnotationIntent, WorkspaceComposerAttachment } from "@/attachments/types";
|
import type { WorkspaceComposerAttachment } from "@/attachments/types";
|
||||||
import { getFileTypeLabel } from "@/attachments/file-types";
|
import { getFileTypeLabel } from "@/attachments/file-types";
|
||||||
import { isPullRequestContextAttachment } from "@/attachments/workspace-attachment-utils";
|
import { isPullRequestContextAttachment } from "@/attachments/workspace-attachment-utils";
|
||||||
import { ICON_SIZE, type Theme } from "@/styles/theme";
|
import { ICON_SIZE, type Theme } from "@/styles/theme";
|
||||||
@@ -36,13 +36,6 @@ function getPullRequestContextSubtitle(attachment: WorkspaceComposerAttachment):
|
|||||||
return "Review";
|
return "Review";
|
||||||
}
|
}
|
||||||
|
|
||||||
const BROWSER_INTENT_LABEL_KEYS: Record<BrowserAnnotationIntent, string> = {
|
|
||||||
fix: "workspace.browser.annotate.intents.fix",
|
|
||||||
change: "workspace.browser.annotate.intents.change",
|
|
||||||
question: "workspace.browser.annotate.intents.question",
|
|
||||||
approve: "workspace.browser.annotate.intents.approve",
|
|
||||||
};
|
|
||||||
|
|
||||||
function getTextAttachmentSubtitle(
|
function getTextAttachmentSubtitle(
|
||||||
attachment: Extract<AgentAttachment, { type: "text" }>,
|
attachment: Extract<AgentAttachment, { type: "text" }>,
|
||||||
t: TFunction,
|
t: TFunction,
|
||||||
@@ -96,11 +89,10 @@ export function getWorkspaceAttachmentPillContent(
|
|||||||
t: TFunction,
|
t: TFunction,
|
||||||
): AttachmentPillContent {
|
): AttachmentPillContent {
|
||||||
if (attachment.kind === "browser_element") {
|
if (attachment.kind === "browser_element") {
|
||||||
const intent = attachment.attachment.intent;
|
|
||||||
return {
|
return {
|
||||||
icon: attachmentBrowserIcon,
|
icon: attachmentBrowserIcon,
|
||||||
title: attachment.attachment.tag,
|
title: attachment.attachment.tag,
|
||||||
subtitle: intent ? t(BROWSER_INTENT_LABEL_KEYS[intent]) : t("composer.attachments.element"),
|
subtitle: t("composer.attachments.element"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (isPullRequestContextAttachment(attachment)) {
|
if (isPullRequestContextAttachment(attachment)) {
|
||||||
|
|||||||
@@ -21,12 +21,6 @@ export interface AttachmentMetadata {
|
|||||||
createdAt: number;
|
createdAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The kind of review feedback the user is attaching to a selected browser
|
|
||||||
* element, sent to the agent alongside the element context.
|
|
||||||
*/
|
|
||||||
export type BrowserAnnotationIntent = "fix" | "change" | "question" | "approve";
|
|
||||||
|
|
||||||
export interface BrowserElementAttachment {
|
export interface BrowserElementAttachment {
|
||||||
url: string;
|
url: string;
|
||||||
selector: string;
|
selector: string;
|
||||||
@@ -50,8 +44,6 @@ export interface BrowserElementAttachment {
|
|||||||
children: string[];
|
children: string[];
|
||||||
/** Free-text review note the user wrote about this element, if any. */
|
/** Free-text review note the user wrote about this element, if any. */
|
||||||
comment?: string;
|
comment?: string;
|
||||||
/** What the user wants the agent to do with this element, if annotated. */
|
|
||||||
intent?: BrowserAnnotationIntent;
|
|
||||||
/**
|
/**
|
||||||
* Cropped screenshot of the selected element, sent to the agent as an image
|
* Cropped screenshot of the selected element, sent to the agent as an image
|
||||||
* alongside the textual element context. Persisted via the attachment store;
|
* alongside the textual element context. Persisted via the attachment store;
|
||||||
|
|||||||
@@ -12,21 +12,22 @@ import { Pressable, Text, TextInput, View, type StyleProp, type ViewStyle } from
|
|||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
|
Camera,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
Copy,
|
|
||||||
Maximize,
|
Maximize,
|
||||||
Monitor,
|
Monitor,
|
||||||
MousePointer2,
|
MousePointer2,
|
||||||
PencilRuler,
|
|
||||||
RotateCw,
|
RotateCw,
|
||||||
Smartphone,
|
Smartphone,
|
||||||
Tablet,
|
Tablet,
|
||||||
|
Wrench,
|
||||||
X,
|
X,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from "lucide-react-native";
|
} from "lucide-react-native";
|
||||||
import { StyleSheet, useUnistyles, withUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles, withUnistyles } from "react-native-unistyles";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import * as Clipboard from "expo-clipboard";
|
import * as Clipboard from "expo-clipboard";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { useToast } from "@/contexts/toast-context";
|
import { useToast } from "@/contexts/toast-context";
|
||||||
import {
|
import {
|
||||||
@@ -40,11 +41,7 @@ import {
|
|||||||
useWorkspaceAttachments,
|
useWorkspaceAttachments,
|
||||||
useWorkspaceAttachmentsStore,
|
useWorkspaceAttachmentsStore,
|
||||||
} from "@/attachments/workspace-attachments-store";
|
} from "@/attachments/workspace-attachments-store";
|
||||||
import type {
|
import type { AttachmentMetadata, BrowserElementAttachment } from "@/attachments/types";
|
||||||
AttachmentMetadata,
|
|
||||||
BrowserAnnotationIntent,
|
|
||||||
BrowserElementAttachment,
|
|
||||||
} from "@/attachments/types";
|
|
||||||
import { persistAttachmentFromDataUrl } from "@/attachments/service";
|
import { persistAttachmentFromDataUrl } from "@/attachments/service";
|
||||||
import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout";
|
import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout";
|
||||||
import {
|
import {
|
||||||
@@ -52,7 +49,6 @@ import {
|
|||||||
isElectronRuntime,
|
isElectronRuntime,
|
||||||
type DesktopBrowserShortcutEvent,
|
type DesktopBrowserShortcutEvent,
|
||||||
} from "@/desktop/host";
|
} from "@/desktop/host";
|
||||||
import { isDev } from "@/constants/platform";
|
|
||||||
import { useBrowserStore, normalizeWorkspaceBrowserUrl } from "@/stores/browser-store";
|
import { useBrowserStore, normalizeWorkspaceBrowserUrl } from "@/stores/browser-store";
|
||||||
import {
|
import {
|
||||||
prepareBrowserWebview,
|
prepareBrowserWebview,
|
||||||
@@ -79,25 +75,14 @@ type WebTextInput = TextInput & {
|
|||||||
getNativeRef?: () => unknown;
|
getNativeRef?: () => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
type BrowserElementSelection = Omit<
|
type BrowserElementSelection = Omit<BrowserElementAttachment, "formatted" | "comment"> & {
|
||||||
BrowserElementAttachment,
|
|
||||||
"formatted" | "comment" | "intent"
|
|
||||||
> & {
|
|
||||||
attributes?: Record<string, string>;
|
attributes?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface BrowserElementAnnotation {
|
interface BrowserElementAnnotation {
|
||||||
comment: string;
|
comment: string;
|
||||||
intent: BrowserAnnotationIntent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const BROWSER_ANNOTATION_INTENTS: readonly BrowserAnnotationIntent[] = [
|
|
||||||
"fix",
|
|
||||||
"change",
|
|
||||||
"question",
|
|
||||||
"approve",
|
|
||||||
];
|
|
||||||
|
|
||||||
type DeviceSizeId =
|
type DeviceSizeId =
|
||||||
| "responsive"
|
| "responsive"
|
||||||
| "iphone-se"
|
| "iphone-se"
|
||||||
@@ -223,10 +208,6 @@ function formatElementAttachment(
|
|||||||
const html = truncateText(selection.outerHTML.trim(), 800);
|
const html = truncateText(selection.outerHTML.trim(), 800);
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
|
|
||||||
if (annotation) {
|
|
||||||
parts.push(`intent: ${annotation.intent}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selection.reactSource?.fileName) {
|
if (selection.reactSource?.fileName) {
|
||||||
const loc = [
|
const loc = [
|
||||||
selection.reactSource.fileName,
|
selection.reactSource.fileName,
|
||||||
@@ -289,7 +270,6 @@ function buildBrowserElementAttachment(
|
|||||||
parentChain: selection.parentChain,
|
parentChain: selection.parentChain,
|
||||||
children: selection.children,
|
children: selection.children,
|
||||||
...(comment ? { comment } : {}),
|
...(comment ? { comment } : {}),
|
||||||
...(annotation ? { intent: annotation.intent } : {}),
|
|
||||||
...(screenshot ? { screenshot } : {}),
|
...(screenshot ? { screenshot } : {}),
|
||||||
formatted: formatElementAttachment(selection, annotation),
|
formatted: formatElementAttachment(selection, annotation),
|
||||||
};
|
};
|
||||||
@@ -641,10 +621,11 @@ export function BrowserPane({
|
|||||||
const pendingNavigationUrlRef = useRef<string | null>(null);
|
const pendingNavigationUrlRef = useRef<string | null>(null);
|
||||||
const domReadyRef = useRef(false);
|
const domReadyRef = useRef(false);
|
||||||
const annotationMarkersRef = useRef<BrowserAnnotationMarker[]>([]);
|
const annotationMarkersRef = useRef<BrowserAnnotationMarker[]>([]);
|
||||||
const [selectorActive, setSelectorActive] = useState(false);
|
const [selectorMode, setSelectorMode] = useState<"annotate" | "screenshot" | null>(null);
|
||||||
|
const selectorActive = selectorMode !== null;
|
||||||
// Which action the active selector performs on click: open the annotation card
|
// Which action the active selector performs on click: open the annotation card
|
||||||
// ("annotate") or copy the element to the clipboard ("grab").
|
// ("annotate") or copy a screenshot of the element to the clipboard ("screenshot").
|
||||||
const selectorModeRef = useRef<"annotate" | "grab">("annotate");
|
const selectorModeRef = useRef<"annotate" | "screenshot">("annotate");
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const toastRef = useRef(toast);
|
const toastRef = useRef(toast);
|
||||||
toastRef.current = toast;
|
toastRef.current = toast;
|
||||||
@@ -1074,7 +1055,7 @@ export function BrowserPane({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const grabElementToClipboard = useCallback(
|
const screenshotElementToClipboard = useCallback(
|
||||||
async (selection: BrowserElementSelection) => {
|
async (selection: BrowserElementSelection) => {
|
||||||
const text = formatElementAttachment(selection);
|
const text = formatElementAttachment(selection);
|
||||||
const copyElement = getDesktopHost()?.browser?.copyElement;
|
const copyElement = getDesktopHost()?.browser?.copyElement;
|
||||||
@@ -1087,19 +1068,23 @@ export function BrowserPane({
|
|||||||
const dataUrl = await captureElement(browserIdRef.current, { x, y, width, height });
|
const dataUrl = await captureElement(browserIdRef.current, { x, y, width, height });
|
||||||
imageDataUrl = dataUrl ?? undefined;
|
imageDataUrl = dataUrl ?? undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("[browser-pane] capture element for grab failed", error);
|
console.warn("[browser-pane] capture element for screenshot failed", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const copiedMessage = imageDataUrl
|
||||||
|
? t("workspace.browser.controls.screenshotCopied")
|
||||||
|
: t("workspace.browser.controls.elementCopied");
|
||||||
|
|
||||||
// Copy via the main process; the renderer's navigator.clipboard rejects
|
// Copy via the main process; the renderer's navigator.clipboard rejects
|
||||||
// with NotAllowedError because focus is inside the guest <webview>.
|
// with NotAllowedError because focus is inside the guest <webview>.
|
||||||
if (typeof copyElement === "function") {
|
if (typeof copyElement === "function") {
|
||||||
try {
|
try {
|
||||||
const ok = await copyElement({ text, imageDataUrl });
|
const ok = await copyElement({ text, imageDataUrl });
|
||||||
if (ok) {
|
if (ok) {
|
||||||
toastRef.current?.copied(t("workspace.browser.controls.grabElementLabel"));
|
toastRef.current?.show(copiedMessage, { variant: "success" });
|
||||||
} else {
|
} else {
|
||||||
toastRef.current?.error(t("workspace.browser.controls.grabFailed"));
|
toastRef.current?.error(t("workspace.browser.controls.screenshotFailed"));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1110,10 +1095,12 @@ export function BrowserPane({
|
|||||||
// Fallback to expo-clipboard (text only) when the bridge is unavailable.
|
// Fallback to expo-clipboard (text only) when the bridge is unavailable.
|
||||||
try {
|
try {
|
||||||
await Clipboard.setStringAsync(text);
|
await Clipboard.setStringAsync(text);
|
||||||
toastRef.current?.copied(t("workspace.browser.controls.grabElementLabel"));
|
toastRef.current?.show(t("workspace.browser.controls.elementCopied"), {
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("[browser-pane] clipboard fallback failed", error);
|
console.warn("[browser-pane] clipboard fallback failed", error);
|
||||||
toastRef.current?.error(t("workspace.browser.controls.grabFailed"));
|
toastRef.current?.error(t("workspace.browser.controls.screenshotFailed"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[t],
|
[t],
|
||||||
@@ -1121,8 +1108,8 @@ export function BrowserPane({
|
|||||||
|
|
||||||
const handleSelectorResult = useCallback(
|
const handleSelectorResult = useCallback(
|
||||||
(selection: BrowserElementSelection) => {
|
(selection: BrowserElementSelection) => {
|
||||||
if (selectorModeRef.current === "grab") {
|
if (selectorModeRef.current === "screenshot") {
|
||||||
void grabElementToClipboard(selection);
|
void screenshotElementToClipboard(selection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pendingScreenshotRef.current = undefined;
|
pendingScreenshotRef.current = undefined;
|
||||||
@@ -1132,7 +1119,7 @@ export function BrowserPane({
|
|||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[captureElementScreenshot, grabElementToClipboard],
|
[captureElementScreenshot, screenshotElementToClipboard],
|
||||||
);
|
);
|
||||||
|
|
||||||
const submitAnnotation = useCallback(
|
const submitAnnotation = useCallback(
|
||||||
@@ -1155,15 +1142,15 @@ export function BrowserPane({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const startElementSelector = useCallback(
|
const startElementSelector = useCallback(
|
||||||
(mode: "annotate" | "grab") => {
|
(mode: "annotate" | "screenshot") => {
|
||||||
const webview = webviewRef.current;
|
const webview = webviewRef.current;
|
||||||
if (!webview || !domReadyRef.current) return;
|
if (!webview || !domReadyRef.current) return;
|
||||||
// Annotate needs a workspace scope to attach to; grab only copies.
|
// Annotate needs a workspace scope to attach to; screenshot only copies.
|
||||||
if (mode === "annotate" && !workspaceAttachmentScopeKey) return;
|
if (mode === "annotate" && !workspaceAttachmentScopeKey) return;
|
||||||
selectorModeRef.current = mode;
|
selectorModeRef.current = mode;
|
||||||
pendingScreenshotRef.current = undefined;
|
pendingScreenshotRef.current = undefined;
|
||||||
setPendingSelection(null);
|
setPendingSelection(null);
|
||||||
setSelectorActive(true);
|
setSelectorMode(mode);
|
||||||
|
|
||||||
const js = `
|
const js = `
|
||||||
(function() {
|
(function() {
|
||||||
@@ -1389,11 +1376,11 @@ export function BrowserPane({
|
|||||||
const poll = startSelectorResultPolling({
|
const poll = startSelectorResultPolling({
|
||||||
webview,
|
webview,
|
||||||
onSelection: handleSelectorResult,
|
onSelection: handleSelectorResult,
|
||||||
onDone: () => setSelectorActive(false),
|
onDone: () => setSelectorMode(null),
|
||||||
});
|
});
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
window.clearInterval(poll);
|
window.clearInterval(poll);
|
||||||
setSelectorActive(false);
|
setSelectorMode(null);
|
||||||
if (webviewRef.current !== webview || !domReadyRef.current) {
|
if (webviewRef.current !== webview || !domReadyRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1402,10 +1389,10 @@ export function BrowserPane({
|
|||||||
return undefined;
|
return undefined;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setSelectorActive(false);
|
setSelectorMode(null);
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
setSelectorActive(false);
|
setSelectorMode(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleSelectorResult, workspaceAttachmentScopeKey],
|
[handleSelectorResult, workspaceAttachmentScopeKey],
|
||||||
@@ -1413,7 +1400,7 @@ export function BrowserPane({
|
|||||||
|
|
||||||
const cancelElementSelector = useCallback(() => {
|
const cancelElementSelector = useCallback(() => {
|
||||||
const webview = webviewRef.current;
|
const webview = webviewRef.current;
|
||||||
setSelectorActive(false);
|
setSelectorMode(null);
|
||||||
if (webview && domReadyRef.current) {
|
if (webview && domReadyRef.current) {
|
||||||
try {
|
try {
|
||||||
clearWebviewSelector(webview);
|
clearWebviewSelector(webview);
|
||||||
@@ -1470,12 +1457,12 @@ export function BrowserPane({
|
|||||||
startElementSelector("annotate");
|
startElementSelector("annotate");
|
||||||
}, [cancelElementSelector, selectorActive, startElementSelector]);
|
}, [cancelElementSelector, selectorActive, startElementSelector]);
|
||||||
|
|
||||||
const handleToggleGrab = useCallback(() => {
|
const handleToggleScreenshot = useCallback(() => {
|
||||||
if (selectorActive) {
|
if (selectorActive) {
|
||||||
cancelElementSelector();
|
cancelElementSelector();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
startElementSelector("grab");
|
startElementSelector("screenshot");
|
||||||
}, [cancelElementSelector, selectorActive, startElementSelector]);
|
}, [cancelElementSelector, selectorActive, startElementSelector]);
|
||||||
|
|
||||||
const handleOpenDevTools = useCallback(() => {
|
const handleOpenDevTools = useCallback(() => {
|
||||||
@@ -1521,13 +1508,21 @@ export function BrowserPane({
|
|||||||
],
|
],
|
||||||
[browser?.canGoForward],
|
[browser?.canGoForward],
|
||||||
);
|
);
|
||||||
const selectorIconButtonStyle = useCallback(
|
const annotateIconButtonStyle = useCallback(
|
||||||
({ hovered, pressed }: { hovered?: boolean; pressed?: boolean }) => [
|
({ hovered, pressed }: { hovered?: boolean; pressed?: boolean }) => [
|
||||||
styles.iconButton,
|
styles.iconButton,
|
||||||
selectorActive && styles.selectorActiveButton,
|
selectorMode === "annotate" && styles.selectorActiveButton,
|
||||||
(hovered || pressed) && styles.iconButtonHovered,
|
(hovered || pressed) && styles.iconButtonHovered,
|
||||||
],
|
],
|
||||||
[selectorActive],
|
[selectorMode],
|
||||||
|
);
|
||||||
|
const screenshotIconButtonStyle = useCallback(
|
||||||
|
({ hovered, pressed }: { hovered?: boolean; pressed?: boolean }) => [
|
||||||
|
styles.iconButton,
|
||||||
|
selectorMode === "screenshot" && styles.selectorActiveButton,
|
||||||
|
(hovered || pressed) && styles.iconButtonHovered,
|
||||||
|
],
|
||||||
|
[selectorMode],
|
||||||
);
|
);
|
||||||
|
|
||||||
const devicePreset = useMemo(
|
const devicePreset = useMemo(
|
||||||
@@ -1633,36 +1628,46 @@ export function BrowserPane({
|
|||||||
onSelect={setDeviceSizeId}
|
onSelect={setDeviceSizeId}
|
||||||
triggerStyle={baseIconButtonStyle}
|
triggerStyle={baseIconButtonStyle}
|
||||||
/>
|
/>
|
||||||
{isDev ? (
|
<ToolbarButton
|
||||||
<ToolbarButton
|
label={t("workspace.browser.controls.openDevTools")}
|
||||||
label={t("workspace.browser.controls.openDevTools")}
|
onPress={handleOpenDevTools}
|
||||||
onPress={handleOpenDevTools}
|
style={baseIconButtonStyle}
|
||||||
style={baseIconButtonStyle}
|
>
|
||||||
>
|
<Wrench size={16} color={theme.colors.foregroundMuted} />
|
||||||
<PencilRuler size={16} color={theme.colors.foregroundMuted} />
|
</ToolbarButton>
|
||||||
</ToolbarButton>
|
|
||||||
) : null}
|
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
label={
|
label={
|
||||||
selectorActive
|
selectorMode === "annotate"
|
||||||
? t("workspace.browser.controls.cancelSelector")
|
? t("workspace.browser.controls.cancelSelector")
|
||||||
: t("workspace.browser.controls.selectElement")
|
: t("workspace.browser.controls.annotateElement")
|
||||||
}
|
}
|
||||||
active={selectorActive}
|
active={selectorMode === "annotate"}
|
||||||
onPress={handleToggleElementSelector}
|
onPress={handleToggleElementSelector}
|
||||||
style={selectorIconButtonStyle}
|
style={annotateIconButtonStyle}
|
||||||
>
|
>
|
||||||
<MousePointer2
|
<MousePointer2
|
||||||
size={16}
|
size={16}
|
||||||
color={selectorActive ? theme.colors.accent : theme.colors.foregroundMuted}
|
color={
|
||||||
|
selectorMode === "annotate" ? theme.colors.accent : theme.colors.foregroundMuted
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
label={t("workspace.browser.controls.grabElement")}
|
label={
|
||||||
onPress={handleToggleGrab}
|
selectorMode === "screenshot"
|
||||||
style={baseIconButtonStyle}
|
? t("workspace.browser.controls.cancelSelector")
|
||||||
|
: t("workspace.browser.controls.screenshotElement")
|
||||||
|
}
|
||||||
|
active={selectorMode === "screenshot"}
|
||||||
|
onPress={handleToggleScreenshot}
|
||||||
|
style={screenshotIconButtonStyle}
|
||||||
>
|
>
|
||||||
<Copy size={16} color={theme.colors.foregroundMuted} />
|
<Camera
|
||||||
|
size={16}
|
||||||
|
color={
|
||||||
|
selectorMode === "screenshot" ? theme.colors.accent : theme.colors.foregroundMuted
|
||||||
|
}
|
||||||
|
/>
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -1690,45 +1695,6 @@ export function BrowserPane({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const INTENT_LABEL_KEYS: Record<BrowserAnnotationIntent, string> = {
|
|
||||||
fix: "workspace.browser.annotate.intents.fix",
|
|
||||||
change: "workspace.browser.annotate.intents.change",
|
|
||||||
question: "workspace.browser.annotate.intents.question",
|
|
||||||
approve: "workspace.browser.annotate.intents.approve",
|
|
||||||
};
|
|
||||||
|
|
||||||
function IntentChip({
|
|
||||||
active,
|
|
||||||
intent,
|
|
||||||
label,
|
|
||||||
onSelect,
|
|
||||||
}: {
|
|
||||||
active: boolean;
|
|
||||||
intent: BrowserAnnotationIntent;
|
|
||||||
label: string;
|
|
||||||
onSelect: (intent: BrowserAnnotationIntent) => void;
|
|
||||||
}) {
|
|
||||||
const handlePress = useCallback(() => {
|
|
||||||
onSelect(intent);
|
|
||||||
}, [intent, onSelect]);
|
|
||||||
const chipStyle = useMemo(() => [styles.intentChip, active && styles.intentChipActive], [active]);
|
|
||||||
const textStyle = useMemo(
|
|
||||||
() => [styles.intentChipText, active && styles.intentChipTextActive],
|
|
||||||
[active],
|
|
||||||
);
|
|
||||||
const accessibilityState = useMemo(() => ({ selected: active }), [active]);
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityState={accessibilityState}
|
|
||||||
onPress={handlePress}
|
|
||||||
style={chipStyle}
|
|
||||||
>
|
|
||||||
<Text style={textStyle}>{label}</Text>
|
|
||||||
</Pressable>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function BrowserElementAnnotationCard({
|
function BrowserElementAnnotationCard({
|
||||||
selection,
|
selection,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
@@ -1740,14 +1706,11 @@ function BrowserElementAnnotationCard({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [comment, setComment] = useState("");
|
const [comment, setComment] = useState("");
|
||||||
const [intent, setIntent] = useState<BrowserAnnotationIntent>("fix");
|
|
||||||
const commentRef = useRef(comment);
|
const commentRef = useRef(comment);
|
||||||
commentRef.current = comment;
|
commentRef.current = comment;
|
||||||
const intentRef = useRef(intent);
|
|
||||||
intentRef.current = intent;
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
onSubmit({ comment: commentRef.current, intent: intentRef.current });
|
onSubmit({ comment: commentRef.current });
|
||||||
}, [onSubmit]);
|
}, [onSubmit]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1792,17 +1755,6 @@ function BrowserElementAnnotationCard({
|
|||||||
<Text numberOfLines={1} style={styles.annotationElement}>
|
<Text numberOfLines={1} style={styles.annotationElement}>
|
||||||
{elementLabel}
|
{elementLabel}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.annotationIntents}>
|
|
||||||
{BROWSER_ANNOTATION_INTENTS.map((option) => (
|
|
||||||
<IntentChip
|
|
||||||
key={option}
|
|
||||||
active={option === intent}
|
|
||||||
intent={option}
|
|
||||||
label={t(INTENT_LABEL_KEYS[option])}
|
|
||||||
onSelect={setIntent}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
<ThemedAnnotationInput
|
<ThemedAnnotationInput
|
||||||
accessibilityLabel={t("workspace.browser.annotate.placeholder")}
|
accessibilityLabel={t("workspace.browser.annotate.placeholder")}
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -1814,24 +1766,12 @@ function BrowserElementAnnotationCard({
|
|||||||
value={comment}
|
value={comment}
|
||||||
/>
|
/>
|
||||||
<View style={styles.annotationActions}>
|
<View style={styles.annotationActions}>
|
||||||
<Pressable
|
<Button variant="ghost" size="sm" onPress={onCancel}>
|
||||||
accessibilityRole="button"
|
{t("workspace.browser.annotate.cancel")}
|
||||||
onPress={onCancel}
|
</Button>
|
||||||
style={styles.annotationSecondaryButton}
|
<Button variant="default" size="sm" onPress={handleSubmit}>
|
||||||
>
|
{t("workspace.browser.annotate.submit")}
|
||||||
<Text style={styles.annotationSecondaryText}>
|
</Button>
|
||||||
{t("workspace.browser.annotate.cancel")}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable
|
|
||||||
accessibilityRole="button"
|
|
||||||
onPress={handleSubmit}
|
|
||||||
style={styles.annotationPrimaryButton}
|
|
||||||
>
|
|
||||||
<Text style={styles.annotationPrimaryText}>
|
|
||||||
{t("workspace.browser.annotate.submit")}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -1986,31 +1926,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
annotationElement: {
|
annotationElement: {
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
},
|
marginBottom: theme.spacing[2],
|
||||||
annotationIntents: {
|
|
||||||
flexDirection: "row",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
gap: theme.spacing[1],
|
|
||||||
},
|
|
||||||
intentChip: {
|
|
||||||
paddingHorizontal: theme.spacing[2],
|
|
||||||
paddingVertical: theme.spacing[1],
|
|
||||||
borderRadius: theme.borderRadius.md,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
backgroundColor: theme.colors.surface1,
|
|
||||||
},
|
|
||||||
intentChipActive: {
|
|
||||||
borderColor: theme.colors.accent,
|
|
||||||
backgroundColor: `${String(theme.colors.accent)}20`,
|
|
||||||
},
|
|
||||||
intentChipText: {
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
fontWeight: "500",
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
},
|
|
||||||
intentChipTextActive: {
|
|
||||||
color: theme.colors.accent,
|
|
||||||
},
|
},
|
||||||
annotationInput: {
|
annotationInput: {
|
||||||
minHeight: 64,
|
minHeight: 64,
|
||||||
@@ -2029,27 +1945,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
annotationSecondaryButton: {
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
borderRadius: theme.borderRadius.md,
|
|
||||||
},
|
|
||||||
annotationSecondaryText: {
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
fontWeight: "500",
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
},
|
|
||||||
annotationPrimaryButton: {
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
borderRadius: theme.borderRadius.md,
|
|
||||||
backgroundColor: theme.colors.accent,
|
|
||||||
},
|
|
||||||
annotationPrimaryText: {
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
fontWeight: "600",
|
|
||||||
color: theme.colors.accentForeground ?? "#ffffff",
|
|
||||||
},
|
|
||||||
unavailableState: {
|
unavailableState: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ export function getAttachmentKey(attachment: WorkspaceComposerAttachment): strin
|
|||||||
tag: attachment.attachment.tag,
|
tag: attachment.attachment.tag,
|
||||||
text: attachment.attachment.text,
|
text: attachment.attachment.text,
|
||||||
html: attachment.attachment.outerHTML,
|
html: attachment.attachment.outerHTML,
|
||||||
intent: attachment.attachment.intent ?? null,
|
|
||||||
comment: attachment.attachment.comment ?? null,
|
comment: attachment.attachment.comment ?? null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,22 +422,17 @@ export const ar: TranslationResources = {
|
|||||||
enterUrl: "أدخل URL",
|
enterUrl: "أدخل URL",
|
||||||
openDevTools: "افتح أدوات تطوير المتصفح",
|
openDevTools: "افتح أدوات تطوير المتصفح",
|
||||||
cancelSelector: "إلغاء محدد العنصر",
|
cancelSelector: "إلغاء محدد العنصر",
|
||||||
selectElement: "حدد العنصر",
|
annotateElement: "التعليق على العنصر",
|
||||||
grabElement: "نسخ العنصر إلى الحافظة",
|
screenshotElement: "لقطة للعنصر",
|
||||||
grabElementLabel: "العنصر",
|
screenshotCopied: "تم نسخ لقطة الشاشة إلى الحافظة",
|
||||||
grabFailed: "تعذّر نسخ العنصر",
|
elementCopied: "تم نسخ العنصر إلى الحافظة",
|
||||||
|
screenshotFailed: "تعذّر نسخ لقطة الشاشة",
|
||||||
},
|
},
|
||||||
annotate: {
|
annotate: {
|
||||||
title: "إرسال ملاحظات إلى الوكيل",
|
title: "التعليق على العنصر",
|
||||||
placeholder: "صف ما الذي ينبغي تغييره…",
|
placeholder: "رسالة إلى الوكيل حول هذا العنصر…",
|
||||||
submit: "إرفاق",
|
submit: "إرفاق",
|
||||||
cancel: "إلغاء",
|
cancel: "إلغاء",
|
||||||
intents: {
|
|
||||||
fix: "إصلاح",
|
|
||||||
change: "تغيير",
|
|
||||||
question: "سؤال",
|
|
||||||
approve: "موافقة",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
devices: {
|
devices: {
|
||||||
label: "حجم الجهاز",
|
label: "حجم الجهاز",
|
||||||
|
|||||||
@@ -422,22 +422,17 @@ export const en = {
|
|||||||
enterUrl: "Enter URL",
|
enterUrl: "Enter URL",
|
||||||
openDevTools: "Open browser dev tools",
|
openDevTools: "Open browser dev tools",
|
||||||
cancelSelector: "Cancel element selector",
|
cancelSelector: "Cancel element selector",
|
||||||
selectElement: "Select element",
|
annotateElement: "Annotate element",
|
||||||
grabElement: "Copy element to clipboard",
|
screenshotElement: "Screenshot element",
|
||||||
grabElementLabel: "element",
|
screenshotCopied: "Copied screenshot to clipboard",
|
||||||
grabFailed: "Couldn't copy element",
|
elementCopied: "Copied element to clipboard",
|
||||||
|
screenshotFailed: "Couldn't copy screenshot",
|
||||||
},
|
},
|
||||||
annotate: {
|
annotate: {
|
||||||
title: "Send feedback to agent",
|
title: "Annotate element",
|
||||||
placeholder: "Describe what should change…",
|
placeholder: "Message to the agent about this element…",
|
||||||
submit: "Attach",
|
submit: "Attach",
|
||||||
cancel: "Cancel",
|
cancel: "Cancel",
|
||||||
intents: {
|
|
||||||
fix: "Fix",
|
|
||||||
change: "Change",
|
|
||||||
question: "Question",
|
|
||||||
approve: "Approve",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
devices: {
|
devices: {
|
||||||
label: "Device size",
|
label: "Device size",
|
||||||
|
|||||||
@@ -426,22 +426,17 @@ export const es: TranslationResources = {
|
|||||||
enterUrl: "IngreseURL",
|
enterUrl: "IngreseURL",
|
||||||
openDevTools: "Abrir herramientas de desarrollo del navegador",
|
openDevTools: "Abrir herramientas de desarrollo del navegador",
|
||||||
cancelSelector: "Cancelar selector de elementos",
|
cancelSelector: "Cancelar selector de elementos",
|
||||||
selectElement: "Seleccionar elemento",
|
annotateElement: "Anotar elemento",
|
||||||
grabElement: "Copiar elemento al portapapeles",
|
screenshotElement: "Capturar elemento",
|
||||||
grabElementLabel: "elemento",
|
screenshotCopied: "Captura copiada al portapapeles",
|
||||||
grabFailed: "No se pudo copiar el elemento",
|
elementCopied: "Elemento copiado al portapapeles",
|
||||||
|
screenshotFailed: "No se pudo copiar la captura",
|
||||||
},
|
},
|
||||||
annotate: {
|
annotate: {
|
||||||
title: "Enviar comentarios al agente",
|
title: "Anotar elemento",
|
||||||
placeholder: "Describe qué debería cambiar…",
|
placeholder: "Mensaje al agente sobre este elemento…",
|
||||||
submit: "Adjuntar",
|
submit: "Adjuntar",
|
||||||
cancel: "Cancelar",
|
cancel: "Cancelar",
|
||||||
intents: {
|
|
||||||
fix: "Corregir",
|
|
||||||
change: "Cambiar",
|
|
||||||
question: "Pregunta",
|
|
||||||
approve: "Aprobar",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
devices: {
|
devices: {
|
||||||
label: "Tamaño del dispositivo",
|
label: "Tamaño del dispositivo",
|
||||||
|
|||||||
@@ -426,22 +426,17 @@ export const fr: TranslationResources = {
|
|||||||
enterUrl: "EntrezURL",
|
enterUrl: "EntrezURL",
|
||||||
openDevTools: "Outils de développement du navigateur ouvert",
|
openDevTools: "Outils de développement du navigateur ouvert",
|
||||||
cancelSelector: "Annuler le sélecteur d'élément",
|
cancelSelector: "Annuler le sélecteur d'élément",
|
||||||
selectElement: "Sélectionner un élément",
|
annotateElement: "Annoter l'élément",
|
||||||
grabElement: "Copier l'élément dans le presse-papiers",
|
screenshotElement: "Capturer l'élément",
|
||||||
grabElementLabel: "élément",
|
screenshotCopied: "Capture d'écran copiée dans le presse-papiers",
|
||||||
grabFailed: "Impossible de copier l'élément",
|
elementCopied: "Élément copié dans le presse-papiers",
|
||||||
|
screenshotFailed: "Impossible de copier la capture",
|
||||||
},
|
},
|
||||||
annotate: {
|
annotate: {
|
||||||
title: "Envoyer un retour à l'agent",
|
title: "Annoter l'élément",
|
||||||
placeholder: "Décrivez ce qui doit changer…",
|
placeholder: "Message à l'agent concernant cet élément…",
|
||||||
submit: "Joindre",
|
submit: "Joindre",
|
||||||
cancel: "Annuler",
|
cancel: "Annuler",
|
||||||
intents: {
|
|
||||||
fix: "Corriger",
|
|
||||||
change: "Modifier",
|
|
||||||
question: "Question",
|
|
||||||
approve: "Approuver",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
devices: {
|
devices: {
|
||||||
label: "Taille de l'appareil",
|
label: "Taille de l'appareil",
|
||||||
|
|||||||
@@ -426,22 +426,17 @@ export const ja: TranslationResources = {
|
|||||||
enterUrl: "URLを入力",
|
enterUrl: "URLを入力",
|
||||||
openDevTools: "ブラウザ開発ツールを開く",
|
openDevTools: "ブラウザ開発ツールを開く",
|
||||||
cancelSelector: "要素セレクターをキャンセル",
|
cancelSelector: "要素セレクターをキャンセル",
|
||||||
selectElement: "要素を選択",
|
annotateElement: "要素に注釈を付ける",
|
||||||
grabElement: "要素をクリップボードにコピー",
|
screenshotElement: "要素のスクリーンショット",
|
||||||
grabElementLabel: "要素",
|
screenshotCopied: "スクリーンショットをクリップボードにコピーしました",
|
||||||
grabFailed: "要素をコピーできませんでした",
|
elementCopied: "要素をクリップボードにコピーしました",
|
||||||
|
screenshotFailed: "スクリーンショットをコピーできませんでした",
|
||||||
},
|
},
|
||||||
annotate: {
|
annotate: {
|
||||||
title: "エージェントにフィードバックを送信",
|
title: "要素に注釈を付ける",
|
||||||
placeholder: "変更すべき内容を記述…",
|
placeholder: "この要素についてエージェントへのメッセージ…",
|
||||||
submit: "添付",
|
submit: "添付",
|
||||||
cancel: "キャンセル",
|
cancel: "キャンセル",
|
||||||
intents: {
|
|
||||||
fix: "修正",
|
|
||||||
change: "変更",
|
|
||||||
question: "質問",
|
|
||||||
approve: "承認",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
devices: {
|
devices: {
|
||||||
label: "デバイスサイズ",
|
label: "デバイスサイズ",
|
||||||
|
|||||||
@@ -426,22 +426,17 @@ export const ptBR: TranslationResources = {
|
|||||||
enterUrl: "Inserir URL",
|
enterUrl: "Inserir URL",
|
||||||
openDevTools: "Abrir ferramentas de desenvolvedor do navegador",
|
openDevTools: "Abrir ferramentas de desenvolvedor do navegador",
|
||||||
cancelSelector: "Cancelar seletor de elemento",
|
cancelSelector: "Cancelar seletor de elemento",
|
||||||
selectElement: "Selecionar elemento",
|
annotateElement: "Anotar elemento",
|
||||||
grabElement: "Copiar elemento para a área de transferência",
|
screenshotElement: "Capturar elemento",
|
||||||
grabElementLabel: "elemento",
|
screenshotCopied: "Captura copiada para a área de transferência",
|
||||||
grabFailed: "Não foi possível copiar o elemento",
|
elementCopied: "Elemento copiado para a área de transferência",
|
||||||
|
screenshotFailed: "Não foi possível copiar a captura",
|
||||||
},
|
},
|
||||||
annotate: {
|
annotate: {
|
||||||
title: "Enviar feedback ao agente",
|
title: "Anotar elemento",
|
||||||
placeholder: "Descreva o que deve mudar…",
|
placeholder: "Mensagem ao agente sobre este elemento…",
|
||||||
submit: "Anexar",
|
submit: "Anexar",
|
||||||
cancel: "Cancelar",
|
cancel: "Cancelar",
|
||||||
intents: {
|
|
||||||
fix: "Corrigir",
|
|
||||||
change: "Alterar",
|
|
||||||
question: "Pergunta",
|
|
||||||
approve: "Aprovar",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
devices: {
|
devices: {
|
||||||
label: "Tamanho do dispositivo",
|
label: "Tamanho do dispositivo",
|
||||||
|
|||||||
@@ -426,22 +426,17 @@ export const ru: TranslationResources = {
|
|||||||
enterUrl: "Введите URL",
|
enterUrl: "Введите URL",
|
||||||
openDevTools: "Открыть инструменты разработки браузера",
|
openDevTools: "Открыть инструменты разработки браузера",
|
||||||
cancelSelector: "Отменить выбор элемента",
|
cancelSelector: "Отменить выбор элемента",
|
||||||
selectElement: "Выберите элемент",
|
annotateElement: "Аннотировать элемент",
|
||||||
grabElement: "Скопировать элемент в буфер обмена",
|
screenshotElement: "Снимок элемента",
|
||||||
grabElementLabel: "элемент",
|
screenshotCopied: "Снимок скопирован в буфер обмена",
|
||||||
grabFailed: "Не удалось скопировать элемент",
|
elementCopied: "Элемент скопирован в буфер обмена",
|
||||||
|
screenshotFailed: "Не удалось скопировать снимок",
|
||||||
},
|
},
|
||||||
annotate: {
|
annotate: {
|
||||||
title: "Отправить отзыв агенту",
|
title: "Аннотировать элемент",
|
||||||
placeholder: "Опишите, что нужно изменить…",
|
placeholder: "Сообщение агенту об этом элементе…",
|
||||||
submit: "Прикрепить",
|
submit: "Прикрепить",
|
||||||
cancel: "Отмена",
|
cancel: "Отмена",
|
||||||
intents: {
|
|
||||||
fix: "Исправить",
|
|
||||||
change: "Изменить",
|
|
||||||
question: "Вопрос",
|
|
||||||
approve: "Одобрить",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
devices: {
|
devices: {
|
||||||
label: "Размер устройства",
|
label: "Размер устройства",
|
||||||
|
|||||||
@@ -422,22 +422,17 @@ export const zhCN: TranslationResources = {
|
|||||||
enterUrl: "输入 URL",
|
enterUrl: "输入 URL",
|
||||||
openDevTools: "打开浏览器开发者工具",
|
openDevTools: "打开浏览器开发者工具",
|
||||||
cancelSelector: "取消元素选择器",
|
cancelSelector: "取消元素选择器",
|
||||||
selectElement: "选择元素",
|
annotateElement: "标注元素",
|
||||||
grabElement: "复制元素到剪贴板",
|
screenshotElement: "截图元素",
|
||||||
grabElementLabel: "元素",
|
screenshotCopied: "已将截图复制到剪贴板",
|
||||||
grabFailed: "复制元素失败",
|
elementCopied: "已将元素复制到剪贴板",
|
||||||
|
screenshotFailed: "无法复制截图",
|
||||||
},
|
},
|
||||||
annotate: {
|
annotate: {
|
||||||
title: "发送反馈给智能体",
|
title: "标注元素",
|
||||||
placeholder: "描述需要修改的内容…",
|
placeholder: "给智能体关于此元素的留言…",
|
||||||
submit: "附加",
|
submit: "附加",
|
||||||
cancel: "取消",
|
cancel: "取消",
|
||||||
intents: {
|
|
||||||
fix: "修复",
|
|
||||||
change: "修改",
|
|
||||||
question: "提问",
|
|
||||||
approve: "认可",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
devices: {
|
devices: {
|
||||||
label: "设备尺寸",
|
label: "设备尺寸",
|
||||||
|
|||||||
Reference in New Issue
Block a user