feat: 接入本地执行内核与运行面板
This commit is contained in:
1 parent
32ca0a1af8
commit
6a2cc24898
23 files changed
+2937
-41
No files matched your search
@@ -0,0 +1,134 @@
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
import { browserSession } from "../browser/session";
|
||||
import type { BrowserLike, RunContext } from "../engine/types";
|
||||
|
||||
async function session(ctx: RunContext): Promise<BrowserLike> {
|
||||
if (!ctx.browser) ctx.browser = await browserSession();
|
||||
return ctx.browser;
|
||||
}
|
||||
|
||||
function selectorOf(args: Record<string, unknown>): unknown {
|
||||
const selector = args.uiSelector ?? args.selector ?? args.target ?? args.uiWindowSelector;
|
||||
if (selector === undefined || selector === null || selector === "") {
|
||||
throw new RuntimeError("validation_error", "元素选择器为空", {
|
||||
recommendation: "填写 css:#id、text:提交、aria:button=提交 或使用拾取器"
|
||||
});
|
||||
}
|
||||
return selector;
|
||||
}
|
||||
|
||||
register(["WebBrowser_CreateV3", "WebBrowser_Create", "WebBrowser_CreateBackground"], async (ctx, node, args) => {
|
||||
const browser = await session(ctx);
|
||||
const url = String(args.url ?? "");
|
||||
if (url) {
|
||||
const result = await browser.navigate(url);
|
||||
ctx.log(`已打开 ${result.url}`, "info", node.id);
|
||||
return result;
|
||||
}
|
||||
return { url: await browser.currentUrl(), title: "" };
|
||||
});
|
||||
|
||||
register(["WebBrowser_GoUrl", "WebBrowser_Navigate"], async (ctx, node, args) => {
|
||||
const browser = await session(ctx);
|
||||
const url = String(args.url ?? "");
|
||||
if (!url) {
|
||||
throw new RuntimeError("validation_error", "跳转地址为空", {
|
||||
nodeId: node.id,
|
||||
componentId: node.componentId,
|
||||
recommendation: "填写要访问的 URL"
|
||||
});
|
||||
}
|
||||
const result = await browser.navigate(url);
|
||||
ctx.log(`已跳转 ${result.url}`, "info", node.id);
|
||||
return result;
|
||||
});
|
||||
|
||||
register("WebBrowser_Switch", async (ctx, node) => {
|
||||
await session(ctx);
|
||||
ctx.log("网页切换已由统一运行浏览器接管", "info", node.id);
|
||||
return true;
|
||||
});
|
||||
|
||||
register("WebBrowser_Close", async (ctx) => {
|
||||
if (ctx.browser) await ctx.browser.close();
|
||||
ctx.browser = undefined;
|
||||
return true;
|
||||
});
|
||||
|
||||
register(["Element_Click_V6", "Element_Click_V3", "Element_Click"], async (ctx, node, args) => {
|
||||
const browser = await session(ctx);
|
||||
const result = await browser.click(selectorOf(args));
|
||||
ctx.log(`已点击 ${(result as { selector?: string }).selector || ""}`, "info", node.id);
|
||||
return result;
|
||||
});
|
||||
|
||||
register(["Element_SetValue_V6", "Element_SetValue_V4", "Element_SetValue"], async (ctx, node, args) => {
|
||||
const browser = await session(ctx);
|
||||
const text = String(args.text ?? args.value ?? "");
|
||||
const result = await browser.type(selectorOf(args), text, true);
|
||||
ctx.log(`已输入 ${text.length > 40 ? `${text.slice(0, 40)}…` : text}`, "info", node.id);
|
||||
if (String(args.isEnter ?? "0") === "1" || String(args.isEnter) === "true") {
|
||||
await browser.runJs("document.activeElement && document.activeElement.blur()");
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
register(["Element_GetText_V5", "Element_GetText"], async (ctx, node, args) => {
|
||||
const browser = await session(ctx);
|
||||
const attrType = String(args.attr_type ?? "");
|
||||
const attrName = String(args.attr_name ?? "");
|
||||
const attribute = attrType && attrType !== "default" ? attrName || attrType : undefined;
|
||||
const text = await browser.getText(selectorOf(args), attribute);
|
||||
ctx.log(`读取到 ${text.length > 60 ? `${text.slice(0, 60)}…` : text}`, "info", node.id);
|
||||
return text;
|
||||
});
|
||||
|
||||
register(["Element_Exists", "Element_ElementExists"], async (ctx, node, args) => {
|
||||
const browser = await session(ctx);
|
||||
const exists = await browser.exists(selectorOf(args));
|
||||
ctx.log(exists ? "元素存在" : "元素不存在", "info", node.id);
|
||||
return exists;
|
||||
});
|
||||
|
||||
register(["Element_SelectOperation_v1", "Element_SelectOperation"], async (ctx, node, args) => {
|
||||
const browser = await session(ctx);
|
||||
const value = String(args.itemsToSelect ?? args.choose ?? args.value ?? "");
|
||||
const result = await browser.select(selectorOf(args), value);
|
||||
ctx.log(`已选择 ${value}`, "info", node.id);
|
||||
return result;
|
||||
});
|
||||
|
||||
register(["WebBrowser_RunJs", "WebBrowser_RunJsInIeByTitle"], async (ctx, node, args) => {
|
||||
const browser = await session(ctx);
|
||||
const script = String(args.jsCode ?? args.script ?? args.code ?? "");
|
||||
if (!script) throw new RuntimeError("validation_error", "JS 代码为空", { nodeId: node.id, componentId: node.componentId });
|
||||
return browser.runJs(script);
|
||||
});
|
||||
|
||||
register("WebBrowser_Screenshot", async (ctx, node, args) => {
|
||||
const browser = await session(ctx);
|
||||
const target = String(args.file_path ?? args.image_path ?? `${ctx.evidenceDir}/${node.id}.png`);
|
||||
const file = await browser.screenshot(target);
|
||||
ctx.log(`截图已保存 ${file}`, "info", node.id);
|
||||
return file;
|
||||
});
|
||||
|
||||
register("Image_Screenshot", async (ctx, node, args) => {
|
||||
const { desktopCapturer, screen } = await import("electron");
|
||||
const folder = String(args.outImagePath ?? args.image_path ?? ctx.evidenceDir);
|
||||
const name = String(args.outImageName ?? `${node.id}.png`);
|
||||
const { promises: fs } = await import("node:fs");
|
||||
const path = await import("node:path");
|
||||
const target = path.resolve(folder.endsWith(".png") ? folder : path.join(folder, name));
|
||||
const primary = screen.getPrimaryDisplay();
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ["screen"],
|
||||
thumbnailSize: { width: primary.size.width, height: primary.size.height }
|
||||
});
|
||||
const source = sources[0];
|
||||
if (!source) throw new RuntimeError("desktop_error", "无法获取屏幕截图");
|
||||
await fs.mkdir(path.dirname(target), { recursive: true });
|
||||
await fs.writeFile(target, source.thumbnail.toPNG());
|
||||
return target;
|
||||
});
|
||||
Reference in new issue
Block a user