copilot-liku-cli 0.0.1
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/ARCHITECTURE.md +411 -0
- package/CONFIGURATION.md +302 -0
- package/CONTRIBUTING.md +225 -0
- package/ELECTRON_README.md +121 -0
- package/INSTALLATION.md +350 -0
- package/LICENSE.md +1 -0
- package/PROJECT_STATUS.md +229 -0
- package/QUICKSTART.md +255 -0
- package/README.md +167 -0
- package/TESTING.md +274 -0
- package/package.json +61 -0
- package/scripts/start.js +30 -0
- package/src/assets/tray-icon.png +0 -0
- package/src/cli/commands/agent.js +327 -0
- package/src/cli/commands/click.js +108 -0
- package/src/cli/commands/drag.js +85 -0
- package/src/cli/commands/find.js +109 -0
- package/src/cli/commands/keys.js +132 -0
- package/src/cli/commands/mouse.js +79 -0
- package/src/cli/commands/repl.js +290 -0
- package/src/cli/commands/screenshot.js +72 -0
- package/src/cli/commands/scroll.js +74 -0
- package/src/cli/commands/start.js +67 -0
- package/src/cli/commands/type.js +57 -0
- package/src/cli/commands/wait.js +84 -0
- package/src/cli/commands/window.js +104 -0
- package/src/cli/liku.js +249 -0
- package/src/cli/util/output.js +174 -0
- package/src/main/agents/base-agent.js +410 -0
- package/src/main/agents/builder.js +484 -0
- package/src/main/agents/index.js +62 -0
- package/src/main/agents/orchestrator.js +362 -0
- package/src/main/agents/researcher.js +511 -0
- package/src/main/agents/state-manager.js +344 -0
- package/src/main/agents/supervisor.js +365 -0
- package/src/main/agents/verifier.js +452 -0
- package/src/main/ai-service.js +1633 -0
- package/src/main/index.js +2208 -0
- package/src/main/inspect-service.js +467 -0
- package/src/main/system-automation.js +1186 -0
- package/src/main/ui-automation/config.js +76 -0
- package/src/main/ui-automation/core/helpers.js +41 -0
- package/src/main/ui-automation/core/index.js +15 -0
- package/src/main/ui-automation/core/powershell.js +82 -0
- package/src/main/ui-automation/elements/finder.js +274 -0
- package/src/main/ui-automation/elements/index.js +14 -0
- package/src/main/ui-automation/elements/wait.js +66 -0
- package/src/main/ui-automation/index.js +164 -0
- package/src/main/ui-automation/interactions/element-click.js +211 -0
- package/src/main/ui-automation/interactions/high-level.js +230 -0
- package/src/main/ui-automation/interactions/index.js +47 -0
- package/src/main/ui-automation/keyboard/index.js +15 -0
- package/src/main/ui-automation/keyboard/input.js +179 -0
- package/src/main/ui-automation/mouse/click.js +186 -0
- package/src/main/ui-automation/mouse/drag.js +88 -0
- package/src/main/ui-automation/mouse/index.js +30 -0
- package/src/main/ui-automation/mouse/movement.js +51 -0
- package/src/main/ui-automation/mouse/scroll.js +116 -0
- package/src/main/ui-automation/screenshot.js +183 -0
- package/src/main/ui-automation/window/index.js +23 -0
- package/src/main/ui-automation/window/manager.js +305 -0
- package/src/main/utils/time.js +62 -0
- package/src/main/visual-awareness.js +597 -0
- package/src/renderer/chat/chat.js +671 -0
- package/src/renderer/chat/index.html +725 -0
- package/src/renderer/chat/preload.js +112 -0
- package/src/renderer/overlay/index.html +648 -0
- package/src/renderer/overlay/overlay.js +782 -0
- package/src/renderer/overlay/preload.js +90 -0
- package/src/shared/grid-math.js +82 -0
- package/src/shared/inspect-types.js +230 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inspect Service Module
|
|
3
|
+
* Manages inspect overlay state, region detection, and AI context integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { screen } = require('electron');
|
|
7
|
+
const visualAwareness = require('./visual-awareness');
|
|
8
|
+
const inspectTypes = require('../shared/inspect-types');
|
|
9
|
+
|
|
10
|
+
// ===== STATE =====
|
|
11
|
+
let inspectMode = false;
|
|
12
|
+
let currentRegions = [];
|
|
13
|
+
let windowContext = null;
|
|
14
|
+
let actionTraces = [];
|
|
15
|
+
let selectedRegionId = null;
|
|
16
|
+
|
|
17
|
+
const MAX_ACTION_TRACES = 100;
|
|
18
|
+
|
|
19
|
+
// ===== INSPECT MODE MANAGEMENT =====
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Enable or disable inspect mode
|
|
23
|
+
* @param {boolean} enabled - Whether inspect mode should be enabled
|
|
24
|
+
*/
|
|
25
|
+
function setInspectMode(enabled) {
|
|
26
|
+
inspectMode = enabled;
|
|
27
|
+
if (!enabled) {
|
|
28
|
+
// Clear all state when disabling inspect mode
|
|
29
|
+
clearRegions();
|
|
30
|
+
}
|
|
31
|
+
return inspectMode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if inspect mode is active
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
function isInspectModeActive() {
|
|
39
|
+
return inspectMode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ===== REGION MANAGEMENT =====
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Update inspect regions from various sources
|
|
46
|
+
* @param {Object[]} rawRegions - Raw region data from detection
|
|
47
|
+
* @param {string} source - Source of detection (accessibility, ocr, heuristic)
|
|
48
|
+
* @returns {Object[]} Processed regions
|
|
49
|
+
*/
|
|
50
|
+
function updateRegions(rawRegions, source = 'unknown') {
|
|
51
|
+
if (!Array.isArray(rawRegions)) return [...currentRegions];
|
|
52
|
+
|
|
53
|
+
// Convert raw regions to inspect regions
|
|
54
|
+
// Note: Accessibility API coordinates are already in screen space,
|
|
55
|
+
// so no DPI scaling is needed here. Scale factor is stored in
|
|
56
|
+
// windowContext for AI reference.
|
|
57
|
+
const newRegions = rawRegions
|
|
58
|
+
.filter(r => r && (r.bounds || (r.x !== undefined && r.y !== undefined)))
|
|
59
|
+
.map(r => {
|
|
60
|
+
const bounds = r.bounds || { x: r.x, y: r.y, width: r.width || 0, height: r.height || 0 };
|
|
61
|
+
|
|
62
|
+
return inspectTypes.createInspectRegion({
|
|
63
|
+
...r,
|
|
64
|
+
bounds: {
|
|
65
|
+
x: Math.round(bounds.x || bounds.X || 0),
|
|
66
|
+
y: Math.round(bounds.y || bounds.Y || 0),
|
|
67
|
+
width: Math.round(bounds.width || bounds.Width || 0),
|
|
68
|
+
height: Math.round(bounds.height || bounds.Height || 0)
|
|
69
|
+
},
|
|
70
|
+
source,
|
|
71
|
+
confidence: r.confidence || calculateConfidence(r, source)
|
|
72
|
+
});
|
|
73
|
+
})
|
|
74
|
+
.filter(r => r.bounds.width > 0 && r.bounds.height > 0);
|
|
75
|
+
|
|
76
|
+
// Merge with existing regions (prefer newer, dedupe by overlap)
|
|
77
|
+
currentRegions = mergeRegions(currentRegions, newRegions);
|
|
78
|
+
|
|
79
|
+
return [...currentRegions];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Clear all regions
|
|
84
|
+
*/
|
|
85
|
+
function clearRegions() {
|
|
86
|
+
currentRegions = [];
|
|
87
|
+
selectedRegionId = null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get current inspect regions
|
|
92
|
+
* @returns {Object[]} Copy of current regions array
|
|
93
|
+
*/
|
|
94
|
+
function getRegions() {
|
|
95
|
+
// Return a shallow copy to prevent external mutations
|
|
96
|
+
return [...currentRegions];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Select a region by ID
|
|
101
|
+
* @param {string} regionId - ID of region to select
|
|
102
|
+
* @returns {Object|null} Selected region or null
|
|
103
|
+
*/
|
|
104
|
+
function selectRegion(regionId) {
|
|
105
|
+
const region = currentRegions.find(r => r.id === regionId);
|
|
106
|
+
if (region) {
|
|
107
|
+
selectedRegionId = regionId;
|
|
108
|
+
}
|
|
109
|
+
return region;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get currently selected region
|
|
114
|
+
* @returns {Object|null}
|
|
115
|
+
*/
|
|
116
|
+
function getSelectedRegion() {
|
|
117
|
+
return currentRegions.find(r => r.id === selectedRegionId) || null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Find region at a specific point
|
|
122
|
+
* @param {number} x - X coordinate
|
|
123
|
+
* @param {number} y - Y coordinate
|
|
124
|
+
* @returns {Object|null}
|
|
125
|
+
*/
|
|
126
|
+
function findRegionAt(x, y) {
|
|
127
|
+
return inspectTypes.findRegionAtPoint(x, y, currentRegions);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ===== WINDOW CONTEXT =====
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Update window context from active window info
|
|
134
|
+
* @param {Object} windowInfo - Window information
|
|
135
|
+
*/
|
|
136
|
+
async function updateWindowContext(windowInfo = null) {
|
|
137
|
+
if (!windowInfo) {
|
|
138
|
+
// Fetch from visual awareness
|
|
139
|
+
windowInfo = await visualAwareness.getActiveWindow();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (windowInfo && !windowInfo.error) {
|
|
143
|
+
const scaleFactor = getScaleFactor();
|
|
144
|
+
windowContext = inspectTypes.createWindowContext({
|
|
145
|
+
...windowInfo,
|
|
146
|
+
scaleFactor
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return windowContext;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get current window context
|
|
155
|
+
* @returns {Object|null}
|
|
156
|
+
*/
|
|
157
|
+
function getWindowContext() {
|
|
158
|
+
return windowContext;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get display scale factor
|
|
163
|
+
* @returns {number}
|
|
164
|
+
*/
|
|
165
|
+
function getScaleFactor() {
|
|
166
|
+
try {
|
|
167
|
+
return screen.getPrimaryDisplay().scaleFactor || 1;
|
|
168
|
+
} catch (e) {
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ===== ACTION TRACING =====
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Record an action for tracing
|
|
177
|
+
* @param {Object} action - Action data
|
|
178
|
+
* @param {string} [targetId] - ID of target region
|
|
179
|
+
* @returns {Object} Action trace
|
|
180
|
+
*/
|
|
181
|
+
function recordAction(action, targetId = null) {
|
|
182
|
+
const trace = inspectTypes.createActionTrace({
|
|
183
|
+
type: action.type,
|
|
184
|
+
targetId: targetId || action.targetId,
|
|
185
|
+
x: action.x || 0,
|
|
186
|
+
y: action.y || 0,
|
|
187
|
+
outcome: 'pending'
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
actionTraces.push(trace);
|
|
191
|
+
|
|
192
|
+
// Trim history
|
|
193
|
+
while (actionTraces.length > MAX_ACTION_TRACES) {
|
|
194
|
+
actionTraces.shift();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return trace;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Update action outcome
|
|
202
|
+
* @param {string} actionId - ID of action to update
|
|
203
|
+
* @param {string} outcome - New outcome (success, failed)
|
|
204
|
+
*/
|
|
205
|
+
function updateActionOutcome(actionId, outcome) {
|
|
206
|
+
const trace = actionTraces.find(t => t.actionId === actionId);
|
|
207
|
+
if (trace) {
|
|
208
|
+
trace.outcome = outcome;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get action traces
|
|
214
|
+
* @param {number} [limit] - Max traces to return
|
|
215
|
+
* @returns {Object[]}
|
|
216
|
+
*/
|
|
217
|
+
function getActionTraces(limit = 10) {
|
|
218
|
+
return actionTraces.slice(-limit);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ===== AI CONTEXT GENERATION =====
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Generate AI context payload including inspect regions and window context
|
|
225
|
+
* @param {Object} options - Options for context generation
|
|
226
|
+
* @returns {Object} AI context payload
|
|
227
|
+
*/
|
|
228
|
+
function generateAIContext(options = {}) {
|
|
229
|
+
const { maxRegions = 50, includeTraces = true } = options;
|
|
230
|
+
|
|
231
|
+
// Format regions for AI
|
|
232
|
+
const formattedRegions = currentRegions
|
|
233
|
+
.slice(0, maxRegions)
|
|
234
|
+
.map(r => inspectTypes.formatRegionForAI(r));
|
|
235
|
+
|
|
236
|
+
const context = {
|
|
237
|
+
inspectMode: inspectMode,
|
|
238
|
+
windowContext: windowContext ? {
|
|
239
|
+
appName: windowContext.appName,
|
|
240
|
+
windowTitle: windowContext.windowTitle,
|
|
241
|
+
bounds: windowContext.bounds,
|
|
242
|
+
scaleFactor: windowContext.scaleFactor
|
|
243
|
+
} : null,
|
|
244
|
+
regions: formattedRegions,
|
|
245
|
+
regionCount: currentRegions.length,
|
|
246
|
+
selectedRegion: getSelectedRegion() ? inspectTypes.formatRegionForAI(getSelectedRegion()) : null
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
if (includeTraces) {
|
|
250
|
+
context.recentActions = getActionTraces(5);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return context;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Generate inspect instructions for AI system prompt
|
|
258
|
+
* @returns {string}
|
|
259
|
+
*/
|
|
260
|
+
function generateAIInstructions() {
|
|
261
|
+
if (!inspectMode || currentRegions.length === 0) {
|
|
262
|
+
return '';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return `
|
|
266
|
+
## Inspect Mode Active
|
|
267
|
+
|
|
268
|
+
You have access to detected UI regions. Each region has:
|
|
269
|
+
- **id**: Unique identifier for targeting
|
|
270
|
+
- **label**: Human-readable name
|
|
271
|
+
- **role**: UI role (button, textbox, etc.)
|
|
272
|
+
- **center**: Click coordinates {x, y}
|
|
273
|
+
- **confidence**: Detection confidence (0-1)
|
|
274
|
+
|
|
275
|
+
**IMPORTANT**: When clicking detected regions:
|
|
276
|
+
1. Use the region's center coordinates for highest accuracy
|
|
277
|
+
2. If confidence < 0.7, verify with the user before clicking
|
|
278
|
+
3. Reference regions by their label or id in your explanations
|
|
279
|
+
|
|
280
|
+
Current regions available: ${currentRegions.length}
|
|
281
|
+
`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ===== REGION DETECTION INTEGRATION =====
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Detect regions from current screen using available methods
|
|
288
|
+
* @param {Object} options - Detection options
|
|
289
|
+
* @returns {Object} Detection results
|
|
290
|
+
*/
|
|
291
|
+
async function detectRegions(options = {}) {
|
|
292
|
+
const results = {
|
|
293
|
+
regions: [],
|
|
294
|
+
sources: [],
|
|
295
|
+
timestamp: Date.now()
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
// Try accessibility API first
|
|
300
|
+
const uiElements = await visualAwareness.detectUIElements({ depth: 3 });
|
|
301
|
+
if (uiElements.elements && uiElements.elements.length > 0) {
|
|
302
|
+
updateRegions(
|
|
303
|
+
uiElements.elements.map(e => ({
|
|
304
|
+
label: e.Name || e.ClassName || '',
|
|
305
|
+
role: e.ControlType?.replace('ControlType.', '') || 'element',
|
|
306
|
+
bounds: e.Bounds,
|
|
307
|
+
confidence: e.IsEnabled ? 0.9 : 0.6
|
|
308
|
+
})),
|
|
309
|
+
'accessibility'
|
|
310
|
+
);
|
|
311
|
+
results.sources.push('accessibility');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Update window context
|
|
315
|
+
await updateWindowContext();
|
|
316
|
+
|
|
317
|
+
// Return copy of regions to prevent external mutation
|
|
318
|
+
results.regions = [...currentRegions];
|
|
319
|
+
results.windowContext = windowContext;
|
|
320
|
+
|
|
321
|
+
} catch (error) {
|
|
322
|
+
console.error('[INSPECT] Region detection error:', error);
|
|
323
|
+
results.error = error.message;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return results;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ===== HELPER FUNCTIONS =====
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Calculate confidence based on source and properties
|
|
333
|
+
* @param {Object} region - Region data
|
|
334
|
+
* @param {string} source - Detection source
|
|
335
|
+
* @returns {number} Confidence 0-1
|
|
336
|
+
*/
|
|
337
|
+
function calculateConfidence(region, source) {
|
|
338
|
+
let base = 0.5;
|
|
339
|
+
|
|
340
|
+
// Source-based confidence
|
|
341
|
+
if (source === 'accessibility') base = 0.85;
|
|
342
|
+
else if (source === 'ocr') base = 0.7;
|
|
343
|
+
else if (source === 'heuristic') base = 0.5;
|
|
344
|
+
|
|
345
|
+
// Boost for having label/text
|
|
346
|
+
if (region.label || region.Name) base = Math.min(1, base + 0.1);
|
|
347
|
+
if (region.text || region.Value) base = Math.min(1, base + 0.05);
|
|
348
|
+
|
|
349
|
+
// Boost for known roles
|
|
350
|
+
const knownRoles = ['button', 'textbox', 'checkbox', 'link', 'menuitem'];
|
|
351
|
+
const role = (region.role || region.ControlType || '').toLowerCase();
|
|
352
|
+
if (knownRoles.some(r => role.includes(r))) {
|
|
353
|
+
base = Math.min(1, base + 0.1);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return Math.round(base * 100) / 100;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Merge regions, preferring newer and deduping overlaps
|
|
361
|
+
* @param {Object[]} existing - Existing regions
|
|
362
|
+
* @param {Object[]} incoming - New regions
|
|
363
|
+
* @returns {Object[]} Merged regions
|
|
364
|
+
*/
|
|
365
|
+
function mergeRegions(existing, incoming) {
|
|
366
|
+
const merged = [];
|
|
367
|
+
const usedExisting = new Set();
|
|
368
|
+
const addedIds = new Set();
|
|
369
|
+
|
|
370
|
+
// Add incoming regions, checking for overlaps with existing
|
|
371
|
+
for (const inc of incoming) {
|
|
372
|
+
let isDupe = false;
|
|
373
|
+
for (const ex of existing) {
|
|
374
|
+
if (usedExisting.has(ex.id)) continue; // Skip already processed existing regions
|
|
375
|
+
|
|
376
|
+
if (regionsOverlap(inc, ex, 0.8)) {
|
|
377
|
+
// Significant overlap - prefer higher confidence
|
|
378
|
+
const winner = inc.confidence >= ex.confidence ? inc : ex;
|
|
379
|
+
if (!addedIds.has(winner.id)) {
|
|
380
|
+
merged.push(winner);
|
|
381
|
+
addedIds.add(winner.id);
|
|
382
|
+
}
|
|
383
|
+
usedExisting.add(ex.id);
|
|
384
|
+
isDupe = true;
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (!isDupe && !addedIds.has(inc.id)) {
|
|
389
|
+
merged.push(inc);
|
|
390
|
+
addedIds.add(inc.id);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Add remaining existing regions not overlapping
|
|
395
|
+
for (const ex of existing) {
|
|
396
|
+
if (!usedExisting.has(ex.id) && !addedIds.has(ex.id)) {
|
|
397
|
+
const hasOverlap = incoming.some(inc => regionsOverlap(ex, inc, 0.5));
|
|
398
|
+
if (!hasOverlap) {
|
|
399
|
+
merged.push(ex);
|
|
400
|
+
addedIds.add(ex.id);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return merged;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Check if two regions significantly overlap
|
|
410
|
+
* @param {Object} r1 - First region
|
|
411
|
+
* @param {Object} r2 - Second region
|
|
412
|
+
* @param {number} threshold - Overlap threshold (0-1)
|
|
413
|
+
* @returns {boolean}
|
|
414
|
+
*/
|
|
415
|
+
function regionsOverlap(r1, r2, threshold = 0.5) {
|
|
416
|
+
const b1 = r1.bounds;
|
|
417
|
+
const b2 = r2.bounds;
|
|
418
|
+
|
|
419
|
+
const x1 = Math.max(b1.x, b2.x);
|
|
420
|
+
const y1 = Math.max(b1.y, b2.y);
|
|
421
|
+
const x2 = Math.min(b1.x + b1.width, b2.x + b2.width);
|
|
422
|
+
const y2 = Math.min(b1.y + b1.height, b2.y + b2.height);
|
|
423
|
+
|
|
424
|
+
if (x2 <= x1 || y2 <= y1) return false;
|
|
425
|
+
|
|
426
|
+
const intersectArea = (x2 - x1) * (y2 - y1);
|
|
427
|
+
const r1Area = b1.width * b1.height;
|
|
428
|
+
const r2Area = b2.width * b2.height;
|
|
429
|
+
const minArea = Math.min(r1Area, r2Area);
|
|
430
|
+
|
|
431
|
+
// Handle zero area case
|
|
432
|
+
if (minArea <= 0) return false;
|
|
433
|
+
|
|
434
|
+
return intersectArea / minArea >= threshold;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ===== EXPORTS =====
|
|
438
|
+
module.exports = {
|
|
439
|
+
// Mode management
|
|
440
|
+
setInspectMode,
|
|
441
|
+
isInspectModeActive,
|
|
442
|
+
|
|
443
|
+
// Region management
|
|
444
|
+
updateRegions,
|
|
445
|
+
clearRegions,
|
|
446
|
+
getRegions,
|
|
447
|
+
selectRegion,
|
|
448
|
+
getSelectedRegion,
|
|
449
|
+
findRegionAt,
|
|
450
|
+
|
|
451
|
+
// Window context
|
|
452
|
+
updateWindowContext,
|
|
453
|
+
getWindowContext,
|
|
454
|
+
getScaleFactor,
|
|
455
|
+
|
|
456
|
+
// Action tracing
|
|
457
|
+
recordAction,
|
|
458
|
+
updateActionOutcome,
|
|
459
|
+
getActionTraces,
|
|
460
|
+
|
|
461
|
+
// AI integration
|
|
462
|
+
generateAIContext,
|
|
463
|
+
generateAIInstructions,
|
|
464
|
+
|
|
465
|
+
// Detection
|
|
466
|
+
detectRegions
|
|
467
|
+
};
|