sentienceapi 0.95.0__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 +253 -0
- sentience/_extension_loader.py +195 -0
- sentience/action_executor.py +215 -0
- sentience/actions.py +1020 -0
- sentience/agent.py +1181 -0
- sentience/agent_config.py +46 -0
- sentience/agent_runtime.py +424 -0
- sentience/asserts/__init__.py +70 -0
- sentience/asserts/expect.py +621 -0
- sentience/asserts/query.py +383 -0
- sentience/async_api.py +108 -0
- sentience/backends/__init__.py +137 -0
- sentience/backends/actions.py +343 -0
- sentience/backends/browser_use_adapter.py +241 -0
- sentience/backends/cdp_backend.py +393 -0
- sentience/backends/exceptions.py +211 -0
- sentience/backends/playwright_backend.py +194 -0
- sentience/backends/protocol.py +216 -0
- sentience/backends/sentience_context.py +469 -0
- sentience/backends/snapshot.py +427 -0
- sentience/base_agent.py +196 -0
- sentience/browser.py +1215 -0
- sentience/browser_evaluator.py +299 -0
- sentience/canonicalization.py +207 -0
- sentience/cli.py +130 -0
- sentience/cloud_tracing.py +807 -0
- sentience/constants.py +6 -0
- sentience/conversational_agent.py +543 -0
- sentience/element_filter.py +136 -0
- sentience/expect.py +188 -0
- sentience/extension/background.js +104 -0
- sentience/extension/content.js +161 -0
- sentience/extension/injected_api.js +914 -0
- sentience/extension/manifest.json +36 -0
- sentience/extension/pkg/sentience_core.d.ts +51 -0
- sentience/extension/pkg/sentience_core.js +323 -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/formatting.py +15 -0
- sentience/generator.py +202 -0
- sentience/inspector.py +367 -0
- sentience/llm_interaction_handler.py +191 -0
- sentience/llm_provider.py +875 -0
- sentience/llm_provider_utils.py +120 -0
- sentience/llm_response_builder.py +153 -0
- sentience/models.py +846 -0
- sentience/ordinal.py +280 -0
- sentience/overlay.py +222 -0
- sentience/protocols.py +228 -0
- sentience/query.py +303 -0
- sentience/read.py +188 -0
- sentience/recorder.py +589 -0
- sentience/schemas/trace_v1.json +335 -0
- sentience/screenshot.py +100 -0
- sentience/sentience_methods.py +86 -0
- sentience/snapshot.py +706 -0
- sentience/snapshot_diff.py +126 -0
- sentience/text_search.py +262 -0
- sentience/trace_event_builder.py +148 -0
- sentience/trace_file_manager.py +197 -0
- sentience/trace_indexing/__init__.py +27 -0
- sentience/trace_indexing/index_schema.py +199 -0
- sentience/trace_indexing/indexer.py +414 -0
- sentience/tracer_factory.py +322 -0
- sentience/tracing.py +449 -0
- sentience/utils/__init__.py +40 -0
- sentience/utils/browser.py +46 -0
- sentience/utils/element.py +257 -0
- sentience/utils/formatting.py +59 -0
- sentience/utils.py +296 -0
- sentience/verification.py +380 -0
- sentience/visual_agent.py +2058 -0
- sentience/wait.py +139 -0
- sentienceapi-0.95.0.dist-info/METADATA +984 -0
- sentienceapi-0.95.0.dist-info/RECORD +82 -0
- sentienceapi-0.95.0.dist-info/WHEEL +5 -0
- sentienceapi-0.95.0.dist-info/entry_points.txt +2 -0
- sentienceapi-0.95.0.dist-info/licenses/LICENSE +24 -0
- sentienceapi-0.95.0.dist-info/licenses/LICENSE-APACHE +201 -0
- sentienceapi-0.95.0.dist-info/licenses/LICENSE-MIT +21 -0
- sentienceapi-0.95.0.dist-info/top_level.txt +1 -0
sentience/expect.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Expect/Assert functionality
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from .browser import AsyncSentienceBrowser, SentienceBrowser
|
|
9
|
+
from .models import Element
|
|
10
|
+
from .query import query
|
|
11
|
+
from .wait import wait_for, wait_for_async
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Expectation:
|
|
15
|
+
"""Assertion helper for element expectations"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, browser: SentienceBrowser, selector: str | dict):
|
|
18
|
+
self.browser = browser
|
|
19
|
+
self.selector = selector
|
|
20
|
+
|
|
21
|
+
def to_be_visible(self, timeout: float = 10.0) -> Element:
|
|
22
|
+
"""Assert element is visible (exists and in viewport)"""
|
|
23
|
+
result = wait_for(self.browser, self.selector, timeout=timeout)
|
|
24
|
+
|
|
25
|
+
if not result.found:
|
|
26
|
+
raise AssertionError(f"Element not found: {self.selector} (timeout: {timeout}s)")
|
|
27
|
+
|
|
28
|
+
element = result.element
|
|
29
|
+
if not element.in_viewport:
|
|
30
|
+
raise AssertionError(f"Element found but not visible in viewport: {self.selector}")
|
|
31
|
+
|
|
32
|
+
return element
|
|
33
|
+
|
|
34
|
+
def to_exist(self, timeout: float = 10.0) -> Element:
|
|
35
|
+
"""Assert element exists"""
|
|
36
|
+
result = wait_for(self.browser, self.selector, timeout=timeout)
|
|
37
|
+
|
|
38
|
+
if not result.found:
|
|
39
|
+
raise AssertionError(f"Element does not exist: {self.selector} (timeout: {timeout}s)")
|
|
40
|
+
|
|
41
|
+
return result.element
|
|
42
|
+
|
|
43
|
+
def to_have_text(self, expected_text: str, timeout: float = 10.0) -> Element:
|
|
44
|
+
"""Assert element has specific text"""
|
|
45
|
+
result = wait_for(self.browser, self.selector, timeout=timeout)
|
|
46
|
+
|
|
47
|
+
if not result.found:
|
|
48
|
+
raise AssertionError(f"Element not found: {self.selector} (timeout: {timeout}s)")
|
|
49
|
+
|
|
50
|
+
element = result.element
|
|
51
|
+
if not element.text or expected_text not in element.text:
|
|
52
|
+
raise AssertionError(
|
|
53
|
+
f"Element text mismatch. Expected '{expected_text}', got '{element.text}'"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return element
|
|
57
|
+
|
|
58
|
+
def to_have_count(self, expected_count: int, timeout: float = 10.0) -> None:
|
|
59
|
+
"""Assert selector matches exactly N elements"""
|
|
60
|
+
from .snapshot import snapshot
|
|
61
|
+
|
|
62
|
+
start_time = time.time()
|
|
63
|
+
while time.time() - start_time < timeout:
|
|
64
|
+
snap = snapshot(self.browser)
|
|
65
|
+
matches = query(snap, self.selector)
|
|
66
|
+
|
|
67
|
+
if len(matches) == expected_count:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
time.sleep(0.25)
|
|
71
|
+
|
|
72
|
+
# Final check
|
|
73
|
+
snap = snapshot(self.browser)
|
|
74
|
+
matches = query(snap, self.selector)
|
|
75
|
+
actual_count = len(matches)
|
|
76
|
+
|
|
77
|
+
raise AssertionError(
|
|
78
|
+
f"Element count mismatch. Expected {expected_count}, got {actual_count}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def expect(browser: SentienceBrowser, selector: str | dict) -> Expectation:
|
|
83
|
+
"""
|
|
84
|
+
Create expectation helper for assertions
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
browser: SentienceBrowser instance
|
|
88
|
+
selector: String DSL or dict query
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Expectation helper
|
|
92
|
+
"""
|
|
93
|
+
return Expectation(browser, selector)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ExpectationAsync:
|
|
97
|
+
"""Assertion helper for element expectations (async)"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, browser: AsyncSentienceBrowser, selector: str | dict):
|
|
100
|
+
self.browser = browser
|
|
101
|
+
self.selector = selector
|
|
102
|
+
|
|
103
|
+
async def to_be_visible(self, timeout: float = 10.0) -> Element:
|
|
104
|
+
"""Assert element is visible (exists and in viewport)"""
|
|
105
|
+
result = await wait_for_async(self.browser, self.selector, timeout=timeout)
|
|
106
|
+
|
|
107
|
+
if not result.found:
|
|
108
|
+
raise AssertionError(f"Element not found: {self.selector} (timeout: {timeout}s)")
|
|
109
|
+
|
|
110
|
+
element = result.element
|
|
111
|
+
if not element.in_viewport:
|
|
112
|
+
raise AssertionError(f"Element found but not visible in viewport: {self.selector}")
|
|
113
|
+
|
|
114
|
+
return element
|
|
115
|
+
|
|
116
|
+
async def to_exist(self, timeout: float = 10.0) -> Element:
|
|
117
|
+
"""Assert element exists"""
|
|
118
|
+
result = await wait_for_async(self.browser, self.selector, timeout=timeout)
|
|
119
|
+
|
|
120
|
+
if not result.found:
|
|
121
|
+
raise AssertionError(f"Element does not exist: {self.selector} (timeout: {timeout}s)")
|
|
122
|
+
|
|
123
|
+
return result.element
|
|
124
|
+
|
|
125
|
+
async def to_have_text(self, expected_text: str, timeout: float = 10.0) -> Element:
|
|
126
|
+
"""Assert element has specific text"""
|
|
127
|
+
result = await wait_for_async(self.browser, self.selector, timeout=timeout)
|
|
128
|
+
|
|
129
|
+
if not result.found:
|
|
130
|
+
raise AssertionError(f"Element not found: {self.selector} (timeout: {timeout}s)")
|
|
131
|
+
|
|
132
|
+
element = result.element
|
|
133
|
+
if not element.text or expected_text not in element.text:
|
|
134
|
+
raise AssertionError(
|
|
135
|
+
f"Element text mismatch. Expected '{expected_text}', got '{element.text}'"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return element
|
|
139
|
+
|
|
140
|
+
async def to_have_count(self, expected_count: int, timeout: float = 10.0) -> None:
|
|
141
|
+
"""Assert selector matches exactly N elements"""
|
|
142
|
+
from .snapshot import snapshot_async
|
|
143
|
+
|
|
144
|
+
start_time = time.time()
|
|
145
|
+
while time.time() - start_time < timeout:
|
|
146
|
+
snap = await snapshot_async(self.browser)
|
|
147
|
+
matches = query(snap, self.selector)
|
|
148
|
+
|
|
149
|
+
if len(matches) == expected_count:
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
await asyncio.sleep(0.25)
|
|
153
|
+
|
|
154
|
+
# Final check
|
|
155
|
+
snap = await snapshot_async(self.browser)
|
|
156
|
+
matches = query(snap, self.selector)
|
|
157
|
+
actual_count = len(matches)
|
|
158
|
+
|
|
159
|
+
raise AssertionError(
|
|
160
|
+
f"Element count mismatch. Expected {expected_count}, got {actual_count}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def expect_async(browser: AsyncSentienceBrowser, selector: str | dict) -> ExpectationAsync:
|
|
165
|
+
"""
|
|
166
|
+
Create expectation helper for assertions (async)
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
browser: AsyncSentienceBrowser instance
|
|
170
|
+
selector: String DSL or dict query
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
ExpectationAsync helper
|
|
174
|
+
|
|
175
|
+
Example:
|
|
176
|
+
# Assert element is visible
|
|
177
|
+
element = await expect_async(browser, "role=button").to_be_visible()
|
|
178
|
+
|
|
179
|
+
# Assert element has text
|
|
180
|
+
element = await expect_async(browser, "h1").to_have_text("Welcome")
|
|
181
|
+
|
|
182
|
+
# Assert element exists
|
|
183
|
+
element = await expect_async(browser, "role=link").to_exist()
|
|
184
|
+
|
|
185
|
+
# Assert count
|
|
186
|
+
await expect_async(browser, "role=button").to_have_count(5)
|
|
187
|
+
"""
|
|
188
|
+
return ExpectationAsync(browser, selector)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import init, { analyze_page_with_options, analyze_page, prune_for_api } from "../pkg/sentience_core.js";
|
|
2
|
+
|
|
3
|
+
let wasmReady = !1, wasmInitPromise = null;
|
|
4
|
+
|
|
5
|
+
async function initWASM() {
|
|
6
|
+
if (!wasmReady) return wasmInitPromise || (wasmInitPromise = (async () => {
|
|
7
|
+
try {
|
|
8
|
+
globalThis.js_click_element = () => {}, await init(), wasmReady = !0;
|
|
9
|
+
} catch (error) {
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
})(), wasmInitPromise);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function handleScreenshotCapture(_tabId, options = {}) {
|
|
16
|
+
try {
|
|
17
|
+
const {format: format = "png", quality: quality = 90} = options;
|
|
18
|
+
return await chrome.tabs.captureVisibleTab(null, {
|
|
19
|
+
format: format,
|
|
20
|
+
quality: quality
|
|
21
|
+
});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
throw new Error(`Failed to capture screenshot: ${error.message}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function handleSnapshotProcessing(rawData, options = {}) {
|
|
28
|
+
const startTime = performance.now();
|
|
29
|
+
try {
|
|
30
|
+
if (!Array.isArray(rawData)) throw new Error("rawData must be an array");
|
|
31
|
+
if (rawData.length > 1e4 && (rawData = rawData.slice(0, 1e4)), await initWASM(),
|
|
32
|
+
!wasmReady) throw new Error("WASM module not initialized");
|
|
33
|
+
let analyzedElements, prunedRawData;
|
|
34
|
+
try {
|
|
35
|
+
const wasmPromise = new Promise((resolve, reject) => {
|
|
36
|
+
try {
|
|
37
|
+
let result;
|
|
38
|
+
result = options.limit || options.filter ? analyze_page_with_options(rawData, options) : analyze_page(rawData),
|
|
39
|
+
resolve(result);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
reject(e);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
analyzedElements = await Promise.race([ wasmPromise, new Promise((_, reject) => setTimeout(() => reject(new Error("WASM processing timeout (>18s)")), 18e3)) ]);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
const errorMsg = e.message || "Unknown WASM error";
|
|
47
|
+
throw new Error(`WASM analyze_page failed: ${errorMsg}`);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
prunedRawData = prune_for_api(rawData);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
prunedRawData = rawData;
|
|
53
|
+
}
|
|
54
|
+
performance.now();
|
|
55
|
+
return {
|
|
56
|
+
elements: analyzedElements,
|
|
57
|
+
raw_elements: prunedRawData
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
performance.now();
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
initWASM().catch(err => {}), chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
66
|
+
try {
|
|
67
|
+
return "captureScreenshot" === request.action ? (handleScreenshotCapture(sender.tab.id, request.options).then(screenshot => {
|
|
68
|
+
sendResponse({
|
|
69
|
+
success: !0,
|
|
70
|
+
screenshot: screenshot
|
|
71
|
+
});
|
|
72
|
+
}).catch(error => {
|
|
73
|
+
sendResponse({
|
|
74
|
+
success: !1,
|
|
75
|
+
error: error.message || "Screenshot capture failed"
|
|
76
|
+
});
|
|
77
|
+
}), !0) : "processSnapshot" === request.action ? (handleSnapshotProcessing(request.rawData, request.options).then(result => {
|
|
78
|
+
sendResponse({
|
|
79
|
+
success: !0,
|
|
80
|
+
result: result
|
|
81
|
+
});
|
|
82
|
+
}).catch(error => {
|
|
83
|
+
sendResponse({
|
|
84
|
+
success: !1,
|
|
85
|
+
error: error.message || "Snapshot processing failed"
|
|
86
|
+
});
|
|
87
|
+
}), !0) : (sendResponse({
|
|
88
|
+
success: !1,
|
|
89
|
+
error: "Unknown action"
|
|
90
|
+
}), !1);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
try {
|
|
93
|
+
sendResponse({
|
|
94
|
+
success: !1,
|
|
95
|
+
error: `Fatal error: ${error.message || "Unknown error"}`
|
|
96
|
+
});
|
|
97
|
+
} catch (e) {}
|
|
98
|
+
return !1;
|
|
99
|
+
}
|
|
100
|
+
}), self.addEventListener("error", event => {
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
}), self.addEventListener("unhandledrejection", event => {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
!function() {
|
|
2
|
+
"use strict";
|
|
3
|
+
window, window.top;
|
|
4
|
+
document.documentElement.dataset.sentienceExtensionId = chrome.runtime.id, window.addEventListener("message", event => {
|
|
5
|
+
var data;
|
|
6
|
+
if (event.source === window) switch (event.data.type) {
|
|
7
|
+
case "SENTIENCE_SCREENSHOT_REQUEST":
|
|
8
|
+
data = event.data, chrome.runtime.sendMessage({
|
|
9
|
+
action: "captureScreenshot",
|
|
10
|
+
options: data.options
|
|
11
|
+
}, response => {
|
|
12
|
+
window.postMessage({
|
|
13
|
+
type: "SENTIENCE_SCREENSHOT_RESULT",
|
|
14
|
+
requestId: data.requestId,
|
|
15
|
+
screenshot: response?.success ? response.screenshot : null,
|
|
16
|
+
error: response?.error
|
|
17
|
+
}, "*");
|
|
18
|
+
});
|
|
19
|
+
break;
|
|
20
|
+
|
|
21
|
+
case "SENTIENCE_SNAPSHOT_REQUEST":
|
|
22
|
+
!function(data) {
|
|
23
|
+
const startTime = performance.now();
|
|
24
|
+
let responded = !1;
|
|
25
|
+
const timeoutId = setTimeout(() => {
|
|
26
|
+
if (!responded) {
|
|
27
|
+
responded = !0;
|
|
28
|
+
const duration = performance.now() - startTime;
|
|
29
|
+
window.postMessage({
|
|
30
|
+
type: "SENTIENCE_SNAPSHOT_RESULT",
|
|
31
|
+
requestId: data.requestId,
|
|
32
|
+
error: "WASM processing timeout - background script may be unresponsive",
|
|
33
|
+
duration: duration
|
|
34
|
+
}, "*");
|
|
35
|
+
}
|
|
36
|
+
}, 2e4);
|
|
37
|
+
try {
|
|
38
|
+
chrome.runtime.sendMessage({
|
|
39
|
+
action: "processSnapshot",
|
|
40
|
+
rawData: data.rawData,
|
|
41
|
+
options: data.options
|
|
42
|
+
}, response => {
|
|
43
|
+
if (responded) return;
|
|
44
|
+
responded = !0, clearTimeout(timeoutId);
|
|
45
|
+
const duration = performance.now() - startTime;
|
|
46
|
+
chrome.runtime.lastError ? window.postMessage({
|
|
47
|
+
type: "SENTIENCE_SNAPSHOT_RESULT",
|
|
48
|
+
requestId: data.requestId,
|
|
49
|
+
error: `Chrome runtime error: ${chrome.runtime.lastError.message}`,
|
|
50
|
+
duration: duration
|
|
51
|
+
}, "*") : response?.success ? window.postMessage({
|
|
52
|
+
type: "SENTIENCE_SNAPSHOT_RESULT",
|
|
53
|
+
requestId: data.requestId,
|
|
54
|
+
elements: response.result.elements,
|
|
55
|
+
raw_elements: response.result.raw_elements,
|
|
56
|
+
duration: duration
|
|
57
|
+
}, "*") : window.postMessage({
|
|
58
|
+
type: "SENTIENCE_SNAPSHOT_RESULT",
|
|
59
|
+
requestId: data.requestId,
|
|
60
|
+
error: response?.error || "Processing failed",
|
|
61
|
+
duration: duration
|
|
62
|
+
}, "*");
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (!responded) {
|
|
66
|
+
responded = !0, clearTimeout(timeoutId);
|
|
67
|
+
const duration = performance.now() - startTime;
|
|
68
|
+
window.postMessage({
|
|
69
|
+
type: "SENTIENCE_SNAPSHOT_RESULT",
|
|
70
|
+
requestId: data.requestId,
|
|
71
|
+
error: `Failed to send message: ${error.message}`,
|
|
72
|
+
duration: duration
|
|
73
|
+
}, "*");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}(event.data);
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case "SENTIENCE_SHOW_OVERLAY":
|
|
80
|
+
!function(data) {
|
|
81
|
+
const {elements: elements, targetElementId: targetElementId} = data;
|
|
82
|
+
if (!elements || !Array.isArray(elements)) return;
|
|
83
|
+
removeOverlay();
|
|
84
|
+
const host = document.createElement("div");
|
|
85
|
+
host.id = OVERLAY_HOST_ID, host.style.cssText = "\n position: fixed !important;\n top: 0 !important;\n left: 0 !important;\n width: 100vw !important;\n height: 100vh !important;\n pointer-events: none !important;\n z-index: 2147483647 !important;\n margin: 0 !important;\n padding: 0 !important;\n ",
|
|
86
|
+
document.body.appendChild(host);
|
|
87
|
+
const shadow = host.attachShadow({
|
|
88
|
+
mode: "closed"
|
|
89
|
+
}), maxImportance = Math.max(...elements.map(e => e.importance || 0), 1);
|
|
90
|
+
elements.forEach(element => {
|
|
91
|
+
const bbox = element.bbox;
|
|
92
|
+
if (!bbox) return;
|
|
93
|
+
const isTarget = element.id === targetElementId, isPrimary = element.visual_cues?.is_primary || !1, importance = element.importance || 0;
|
|
94
|
+
let color;
|
|
95
|
+
color = isTarget ? "#FF0000" : isPrimary ? "#0066FF" : "#00FF00";
|
|
96
|
+
const importanceRatio = maxImportance > 0 ? importance / maxImportance : .5, borderOpacity = isTarget ? 1 : isPrimary ? .9 : Math.max(.4, .5 + .5 * importanceRatio), fillOpacity = .2 * borderOpacity, borderWidth = isTarget ? 2 : isPrimary ? 1.5 : Math.max(.5, Math.round(2 * importanceRatio)), hexOpacity = Math.round(255 * fillOpacity).toString(16).padStart(2, "0"), box = document.createElement("div");
|
|
97
|
+
if (box.style.cssText = `\n position: absolute;\n left: ${bbox.x}px;\n top: ${bbox.y}px;\n width: ${bbox.width}px;\n height: ${bbox.height}px;\n border: ${borderWidth}px solid ${color};\n background-color: ${color}${hexOpacity};\n box-sizing: border-box;\n opacity: ${borderOpacity};\n pointer-events: none;\n `,
|
|
98
|
+
importance > 0 || isPrimary) {
|
|
99
|
+
const badge = document.createElement("span");
|
|
100
|
+
badge.textContent = isPrimary ? `⭐${importance}` : `${importance}`, badge.style.cssText = `\n position: absolute;\n top: -18px;\n left: 0;\n background: ${color};\n color: white;\n font-size: 11px;\n font-weight: bold;\n padding: 2px 6px;\n font-family: Arial, sans-serif;\n border-radius: 3px;\n opacity: 0.95;\n white-space: nowrap;\n pointer-events: none;\n `,
|
|
101
|
+
box.appendChild(badge);
|
|
102
|
+
}
|
|
103
|
+
if (isTarget) {
|
|
104
|
+
const targetIndicator = document.createElement("span");
|
|
105
|
+
targetIndicator.textContent = "🎯", targetIndicator.style.cssText = "\n position: absolute;\n top: -18px;\n right: 0;\n font-size: 16px;\n pointer-events: none;\n ",
|
|
106
|
+
box.appendChild(targetIndicator);
|
|
107
|
+
}
|
|
108
|
+
shadow.appendChild(box);
|
|
109
|
+
}), overlayTimeout = setTimeout(() => {
|
|
110
|
+
removeOverlay();
|
|
111
|
+
}, 5e3);
|
|
112
|
+
}(event.data);
|
|
113
|
+
break;
|
|
114
|
+
|
|
115
|
+
case "SENTIENCE_CLEAR_OVERLAY":
|
|
116
|
+
removeOverlay();
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case "SENTIENCE_SHOW_GRID_OVERLAY":
|
|
120
|
+
!function(data) {
|
|
121
|
+
const {grids: grids, targetGridId: targetGridId} = data;
|
|
122
|
+
if (!grids || !Array.isArray(grids)) return;
|
|
123
|
+
removeOverlay();
|
|
124
|
+
const host = document.createElement("div");
|
|
125
|
+
host.id = OVERLAY_HOST_ID, host.style.cssText = "\n position: fixed !important;\n top: 0 !important;\n left: 0 !important;\n width: 100vw !important;\n height: 100vh !important;\n pointer-events: none !important;\n z-index: 2147483647 !important;\n margin: 0 !important;\n padding: 0 !important;\n ",
|
|
126
|
+
document.body.appendChild(host);
|
|
127
|
+
const shadow = host.attachShadow({
|
|
128
|
+
mode: "closed"
|
|
129
|
+
});
|
|
130
|
+
grids.forEach(grid => {
|
|
131
|
+
const bbox = grid.bbox;
|
|
132
|
+
if (!bbox) return;
|
|
133
|
+
const isTarget = grid.grid_id === targetGridId, isDominant = !0 === grid.is_dominant;
|
|
134
|
+
let color = "#9B59B6";
|
|
135
|
+
isTarget ? color = "#FF0000" : isDominant && (color = "#FF8C00");
|
|
136
|
+
const borderStyle = isTarget ? "solid" : "dashed", borderWidth = isTarget ? 3 : isDominant ? 2.5 : 2, opacity = isTarget ? 1 : isDominant ? .9 : .8, fillOpacity = .1 * opacity, hexOpacity = Math.round(255 * fillOpacity).toString(16).padStart(2, "0"), box = document.createElement("div");
|
|
137
|
+
box.style.cssText = `\n position: absolute;\n left: ${bbox.x}px;\n top: ${bbox.y}px;\n width: ${bbox.width}px;\n height: ${bbox.height}px;\n border: ${borderWidth}px ${borderStyle} ${color};\n background-color: ${color}${hexOpacity};\n box-sizing: border-box;\n opacity: ${opacity};\n pointer-events: none;\n `;
|
|
138
|
+
let labelText = grid.label ? `Grid ${grid.grid_id}: ${grid.label}` : `Grid ${grid.grid_id}`;
|
|
139
|
+
grid.is_dominant && (labelText = `⭐ ${labelText} (dominant)`);
|
|
140
|
+
const badge = document.createElement("span");
|
|
141
|
+
if (badge.textContent = labelText, badge.style.cssText = `\n position: absolute;\n top: -18px;\n left: 0;\n background: ${color};\n color: white;\n font-size: 11px;\n font-weight: bold;\n padding: 2px 6px;\n font-family: Arial, sans-serif;\n border-radius: 3px;\n opacity: 0.95;\n white-space: nowrap;\n pointer-events: none;\n `,
|
|
142
|
+
box.appendChild(badge), isTarget) {
|
|
143
|
+
const targetIndicator = document.createElement("span");
|
|
144
|
+
targetIndicator.textContent = "🎯", targetIndicator.style.cssText = "\n position: absolute;\n top: -18px;\n right: 0;\n font-size: 16px;\n pointer-events: none;\n ",
|
|
145
|
+
box.appendChild(targetIndicator);
|
|
146
|
+
}
|
|
147
|
+
shadow.appendChild(box);
|
|
148
|
+
}), overlayTimeout = setTimeout(() => {
|
|
149
|
+
removeOverlay();
|
|
150
|
+
}, 5e3);
|
|
151
|
+
}(event.data);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
const OVERLAY_HOST_ID = "sentience-overlay-host";
|
|
155
|
+
let overlayTimeout = null;
|
|
156
|
+
function removeOverlay() {
|
|
157
|
+
const existing = document.getElementById(OVERLAY_HOST_ID);
|
|
158
|
+
existing && existing.remove(), overlayTimeout && (clearTimeout(overlayTimeout),
|
|
159
|
+
overlayTimeout = null);
|
|
160
|
+
}
|
|
161
|
+
}();
|