feat: 视觉能力去 Electron 化,经 HostBridge.screen 注入(迁移阶段1-A9)

- HostBridge 新增可选 screen 服务(listDisplays/primaryDisplay/dipToScreenPoint/
  displayMatching/captureDisplay),vision 3 处、cv-vision 2 处动态 import(electron)
  全部改为 requireScreenService();未注入时抛稳定的 host_unavailable
- demo 宿主 src/main/index.ts 用 desktopCapturer/screen 实现 screen 服务
- check-deps.mjs 升级:新增动态 import()/require() 扫描(旧正则实测 0 命中的盲区),
  合成用例验证命中,升级后真实源码 0 违规
- 包依赖闭包:补声明 @cantoo/pdf-lib、@napi-rs/canvas、pdfjs-dist
- cv-vision 测试从 vi.mock(electron) 改为宿主桥注入,11/11 通过
This commit is contained in:
yu committed 2026-11-06 10:53:42 +08:00
1 parent 1da00827ac
commit 1a20dd72d1
7 files changed
+161 -62

No files matched your search

+35 -3
View File
@@ -1,4 +1,4 @@
import { app, BrowserWindow, dialog, ipcMain, screen, shell } from "electron";
import { app, BrowserWindow, desktopCapturer, dialog, ipcMain, screen, shell } from "electron";
import path from "node:path";
import type { AppInfo, AppSettings, FlowDefinition, FlowSaveOptions, FlowSaveResult, FlowSummary, FlowsImportResult } from "../shared/types";
import {
@@ -24,7 +24,38 @@ import { logMain } from "./logger";
// 子流程仓库在宿主(flows.ts),包内能力经 host-bridge 取用(阶段 2 正式化 HostBridge)
setSubflowSource({ listFlows: async () => listFlows(), getFlow: async (id) => getFlow(id) });
// 宿主能力桥(决策点 4 最小面):人机交互窗口工厂 / DPI 坐标换算 / AI 配置读取
// 宿主屏幕服务(迁移阶段1-A9):视觉能力经宿主桥拿 desktopCapturer/screen,Core 零 Electron 依赖
const hostScreenService = {
listDisplays: () =>
screen.getAllDisplays().map((display) => ({
id: String(display.id),
bounds: display.bounds,
scaleFactor: display.scaleFactor || 1
})),
primaryDisplay: () => {
const display = screen.getPrimaryDisplay();
return { id: String(display.id), bounds: display.bounds, scaleFactor: display.scaleFactor || 1 };
},
dipToScreenPoint: (point: { x: number; y: number }) => screen.dipToScreenPoint(point),
displayMatching: (rect: { x: number; y: number; width: number; height: number }) => {
const display = screen.getDisplayMatching(rect);
return { id: String(display.id), bounds: display.bounds, scaleFactor: display.scaleFactor || 1 };
},
captureDisplay: async (displayId: string, wantWidth: number, wantHeight: number): Promise<Uint8Array> => {
const sources = await desktopCapturer.getSources({
types: ["screen"],
thumbnailSize: { width: wantWidth, height: wantHeight }
});
const source =
sources.find((item) => item.display_id === displayId) ||
sources.find((item) => item.thumbnail.getSize().width === wantWidth) ||
sources[0];
if (!source || source.thumbnail.isEmpty()) throw new Error(`无法获取显示器 ${displayId} 的屏幕截图`);
return source.thumbnail.toPNG();
}
};
// 宿主能力桥(决策点 4 最小面):人机交互窗口工厂 / DPI 坐标换算 / AI 配置读取 / 屏幕服务
setHostBridge({
createFormWindow: ({ title }) => {
const win = new BrowserWindow({
@@ -46,7 +77,8 @@ setHostBridge({
loadAiConfig: () => {
const ai = loadSettings().aiConfig;
return { baseUrl: ai.baseUrl ?? "", apiKey: ai.apiKey ?? "", model: ai.model ?? "", visionModel: ai.visionModel ?? "" };
}
},
screen: hostScreenService
});
let mainWindow: BrowserWindow | null = null;