fe style change

This commit is contained in:
Pakchoioioi 2026-02-21 22:30:59 +00:00
parent 4df123f246
commit cb9810aa04
10 changed files with 167 additions and 82 deletions

View file

@ -49,7 +49,7 @@ def _is_cdp_port_alive(port: int, timeout: float = 1.0) -> bool:
try:
with socket.create_connection(("localhost", port), timeout=timeout):
return True
except (ConnectionRefusedError, OSError, socket.timeout):
except (TimeoutError, ConnectionRefusedError, OSError):
return False
@ -234,7 +234,11 @@ def browser_agent(options: Chat):
browser_log_to_file=True,
stealth=True,
session_id=toolkit_session_id,
**({"default_start_url": "about:blank"} if not use_pool_browser else {}),
**(
{"default_start_url": "about:blank"}
if not use_pool_browser
else {}
),
cdp_url=f"http://localhost:{selected_port}",
cdp_keep_current_page=use_pool_browser,
enabled_tools=[

View file

@ -78,15 +78,11 @@ def _find_chromium_executable() -> str | None:
system = pf.system()
if system == "Darwin":
cache_dir = os.path.join(
home, "Library", "Caches", "ms-playwright"
)
cache_dir = os.path.join(home, "Library", "Caches", "ms-playwright")
elif system == "Linux":
cache_dir = os.path.join(home, ".cache", "ms-playwright")
elif system == "Windows":
cache_dir = os.path.join(
home, "AppData", "Local", "ms-playwright"
)
cache_dir = os.path.join(home, "AppData", "Local", "ms-playwright")
else:
return None
@ -138,9 +134,7 @@ def _find_chromium_executable() -> str | None:
),
]
elif system == "Linux":
candidates = [
os.path.join(base, "chrome-linux", "chrome")
]
candidates = [os.path.join(base, "chrome-linux", "chrome")]
else: # Windows
candidates = [
os.path.join(base, "chrome-win64", "chrome.exe"),
@ -160,8 +154,7 @@ def _find_system_chrome() -> str | None:
if system == "Darwin":
chrome_path = (
"/Applications/Google Chrome.app"
"/Contents/MacOS/Google Chrome"
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
)
if os.path.exists(chrome_path):
return chrome_path
@ -1007,9 +1000,7 @@ async def launch_cdp_browser():
),
)
logger.info(
f"[BROWSER LAUNCH] Found available port: {port}"
)
logger.info(f"[BROWSER LAUNCH] Found available port: {port}")
# 2. Find Chromium executable
chrome_executable = _find_chromium_executable()
@ -1022,9 +1013,7 @@ async def launch_cdp_browser():
),
)
logger.info(
f"[BROWSER LAUNCH] Using Chromium: {chrome_executable}"
)
logger.info(f"[BROWSER LAUNCH] Using Chromium: {chrome_executable}")
# 3. Create user data directory
user_data_dir = os.path.join(
@ -1044,10 +1033,7 @@ async def launch_cdp_browser():
"about:blank",
]
logger.info(
"[BROWSER LAUNCH] Spawning Chromium"
f" on port {port}"
)
logger.info(f"[BROWSER LAUNCH] Spawning Chromium on port {port}")
process = subprocess.Popen(
args,
@ -1056,10 +1042,7 @@ async def launch_cdp_browser():
)
_launched_browser_processes[port] = process
logger.info(
"[BROWSER LAUNCH] Chromium spawned,"
f" PID: {process.pid}"
)
logger.info(f"[BROWSER LAUNCH] Chromium spawned, PID: {process.pid}")
# 5. Poll for browser readiness (max 5 seconds)
max_wait = 5.0
@ -1104,18 +1087,13 @@ async def launch_cdp_browser():
"success": True,
"port": port,
"data": browser_info,
"message": (
"Browser launched successfully"
f" on port {port}"
),
"message": (f"Browser launched successfully on port {port}"),
}
except HTTPException:
raise
except Exception as e:
logger.error(
f"[BROWSER LAUNCH] Failed: {e}", exc_info=True
)
logger.error(f"[BROWSER LAUNCH] Failed: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail=f"Failed to launch browser: {e!s}",
@ -1171,10 +1149,7 @@ async def launch_chrome_with_profile(
if not os.path.isdir(profile_path):
raise HTTPException(
status_code=404,
detail=(
f"Profile '{request.profile_directory}'"
" not found"
),
detail=(f"Profile '{request.profile_directory}' not found"),
)
# 4. Find available port
@ -1203,9 +1178,7 @@ async def launch_chrome_with_profile(
os.makedirs(wrapper_dir, exist_ok=True)
# Symlink requested profile into wrapper
link_dst = os.path.join(
wrapper_dir, request.profile_directory
)
link_dst = os.path.join(wrapper_dir, request.profile_directory)
real_profile = os.path.join(
chrome_user_data_dir, request.profile_directory
)
@ -1216,12 +1189,8 @@ async def launch_chrome_with_profile(
os.symlink(real_profile, link_dst)
# Copy Local State (small JSON, needed by Chrome)
local_state_src = os.path.join(
chrome_user_data_dir, "Local State"
)
local_state_dst = os.path.join(
wrapper_dir, "Local State"
)
local_state_src = os.path.join(chrome_user_data_dir, "Local State")
local_state_dst = os.path.join(wrapper_dir, "Local State")
if os.path.isfile(local_state_src):
shutil.copy2(local_state_src, local_state_dst)
@ -1241,10 +1210,7 @@ async def launch_chrome_with_profile(
)
_launched_browser_processes[port] = process
logger.info(
"[CHROME LAUNCH] Chrome spawned,"
f" PID: {process.pid}"
)
logger.info(f"[CHROME LAUNCH] Chrome spawned, PID: {process.pid}")
# 6. Poll for browser readiness (max 8 seconds,
# real Chrome may be slower than Chromium)
@ -1273,16 +1239,12 @@ async def launch_chrome_with_profile(
elapsed = time.time() - start_time
raise HTTPException(
status_code=409,
detail=(
"Chrome failed to start CDP"
f" (waited {elapsed:.1f}s)"
),
detail=(f"Chrome failed to start CDP (waited {elapsed:.1f}s)"),
)
elapsed = time.time() - start_time
logger.info(
"[CHROME LAUNCH] Chrome ready on"
f" port {port} after {elapsed:.1f}s"
f"[CHROME LAUNCH] Chrome ready on port {port} after {elapsed:.1f}s"
)
return {
@ -1300,9 +1262,7 @@ async def launch_chrome_with_profile(
except HTTPException:
raise
except Exception as e:
logger.error(
f"[CHROME LAUNCH] Failed: {e}", exc_info=True
)
logger.error(f"[CHROME LAUNCH] Failed: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail=f"Failed to launch Chrome: {e!s}",

View file

@ -58,14 +58,14 @@ const Layout = () => {
if (['running', 'pause'].includes(currentStatus)) {
setNoticeOpen(true);
} else {
window.electronAPI.closeWindow(true);
window.electronAPI?.closeWindow(true);
}
};
window.ipcRenderer.on('before-close', handleBeforeClose);
window.ipcRenderer?.on('before-close', handleBeforeClose);
return () => {
window.ipcRenderer.removeAllListeners('before-close');
window.ipcRenderer?.removeAllListeners('before-close');
};
}, [chatStore.tasks, chatStore.activeTaskId]);

View file

@ -66,8 +66,8 @@ function HeaderWin() {
isInstalling || installationState === 'waiting-backend';
useEffect(() => {
const p = window.electronAPI.getPlatform();
setPlatform(p);
const p = window.electronAPI?.getPlatform();
if (p) setPlatform(p);
}, []);
const logoSrc = appearance === 'dark' ? folderIconWhite : folderIconBlack;

View file

@ -21,8 +21,8 @@ export default function WindowControls() {
const [platform, setPlatform] = useState<string>('');
useEffect(() => {
const p = window.electronAPI.getPlatform();
setPlatform(p);
const p = window.electronAPI?.getPlatform();
if (p) setPlatform(p);
// Hide custom controls on macOS (uses native traffic lights)
// and on Windows (now uses native frame with native controls)

View file

@ -96,7 +96,7 @@ export function WorkSpaceMenu() {
useEffect(() => {
if (!chatStore) return;
const cleanup = window.electronAPI.onWebviewNavigated(
const cleanup = window.electronAPI?.onWebviewNavigated(
(id: string, url: string) => {
if (!chatStore.activeTaskId) return;
let webViewUrls = [

View file

@ -24,7 +24,7 @@ const Update = () => {
const { t } = useTranslation();
const checkUpdate = () => {
window.ipcRenderer.invoke('check-update');
window.ipcRenderer?.invoke('check-update');
};
const onUpdateCanAvailable = useCallback(

View file

@ -55,7 +55,7 @@ export const useInstallationSetup = () => {
// Immediately check backend status once
const checkBackendStatus = async () => {
try {
const backendPort = await window.electronAPI.getBackendPort();
const backendPort = await window.electronAPI?.getBackendPort();
if (backendPort && backendPort > 0) {
console.log(
'[useInstallationSetup] Backend immediately detected on port:',
@ -101,7 +101,7 @@ export const useInstallationSetup = () => {
// This is a fallback in case the backend-ready event is missed
const pollInterval = setInterval(async () => {
try {
const backendPort = await window.electronAPI.getBackendPort();
const backendPort = await window.electronAPI?.getBackendPort();
if (backendPort && backendPort > 0) {
console.log(
'[useInstallationSetup] Backend poll detected ready on port:',
@ -178,7 +178,7 @@ export const useInstallationSetup = () => {
const checkToolInstalled = async () => {
try {
const result = await window.ipcRenderer.invoke('check-tool-installed');
const result = await window.ipcRenderer?.invoke('check-tool-installed');
if (result.success) {
if (result.isInstalled) {
@ -217,7 +217,7 @@ export const useInstallationSetup = () => {
const checkBackendStatus = async (_toolResult?: any) => {
try {
const installationStatus =
await window.electronAPI.getInstallationStatus();
await window.electronAPI?.getInstallationStatus();
if (installationStatus.success && installationStatus.isInstalling) {
startInstallation();
@ -325,16 +325,16 @@ export const useInstallationSetup = () => {
}
};
window.electronAPI.onInstallDependenciesStart(handleInstallStart);
window.electronAPI.onInstallDependenciesLog(handleInstallLog);
window.electronAPI.onInstallDependenciesComplete(handleInstallComplete);
window.electronAPI.onBackendReady(handleBackendReady);
window.electronAPI?.onInstallDependenciesStart(handleInstallStart);
window.electronAPI?.onInstallDependenciesLog(handleInstallLog);
window.electronAPI?.onInstallDependenciesComplete(handleInstallComplete);
window.electronAPI?.onBackendReady(handleBackendReady);
return () => {
window.electronAPI.removeAllListeners('install-dependencies-start');
window.electronAPI.removeAllListeners('install-dependencies-log');
window.electronAPI.removeAllListeners('install-dependencies-complete');
window.electronAPI.removeAllListeners('backend-ready');
window.electronAPI?.removeAllListeners('install-dependencies-start');
window.electronAPI?.removeAllListeners('install-dependencies-log');
window.electronAPI?.removeAllListeners('install-dependencies-complete');
window.electronAPI?.removeAllListeners('backend-ready');
};
}, [
startInstallation,

View file

@ -20,6 +20,7 @@ import {
Chrome,
Cookie,
Globe,
Link2,
Loader2,
Plus,
RefreshCw,
@ -81,6 +82,12 @@ export default function Browser() {
null
);
// Connect Existing Browser dialog
const [showConnectDialog, setShowConnectDialog] = useState(false);
const [connectPort, setConnectPort] = useState('');
const [connectChecking, setConnectChecking] = useState(false);
const [connectError, setConnectError] = useState('');
// Extract main domain (e.g., "aa.bb.cc" -> "bb.cc", "www.google.com" -> "google.com")
const getMainDomain = (domain: string): string => {
// Remove leading dot if present
@ -281,6 +288,59 @@ export default function Browser() {
});
}
};
const handleConnectExistingBrowser = () => {
setConnectPort('');
setConnectError('');
setShowConnectDialog(true);
};
const handleCheckAndConnect = async () => {
const portNum = parseInt(connectPort, 10);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
setConnectError(t('layout.invalid-port'));
return;
}
// Check if port is already in the pool
if (cdpBrowsers.some((b) => b.port === portNum)) {
setConnectError(t('layout.port-already-in-use'));
return;
}
setConnectChecking(true);
setConnectError('');
try {
// Probe the port to check if a CDP browser is listening
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
const response = await fetch(`http://localhost:${portNum}/json/version`, {
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
setConnectError(t('layout.no-browser-on-port', { port: portNum }));
return;
}
// Port is alive — add to CDP pool
if (window.electronAPI?.addCdpBrowser) {
await window.electronAPI.addCdpBrowser(
portNum,
true,
`External Browser (${portNum})`
);
}
toast.success(t('layout.connected-browser', { port: portNum }));
setShowConnectDialog(false);
} catch {
setConnectError(t('layout.no-browser-on-port', { port: portNum }));
} finally {
setConnectChecking(false);
}
};
const handleBrowserLogin = async () => {
setLoginLoading(true);
@ -499,6 +559,59 @@ export default function Browser() {
</div>
</div>
)}
{/* Connect Existing Browser Dialog */}
{showConnectDialog && (
<div className="bg-black/50 fixed inset-0 z-50 flex items-center justify-center">
<div className="w-full max-w-md rounded-xl bg-surface-primary p-6 shadow-lg">
<div className="text-body-base mb-2 font-bold text-text-heading">
{t('layout.connect-existing-browser')}
</div>
<p className="mb-4 text-label-xs text-text-label">
{t('layout.connect-existing-browser-description')}
</p>
<input
type="text"
value={connectPort}
onChange={(e) => {
setConnectPort(e.target.value);
setConnectError('');
}}
placeholder={t('layout.enter-port-number')}
className="w-full rounded-lg border border-border-disabled bg-surface-secondary px-4 py-2 text-body-sm text-text-body outline-none focus:border-border-focus"
onKeyDown={(e) => {
if (e.key === 'Enter') handleCheckAndConnect();
}}
/>
{connectError && (
<p className="mt-2 text-label-xs text-text-cuation">
{connectError}
</p>
)}
<div className="mt-4 flex justify-end gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => setShowConnectDialog(false)}
>
{t('layout.cancel')}
</Button>
<Button
variant="primary"
size="sm"
onClick={handleCheckAndConnect}
disabled={connectChecking}
>
{connectChecking ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Link2 className="h-4 w-4" />
)}
{t('layout.check-and-connect')}
</Button>
</div>
</div>
</div>
)}
<div className="flex h-auto w-full px-6">
{/* Left Sidebar */}
@ -562,6 +675,14 @@ export default function Browser() {
? t('layout.launching-chrome')
: t('layout.open-my-chrome')}
</Button>
<Button
variant="outline"
size="sm"
onClick={handleConnectExistingBrowser}
>
<Link2 className="h-4 w-4 text-button-tertiery-text-default" />
{t('layout.connect-existing-browser')}
</Button>
</div>
{/* CDP Browser Pool */}

View file

@ -170,7 +170,7 @@ export default function Home() {
const webviewContainer = document.getElementById('webview-container');
if (webviewContainer) {
const rect = webviewContainer.getBoundingClientRect();
window.electronAPI.setSize({
window.electronAPI?.setSize({
x: rect.left,
y: rect.top,
width: rect.width,