mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Allow selection of multiple browser viewport sizes and adjusting screenshot quality
This commit is contained in:
@@ -61,7 +61,7 @@
|
||||
},
|
||||
"jest": {
|
||||
"transformIgnorePatterns": [
|
||||
"/node_modules/(?!(rehype-highlight|react-remark|unist-util-visit|vfile|unified|bail|is-plain-obj|trough|vfile-message|unist-util-stringify-position|mdast-util-from-markdown|mdast-util-to-string|micromark|decode-named-character-reference|character-entities|markdown-table|zwitch|longest-streak|escape-string-regexp|unist-util-is|hast-util-to-text|@vscode/webview-ui-toolkit|@microsoft/fast-react-wrapper|@microsoft/fast-element|@microsoft/fast-foundation|@microsoft/fast-web-utilities|exenv-es6)/)"
|
||||
"/node_modules/(?!(rehype-highlight|react-remark|unist-util-visit|unist-util-find-after|vfile|unified|bail|is-plain-obj|trough|vfile-message|unist-util-stringify-position|mdast-util-from-markdown|mdast-util-to-string|micromark|decode-named-character-reference|character-entities|markdown-table|zwitch|longest-streak|escape-string-regexp|unist-util-is|hast-util-to-text|@vscode/webview-ui-toolkit|@microsoft/fast-react-wrapper|@microsoft/fast-element|@microsoft/fast-foundation|@microsoft/fast-web-utilities|exenv-es6)/)"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"\\.(css|less|scss|sass)$": "identity-obj-proxy"
|
||||
|
||||
@@ -28,6 +28,11 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
||||
const [maxActionHeight, setMaxActionHeight] = useState(0)
|
||||
const [consoleLogsExpanded, setConsoleLogsExpanded] = useState(false)
|
||||
|
||||
const { browserViewportSize = "900x600" } = useExtensionState()
|
||||
const [viewportWidth, viewportHeight] = browserViewportSize.split("x").map(Number)
|
||||
const aspectRatio = (viewportHeight / viewportWidth * 100).toFixed(2)
|
||||
const defaultMousePosition = `${Math.round(viewportWidth/2)},${Math.round(viewportHeight/2)}`
|
||||
|
||||
const isLastApiReqInterrupted = useMemo(() => {
|
||||
// Check if last api_req_started is cancelled
|
||||
const lastApiReqStarted = [...messages].reverse().find((m) => m.say === "api_req_started")
|
||||
@@ -165,13 +170,13 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
||||
const displayState = isLastPage
|
||||
? {
|
||||
url: currentPage?.currentState.url || latestState.url || initialUrl,
|
||||
mousePosition: currentPage?.currentState.mousePosition || latestState.mousePosition || "700,400",
|
||||
mousePosition: currentPage?.currentState.mousePosition || latestState.mousePosition || defaultMousePosition,
|
||||
consoleLogs: currentPage?.currentState.consoleLogs,
|
||||
screenshot: currentPage?.currentState.screenshot || latestState.screenshot,
|
||||
}
|
||||
: {
|
||||
url: currentPage?.currentState.url || initialUrl,
|
||||
mousePosition: currentPage?.currentState.mousePosition || "700,400",
|
||||
mousePosition: currentPage?.currentState.mousePosition || defaultMousePosition,
|
||||
consoleLogs: currentPage?.currentState.consoleLogs,
|
||||
screenshot: currentPage?.currentState.screenshot,
|
||||
}
|
||||
@@ -220,10 +225,9 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
||||
}, [isBrowsing, currentPage?.nextAction?.messages])
|
||||
|
||||
// Use latest click position while browsing, otherwise use display state
|
||||
const { browserLargeViewport } = useExtensionState()
|
||||
const mousePosition = isBrowsing ? latestClickPosition || displayState.mousePosition : displayState.mousePosition
|
||||
const mousePosition = isBrowsing ? latestClickPosition || displayState.mousePosition : displayState.mousePosition || defaultMousePosition
|
||||
|
||||
const [browserSessionRow, { height }] = useSize(
|
||||
const [browserSessionRow, { height: rowHeight }] = useSize(
|
||||
<div style={{ padding: "10px 6px 10px 15px", marginBottom: -10 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "10px", marginBottom: "10px" }}>
|
||||
{isBrowsing ? (
|
||||
@@ -277,9 +281,10 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
||||
|
||||
{/* Screenshot Area */}
|
||||
<div
|
||||
data-testid="screenshot-container"
|
||||
style={{
|
||||
width: "100%",
|
||||
paddingBottom: browserLargeViewport ? "62.5%" : "66.67%", // 800/1280 = 0.625, 600/900 = 0.667
|
||||
paddingBottom: `${aspectRatio}%`, // height/width ratio
|
||||
position: "relative",
|
||||
backgroundColor: "var(--vscode-input-background)",
|
||||
}}>
|
||||
@@ -321,8 +326,8 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
||||
<BrowserCursor
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: `${(parseInt(mousePosition.split(",")[1]) / (browserLargeViewport ? 800 : 600)) * 100}%`,
|
||||
left: `${(parseInt(mousePosition.split(",")[0]) / (browserLargeViewport ? 1280 : 900)) * 100}%`,
|
||||
top: `${(parseInt(mousePosition.split(",")[1]) / viewportHeight) * 100}%`,
|
||||
left: `${(parseInt(mousePosition.split(",")[0]) / viewportWidth) * 100}%`,
|
||||
transition: "top 0.3s ease-out, left 0.3s ease-out",
|
||||
}}
|
||||
/>
|
||||
@@ -389,13 +394,13 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
||||
// Height change effect
|
||||
useEffect(() => {
|
||||
const isInitialRender = prevHeightRef.current === 0
|
||||
if (isLast && height !== 0 && height !== Infinity && height !== prevHeightRef.current) {
|
||||
if (isLast && rowHeight !== 0 && rowHeight !== Infinity && rowHeight !== prevHeightRef.current) {
|
||||
if (!isInitialRender) {
|
||||
onHeightChange(height > prevHeightRef.current)
|
||||
onHeightChange(rowHeight > prevHeightRef.current)
|
||||
}
|
||||
prevHeightRef.current = height
|
||||
prevHeightRef.current = rowHeight
|
||||
}
|
||||
}, [height, isLast, onHeightChange])
|
||||
}, [rowHeight, isLast, onHeightChange])
|
||||
|
||||
return browserSessionRow
|
||||
}, deepEqual)
|
||||
@@ -552,6 +557,7 @@ const BrowserCursor: React.FC<{ style?: React.CSSProperties }> = ({ style }) =>
|
||||
...style,
|
||||
}}
|
||||
alt="cursor"
|
||||
aria-label="cursor"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
setSoundVolume,
|
||||
diffEnabled,
|
||||
setDiffEnabled,
|
||||
browserLargeViewport,
|
||||
setBrowserLargeViewport,
|
||||
browserViewportSize,
|
||||
setBrowserViewportSize,
|
||||
openRouterModels,
|
||||
setAllowedCommands,
|
||||
allowedCommands,
|
||||
@@ -44,6 +44,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
setPreferredLanguage,
|
||||
writeDelayMs,
|
||||
setWriteDelayMs,
|
||||
screenshotQuality,
|
||||
setScreenshotQuality,
|
||||
} = useExtensionState()
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
|
||||
@@ -69,10 +71,11 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
vscode.postMessage({ type: "soundEnabled", bool: soundEnabled })
|
||||
vscode.postMessage({ type: "soundVolume", value: soundVolume })
|
||||
vscode.postMessage({ type: "diffEnabled", bool: diffEnabled })
|
||||
vscode.postMessage({ type: "browserLargeViewport", bool: browserLargeViewport })
|
||||
vscode.postMessage({ type: "browserViewportSize", text: browserViewportSize })
|
||||
vscode.postMessage({ type: "fuzzyMatchThreshold", value: fuzzyMatchThreshold ?? 1.0 })
|
||||
vscode.postMessage({ type: "preferredLanguage", text: preferredLanguage })
|
||||
vscode.postMessage({ type: "writeDelayMs", value: writeDelayMs })
|
||||
vscode.postMessage({ type: "screenshotQuality", value: screenshotQuality ?? 75 })
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
@@ -128,7 +131,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
marginBottom: "17px",
|
||||
paddingRight: 17,
|
||||
}}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>Settings</h3>
|
||||
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>Provider Settings</h3>
|
||||
<VSCodeButton onClick={handleSubmit}>Done</VSCodeButton>
|
||||
</div>
|
||||
<div
|
||||
@@ -143,6 +147,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
<div style={{ marginBottom: 15 }}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: 0, marginBottom: 15 }}>Agent Settings</h3>
|
||||
|
||||
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>Preferred Language</label>
|
||||
<select
|
||||
value={preferredLanguage}
|
||||
@@ -264,7 +270,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 5, border: "2px solid var(--vscode-errorForeground)", borderRadius: "4px", padding: "10px" }}>
|
||||
<div style={{ marginBottom: 15, border: "2px solid var(--vscode-errorForeground)", borderRadius: "4px", padding: "10px" }}>
|
||||
<h4 style={{ fontWeight: 500, margin: "0 0 10px 0", color: "var(--vscode-errorForeground)" }}>⚠️ High-Risk Auto-Approve Settings</h4>
|
||||
<p style={{ fontSize: "12px", marginBottom: 15, color: "var(--vscode-descriptionForeground)" }}>
|
||||
The following settings allow Cline to automatically perform potentially dangerous operations without requiring approval.
|
||||
@@ -422,24 +428,69 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
<h4 style={{ fontWeight: 500, marginBottom: 10 }}>Experimental Features</h4>
|
||||
|
||||
<div style={{ marginBottom: 10 }}>
|
||||
<VSCodeCheckbox checked={browserLargeViewport} onChange={(e: any) => setBrowserLargeViewport(e.target.checked)}>
|
||||
<span style={{ fontWeight: "500" }}>Use larger browser viewport (1280x800)</span>
|
||||
</VSCodeCheckbox>
|
||||
<p
|
||||
style={{
|
||||
<div style={{ marginBottom: 15 }}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: 0, marginBottom: 15 }}>Browser Settings</h3>
|
||||
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>Viewport Size</label>
|
||||
<select
|
||||
value={browserViewportSize}
|
||||
onChange={(e) => setBrowserViewportSize(e.target.value)}
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "4px 8px",
|
||||
backgroundColor: "var(--vscode-input-background)",
|
||||
color: "var(--vscode-input-foreground)",
|
||||
border: "1px solid var(--vscode-input-border)",
|
||||
borderRadius: "2px",
|
||||
height: "28px"
|
||||
}}>
|
||||
<option value="1280x800">Large Desktop (1280x800)</option>
|
||||
<option value="900x600">Small Desktop (900x600)</option>
|
||||
<option value="768x1024">Tablet (768x1024)</option>
|
||||
<option value="360x640">Mobile (360x640)</option>
|
||||
</select>
|
||||
<p style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
When enabled, Cline will use a larger viewport size for browser interactions.
|
||||
</p>
|
||||
Select the viewport size for browser interactions. This affects how websites are displayed and interacted with.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 15 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||
<span style={{ fontWeight: "500", minWidth: '100px' }}>Screenshot Quality</span>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
step="1"
|
||||
value={screenshotQuality ?? 75}
|
||||
onChange={(e) => setScreenshotQuality(parseInt(e.target.value))}
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
accentColor: 'var(--vscode-button-background)',
|
||||
height: '2px'
|
||||
}}
|
||||
/>
|
||||
<span style={{ minWidth: '35px', textAlign: 'left' }}>
|
||||
{screenshotQuality ?? 75}%
|
||||
</span>
|
||||
</div>
|
||||
<p style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
Adjust the WebP quality of browser screenshots. Higher values provide clearer screenshots but increase token usage.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
<div style={{ marginBottom: 10 }}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: 0, marginBottom: 15 }}>Notification Settings</h3>
|
||||
<VSCodeCheckbox checked={soundEnabled} onChange={(e: any) => setSoundEnabled(e.target.checked)}>
|
||||
<span style={{ fontWeight: "500" }}>Enable sound effects</span>
|
||||
</VSCodeCheckbox>
|
||||
@@ -468,9 +519,10 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
accentColor: 'var(--vscode-button-background)',
|
||||
height: '2px'
|
||||
}}
|
||||
aria-label="Volume"
|
||||
/>
|
||||
<span style={{ minWidth: '35px', textAlign: 'left' }}>
|
||||
{Math.round((soundVolume ?? 0.5) * 100)}%
|
||||
{((soundVolume ?? 0.5) * 100).toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,6 +61,17 @@ jest.mock('@vscode/webview-ui-toolkit/react', () => ({
|
||||
<div onChange={onChange}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
VSCodeSlider: ({ value, onChange }: any) => (
|
||||
<input
|
||||
type="range"
|
||||
value={value}
|
||||
onChange={(e) => onChange({ target: { value: Number(e.target.value) } })}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
style={{ flexGrow: 1, height: '2px' }}
|
||||
/>
|
||||
)
|
||||
}))
|
||||
|
||||
@@ -75,6 +86,8 @@ const mockPostMessage = (state: any) => {
|
||||
shouldShowAnnouncement: false,
|
||||
allowedCommands: [],
|
||||
alwaysAllowExecute: false,
|
||||
soundEnabled: false,
|
||||
soundVolume: 0.5,
|
||||
...state
|
||||
}
|
||||
}, '*')
|
||||
@@ -106,7 +119,7 @@ describe('SettingsView - Sound Settings', () => {
|
||||
expect(soundCheckbox).not.toBeChecked()
|
||||
|
||||
// Volume slider should not be visible when sound is disabled
|
||||
expect(screen.queryByRole('slider')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('slider', { name: /volume/i })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('toggles sound setting and sends message to VSCode', () => {
|
||||
@@ -142,9 +155,9 @@ describe('SettingsView - Sound Settings', () => {
|
||||
fireEvent.click(soundCheckbox)
|
||||
|
||||
// Volume slider should be visible
|
||||
const volumeSlider = screen.getByRole('slider')
|
||||
const volumeSlider = screen.getByRole('slider', { name: /volume/i })
|
||||
expect(volumeSlider).toBeInTheDocument()
|
||||
expect(volumeSlider).toHaveValue('0.5') // Default value
|
||||
expect(volumeSlider).toHaveValue('0.5')
|
||||
})
|
||||
|
||||
it('updates volume and sends message to VSCode when slider changes', () => {
|
||||
@@ -157,23 +170,18 @@ describe('SettingsView - Sound Settings', () => {
|
||||
fireEvent.click(soundCheckbox)
|
||||
|
||||
// Change volume
|
||||
const volumeSlider = screen.getByRole('slider')
|
||||
const volumeSlider = screen.getByRole('slider', { name: /volume/i })
|
||||
fireEvent.change(volumeSlider, { target: { value: '0.75' } })
|
||||
|
||||
// Verify volume display updates
|
||||
expect(screen.getByText('75%')).toBeInTheDocument()
|
||||
|
||||
// Click Done to save settings
|
||||
const doneButton = screen.getByText('Done')
|
||||
fireEvent.click(doneButton)
|
||||
|
||||
// Verify message sent to VSCode
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'soundVolume',
|
||||
value: 0.75
|
||||
})
|
||||
)
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: 'soundVolume',
|
||||
value: 0.75
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -32,14 +32,16 @@ export interface ExtensionStateContextType extends ExtensionState {
|
||||
setSoundEnabled: (value: boolean) => void
|
||||
setSoundVolume: (value: number) => void
|
||||
setDiffEnabled: (value: boolean) => void
|
||||
setBrowserLargeViewport: (value: boolean) => void
|
||||
setBrowserViewportSize: (value: string) => void
|
||||
setFuzzyMatchThreshold: (value: number) => void
|
||||
preferredLanguage: string
|
||||
setPreferredLanguage: (value: string) => void
|
||||
setWriteDelayMs: (value: number) => void
|
||||
screenshotQuality?: number
|
||||
setScreenshotQuality: (value: number) => void
|
||||
}
|
||||
|
||||
const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
|
||||
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
|
||||
|
||||
export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [state, setState] = useState<ExtensionState>({
|
||||
@@ -54,6 +56,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
fuzzyMatchThreshold: 1.0,
|
||||
preferredLanguage: 'English',
|
||||
writeDelayMs: 1000,
|
||||
browserViewportSize: "900x600",
|
||||
screenshotQuality: 75,
|
||||
})
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
const [showWelcome, setShowWelcome] = useState(false)
|
||||
@@ -151,6 +155,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
soundVolume: state.soundVolume,
|
||||
fuzzyMatchThreshold: state.fuzzyMatchThreshold,
|
||||
writeDelayMs: state.writeDelayMs,
|
||||
screenshotQuality: state.screenshotQuality,
|
||||
setApiConfiguration: (value) => setState((prevState) => ({
|
||||
...prevState,
|
||||
apiConfiguration: value
|
||||
@@ -166,10 +171,11 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })),
|
||||
setSoundVolume: (value) => setState((prevState) => ({ ...prevState, soundVolume: value })),
|
||||
setDiffEnabled: (value) => setState((prevState) => ({ ...prevState, diffEnabled: value })),
|
||||
setBrowserLargeViewport: (value) => setState((prevState) => ({ ...prevState, browserLargeViewport: value })),
|
||||
setBrowserViewportSize: (value: string) => setState((prevState) => ({ ...prevState, browserViewportSize: value })),
|
||||
setFuzzyMatchThreshold: (value) => setState((prevState) => ({ ...prevState, fuzzyMatchThreshold: value })),
|
||||
setPreferredLanguage: (value) => setState((prevState) => ({ ...prevState, preferredLanguage: value })),
|
||||
setWriteDelayMs: (value) => setState((prevState) => ({ ...prevState, writeDelayMs: value })),
|
||||
setScreenshotQuality: (value) => setState((prevState) => ({ ...prevState, screenshotQuality: value })),
|
||||
}
|
||||
|
||||
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user