返回 DeepSeek-Reasonix
pageSemantic.ts
根目录 / desktop / electron / src / main / browser / pageSemantic.ts
1 /// <reference lib="dom" />
2 import { InjectedScript } from "reasonix-playwright-injected";
3 import type { PageRegistry, SnapshotInput, SnapshotOutput } from "./snapshotScript.js";
4
5 type Registry = PageRegistry & { kernel: InjectedScript };
6 export function pageRegistry(key: string): Registry {
7 const host = window as unknown as Record<string, unknown>;
8 let registry = host[key] as Registry | undefined;
9 if (!registry?.kernel) {
10 registry = { docId: `${performance.timeOrigin}:${Math.random().toString(36).slice(2)}`, snapshotId: "", refs: new Map(), kernel: new InjectedScript(window, { isUnderTest: false, sdkLanguage: "javascript", testIdAttributeName: "data-testid", stableRafCount: 1, browserName: "chromium", customEngines: [], isUtilityWorld: true }) };
11 Object.defineProperty(host, key, { value: registry, configurable: true, enumerable: false });
12 }
13 return registry;
14 }
15
16 export function pageSemanticSnapshot(input: SnapshotInput): SnapshotOutput {
17 const registry = pageRegistry(input.key);
18 registry.snapshotId = input.snapshotId;
19 registry.refs = new Map();
20 const root = input.selector ? registry.kernel.querySelectorAll(registry.kernel.parseSelector(input.selector), document) : [document.body ?? document.documentElement];
21 if (root.length > 1) throw new Error("ambiguous selector: snapshot scope matches multiple elements");
22 if (!root.length) return { docId: registry.docId, tree: `(no element matches selector ${JSON.stringify(input.selector)})`, refs: 0, nodes: 0, truncated: 0 };
23 const snapshot = registry.kernel.ariaSnapshotWithRefs(root[0], { mode: "ai" });
24 let next = 0;
25 const mapped = new Map<string, string>();
26 const lines = snapshot.text.split("\n");
27 const limit = Math.min(4000, Math.max(1, input.budget));
28 const tree = lines.slice(0, limit).join("\n").replace(/\[ref=([^\]]+)\]/g, (_match, ref: string) => {
29 let external = mapped.get(ref);
30 if (!external) {
31 const matches = registry.kernel.querySelectorAll(registry.kernel.parseSelector(`aria-ref=${ref}`), document);
32 if (matches.length !== 1) throw new Error("Playwright reference contract changed");
33 external = `${input.prefix}e${++next}`;
34 mapped.set(ref, external);
35 registry.refs.set(external, matches[0]);
36 }
37 return `ref=${external}`;
38 });
39 const truncated = Math.max(0, lines.length - limit);
40 return { docId: registry.docId, tree: tree + (truncated ? `\n… (${truncated} more nodes)` : ""), refs: registry.refs.size, nodes: Math.min(limit, lines.length), truncated };
41 }
42
43 export interface QueryInput { key: string; snapshotId: string; docId: string; prefix: string; role?: string; name?: string; text?: string; testId?: string; containerRef?: string; state?: string }
44 export function pageQuery(input: QueryInput) {
45 const registry = pageRegistry(input.key);
46 if (registry.snapshotId !== input.snapshotId || registry.docId !== input.docId) return { error: "stale_document" };
47 const root = input.containerRef ? registry.refs.get(input.containerRef) : document;
48 if (!root || (root instanceof Element && !root.isConnected)) return { error: "stale_document" };
49 const quote = (text: string) => JSON.stringify(text);
50 let selector: string;
51 if (input.role) {
52 if (!/^[a-z]+$/.test(input.role)) throw new Error("invalid role");
53 selector = `internal:role=${input.role}${input.name !== undefined ? `[name=${quote(input.name)}s]` : ""}`;
54 } else if (input.testId !== undefined) selector = `internal:testid=[data-testid=${quote(input.testId)}s]`;
55 else if (input.text !== undefined) selector = `internal:text=${quote(input.text)}s`;
56 else throw new Error("query requires role, text or testId");
57 const matches = registry.kernel.querySelectorAll(registry.kernel.parseSelector(selector), root);
58 const refs: string[] = [];
59 for (const element of matches.slice(0, 100)) {
60 let ref = [...registry.refs].find(([, candidate]) => candidate === element)?.[0];
61 if (!ref) {
62 if (registry.refs.size >= 4000) throw new Error("reference budget exceeded; observe a smaller scope");
63 ref = `${input.prefix}e${registry.refs.size + 1}`; registry.refs.set(ref, element);
64 }
65 refs.push(ref);
66 }
67 return { count: matches.length, refs, ambiguous: matches.length > 1, state: input.state && matches.length === 1 ? registry.kernel.elementState(matches[0], input.state).matches : undefined };
68 }
69
70 export function pageReady() { return { ready: document.readyState !== "loading", url: location.href, width: innerWidth, height: innerHeight, scrollX, scrollY }; }
71
71 lines TYPESCRIPT