Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions packages/flow/openadapt_flow/compile_agent/observe.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,16 @@
"""

_OBSERVE_JS = (
"(sel) => {"
"(args) => {"
+ _NAME_JS
+ """
var sel = Array.isArray(args) ? args[0] : args;
var values = (Array.isArray(args) && args[1]) ? args[1] : [];
function esc(s) { return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'); }
var res = values.map(function (v) {
return new RegExp('(?<![\\\\p{L}\\\\p{N}_])' +
v.split(/\\\\s+/).map(esc).join('\\\\s+') + '(?![\\\\p{L}\\\\p{N}_])', 'gu');
});
function visible(el) {
var r = el.getBoundingClientRect();
if (r.width <= 0 || r.height <= 0) return false;
Expand All @@ -103,6 +110,44 @@
var rows = Array.prototype.slice.call(document.querySelectorAll('tr'));
var out = [];
var all = document.body.querySelectorAll('*');
var matchedParents = new Set();
var skipDescendants = new Set();

if (res.length > 0) {
for (var j = 0; j < all.length; j++) {
var el = all[j];
if (!visible(el)) continue;
var tag = el.tagName.toLowerCase();
if (tag === 'script' || tag === 'style' || tag === 'noscript') continue;
if (tag === 'input' || tag === 'textarea' || tag === 'select') continue;
var text = String(el.innerText || '');
for (var i = 0; i < res.length; i++) {
res[i].lastIndex = 0;
if (!res[i].test(text)) continue;
var inner = false;
for (var c = 0; c < el.children.length && !inner; c++) {
res[i].lastIndex = 0;
inner = res[i].test(String(el.children[c].innerText || ''));
}
if (inner) continue;
var ancestorMatched = false;
var p = el.parentElement;
while (p && p !== document.body) {
if (matchedParents.has(p)) { ancestorMatched = true; break; }
p = p.parentElement;
}
if (ancestorMatched) continue;

matchedParents.add(el);
var desc = el.querySelectorAll('*');
for (var d = 0; d < desc.length; d++) {
skipDescendants.add(desc[d]);
}
break;
}
}
}

for (var i = 0; i < all.length; i++) {
var el = all[i];
var tag = el.tagName.toLowerCase();
Expand Down Expand Up @@ -132,7 +177,11 @@
continue;
}
if (el.closest(sel)) continue;
var text = ownText(el);
if (skipDescendants.has(el)) continue;

var text = matchedParents.has(el)
? String(el.innerText || '').replace(/\\s+/g, ' ').trim()
: ownText(el);
if (!text || !visible(el)) continue;
out.push({kind: /^h[1-6]$/.test(tag) ? 'heading' : 'text', tag: tag,
text: text.slice(0, 200), row: row});
Expand Down Expand Up @@ -494,7 +543,10 @@ def observe(
) -> StructuralObservation:
"""Read ``page``'s structure: one line per text, heading, row or control."""

raw = page.evaluate(_OBSERVE_JS, INTERACTIVE)
from openadapt_flow.privacy import listing_bindings

bound_values = list(listing_bindings(bindings or {}).values())
raw = page.evaluate(_OBSERVE_JS, [INTERACTIVE, bound_values])
body, controls = _render(raw)
header = f"page: {raw.get('title') or ''} ({raw.get('url') or ''})"
text = scrub_lines(header + "\n" + body, keep, bindings)
Expand Down
19 changes: 19 additions & 0 deletions packages/flow/tests/test_task_placeholders.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,22 @@ async def close(self) -> None:
)
agent.drive(request, _harness(page))
_assert_no_raw_value("\n".join(sent))


def test_split_element_task_value_bound_in_listing(page) -> None:
from openadapt_flow.compile_agent.observe import observe

page.set_content(
"<!doctype html><html><body>"
"<p>Selected: <b>MRN</b> P1</p>"
"<div>Patient: <span>Jane</span> <span>Sample</span></div>"
"</body></html>"
)
bindings = privacy.task_bindings(identity=["Jane Sample", "MRN P1"])
obs = observe(page, bindings=bindings)

# Both values split across child inline elements match via parent joined text
assert f"Selected: {privacy.placeholder('identity_2')}" in obs.text
assert f"Patient: {privacy.placeholder('identity_1')}" in obs.text
assert "Jane Sample" not in obs.text
assert "MRN P1" not in obs.text
Loading