22Snapshot functionality - calls window.sentience.snapshot() or server-side API
33"""
44
5+ import json
6+ import os
7+ import time
58from typing import Any
69
710import requests
811
912from .browser import SentienceBrowser
10- from .models import Snapshot
13+ from .models import Snapshot, SnapshotOptions
14+
15+
16+ def _save_trace_to_file(raw_elements: list[dict[str, Any]], trace_path: str | None = None) -> None:
17+ """
18+ Save raw_elements to a JSON file for benchmarking/training
19+
20+ Args:
21+ raw_elements: Raw elements data from snapshot
22+ trace_path: Path to save trace file. If None, uses "trace_{timestamp}.json"
23+ """
24+ # Default filename if none provided
25+ filename = trace_path or f"trace_{int(time.time())}.json"
26+
27+ # Ensure directory exists
28+ directory = os.path.dirname(filename)
29+ if directory:
30+ os.makedirs(directory, exist_ok=True)
31+
32+ # Save the raw elements to JSON
33+ with open(filename, "w") as f:
34+ json.dump(raw_elements, f, indent=2)
35+
36+ print(f"[SDK] Trace saved to: {filename}")
1137
1238
1339def snapshot(
@@ -16,6 +42,8 @@ def snapshot(
1642 limit: int | None = None,
1743 filter: dict[str, Any] | None = None,
1844 use_api: bool | None = None,
45+ save_trace: bool = False,
46+ trace_path: str | None = None,
1947) -> Snapshot:
2048 """
2149 Take a snapshot of the current page
@@ -27,26 +55,38 @@ def snapshot(
2755 filter: Filter options (min_area, allowed_roles, min_z_index)
2856 use_api: Force use of server-side API if True, local extension if False.
2957 If None, uses API if api_key is set, otherwise uses local extension.
58+ save_trace: Whether to save raw_elements to JSON for benchmarking/training
59+ trace_path: Path to save trace file. If None, uses "trace_{timestamp}.json"
3060
3161 Returns:
3262 Snapshot object
3363 """
64+ # Build SnapshotOptions from individual parameters
65+ options = SnapshotOptions(
66+ screenshot=screenshot if screenshot is not None else False,
67+ limit=limit if limit is not None else 50,
68+ filter=filter,
69+ use_api=use_api,
70+ save_trace=save_trace,
71+ trace_path=trace_path,
72+ )
73+
3474 # Determine if we should use server-side API
35- should_use_api = use_api if use_api is not None else (browser.api_key is not None)
75+ should_use_api = (
76+ options.use_api if options.use_api is not None else (browser.api_key is not None)
77+ )
3678
3779 if should_use_api and browser.api_key:
3880 # Use server-side API (Pro/Enterprise tier)
39- return _snapshot_via_api(browser, screenshot, limit, filter )
81+ return _snapshot_via_api(browser, options )
4082 else:
4183 # Use local extension (Free tier)
42- return _snapshot_via_extension(browser, screenshot, limit, filter )
84+ return _snapshot_via_extension(browser, options )
4385
4486
4587def _snapshot_via_extension(
4688 browser: SentienceBrowser,
47- screenshot: bool | None,
48- limit: int | None,
49- filter: dict[str, Any] | None,
89+ options: SnapshotOptions,
5090) -> Snapshot:
5191 """Take snapshot using local extension (Free tier)"""
5292 if not browser.page:
@@ -77,14 +117,16 @@ def _snapshot_via_extension(
77117 f"Is the extension loaded? Diagnostics: {diag}"
78118 ) from e
79119
80- # Build options
81- options: dict[str, Any] = {}
82- if screenshot is not None:
83- options["screenshot"] = screenshot
84- if limit is not None:
85- options["limit"] = limit
86- if filter is not None:
87- options["filter"] = filter
120+ # Build options dict for extension API (exclude save_trace/trace_path)
121+ ext_options: dict[str, Any] = {}
122+ if options.screenshot is not False:
123+ ext_options["screenshot"] = options.screenshot
124+ if options.limit != 50:
125+ ext_options["limit"] = options.limit
126+ if options.filter is not None:
127+ ext_options["filter"] = (
128+ options.filter.model_dump() if hasattr(options.filter, "model_dump") else options.filter
129+ )
88130
89131 # Call extension API
90132 result = browser.page.evaluate(
@@ -93,19 +135,21 @@ def _snapshot_via_extension(
93135 return window.sentience.snapshot(options);
94136 }
95137 """,
96- options ,
138+ ext_options ,
97139 )
98140
141+ # Save trace if requested
142+ if options.save_trace:
143+ _save_trace_to_file(result.get("raw_elements", []), options.trace_path)
144+
99145 # Validate and parse with Pydantic
100146 snapshot_obj = Snapshot(**result)
101147 return snapshot_obj
102148
103149
104150def _snapshot_via_api(
105151 browser: SentienceBrowser,
106- screenshot: bool | None,
107- limit: int | None,
108- filter: dict[str, Any] | None,
152+ options: SnapshotOptions,
109153) -> Snapshot:
110154 """Take snapshot using server-side API (Pro/Enterprise tier)"""
111155 if not browser.page:
@@ -128,8 +172,8 @@ def _snapshot_via_api(
128172
129173 # Step 1: Get raw data from local extension (always happens locally)
130174 raw_options: dict[str, Any] = {}
131- if screenshot is not None :
132- raw_options["screenshot"] = screenshot
175+ if options. screenshot is not False :
176+ raw_options["screenshot"] = options. screenshot
133177
134178 raw_result = browser.page.evaluate(
135179 """
@@ -140,6 +184,10 @@ def _snapshot_via_api(
140184 raw_options,
141185 )
142186
187+ # Save trace if requested (save raw data before API processing)
188+ if options.save_trace:
189+ _save_trace_to_file(raw_result.get("raw_elements", []), options.trace_path)
190+
143191 # Step 2: Send to server for smart ranking/filtering
144192 # Use raw_elements (raw data) instead of elements (processed data)
145193 # Server validates API key and applies proprietary ranking logic
@@ -148,8 +196,8 @@ def _snapshot_via_api(
148196 "url": raw_result.get("url", ""),
149197 "viewport": raw_result.get("viewport"),
150198 "options": {
151- "limit": limit,
152- "filter": filter,
199+ "limit": options. limit,
200+ "filter": options. filter.model_dump() if options.filter else None ,
153201 },
154202 }
155203
0 commit comments