feat(vom): vom can distinguish important canvas area

This commit is contained in:
Ljy-0827
2026-09-08 10:54:33 +08:00
parent 3c5f838c56
commit ff1216526d
23 changed files with 861 additions and 10 deletions
+87 -1
View File
@@ -9,6 +9,8 @@ import type {
VomOptions,
VomResult,
VomScene,
VomVisualSurface,
VomVisualSurfaceSummary,
} from "./types";
const SKIP_ROLES = new Set(["generic", "none", "presentation", "inlinetextbox"]);
@@ -793,6 +795,70 @@ interface RenderState {
duplicateRefNames: Set<string>;
surfaceMap: Map<number, CondSurface>;
scopeMap: Map<number, ActiveScopeBlock>;
visualSurfaces: Map<number | null, VomVisualSurface[]>;
visualSurfaceSummaries: Map<number | null, VomVisualSurfaceSummary[]>;
}
function groupedByParent<T extends { parentId: number | null }>(
items: T[],
): Map<number | null, T[]> {
const grouped = new Map<number | null, T[]>();
for (const item of items) {
const siblings = grouped.get(item.parentId) ?? [];
siblings.push(item);
grouped.set(item.parentId, siblings);
}
return grouped;
}
function emitVisualEntries(parentId: number | null, depth: number, state: RenderState): void {
if (state.stopped) return;
for (const surface of state.visualSurfaces.get(parentId) ?? []) {
if (depth > state.maxDepth) {
state.truncated = true;
continue;
}
const ref = `e${state.nextRef}`;
const label = cleaned(surface.label) ?? `${surface.renderingKind} visual surface`;
const layers = surface.memberCount > 1 ? `; layers=${surface.memberCount}` : "";
const line = `${" ".repeat(depth)}@${ref} surface ${JSON.stringify(label)} [rendering=${surface.renderingKind}${layers}; visual-only; use: bsk screenshot --ref @${ref}]`;
const nextTokens = state.tokens + estimateTokens(line);
if (nextTokens > state.maxTokens) {
state.truncated = true;
state.stopped = true;
return;
}
state.lines.push(line);
state.tokens = nextTokens;
state.refs.push({
ref,
backendNodeId: surface.backendNodeId,
frameId: surface.frameId,
role: "surface",
name: label,
kind: "surface",
capabilities: ["screenshot"],
line: state.lines.length - 1,
});
state.nextRef += 1;
}
for (const summary of state.visualSurfaceSummaries.get(parentId) ?? []) {
if (depth > state.maxDepth) {
state.truncated = true;
continue;
}
const noun = summary.count === 1 ? "surface is" : "surfaces are";
const line = `${" ".repeat(depth)}[${summary.count} additional visual ${noun} not represented]`;
const nextTokens = state.tokens + estimateTokens(line);
if (nextTokens > state.maxTokens) {
state.truncated = true;
state.stopped = true;
return;
}
state.lines.push(line);
state.tokens = nextTokens;
}
}
function emitActiveScopeBlock(scope: ActiveScopeBlock, depth: number, state: RenderState): void {
@@ -873,6 +939,8 @@ function renderTree(children: Map<number | null, VomNode[]>, state: RenderState)
...(node.role ? { role: node.role } : {}),
...(name ? { name } : {}),
...(context.length > 0 ? { ctx: context.join(" > ") } : {}),
kind: "dom",
capabilities: ["interact", "screenshot"],
line: state.lines.length - 1,
});
state.nextRef += 1;
@@ -882,10 +950,12 @@ function renderTree(children: Map<number | null, VomNode[]>, state: RenderState)
if (scope) emitActiveScopeBlock(scope, depth + 1, state);
if ((ref && shouldSkipRedundantRefChildren(node)) || shouldSkipRedundantChildren(node, state)) {
emitVisualEntries(node.id, depth + 1, state);
continue;
}
pushRenderChildren(stack, children.get(node.id) ?? [], depth + 1);
}
emitVisualEntries(parentId, depth, state);
}
function renderNodes(
@@ -894,6 +964,8 @@ function renderNodes(
initialLines: string[],
surfaces: CondSurface[] = [],
activeScopeBlocks: ActiveScopeBlock[] = [],
visualSurfaces: VomVisualSurface[] = [],
visualSurfaceSummaries: VomVisualSurfaceSummary[] = [],
): RenderState {
const children = buildChildren(nodes);
const state: RenderState = {
@@ -913,6 +985,8 @@ function renderNodes(
duplicateRefNames: duplicateReferenceNames(nodes),
surfaceMap: new Map(surfaces.map((surface) => [surface.triggerId, surface])),
scopeMap: new Map(activeScopeBlocks.map((scope) => [scope.triggerId, scope])),
visualSurfaces: groupedByParent(visualSurfaces),
visualSurfaceSummaries: groupedByParent(visualSurfaceSummaries),
};
renderTree(children, state);
@@ -1124,7 +1198,17 @@ function renderDoubleLayer(scene: VomScene, layer: BlockingLayer, options: VomOp
"@layers 2 focus=L1",
`L1 ${layer.kind} cover=${Math.round(layer.coverage * 100)}%`,
];
const state = renderNodes(visibleNodes, options, header, scene.surfaces, scene.activeScopeBlocks);
const visibleSurface = (parentId: number | null): boolean =>
parentId === null || included.has(parentId);
const state = renderNodes(
visibleNodes,
options,
header,
scene.surfaces,
scene.activeScopeBlocks,
scene.visualSurfaces?.filter((surface) => visibleSurface(surface.parentId)),
scene.visualSurfaceSummaries?.filter((summary) => visibleSurface(summary.parentId)),
);
state.lines.push(renderPageOcclusionLine(hiddenCount));
return {
@@ -1152,6 +1236,8 @@ export function renderVom(scene: VomScene, options: VomOptions = {}): VomResult
],
scene.surfaces,
scene.activeScopeBlocks,
scene.visualSurfaces,
scene.visualSurfaceSummaries,
);
return {