mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-26 07:25:58 +00:00
fe style change
This commit is contained in:
parent
4df123f246
commit
cb9810aa04
10 changed files with 167 additions and 82 deletions
|
|
@ -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=[
|
||||
|
|
|
|||
|
|
@ -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}",
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const Update = () => {
|
|||
const { t } = useTranslation();
|
||||
|
||||
const checkUpdate = () => {
|
||||
window.ipcRenderer.invoke('check-update');
|
||||
window.ipcRenderer?.invoke('check-update');
|
||||
};
|
||||
|
||||
const onUpdateCanAvailable = useCallback(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue