copilot-liku-cli 0.0.3 → 0.0.8
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.
- package/QUICKSTART.md +24 -0
- package/README.md +85 -33
- package/package.json +23 -14
- package/scripts/postinstall.js +63 -0
- package/src/cli/commands/window.js +66 -0
- package/src/main/agents/base-agent.js +15 -7
- package/src/main/agents/builder.js +211 -0
- package/src/main/agents/index.js +7 -4
- package/src/main/agents/orchestrator.js +13 -0
- package/src/main/agents/producer.js +891 -0
- package/src/main/agents/researcher.js +78 -0
- package/src/main/agents/state-manager.js +134 -2
- package/src/main/agents/verifier.js +201 -0
- package/src/main/ai-service.js +349 -35
- package/src/main/index.js +702 -113
- package/src/main/inspect-service.js +24 -1
- package/src/main/python-bridge.js +395 -0
- package/src/main/system-automation.js +876 -131
- package/src/main/ui-automation/core/ui-provider.js +99 -0
- package/src/main/ui-automation/core/uia-host.js +214 -0
- package/src/main/ui-automation/index.js +30 -0
- package/src/main/ui-automation/interactions/element-click.js +6 -6
- package/src/main/ui-automation/interactions/high-level.js +28 -6
- package/src/main/ui-automation/interactions/index.js +21 -0
- package/src/main/ui-automation/interactions/pattern-actions.js +236 -0
- package/src/main/ui-automation/window/index.js +6 -0
- package/src/main/ui-automation/window/manager.js +173 -26
- package/src/main/ui-watcher.js +401 -58
- package/src/main/visual-awareness.js +18 -1
- package/src/native/windows-uia/Program.cs +89 -0
- package/src/native/windows-uia/build.ps1 +24 -0
- package/src/native/windows-uia-dotnet/Program.cs +920 -0
- package/src/native/windows-uia-dotnet/WindowsUIA.csproj +11 -0
- package/src/native/windows-uia-dotnet/build.ps1 +24 -0
- package/src/renderer/chat/chat.js +915 -671
- package/src/renderer/chat/index.html +2 -4
- package/src/renderer/chat/preload.js +8 -1
- package/src/renderer/overlay/overlay.js +157 -8
- package/src/renderer/overlay/preload.js +4 -0
- package/src/shared/inspect-types.js +82 -6
- package/ARCHITECTURE.md +0 -411
- package/CONFIGURATION.md +0 -302
- package/CONTRIBUTING.md +0 -225
- package/ELECTRON_README.md +0 -121
- package/PROJECT_STATUS.md +0 -229
- package/TESTING.md +0 -274
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
display: flex;
|
|
45
45
|
justify-content: space-between;
|
|
46
46
|
align-items: center;
|
|
47
|
-
-webkit-app-region: drag;
|
|
48
47
|
-webkit-user-select: none;
|
|
49
48
|
user-select: none;
|
|
50
49
|
border-bottom: 1px solid var(--border-color);
|
|
@@ -68,7 +67,6 @@
|
|
|
68
67
|
|
|
69
68
|
#titlebar-controls {
|
|
70
69
|
display: flex;
|
|
71
|
-
-webkit-app-region: no-drag;
|
|
72
70
|
height: 100%;
|
|
73
71
|
}
|
|
74
72
|
|
|
@@ -206,7 +204,7 @@
|
|
|
206
204
|
align-items: center;
|
|
207
205
|
padding: 6px 10px;
|
|
208
206
|
background: var(--bg-tertiary);
|
|
209
|
-
border-bottom: 1px solid var(--border-color);
|
|
207
|
+
border-bottom: 1px solid var(--border-color);
|
|
210
208
|
font-size: 11px;
|
|
211
209
|
}
|
|
212
210
|
|
|
@@ -223,7 +221,7 @@
|
|
|
223
221
|
#provider-select,
|
|
224
222
|
#model-select {
|
|
225
223
|
background: var(--bg-secondary);
|
|
226
|
-
border: 1px solid var(--border-color);
|
|
224
|
+
border: 1px solid var(--border-color);
|
|
227
225
|
border-radius: 4px;
|
|
228
226
|
color: var(--text-primary);
|
|
229
227
|
padding: 3px 6px;
|
|
@@ -92,6 +92,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
92
92
|
|
|
93
93
|
// Verify using verifier agent
|
|
94
94
|
agentVerify: (params) => ipcRenderer.invoke('agent-verify', params),
|
|
95
|
+
|
|
96
|
+
// Produce music using producer agent
|
|
97
|
+
agentProduce: (params) => ipcRenderer.invoke('agent-produce', params),
|
|
95
98
|
|
|
96
99
|
// Get agent system status
|
|
97
100
|
agentStatus: () => ipcRenderer.invoke('agent-status'),
|
|
@@ -108,5 +111,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
108
111
|
},
|
|
109
112
|
|
|
110
113
|
// ===== STATE =====
|
|
111
|
-
getState: () => ipcRenderer.invoke('get-state')
|
|
114
|
+
getState: () => ipcRenderer.invoke('get-state'),
|
|
115
|
+
|
|
116
|
+
// ===== DEBUG / SMOKE (guarded in main by LIKU_ENABLE_DEBUG_IPC) =====
|
|
117
|
+
debugToggleChat: () => ipcRenderer.invoke('debug-toggle-chat'),
|
|
118
|
+
debugWindowState: () => ipcRenderer.invoke('debug-window-state')
|
|
112
119
|
});
|
|
@@ -23,6 +23,7 @@ let state = {
|
|
|
23
23
|
// Inspect mode state
|
|
24
24
|
inspectMode: false,
|
|
25
25
|
inspectRegions: [],
|
|
26
|
+
actionableRegions: [], // New: AI-detected regions for overlay
|
|
26
27
|
hoveredRegion: null,
|
|
27
28
|
selectedRegionId: null,
|
|
28
29
|
// Live UI mirror state
|
|
@@ -74,8 +75,15 @@ function draw() {
|
|
|
74
75
|
if (currentMode !== 'selection') return;
|
|
75
76
|
|
|
76
77
|
// 1. Draw Coarse Grid (Always visible in selection)
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
// ADAPTIVE GRID: If we have actionable regions, fade the grid significantly to reduce clutter
|
|
79
|
+
// This prioritizes the "Live UI" view as requested
|
|
80
|
+
const hasRegions = state.actionableRegions && state.actionableRegions.length > 0;
|
|
81
|
+
const gridOpacity = hasRegions ? 0.15 : 0.85;
|
|
82
|
+
const dotOpacity = hasRegions ? 0.15 : 0.85;
|
|
83
|
+
const labelOpacity = hasRegions ? 0.1 : 0.7;
|
|
84
|
+
|
|
85
|
+
ctx.fillStyle = `rgba(0, 122, 255, ${dotOpacity})`;
|
|
86
|
+
ctx.strokeStyle = `rgba(255, 255, 255, ${dotOpacity + 0.1})`;
|
|
79
87
|
ctx.lineWidth = 2;
|
|
80
88
|
|
|
81
89
|
// Font for labels
|
|
@@ -98,7 +106,7 @@ function draw() {
|
|
|
98
106
|
// Draw Dot
|
|
99
107
|
ctx.beginPath();
|
|
100
108
|
ctx.arc(x, y, 6, 0, Math.PI * 2);
|
|
101
|
-
ctx.fillStyle =
|
|
109
|
+
ctx.fillStyle = `rgba(0, 122, 255, ${dotOpacity})`;
|
|
102
110
|
ctx.fill();
|
|
103
111
|
ctx.stroke();
|
|
104
112
|
|
|
@@ -108,12 +116,12 @@ function draw() {
|
|
|
108
116
|
const bgW = metrics.width + 10;
|
|
109
117
|
const bgH = 16;
|
|
110
118
|
|
|
111
|
-
// Label Background
|
|
112
|
-
ctx.fillStyle =
|
|
119
|
+
// Label Background (Fainter if regions present)
|
|
120
|
+
ctx.fillStyle = `rgba(0, 0, 0, ${labelOpacity})`;
|
|
113
121
|
ctx.fillRect(x - bgW / 2, y - 20 - bgH, bgW, bgH);
|
|
114
122
|
|
|
115
123
|
// Label Text
|
|
116
|
-
ctx.fillStyle =
|
|
124
|
+
ctx.fillStyle = `rgba(255, 255, 255, ${hasRegions ? 0.4 : 1.0})`;
|
|
117
125
|
ctx.fillText(label, x, y - 24);
|
|
118
126
|
}
|
|
119
127
|
}
|
|
@@ -156,6 +164,11 @@ function draw() {
|
|
|
156
164
|
if (zoomLevel < 2) {
|
|
157
165
|
drawLocalFineGrid();
|
|
158
166
|
}
|
|
167
|
+
|
|
168
|
+
// 4. Draw Actionable Regions (AI Vision)
|
|
169
|
+
if (state.actionableRegions && state.actionableRegions.length > 0) {
|
|
170
|
+
drawActionableRegions();
|
|
171
|
+
}
|
|
159
172
|
}
|
|
160
173
|
|
|
161
174
|
// Resize handler
|
|
@@ -279,6 +292,111 @@ function drawLocalFineGrid() {
|
|
|
279
292
|
}
|
|
280
293
|
}
|
|
281
294
|
|
|
295
|
+
function drawActionableRegions() {
|
|
296
|
+
const { actionableRegions, hoveredRegion } = state;
|
|
297
|
+
if (!actionableRegions) return;
|
|
298
|
+
|
|
299
|
+
// Style for regions
|
|
300
|
+
ctx.lineWidth = 1;
|
|
301
|
+
ctx.textBaseline = 'top';
|
|
302
|
+
|
|
303
|
+
// Smart De-Cluttering:
|
|
304
|
+
// If we have > 50 regions, we might want to skip drawing containers that fully enclose other regions?
|
|
305
|
+
// For now, relies on the improved visual style to handle density.
|
|
306
|
+
|
|
307
|
+
actionableRegions.forEach((region, index) => {
|
|
308
|
+
const { bounds, label, type, id } = region;
|
|
309
|
+
if (!bounds || bounds.width <= 0 || bounds.height <= 0) return;
|
|
310
|
+
|
|
311
|
+
const x = bounds.x;
|
|
312
|
+
const y = bounds.y;
|
|
313
|
+
const w = bounds.width;
|
|
314
|
+
const h = bounds.height;
|
|
315
|
+
|
|
316
|
+
// Check hover state (from mousemove or DOM interaction)
|
|
317
|
+
const isHovered = hoveredRegion && hoveredRegion.id === id;
|
|
318
|
+
|
|
319
|
+
// 1. Determine Style based on interactivity and state
|
|
320
|
+
const isPrimaryAction = ['Button', 'Hyperlink', 'MenuItem', 'TabItem', 'CheckBox'].includes(type);
|
|
321
|
+
const isInput = ['Edit', 'ComboBox', 'Document'].includes(type);
|
|
322
|
+
|
|
323
|
+
let strokeColor, fillColor, textColor, bgAlpha;
|
|
324
|
+
|
|
325
|
+
if (isHovered) {
|
|
326
|
+
strokeColor = 'rgba(255, 255, 0, 1.0)'; // Bright Yellow highlight
|
|
327
|
+
fillColor = 'rgba(255, 255, 0, 0.1)';
|
|
328
|
+
textColor = '#ffff00';
|
|
329
|
+
bgAlpha = 0.95;
|
|
330
|
+
ctx.lineWidth = 2;
|
|
331
|
+
} else if (isPrimaryAction) {
|
|
332
|
+
strokeColor = 'rgba(0, 255, 255, 0.7)'; // Cyan for clickable
|
|
333
|
+
fillColor = 'rgba(0, 255, 255, 0.02)';
|
|
334
|
+
textColor = '#00ffff';
|
|
335
|
+
bgAlpha = 0.8;
|
|
336
|
+
ctx.lineWidth = 1;
|
|
337
|
+
} else if (isInput) {
|
|
338
|
+
strokeColor = 'rgba(0, 255, 100, 0.6)'; // Greenish for inputs
|
|
339
|
+
fillColor = 'rgba(0, 255, 100, 0.02)';
|
|
340
|
+
textColor = '#00ff66';
|
|
341
|
+
bgAlpha = 0.7;
|
|
342
|
+
ctx.lineWidth = 1;
|
|
343
|
+
} else {
|
|
344
|
+
// Containers/Text - Subtle
|
|
345
|
+
strokeColor = 'rgba(100, 180, 255, 0.3)';
|
|
346
|
+
fillColor = 'transparent';
|
|
347
|
+
textColor = 'rgba(200, 220, 255, 0.8)';
|
|
348
|
+
bgAlpha = 0.6;
|
|
349
|
+
ctx.lineWidth = 0.5;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 2. Draw Outline
|
|
353
|
+
ctx.strokeStyle = strokeColor;
|
|
354
|
+
ctx.fillStyle = fillColor;
|
|
355
|
+
|
|
356
|
+
// Use dashed lines for containers/large areas to reduce noise
|
|
357
|
+
if (!isHovered && w > 300 && h > 300) {
|
|
358
|
+
ctx.setLineDash([4, 4]);
|
|
359
|
+
} else {
|
|
360
|
+
ctx.setLineDash([]);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
ctx.strokeRect(x, y, w, h);
|
|
364
|
+
ctx.fillRect(x, y, w, h);
|
|
365
|
+
ctx.setLineDash([]); // Reset
|
|
366
|
+
|
|
367
|
+
// 3. Draw Label
|
|
368
|
+
// CLARITY FIX: Only show Index by default. Show full Name only on Hover.
|
|
369
|
+
// This keeps the screen clean while allowing "Confident Communication" via ID reference.
|
|
370
|
+
const idx = index + 1;
|
|
371
|
+
let labelText = `#${idx}`;
|
|
372
|
+
|
|
373
|
+
if (isHovered && label) {
|
|
374
|
+
// Show full info on hover
|
|
375
|
+
labelText = `${label} (${type})`;
|
|
376
|
+
ctx.font = 'bold 12px "SF Mono", "Monaco", monospace'; // Larger font on hover
|
|
377
|
+
} else {
|
|
378
|
+
ctx.font = 'bold 10px "SF Mono", "Monaco", monospace';
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const textMetrics = ctx.measureText(labelText);
|
|
382
|
+
const textW = textMetrics.width + 6;
|
|
383
|
+
const textH = isHovered ? 16 : 12;
|
|
384
|
+
|
|
385
|
+
// Label Position: Interior Top-Left
|
|
386
|
+
// Ensure it doesn't go off-screen
|
|
387
|
+
let lx = x;
|
|
388
|
+
let ly = y;
|
|
389
|
+
|
|
390
|
+
// Label Background
|
|
391
|
+
ctx.fillStyle = `rgba(0, 20, 20, ${bgAlpha})`;
|
|
392
|
+
ctx.fillRect(lx, ly, textW, textH);
|
|
393
|
+
|
|
394
|
+
// Label Text
|
|
395
|
+
ctx.fillStyle = textColor;
|
|
396
|
+
ctx.fillText(labelText, lx + 3, ly + 1);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
282
400
|
// ===== INPUT HANDLING =====
|
|
283
401
|
|
|
284
402
|
// Visual Feedback Helper
|
|
@@ -306,6 +424,26 @@ document.addEventListener('mousemove', (e) => {
|
|
|
306
424
|
|
|
307
425
|
if (state.currentMode === 'selection') {
|
|
308
426
|
requestDraw();
|
|
427
|
+
|
|
428
|
+
// 0. HIT TESTING for Actionable Regions
|
|
429
|
+
const { actionableRegions } = state;
|
|
430
|
+
if (actionableRegions && actionableRegions.length > 0) {
|
|
431
|
+
const hit = actionableRegions
|
|
432
|
+
.filter(r =>
|
|
433
|
+
e.clientX >= r.bounds.x &&
|
|
434
|
+
e.clientX <= (r.bounds.x + r.bounds.width) &&
|
|
435
|
+
e.clientY >= r.bounds.y &&
|
|
436
|
+
e.clientY <= (r.bounds.y + r.bounds.height)
|
|
437
|
+
)
|
|
438
|
+
// Sort by area ascending so we pick the smallest/most specific one
|
|
439
|
+
.sort((a,b) => (a.bounds.width * a.bounds.height) - (b.bounds.width * b.bounds.height))[0];
|
|
440
|
+
|
|
441
|
+
if (state.hoveredRegion !== (hit || null)) {
|
|
442
|
+
state.hoveredRegion = hit || null;
|
|
443
|
+
requestDraw();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
309
447
|
// Virtual Interaction Logic
|
|
310
448
|
// Find nearest grid point
|
|
311
449
|
const spacing = state.zoomLevel >= 2 ? FINE_SPACING : COARSE_SPACING;
|
|
@@ -475,7 +613,9 @@ if (window.electronAPI) {
|
|
|
475
613
|
}
|
|
476
614
|
|
|
477
615
|
function handleCommand(data) {
|
|
478
|
-
|
|
616
|
+
if (data.action !== 'update-inspect-regions') {
|
|
617
|
+
console.log('Command:', data.action);
|
|
618
|
+
}
|
|
479
619
|
switch (data.action) {
|
|
480
620
|
case 'toggle-fine':
|
|
481
621
|
state.zoomLevel = state.zoomLevel >= 2 ? 1 : 2;
|
|
@@ -524,7 +664,16 @@ function handleCommand(data) {
|
|
|
524
664
|
break;
|
|
525
665
|
case 'update-inspect-regions':
|
|
526
666
|
if (data.regions) {
|
|
527
|
-
|
|
667
|
+
// Reuse inspection capability for AI vision visualization
|
|
668
|
+
// Check if we should use DOM (legacy/inspect) or Canvas (performance)
|
|
669
|
+
// For high-frequency updates, we'll store in actionableRegions and use canvas
|
|
670
|
+
state.actionableRegions = data.regions;
|
|
671
|
+
requestDraw();
|
|
672
|
+
|
|
673
|
+
// If actual inspect mode is active, also update DOM for interaction
|
|
674
|
+
if (state.inspectMode) {
|
|
675
|
+
updateInspectRegions(data.regions);
|
|
676
|
+
}
|
|
528
677
|
}
|
|
529
678
|
break;
|
|
530
679
|
case 'clear-inspect-regions':
|
|
@@ -67,6 +67,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
67
67
|
// Get current state
|
|
68
68
|
getState: () => ipcRenderer.invoke('get-state'),
|
|
69
69
|
|
|
70
|
+
// Debug / smoke controls (guarded in main by LIKU_ENABLE_DEBUG_IPC)
|
|
71
|
+
debugToggleChat: () => ipcRenderer.invoke('debug-toggle-chat'),
|
|
72
|
+
debugWindowState: () => ipcRenderer.invoke('debug-window-state'),
|
|
73
|
+
|
|
70
74
|
// Grid math helpers (inlined above)
|
|
71
75
|
getGridConstants: () => gridConstants,
|
|
72
76
|
labelToScreenCoordinates: (label) => labelToScreenCoordinates(label),
|
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
* Shared type definitions for inspect regions, window context, and action traces
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Visual Frame Data Contract
|
|
8
|
+
* Standardized schema for any captured visual context (full screen, ROI, window, element)
|
|
9
|
+
* @typedef {Object} VisualFrame
|
|
10
|
+
* @property {string} dataURL - Base64 data URL of the image
|
|
11
|
+
* @property {number} width - Image width in pixels
|
|
12
|
+
* @property {number} height - Image height in pixels
|
|
13
|
+
* @property {number} timestamp - Capture timestamp (ms)
|
|
14
|
+
* @property {number} [originX] - X offset of the captured region on screen (0 for full screen)
|
|
15
|
+
* @property {number} [originY] - Y offset of the captured region on screen (0 for full screen)
|
|
16
|
+
* @property {string} coordinateSpace - Always 'screen-physical' for UIA/input compatibility
|
|
17
|
+
* @property {string} scope - 'screen' | 'region' | 'window' | 'element'
|
|
18
|
+
* @property {string} [sourceId] - Display/window source identifier
|
|
19
|
+
* @property {string} [sourceName] - Human-readable source name
|
|
20
|
+
*/
|
|
21
|
+
|
|
6
22
|
/**
|
|
7
23
|
* Inspect Region Data Contract
|
|
8
24
|
* Represents an actionable region on screen detected through various sources
|
|
@@ -15,6 +31,9 @@
|
|
|
15
31
|
* @property {number} confidence - Detection confidence 0-1
|
|
16
32
|
* @property {string} source - Detection source (accessibility, ocr, heuristic)
|
|
17
33
|
* @property {number} timestamp - When this region was detected
|
|
34
|
+
* @property {Object} [clickPoint] - Preferred click point {x, y} from UIA TryGetClickablePoint
|
|
35
|
+
* @property {number[]|null} [runtimeId] - UIA RuntimeId for stable session-scoped element identity
|
|
36
|
+
* @property {string} coordinateSpace - Coordinate space (default 'screen-physical')
|
|
18
37
|
*/
|
|
19
38
|
|
|
20
39
|
/**
|
|
@@ -42,6 +61,26 @@
|
|
|
42
61
|
* @property {string} outcome - Result (success, failed, pending)
|
|
43
62
|
*/
|
|
44
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Create a VisualFrame from capture data
|
|
66
|
+
* @param {Object} params - Capture parameters
|
|
67
|
+
* @returns {VisualFrame}
|
|
68
|
+
*/
|
|
69
|
+
function createVisualFrame(params) {
|
|
70
|
+
return {
|
|
71
|
+
dataURL: params.dataURL || '',
|
|
72
|
+
width: params.width || 0,
|
|
73
|
+
height: params.height || 0,
|
|
74
|
+
timestamp: params.timestamp || Date.now(),
|
|
75
|
+
originX: params.originX ?? params.x ?? 0,
|
|
76
|
+
originY: params.originY ?? params.y ?? 0,
|
|
77
|
+
coordinateSpace: 'screen-physical',
|
|
78
|
+
scope: params.scope || params.type || 'screen',
|
|
79
|
+
sourceId: params.sourceId || null,
|
|
80
|
+
sourceName: params.sourceName || null
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
45
84
|
/**
|
|
46
85
|
* Create a new inspect region object
|
|
47
86
|
* @param {Object} params - Region parameters
|
|
@@ -61,7 +100,10 @@ function createInspectRegion(params) {
|
|
|
61
100
|
role: params.role || params.controlType || 'unknown',
|
|
62
101
|
confidence: typeof params.confidence === 'number' ? params.confidence : 0.5,
|
|
63
102
|
source: params.source || 'unknown',
|
|
64
|
-
timestamp: params.timestamp || Date.now()
|
|
103
|
+
timestamp: params.timestamp || Date.now(),
|
|
104
|
+
clickPoint: params.clickPoint || null,
|
|
105
|
+
runtimeId: params.runtimeId || null,
|
|
106
|
+
coordinateSpace: params.coordinateSpace || 'screen-physical'
|
|
65
107
|
};
|
|
66
108
|
}
|
|
67
109
|
|
|
@@ -203,21 +245,54 @@ function findRegionAtPoint(x, y, regions) {
|
|
|
203
245
|
* @returns {Object} AI-friendly format
|
|
204
246
|
*/
|
|
205
247
|
function formatRegionForAI(region) {
|
|
248
|
+
const center = region.clickPoint
|
|
249
|
+
? { x: region.clickPoint.x, y: region.clickPoint.y }
|
|
250
|
+
: {
|
|
251
|
+
x: Math.round(region.bounds.x + region.bounds.width / 2),
|
|
252
|
+
y: Math.round(region.bounds.y + region.bounds.height / 2)
|
|
253
|
+
};
|
|
206
254
|
return {
|
|
207
255
|
id: region.id,
|
|
208
256
|
label: region.label,
|
|
209
257
|
text: region.text,
|
|
210
258
|
role: region.role,
|
|
211
259
|
confidence: region.confidence,
|
|
212
|
-
center
|
|
213
|
-
x: Math.round(region.bounds.x + region.bounds.width / 2),
|
|
214
|
-
y: Math.round(region.bounds.y + region.bounds.height / 2)
|
|
215
|
-
},
|
|
260
|
+
center,
|
|
216
261
|
bounds: region.bounds
|
|
217
262
|
};
|
|
218
263
|
}
|
|
219
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Resolve a region target from the regions array
|
|
267
|
+
* Supports targetRegionId (stable) or targetRegionIndex (display order)
|
|
268
|
+
* @param {Object} target - { targetRegionId?, targetRegionIndex? }
|
|
269
|
+
* @param {InspectRegion[]} regions - Current regions array
|
|
270
|
+
* @returns {{ region: InspectRegion, clickX: number, clickY: number } | null}
|
|
271
|
+
*/
|
|
272
|
+
function resolveRegionTarget(target, regions) {
|
|
273
|
+
if (!target || !regions || regions.length === 0) return null;
|
|
274
|
+
|
|
275
|
+
let region = null;
|
|
276
|
+
if (target.targetRegionId) {
|
|
277
|
+
region = regions.find(r => r.id === target.targetRegionId);
|
|
278
|
+
} else if (typeof target.targetRegionIndex === 'number') {
|
|
279
|
+
region = regions[target.targetRegionIndex];
|
|
280
|
+
}
|
|
281
|
+
if (!region) return null;
|
|
282
|
+
|
|
283
|
+
// Prefer clickPoint from UIA, fall back to bounds center
|
|
284
|
+
const clickX = region.clickPoint
|
|
285
|
+
? region.clickPoint.x
|
|
286
|
+
: Math.round(region.bounds.x + region.bounds.width / 2);
|
|
287
|
+
const clickY = region.clickPoint
|
|
288
|
+
? region.clickPoint.y
|
|
289
|
+
: Math.round(region.bounds.y + region.bounds.height / 2);
|
|
290
|
+
|
|
291
|
+
return { region, clickX, clickY };
|
|
292
|
+
}
|
|
293
|
+
|
|
220
294
|
module.exports = {
|
|
295
|
+
createVisualFrame,
|
|
221
296
|
createInspectRegion,
|
|
222
297
|
createWindowContext,
|
|
223
298
|
createActionTrace,
|
|
@@ -226,5 +301,6 @@ module.exports = {
|
|
|
226
301
|
isPointInRegion,
|
|
227
302
|
findClosestRegion,
|
|
228
303
|
findRegionAtPoint,
|
|
229
|
-
formatRegionForAI
|
|
304
|
+
formatRegionForAI,
|
|
305
|
+
resolveRegionTarget
|
|
230
306
|
};
|