sentienceapi 0.90.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sentienceapi might be problematic. Click here for more details.
- sentience/__init__.py +153 -0
- sentience/_extension_loader.py +40 -0
- sentience/actions.py +837 -0
- sentience/agent.py +1246 -0
- sentience/agent_config.py +43 -0
- sentience/async_api.py +101 -0
- sentience/base_agent.py +194 -0
- sentience/browser.py +1037 -0
- sentience/cli.py +130 -0
- sentience/cloud_tracing.py +382 -0
- sentience/conversational_agent.py +509 -0
- sentience/expect.py +188 -0
- sentience/extension/background.js +233 -0
- sentience/extension/content.js +298 -0
- sentience/extension/injected_api.js +1473 -0
- sentience/extension/manifest.json +36 -0
- sentience/extension/pkg/sentience_core.d.ts +51 -0
- sentience/extension/pkg/sentience_core.js +529 -0
- sentience/extension/pkg/sentience_core_bg.wasm +0 -0
- sentience/extension/pkg/sentience_core_bg.wasm.d.ts +10 -0
- sentience/extension/release.json +115 -0
- sentience/extension/test-content.js +4 -0
- sentience/formatting.py +59 -0
- sentience/generator.py +202 -0
- sentience/inspector.py +365 -0
- sentience/llm_provider.py +637 -0
- sentience/models.py +412 -0
- sentience/overlay.py +222 -0
- sentience/query.py +303 -0
- sentience/read.py +185 -0
- sentience/recorder.py +589 -0
- sentience/schemas/trace_v1.json +216 -0
- sentience/screenshot.py +100 -0
- sentience/snapshot.py +516 -0
- sentience/text_search.py +290 -0
- sentience/trace_indexing/__init__.py +27 -0
- sentience/trace_indexing/index_schema.py +111 -0
- sentience/trace_indexing/indexer.py +357 -0
- sentience/tracer_factory.py +211 -0
- sentience/tracing.py +285 -0
- sentience/utils.py +296 -0
- sentience/wait.py +137 -0
- sentienceapi-0.90.17.dist-info/METADATA +917 -0
- sentienceapi-0.90.17.dist-info/RECORD +50 -0
- sentienceapi-0.90.17.dist-info/WHEEL +5 -0
- sentienceapi-0.90.17.dist-info/entry_points.txt +2 -0
- sentienceapi-0.90.17.dist-info/licenses/LICENSE +24 -0
- sentienceapi-0.90.17.dist-info/licenses/LICENSE-APACHE +201 -0
- sentienceapi-0.90.17.dist-info/licenses/LICENSE-MIT +21 -0
- sentienceapi-0.90.17.dist-info/top_level.txt +1 -0
sentience/models.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic models for Sentience SDK - matches spec/snapshot.schema.json
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Literal, Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BBox(BaseModel):
|
|
11
|
+
"""Bounding box coordinates"""
|
|
12
|
+
|
|
13
|
+
x: float
|
|
14
|
+
y: float
|
|
15
|
+
width: float
|
|
16
|
+
height: float
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Viewport(BaseModel):
|
|
20
|
+
"""Viewport dimensions"""
|
|
21
|
+
|
|
22
|
+
width: float
|
|
23
|
+
height: float
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class VisualCues(BaseModel):
|
|
27
|
+
"""Visual analysis cues"""
|
|
28
|
+
|
|
29
|
+
is_primary: bool
|
|
30
|
+
background_color_name: str | None = None
|
|
31
|
+
is_clickable: bool
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Element(BaseModel):
|
|
35
|
+
"""Element from snapshot"""
|
|
36
|
+
|
|
37
|
+
id: int
|
|
38
|
+
role: str
|
|
39
|
+
text: str | None = None
|
|
40
|
+
importance: int
|
|
41
|
+
bbox: BBox
|
|
42
|
+
visual_cues: VisualCues
|
|
43
|
+
in_viewport: bool = True
|
|
44
|
+
is_occluded: bool = False
|
|
45
|
+
z_index: int = 0
|
|
46
|
+
|
|
47
|
+
# ML reranking metadata (optional - can be absent or null)
|
|
48
|
+
rerank_index: int | None = None # 0-based, The rank after ML reranking
|
|
49
|
+
heuristic_index: int | None = None # 0-based, Where it would have been without ML
|
|
50
|
+
ml_probability: float | None = None # Confidence score from ONNX model (0.0 - 1.0)
|
|
51
|
+
ml_score: float | None = None # Raw logit score (optional, for debugging)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Snapshot(BaseModel):
|
|
55
|
+
"""Snapshot response from extension"""
|
|
56
|
+
|
|
57
|
+
status: Literal["success", "error"]
|
|
58
|
+
timestamp: str | None = None
|
|
59
|
+
url: str
|
|
60
|
+
viewport: Viewport | None = None
|
|
61
|
+
elements: list[Element]
|
|
62
|
+
screenshot: str | None = None
|
|
63
|
+
screenshot_format: Literal["png", "jpeg"] | None = None
|
|
64
|
+
error: str | None = None
|
|
65
|
+
requires_license: bool | None = None
|
|
66
|
+
|
|
67
|
+
def save(self, filepath: str) -> None:
|
|
68
|
+
"""Save snapshot as JSON file"""
|
|
69
|
+
import json
|
|
70
|
+
|
|
71
|
+
with open(filepath, "w") as f:
|
|
72
|
+
json.dump(self.model_dump(), f, indent=2)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ActionResult(BaseModel):
|
|
76
|
+
"""Result of an action (click, type, press)"""
|
|
77
|
+
|
|
78
|
+
success: bool
|
|
79
|
+
duration_ms: int
|
|
80
|
+
outcome: Literal["navigated", "dom_updated", "no_change", "error"] | None = None
|
|
81
|
+
url_changed: bool | None = None
|
|
82
|
+
snapshot_after: Snapshot | None = None
|
|
83
|
+
error: dict | None = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class WaitResult(BaseModel):
|
|
87
|
+
"""Result of wait_for operation"""
|
|
88
|
+
|
|
89
|
+
found: bool
|
|
90
|
+
element: Element | None = None
|
|
91
|
+
duration_ms: int
|
|
92
|
+
timeout: bool
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ========== Agent Layer Models ==========
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ScreenshotConfig(BaseModel):
|
|
99
|
+
"""Screenshot format configuration"""
|
|
100
|
+
|
|
101
|
+
format: Literal["png", "jpeg"] = "png"
|
|
102
|
+
quality: int | None = Field(None, ge=1, le=100) # Only for JPEG (1-100)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class SnapshotFilter(BaseModel):
|
|
106
|
+
"""Filter options for snapshot elements"""
|
|
107
|
+
|
|
108
|
+
min_area: int | None = Field(None, ge=0)
|
|
109
|
+
allowed_roles: list[str] | None = None
|
|
110
|
+
min_z_index: int | None = None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SnapshotOptions(BaseModel):
|
|
114
|
+
"""
|
|
115
|
+
Configuration for snapshot calls.
|
|
116
|
+
Matches TypeScript SnapshotOptions interface from sdk-ts/src/snapshot.ts
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
screenshot: bool | ScreenshotConfig = False # Union type: boolean or config
|
|
120
|
+
limit: int = Field(50, ge=1, le=500)
|
|
121
|
+
filter: SnapshotFilter | None = None
|
|
122
|
+
use_api: bool | None = None # Force API vs extension
|
|
123
|
+
save_trace: bool = False # Save raw_elements to JSON for benchmarking/training
|
|
124
|
+
trace_path: str | None = None # Path to save trace (default: "trace_{timestamp}.json")
|
|
125
|
+
goal: str | None = None # Optional goal/task description for the snapshot
|
|
126
|
+
show_overlay: bool = False # Show visual overlay highlighting elements in browser
|
|
127
|
+
|
|
128
|
+
class Config:
|
|
129
|
+
arbitrary_types_allowed = True
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class AgentActionResult(BaseModel):
|
|
133
|
+
"""Result of a single agent action (from agent.act())"""
|
|
134
|
+
|
|
135
|
+
success: bool
|
|
136
|
+
action: Literal["click", "type", "press", "finish", "error"]
|
|
137
|
+
goal: str
|
|
138
|
+
duration_ms: int
|
|
139
|
+
attempt: int
|
|
140
|
+
|
|
141
|
+
# Optional fields based on action type
|
|
142
|
+
element_id: int | None = None
|
|
143
|
+
text: str | None = None
|
|
144
|
+
key: str | None = None
|
|
145
|
+
outcome: Literal["navigated", "dom_updated", "no_change", "error"] | None = None
|
|
146
|
+
url_changed: bool | None = None
|
|
147
|
+
error: str | None = None
|
|
148
|
+
message: str | None = None # For FINISH action
|
|
149
|
+
|
|
150
|
+
def __getitem__(self, key):
|
|
151
|
+
"""
|
|
152
|
+
Support dict-style access for backward compatibility.
|
|
153
|
+
This allows existing code using result["success"] to continue working.
|
|
154
|
+
"""
|
|
155
|
+
import warnings
|
|
156
|
+
|
|
157
|
+
warnings.warn(
|
|
158
|
+
f"Dict-style access result['{key}'] is deprecated. Use result.{key} instead.",
|
|
159
|
+
DeprecationWarning,
|
|
160
|
+
stacklevel=2,
|
|
161
|
+
)
|
|
162
|
+
return getattr(self, key)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ActionTokenUsage(BaseModel):
|
|
166
|
+
"""Token usage for a single action"""
|
|
167
|
+
|
|
168
|
+
goal: str
|
|
169
|
+
prompt_tokens: int
|
|
170
|
+
completion_tokens: int
|
|
171
|
+
total_tokens: int
|
|
172
|
+
model: str
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class TokenStats(BaseModel):
|
|
176
|
+
"""Token usage statistics for an agent session"""
|
|
177
|
+
|
|
178
|
+
total_prompt_tokens: int
|
|
179
|
+
total_completion_tokens: int
|
|
180
|
+
total_tokens: int
|
|
181
|
+
by_action: list[ActionTokenUsage]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class ActionHistory(BaseModel):
|
|
185
|
+
"""Single history entry from agent execution"""
|
|
186
|
+
|
|
187
|
+
goal: str
|
|
188
|
+
action: str # The raw action string from LLM
|
|
189
|
+
result: dict # Will be AgentActionResult but stored as dict for flexibility
|
|
190
|
+
success: bool
|
|
191
|
+
attempt: int
|
|
192
|
+
duration_ms: int
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class ProxyConfig(BaseModel):
|
|
196
|
+
"""
|
|
197
|
+
Proxy configuration for browser networking.
|
|
198
|
+
|
|
199
|
+
Supports HTTP, HTTPS, and SOCKS5 proxies with optional authentication.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
server: str = Field(
|
|
203
|
+
...,
|
|
204
|
+
description="Proxy server URL including scheme and port (e.g., 'http://proxy.example.com:8080')",
|
|
205
|
+
)
|
|
206
|
+
username: str | None = Field(
|
|
207
|
+
None,
|
|
208
|
+
description="Username for proxy authentication (optional)",
|
|
209
|
+
)
|
|
210
|
+
password: str | None = Field(
|
|
211
|
+
None,
|
|
212
|
+
description="Password for proxy authentication (optional)",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def to_playwright_dict(self) -> dict:
|
|
216
|
+
"""
|
|
217
|
+
Convert to Playwright proxy configuration format.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Dict compatible with Playwright's proxy parameter
|
|
221
|
+
"""
|
|
222
|
+
config = {"server": self.server}
|
|
223
|
+
if self.username and self.password:
|
|
224
|
+
config["username"] = self.username
|
|
225
|
+
config["password"] = self.password
|
|
226
|
+
return config
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# ========== Storage State Models (Auth Injection) ==========
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class Cookie(BaseModel):
|
|
233
|
+
"""
|
|
234
|
+
Cookie definition for storage state injection.
|
|
235
|
+
|
|
236
|
+
Matches Playwright's cookie format for storage_state.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
name: str = Field(..., description="Cookie name")
|
|
240
|
+
value: str = Field(..., description="Cookie value")
|
|
241
|
+
domain: str = Field(..., description="Cookie domain (e.g., '.example.com')")
|
|
242
|
+
path: str = Field(default="/", description="Cookie path")
|
|
243
|
+
expires: float | None = Field(None, description="Expiration timestamp (Unix epoch)")
|
|
244
|
+
httpOnly: bool = Field(default=False, description="HTTP-only flag")
|
|
245
|
+
secure: bool = Field(default=False, description="Secure (HTTPS-only) flag")
|
|
246
|
+
sameSite: Literal["Strict", "Lax", "None"] = Field(
|
|
247
|
+
default="Lax", description="SameSite attribute"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class LocalStorageItem(BaseModel):
|
|
252
|
+
"""
|
|
253
|
+
LocalStorage item for a specific origin.
|
|
254
|
+
|
|
255
|
+
Playwright stores localStorage as an array of {name, value} objects.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
name: str = Field(..., description="LocalStorage key")
|
|
259
|
+
value: str = Field(..., description="LocalStorage value")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class OriginStorage(BaseModel):
|
|
263
|
+
"""
|
|
264
|
+
Storage state for a specific origin (localStorage).
|
|
265
|
+
|
|
266
|
+
Represents localStorage data for a single domain.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
origin: str = Field(..., description="Origin URL (e.g., 'https://example.com')")
|
|
270
|
+
localStorage: list[LocalStorageItem] = Field(
|
|
271
|
+
default_factory=list, description="LocalStorage items for this origin"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class StorageState(BaseModel):
|
|
276
|
+
"""
|
|
277
|
+
Complete browser storage state (cookies + localStorage).
|
|
278
|
+
|
|
279
|
+
This is the format used by Playwright's storage_state() method.
|
|
280
|
+
Can be saved to/loaded from JSON files for session injection.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
cookies: list[Cookie] = Field(
|
|
284
|
+
default_factory=list, description="Cookies to inject (global scope)"
|
|
285
|
+
)
|
|
286
|
+
origins: list[OriginStorage] = Field(
|
|
287
|
+
default_factory=list, description="LocalStorage data per origin"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
@classmethod
|
|
291
|
+
def from_dict(cls, data: dict) -> "StorageState":
|
|
292
|
+
"""
|
|
293
|
+
Create StorageState from dictionary (e.g., loaded from JSON).
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
data: Dictionary with 'cookies' and/or 'origins' keys
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
StorageState instance
|
|
300
|
+
"""
|
|
301
|
+
cookies = [
|
|
302
|
+
Cookie(**cookie) if isinstance(cookie, dict) else cookie
|
|
303
|
+
for cookie in data.get("cookies", [])
|
|
304
|
+
]
|
|
305
|
+
origins = []
|
|
306
|
+
for origin_data in data.get("origins", []):
|
|
307
|
+
if isinstance(origin_data, dict):
|
|
308
|
+
# Handle localStorage as array of {name, value} or as dict
|
|
309
|
+
localStorage_data = origin_data.get("localStorage", [])
|
|
310
|
+
if isinstance(localStorage_data, dict):
|
|
311
|
+
# Convert dict to list of LocalStorageItem
|
|
312
|
+
localStorage_items = [
|
|
313
|
+
LocalStorageItem(name=k, value=v) for k, v in localStorage_data.items()
|
|
314
|
+
]
|
|
315
|
+
else:
|
|
316
|
+
# Already a list
|
|
317
|
+
localStorage_items = [
|
|
318
|
+
LocalStorageItem(**item) if isinstance(item, dict) else item
|
|
319
|
+
for item in localStorage_data
|
|
320
|
+
]
|
|
321
|
+
origins.append(
|
|
322
|
+
OriginStorage(
|
|
323
|
+
origin=origin_data.get("origin", ""),
|
|
324
|
+
localStorage=localStorage_items,
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
else:
|
|
328
|
+
origins.append(origin_data)
|
|
329
|
+
return cls(cookies=cookies, origins=origins)
|
|
330
|
+
|
|
331
|
+
def to_playwright_dict(self) -> dict:
|
|
332
|
+
"""
|
|
333
|
+
Convert to Playwright-compatible dictionary format.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Dictionary compatible with Playwright's storage_state parameter
|
|
337
|
+
"""
|
|
338
|
+
return {
|
|
339
|
+
"cookies": [cookie.model_dump() for cookie in self.cookies],
|
|
340
|
+
"origins": [
|
|
341
|
+
{
|
|
342
|
+
"origin": origin.origin,
|
|
343
|
+
"localStorage": [item.model_dump() for item in origin.localStorage],
|
|
344
|
+
}
|
|
345
|
+
for origin in self.origins
|
|
346
|
+
],
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# ========== Text Search Models (findTextRect) ==========
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class TextRect(BaseModel):
|
|
354
|
+
"""
|
|
355
|
+
Rectangle coordinates for text occurrence.
|
|
356
|
+
Includes both absolute (page) and viewport-relative coordinates.
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
x: float = Field(..., description="Absolute X coordinate (page coordinate with scroll offset)")
|
|
360
|
+
y: float = Field(..., description="Absolute Y coordinate (page coordinate with scroll offset)")
|
|
361
|
+
width: float = Field(..., description="Rectangle width in pixels")
|
|
362
|
+
height: float = Field(..., description="Rectangle height in pixels")
|
|
363
|
+
left: float = Field(..., description="Absolute left position (same as x)")
|
|
364
|
+
top: float = Field(..., description="Absolute top position (same as y)")
|
|
365
|
+
right: float = Field(..., description="Absolute right position (x + width)")
|
|
366
|
+
bottom: float = Field(..., description="Absolute bottom position (y + height)")
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
class ViewportRect(BaseModel):
|
|
370
|
+
"""Viewport-relative rectangle coordinates (without scroll offset)"""
|
|
371
|
+
|
|
372
|
+
x: float = Field(..., description="Viewport-relative X coordinate")
|
|
373
|
+
y: float = Field(..., description="Viewport-relative Y coordinate")
|
|
374
|
+
width: float = Field(..., description="Rectangle width in pixels")
|
|
375
|
+
height: float = Field(..., description="Rectangle height in pixels")
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class TextContext(BaseModel):
|
|
379
|
+
"""Context text surrounding a match"""
|
|
380
|
+
|
|
381
|
+
before: str = Field(..., description="Text before the match (up to 20 chars)")
|
|
382
|
+
after: str = Field(..., description="Text after the match (up to 20 chars)")
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class TextMatch(BaseModel):
|
|
386
|
+
"""A single text match with its rectangle and context"""
|
|
387
|
+
|
|
388
|
+
text: str = Field(..., description="The matched text")
|
|
389
|
+
rect: TextRect = Field(..., description="Absolute rectangle coordinates (with scroll offset)")
|
|
390
|
+
viewport_rect: ViewportRect = Field(
|
|
391
|
+
..., description="Viewport-relative rectangle (without scroll offset)"
|
|
392
|
+
)
|
|
393
|
+
context: TextContext = Field(..., description="Surrounding text context")
|
|
394
|
+
in_viewport: bool = Field(..., description="Whether the match is currently visible in viewport")
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class TextRectSearchResult(BaseModel):
|
|
398
|
+
"""
|
|
399
|
+
Result of findTextRect operation.
|
|
400
|
+
Returns all occurrences of text on the page with their exact pixel coordinates.
|
|
401
|
+
"""
|
|
402
|
+
|
|
403
|
+
status: Literal["success", "error"]
|
|
404
|
+
query: str | None = Field(None, description="The search text that was queried")
|
|
405
|
+
case_sensitive: bool | None = Field(None, description="Whether search was case-sensitive")
|
|
406
|
+
whole_word: bool | None = Field(None, description="Whether whole-word matching was used")
|
|
407
|
+
matches: int | None = Field(None, description="Number of matches found")
|
|
408
|
+
results: list[TextMatch] | None = Field(
|
|
409
|
+
None, description="List of text matches with coordinates"
|
|
410
|
+
)
|
|
411
|
+
viewport: Viewport | None = Field(None, description="Current viewport dimensions")
|
|
412
|
+
error: str | None = Field(None, description="Error message if status is 'error'")
|
sentience/overlay.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Visual overlay utilities - show/clear element highlights in browser
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .browser import AsyncSentienceBrowser, SentienceBrowser
|
|
8
|
+
from .models import Element, Snapshot
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def show_overlay(
|
|
12
|
+
browser: SentienceBrowser,
|
|
13
|
+
elements: list[Element] | list[dict[str, Any]] | Snapshot,
|
|
14
|
+
target_element_id: int | None = None,
|
|
15
|
+
) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Display visual overlay highlighting elements in the browser
|
|
18
|
+
|
|
19
|
+
This function shows a Shadow DOM overlay with color-coded borders around
|
|
20
|
+
detected elements. Useful for debugging, learning, and validating element detection.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
browser: SentienceBrowser instance
|
|
24
|
+
elements: Can be:
|
|
25
|
+
- List of Element objects (from snapshot.elements)
|
|
26
|
+
- List of raw element dicts (from snapshot result or API response)
|
|
27
|
+
- Snapshot object (will use snapshot.elements)
|
|
28
|
+
target_element_id: Optional ID of element to highlight in red (default: None)
|
|
29
|
+
|
|
30
|
+
Color Coding:
|
|
31
|
+
- Red: Target element (when target_element_id is specified)
|
|
32
|
+
- Blue: Primary elements (is_primary=true)
|
|
33
|
+
- Green: Regular interactive elements
|
|
34
|
+
|
|
35
|
+
Visual Indicators:
|
|
36
|
+
- Border thickness and opacity scale with importance score
|
|
37
|
+
- Semi-transparent fill for better visibility
|
|
38
|
+
- Importance badges showing scores
|
|
39
|
+
- Star icon for primary elements
|
|
40
|
+
- Target emoji for the target element
|
|
41
|
+
|
|
42
|
+
Auto-clear: Overlay automatically disappears after 5 seconds
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
# Show overlay from snapshot
|
|
46
|
+
snap = snapshot(browser)
|
|
47
|
+
show_overlay(browser, snap)
|
|
48
|
+
|
|
49
|
+
# Show overlay with custom elements
|
|
50
|
+
elements = [{"id": 1, "bbox": {"x": 100, "y": 100, "width": 200, "height": 50}, ...}]
|
|
51
|
+
show_overlay(browser, elements)
|
|
52
|
+
|
|
53
|
+
# Show overlay with target element highlighted in red
|
|
54
|
+
show_overlay(browser, snap, target_element_id=42)
|
|
55
|
+
|
|
56
|
+
# Clear overlay manually before 5 seconds
|
|
57
|
+
clear_overlay(browser)
|
|
58
|
+
"""
|
|
59
|
+
if not browser.page:
|
|
60
|
+
raise RuntimeError("Browser not started. Call browser.start() first.")
|
|
61
|
+
|
|
62
|
+
# Handle different input types
|
|
63
|
+
if isinstance(elements, Snapshot):
|
|
64
|
+
# Extract elements from Snapshot object
|
|
65
|
+
elements_list = [el.model_dump() for el in elements.elements]
|
|
66
|
+
elif isinstance(elements, list) and len(elements) > 0:
|
|
67
|
+
# Check if it's a list of Element objects or dicts
|
|
68
|
+
if hasattr(elements[0], "model_dump"):
|
|
69
|
+
# List of Element objects
|
|
70
|
+
elements_list = [el.model_dump() for el in elements]
|
|
71
|
+
else:
|
|
72
|
+
# Already a list of dicts
|
|
73
|
+
elements_list = elements
|
|
74
|
+
else:
|
|
75
|
+
raise ValueError("elements must be a Snapshot, list of Element objects, or list of dicts")
|
|
76
|
+
|
|
77
|
+
# Call extension API
|
|
78
|
+
browser.page.evaluate(
|
|
79
|
+
"""
|
|
80
|
+
(args) => {
|
|
81
|
+
if (window.sentience && window.sentience.showOverlay) {
|
|
82
|
+
window.sentience.showOverlay(args.elements, args.targetId);
|
|
83
|
+
} else {
|
|
84
|
+
console.warn('[Sentience SDK] showOverlay not available - is extension loaded?');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
""",
|
|
88
|
+
{"elements": elements_list, "targetId": target_element_id},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def clear_overlay(browser: SentienceBrowser) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Clear the visual overlay manually (before 5-second auto-clear)
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
browser: SentienceBrowser instance
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
show_overlay(browser, snap)
|
|
101
|
+
# ... inspect overlay ...
|
|
102
|
+
clear_overlay(browser) # Remove immediately
|
|
103
|
+
"""
|
|
104
|
+
if not browser.page:
|
|
105
|
+
raise RuntimeError("Browser not started. Call browser.start() first.")
|
|
106
|
+
|
|
107
|
+
browser.page.evaluate(
|
|
108
|
+
"""
|
|
109
|
+
() => {
|
|
110
|
+
if (window.sentience && window.sentience.clearOverlay) {
|
|
111
|
+
window.sentience.clearOverlay();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
"""
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def show_overlay_async(
|
|
119
|
+
browser: AsyncSentienceBrowser,
|
|
120
|
+
elements: list[Element] | list[dict[str, Any]] | Snapshot,
|
|
121
|
+
target_element_id: int | None = None,
|
|
122
|
+
) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Display visual overlay highlighting elements in the browser (async)
|
|
125
|
+
|
|
126
|
+
This function shows a Shadow DOM overlay with color-coded borders around
|
|
127
|
+
detected elements. Useful for debugging, learning, and validating element detection.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
browser: AsyncSentienceBrowser instance
|
|
131
|
+
elements: Can be:
|
|
132
|
+
- List of Element objects (from snapshot.elements)
|
|
133
|
+
- List of raw element dicts (from snapshot result or API response)
|
|
134
|
+
- Snapshot object (will use snapshot.elements)
|
|
135
|
+
target_element_id: Optional ID of element to highlight in red (default: None)
|
|
136
|
+
|
|
137
|
+
Color Coding:
|
|
138
|
+
- Red: Target element (when target_element_id is specified)
|
|
139
|
+
- Blue: Primary elements (is_primary=true)
|
|
140
|
+
- Green: Regular interactive elements
|
|
141
|
+
|
|
142
|
+
Visual Indicators:
|
|
143
|
+
- Border thickness and opacity scale with importance score
|
|
144
|
+
- Semi-transparent fill for better visibility
|
|
145
|
+
- Importance badges showing scores
|
|
146
|
+
- Star icon for primary elements
|
|
147
|
+
- Target emoji for the target element
|
|
148
|
+
|
|
149
|
+
Auto-clear: Overlay automatically disappears after 5 seconds
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
# Show overlay from snapshot
|
|
153
|
+
snap = await snapshot_async(browser)
|
|
154
|
+
await show_overlay_async(browser, snap)
|
|
155
|
+
|
|
156
|
+
# Show overlay with custom elements
|
|
157
|
+
elements = [{"id": 1, "bbox": {"x": 100, "y": 100, "width": 200, "height": 50}, ...}]
|
|
158
|
+
await show_overlay_async(browser, elements)
|
|
159
|
+
|
|
160
|
+
# Show overlay with target element highlighted in red
|
|
161
|
+
await show_overlay_async(browser, snap, target_element_id=42)
|
|
162
|
+
|
|
163
|
+
# Clear overlay manually before 5 seconds
|
|
164
|
+
await clear_overlay_async(browser)
|
|
165
|
+
"""
|
|
166
|
+
if not browser.page:
|
|
167
|
+
raise RuntimeError("Browser not started. Call await browser.start() first.")
|
|
168
|
+
|
|
169
|
+
# Handle different input types
|
|
170
|
+
if isinstance(elements, Snapshot):
|
|
171
|
+
# Extract elements from Snapshot object
|
|
172
|
+
elements_list = [el.model_dump() for el in elements.elements]
|
|
173
|
+
elif isinstance(elements, list) and len(elements) > 0:
|
|
174
|
+
# Check if it's a list of Element objects or dicts
|
|
175
|
+
if hasattr(elements[0], "model_dump"):
|
|
176
|
+
# List of Element objects
|
|
177
|
+
elements_list = [el.model_dump() for el in elements]
|
|
178
|
+
else:
|
|
179
|
+
# Already a list of dicts
|
|
180
|
+
elements_list = elements
|
|
181
|
+
else:
|
|
182
|
+
raise ValueError("elements must be a Snapshot, list of Element objects, or list of dicts")
|
|
183
|
+
|
|
184
|
+
# Call extension API
|
|
185
|
+
await browser.page.evaluate(
|
|
186
|
+
"""
|
|
187
|
+
(args) => {
|
|
188
|
+
if (window.sentience && window.sentience.showOverlay) {
|
|
189
|
+
window.sentience.showOverlay(args.elements, args.targetId);
|
|
190
|
+
} else {
|
|
191
|
+
console.warn('[Sentience SDK] showOverlay not available - is extension loaded?');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
""",
|
|
195
|
+
{"elements": elements_list, "targetId": target_element_id},
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
async def clear_overlay_async(browser: AsyncSentienceBrowser) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Clear the visual overlay manually (before 5-second auto-clear) (async)
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
browser: AsyncSentienceBrowser instance
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
await show_overlay_async(browser, snap)
|
|
208
|
+
# ... inspect overlay ...
|
|
209
|
+
await clear_overlay_async(browser) # Remove immediately
|
|
210
|
+
"""
|
|
211
|
+
if not browser.page:
|
|
212
|
+
raise RuntimeError("Browser not started. Call await browser.start() first.")
|
|
213
|
+
|
|
214
|
+
await browser.page.evaluate(
|
|
215
|
+
"""
|
|
216
|
+
() => {
|
|
217
|
+
if (window.sentience && window.sentience.clearOverlay) {
|
|
218
|
+
window.sentience.clearOverlay();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
"""
|
|
222
|
+
)
|