diff --git a/backend/browser_manager.py b/backend/browser_manager.py index 327f91b..006ec9e 100644 --- a/backend/browser_manager.py +++ b/backend/browser_manager.py @@ -225,6 +225,7 @@ async def launch(self, profile: dict[str, Any]) -> RunningProfile: human_preset=profile.get("human_preset", "default"), geoip=bool(profile.get("geoip", False)), color_scheme=profile.get("color_scheme") or None, + search_engine=profile.get("search_engine") or None, user_agent=profile.get("user_agent") or None, viewport={ "width": profile.get("screen_width", 1920), diff --git a/backend/database.py b/backend/database.py index e468256..6fd47fc 100644 --- a/backend/database.py +++ b/backend/database.py @@ -52,6 +52,7 @@ def init_db(): clipboard_sync BOOLEAN DEFAULT 1, auto_launch BOOLEAN DEFAULT 0, color_scheme TEXT, + search_engine TEXT, notes TEXT, user_data_dir TEXT NOT NULL, created_at TEXT NOT NULL, @@ -78,6 +79,12 @@ def init_db(): if "auto_launch" not in cols: conn.execute("ALTER TABLE profiles ADD COLUMN auto_launch BOOLEAN DEFAULT 0") conn.commit() + if "color_scheme" not in cols: + conn.execute("ALTER TABLE profiles ADD COLUMN color_scheme TEXT") + conn.commit() + if "search_engine" not in cols: + conn.execute("ALTER TABLE profiles ADD COLUMN search_engine TEXT") + conn.commit() def _now() -> str: @@ -101,9 +108,9 @@ def create_profile( id, name, fingerprint_seed, proxy, timezone, locale, platform, user_agent, screen_width, screen_height, gpu_vendor, gpu_renderer, hardware_concurrency, humanize, human_preset, headless, geoip, - clipboard_sync, auto_launch, color_scheme, launch_args, notes, + clipboard_sync, auto_launch, color_scheme, search_engine, launch_args, notes, user_data_dir, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", ( profile_id, name, seed, fields.get("proxy"), @@ -123,6 +130,7 @@ def create_profile( fields.get("clipboard_sync", True), fields.get("auto_launch", False), fields.get("color_scheme"), + fields.get("search_engine"), json.dumps(fields.get("launch_args") or []), fields.get("notes"), user_data_dir, now, now, @@ -187,7 +195,7 @@ def update_profile(profile_id: str, **fields: Any) -> dict[str, Any] | None: "name", "fingerprint_seed", "proxy", "timezone", "locale", "platform", "user_agent", "screen_width", "screen_height", "gpu_vendor", "gpu_renderer", "hardware_concurrency", "humanize", "human_preset", "headless", "geoip", - "clipboard_sync", "auto_launch", "color_scheme", "launch_args", "notes", + "clipboard_sync", "auto_launch", "color_scheme", "search_engine", "launch_args", "notes", ): if col in fields: update_cols.append(f"{col} = ?") diff --git a/backend/models.py b/backend/models.py index e3ba352..3c3ee2b 100644 --- a/backend/models.py +++ b/backend/models.py @@ -27,6 +27,7 @@ class ProfileCreate(BaseModel): clipboard_sync: bool = True auto_launch: bool = False color_scheme: Literal["light", "dark", "no-preference"] | None = None + search_engine: Literal["google", "bing", "duckduckgo"] | None = None launch_args: list[str] = Field(default_factory=list) notes: str | None = None tags: list[TagCreate] | None = None @@ -52,6 +53,7 @@ class ProfileUpdate(BaseModel): clipboard_sync: bool | None = None auto_launch: bool | None = None color_scheme: Literal["light", "dark", "no-preference"] | None = Field(default=None) + search_engine: Literal["google", "bing", "duckduckgo"] | None = Field(default=None) launch_args: list[str] | None = None notes: str | None = Field(default=None) tags: list[TagCreate] | None = None @@ -94,6 +96,7 @@ def coerce_clipboard_sync(cls, v: object) -> bool: return v if v is not None else True color_scheme: str | None = None + search_engine: str | None = None launch_args: list[str] = [] notes: str | None = None user_data_dir: str diff --git a/backend/tests/test_database.py b/backend/tests/test_database.py index ef292c5..5a07850 100644 --- a/backend/tests/test_database.py +++ b/backend/tests/test_database.py @@ -71,6 +71,7 @@ def test_create_profile_all_fields(tmp_db: Path): headless=True, geoip=True, color_scheme="dark", + search_engine="google", notes="test note", ) assert p["proxy"] == "http://host:8080" @@ -80,7 +81,7 @@ def test_create_profile_all_fields(tmp_db: Path): assert p["humanize"] == 1 # SQLite stores bool as int assert p["human_preset"] == "careful" assert p["color_scheme"] == "dark" - + assert p["search_engine"] == "google" def test_create_profile_with_tags(tmp_db: Path): p = db.create_profile( diff --git a/backend/tests/test_models.py b/backend/tests/test_models.py index 1bccda7..0235b54 100644 --- a/backend/tests/test_models.py +++ b/backend/tests/test_models.py @@ -51,12 +51,14 @@ def test_profile_create_all_fields(): headless=True, geoip=True, color_scheme="dark", + search_engine="google", notes="test note", tags=[TagCreate(tag="work", color="#ff0000")], ) assert p.platform == "macos" assert p.human_preset == "careful" assert p.color_scheme == "dark" + assert p.search_engine == "google" assert len(p.tags) == 1 @@ -90,6 +92,10 @@ def test_profile_create_invalid_color_scheme(): with pytest.raises(ValidationError): ProfileCreate(name="Bad", color_scheme="auto") +def test_profile_create_invalid_search_engine(): + with pytest.raises(ValidationError): + ProfileCreate(name="Bad", search_engine="yahoo") + # ── ProfileUpdate ──────────────────────────────────────────────────────────── diff --git a/frontend/src/components/ProfileForm.tsx b/frontend/src/components/ProfileForm.tsx index 973e9b9..d0c98bc 100644 --- a/frontend/src/components/ProfileForm.tsx +++ b/frontend/src/components/ProfileForm.tsx @@ -98,6 +98,7 @@ export function ProfileForm({ profile, onSave, onDelete, onCancel }: ProfileForm clipboard_sync: profile.clipboard_sync, auto_launch: profile.auto_launch, color_scheme: profile.color_scheme, + search_engine: profile.search_engine, launch_args: profile.launch_args ?? [], notes: profile.notes, tags: profile.tags ?? [], @@ -467,6 +468,19 @@ export function ProfileForm({ profile, onSave, onDelete, onCancel }: ProfileForm +