feat: 画布式 RPA 桌面端 Day1(1016 目录/画布/容器/属性/持久化)
This commit is contained in:
commit
32ca0a1af8
34 files changed
+15141
No files matched your search
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* 从 Agent-Employee 的 1016 项目录源数据生成产品目录。
|
||||
*
|
||||
* 输入:
|
||||
* - shizai-catalog-data.js:997 厂商组件 + 16 原生控制节点
|
||||
* - componentsNew.json:厂商原始定义(补 vType / validatorRules / tip / displayParam)
|
||||
* 输出:
|
||||
* - resources/catalog.json:1016 全量 + 归一化字段 / 分组 / 控件类型
|
||||
*
|
||||
* 说明:这里只派生元数据,不包含厂商运行时、二进制或业务数据。
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = path.resolve(__dirname, "..");
|
||||
|
||||
const SRC_CATALOG = process.env.RPA_CATALOG_SOURCE ||
|
||||
"D:/code/Agent-Employee/code/frontend/apps/desktop/src/renderer/product-preview/pages/workflows/shizai-catalog-data.js";
|
||||
const SRC_COMPONENTS_NEW = process.env.RPA_COMPONENTS_NEW ||
|
||||
"D:/code/Agent-Employee/项目资料/Rea-AgentShizai-cankao/code/rpa-engine/componentDicts/componentsNew.json";
|
||||
const OUT_FILE = path.join(ROOT, "resources", "catalog.json");
|
||||
|
||||
const GROUPS = [
|
||||
{ id: "control", label: "编排控制", icon: "branch" },
|
||||
{ id: "trigger", label: "流程触发", icon: "trigger" },
|
||||
{ id: "app", label: "应用与系统", icon: "app" },
|
||||
{ id: "web", label: "网页自动化", icon: "web" },
|
||||
{ id: "office", label: "Office 与文档", icon: "table" },
|
||||
{ id: "data", label: "数据处理", icon: "database" },
|
||||
{ id: "file", label: "文件与传输", icon: "file" },
|
||||
{ id: "desktop", label: "桌面与键鼠", icon: "mouse" },
|
||||
{ id: "vision", label: "图像与 OCR", icon: "scan" },
|
||||
{ id: "message", label: "网络与消息", icon: "send" },
|
||||
{ id: "mobile", label: "移动端与设备", icon: "mobile" }
|
||||
];
|
||||
|
||||
const PACKAGE_GROUPS = {
|
||||
app: new Set(["App", "System", "Basic", "CustomConfig", "ScreenControl"]),
|
||||
web: new Set(["WebBrowser", "Element"]),
|
||||
office: new Set(["Excel", "Word", "PDF", "GoogleTable", "FileTypeConversion"]),
|
||||
data: new Set(["Array", "Bivartable", "Collection", "CSV", "Database", "Databook", "Dict", "Ini", "JSON", "List", "Math", "Regex", "StrUtil", "Time"]),
|
||||
file: new Set(["File", "FTP"]),
|
||||
desktop: new Set(["Clipboard", "Dialog", "Keyboard", "Mouse", "Window", "SafeControlInput"]),
|
||||
vision: new Set(["CV", "Image", "OCR", "OCR_BaiDu", "OcrOffline", "Mark"]),
|
||||
message: new Set(["DataService", "Http", "Mail", "MessageNotification", "NLP", "NLP_XunFei", "Scenario"]),
|
||||
mobile: new Set(["Mobile", "peripherals"])
|
||||
};
|
||||
|
||||
const CONTROL_LABELS = {
|
||||
condition: "条件表达式", var_name: "变量名称", var_value: "变量值", count_index: "计数变量",
|
||||
count_start_value: "开始值", count_end_value: "结束值", count_step: "步长", if_for_condition: "循环条件",
|
||||
return_value: "返回值", module_name: "子流程", call_type: "调用方式", fun_name: "方法", args: "输入映射",
|
||||
fun_version: "版本", is_async: "是否异步", array_name: "列表变量", reverse: "是否倒序", start_index: "开始索引",
|
||||
end_index: "结束索引", dictionary_name: "字典变量", event_type: "事件类型", uiSelector: "元素选择器",
|
||||
designated_info: "指定信息", designated_obj: "指定对象", browser_type: "浏览器类型", is_exits: "存在条件",
|
||||
monitor_type: "监控类型", monitor_frequency: "监控频率", input_data: "屏幕判断要求", log_process: "记录过程"
|
||||
};
|
||||
|
||||
const TRIGGERS = [
|
||||
{
|
||||
id: "Trigger.Manual", name: "人工发起", description: "由用户在运行页手动发起流程。",
|
||||
fields: [{ key: "entryName", label: "入口名称", displayType: "text-expression", defaultValue: "立即执行", required: true }]
|
||||
},
|
||||
{
|
||||
id: "Trigger.Schedule", name: "定时触发", description: "按工作日或时间计划创建运行请求。",
|
||||
fields: [
|
||||
{ key: "mode", label: "计划方式", displayType: "dropDownSelect", defaultValue: "工作日", required: true, options: ["工作日", "每天", "每周", "Cron 表达式"] },
|
||||
{ key: "time", label: "执行时间", displayType: "text-expression", defaultValue: "08:30", required: true },
|
||||
{ key: "timezone", label: "时区", displayType: "text-expression", defaultValue: "Asia/Shanghai", required: true }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "Trigger.FileArrival", name: "文件到达触发", description: "监听指定目录的新文件事件。",
|
||||
fields: [
|
||||
{ key: "folder", label: "监听目录", displayType: "selectFile", defaultValue: "D:\\业务文件\\待处理", required: true, widget: "folder" },
|
||||
{ key: "pattern", label: "文件匹配", displayType: "text-expression", defaultValue: "*.xlsx", required: true },
|
||||
{ key: "stableSeconds", label: "文件稳定等待", displayType: "number", defaultValue: "3", required: true, unit: "秒" }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
function cleanDefault(value) {
|
||||
if (value === null || value === undefined) return "";
|
||||
if (typeof value === "object") return "";
|
||||
const text = String(value);
|
||||
if (text === "None" || text === "null") return "";
|
||||
if (/^"(?:[^"\\]|\\.)*"$/.test(text)) {
|
||||
try { return JSON.parse(text); } catch { return text; }
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function widgetFor(displayType, hint) {
|
||||
if (hint) return hint;
|
||||
const t = String(displayType || "");
|
||||
if (/^hidden$/i.test(t)) return "hidden";
|
||||
if (/excelRange|chooseRegion|captureWindow|openWindowSelect|mouseRecord|screenShot|getScreenShot|screenshotSelectUi|clickPosition|collectData|drawDataTable|makeOcrTableSelector|cellRule|dateRange|searchRange/i.test(t)) return "region";
|
||||
if (/^code|jsonEditor|codeInput/i.test(t)) return "code";
|
||||
if (/encryption|designPassword/i.test(t)) return "secret";
|
||||
if (/comboSelector|selectUi|uiwindowselector|phoneSelector|verifyElement|newVerifyElement|cvselectUi|uiselectorOcr/i.test(t)) return "element";
|
||||
if (/selectFile|multiSelectFileOrDir|saveAsDialog|selectControllerFile/i.test(t)) return "file";
|
||||
if (/selectControllerFolder/i.test(t)) return "folder";
|
||||
if (/textArea|multipleTextArea|quillInput|logInput|multiInput|inputItemArray|inputCompose/i.test(t)) return "textarea";
|
||||
if (/checkbox|switchControl|multifileSwitch|placementCheckbox|introViewCheckbox|multifile$/i.test(t)) return "boolean";
|
||||
if (/dropDownSelect|dynamicSelect|browserTypeInput|databaseType|autoComplete|phoneSelector|remoteOptionSelect/i.test(t)) return "select";
|
||||
if (/radioGroup|radioLeft|radio$|buttonGroup|coloredRadio|radioInputNumber/i.test(t)) return "radio";
|
||||
if (/number|sliderBar|rangeInput|unitInput|inputInterval|movingSpeed|touchCoord|pageScroll|windowSize/i.test(t)) return "number";
|
||||
return "text";
|
||||
}
|
||||
|
||||
function fieldTypeFor(widget) {
|
||||
if (widget === "number") return "number";
|
||||
if (widget === "boolean") return "boolean";
|
||||
if (widget === "region" || widget === "element" || widget === "file" || widget === "folder") return "object";
|
||||
return "string";
|
||||
}
|
||||
|
||||
function packageName(component) {
|
||||
return String(component.baseType || component.id).split("_")[0];
|
||||
}
|
||||
|
||||
function groupFor(component) {
|
||||
const pkg = packageName(component);
|
||||
for (const [group, packages] of Object.entries(PACKAGE_GROUPS)) if (packages.has(pkg)) return group;
|
||||
const cats = new Set(component.categories || []);
|
||||
if ([...cats].some((x) => /web|browser|sap/i.test(x))) return "web";
|
||||
if ([...cats].some((x) => /excel|word|pdf/i.test(x))) return "office";
|
||||
if ([...cats].some((x) => /ocr|image|vision/i.test(x))) return "vision";
|
||||
if ([...cats].some((x) => /mobile|peripheral/i.test(x))) return "mobile";
|
||||
if ([...cats].some((x) => /file|ftp/i.test(x))) return "file";
|
||||
if ([...cats].some((x) => /mail|network|notification|nlp/i.test(x))) return "message";
|
||||
if ([...cats].some((x) => /window|cursor|input|controller|element/i.test(x))) return "desktop";
|
||||
return "data";
|
||||
}
|
||||
|
||||
function normalizeVendorField(field) {
|
||||
const widget = widgetFor(field.displayType, field.widget);
|
||||
const valueTypes = Array.isArray(field.valueTypes) ? [...field.valueTypes] : [];
|
||||
const result = {
|
||||
key: field.key,
|
||||
label: field.label || field.key,
|
||||
widget,
|
||||
type: fieldTypeFor(widget),
|
||||
displayType: field.displayType || "",
|
||||
required: Boolean(field.required),
|
||||
defaultValue: cleanDefault(field.defaultValue),
|
||||
placeholder: field.placeholder || "",
|
||||
description: field.description || "",
|
||||
advanced: Boolean(field.advanced),
|
||||
valueTypes
|
||||
};
|
||||
if (Array.isArray(field.options) && field.options.length) result.options = field.options;
|
||||
if (field.unit) result.unit = field.unit;
|
||||
return result;
|
||||
}
|
||||
|
||||
function loadCatalogSource() {
|
||||
const raw = fs.readFileSync(SRC_CATALOG, "utf8");
|
||||
const start = raw.indexOf("=");
|
||||
if (start < 0) throw new Error("catalog source has no assignment");
|
||||
let body = raw.slice(start + 1).trim();
|
||||
if (body.endsWith(";")) body = body.slice(0, -1);
|
||||
return JSON.parse(body);
|
||||
}
|
||||
|
||||
function loadComponentsNew() {
|
||||
if (!fs.existsSync(SRC_COMPONENTS_NEW)) return new Map();
|
||||
const list = JSON.parse(fs.readFileSync(SRC_COMPONENTS_NEW, "utf8"));
|
||||
const map = new Map();
|
||||
for (const item of list) map.set(item.type || item.baseType || item.id, item);
|
||||
return map;
|
||||
}
|
||||
|
||||
function enrichFromVendor(field, vendorComponent) {
|
||||
if (!vendorComponent) return field;
|
||||
const args = vendorComponent.definition?.args0 || [];
|
||||
const arg = args.find((a) => a.name === field.key);
|
||||
if (!arg) return field;
|
||||
const out = { ...field };
|
||||
if (!out.label && arg.label) out.label = arg.label;
|
||||
if (!out.description && arg.tip) out.description = arg.tip;
|
||||
if (arg.displayParam?.text && !out.label) out.label = arg.displayParam.text;
|
||||
if (arg.displayType && !out.displayType) out.displayType = arg.displayType;
|
||||
if (arg.isAdvanced && !out.advanced) out.advanced = true;
|
||||
const vTypes = Array.isArray(arg.vType) ? arg.vType.flatMap((v) => v.types || []) : [];
|
||||
if (vTypes.length) out.valueTypes = [...new Set([...out.valueTypes, ...vTypes])];
|
||||
const rules = arg.validatorRules?.compAttrValue;
|
||||
if (Array.isArray(rules) && rules.some((r) => Number(r.required) === 1)) out.required = true;
|
||||
if (Array.isArray(arg.displayParam?.properties) && arg.displayParam.properties.includes("openDirectory")) out.widget = "folder";
|
||||
return out;
|
||||
}
|
||||
|
||||
function outputOf(component, vendorComponent) {
|
||||
const first = component.outputFields?.[0];
|
||||
const name = cleanDefault(first?.defaultValue);
|
||||
const typeNames = first?.valueTypes || [];
|
||||
return {
|
||||
label: first?.label || "",
|
||||
key: first?.key || "",
|
||||
suggestedName: /^[A-Za-z_\u4e00-\u9fa5][\w\u4e00-\u9fa5]*$/.test(name) ? name : "",
|
||||
valueTypes: typeNames,
|
||||
fields: (component.outputFields || []).map((f) => ({
|
||||
key: f.key, label: f.label || f.key, defaultValue: cleanDefault(f.defaultValue), valueTypes: f.valueTypes || []
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeVendorComponent(component, vendorMap) {
|
||||
const group = groupFor(component);
|
||||
const vendor = vendorMap.get(component.id) || vendorMap.get(component.baseType);
|
||||
const fields = (component.inputFields || []).map((f) => enrichFromVendor(normalizeVendorField(f), vendor));
|
||||
const output = outputOf(component, vendor);
|
||||
const risk = ["web", "app", "desktop", "message", "mobile"].includes(group) ? "high" : "low";
|
||||
return {
|
||||
id: component.id,
|
||||
name: component.name,
|
||||
kind: "vendor",
|
||||
group,
|
||||
categories: component.categories || [],
|
||||
categorySignature: component.categorySignature || "",
|
||||
description: component.description || component.messageTemplate || "",
|
||||
messageTemplate: component.messageTemplate || "",
|
||||
baseType: component.baseType || component.id,
|
||||
functionName: component.functions?.[0]?.function || "",
|
||||
functionCalls: (component.functions || []).map((f) => ({ function: f.function, args: f.arguments || [], assignTo: f.assignTo || "" })),
|
||||
version: component.version || "",
|
||||
tags: component.tags || [],
|
||||
platformTag: component.platformTag || null,
|
||||
icon: group,
|
||||
risk,
|
||||
fields,
|
||||
output,
|
||||
slots: []
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeNativeNode(node) {
|
||||
return {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
kind: "native",
|
||||
group: "control",
|
||||
categories: node.categories || [],
|
||||
categorySignature: (node.categories || []).join("/"),
|
||||
description: node.description || "",
|
||||
messageTemplate: "",
|
||||
baseType: node.id,
|
||||
functionName: node.id,
|
||||
functionCalls: [],
|
||||
version: "",
|
||||
tags: [...(node.categories || []), node.kind || ""],
|
||||
platformTag: null,
|
||||
icon: "branch",
|
||||
risk: node.id.includes("ai_vision") ? "high" : "low",
|
||||
fields: (node.inputFields || []).map((key) => {
|
||||
const booleanField = ["reverse", "is_async", "is_exits", "log_process"].includes(key);
|
||||
return {
|
||||
key,
|
||||
label: CONTROL_LABELS[key] || key,
|
||||
widget: booleanField ? "boolean" : "text",
|
||||
type: booleanField ? "boolean" : "string",
|
||||
displayType: booleanField ? "checkbox" : "text-expression",
|
||||
required: key !== "log_process",
|
||||
defaultValue: "",
|
||||
placeholder: "",
|
||||
description: "",
|
||||
advanced: false,
|
||||
valueTypes: []
|
||||
};
|
||||
}),
|
||||
output: { label: "", key: "", suggestedName: node.outputFields?.[0] || "", valueTypes: [], fields: [] },
|
||||
slots: node.childSlots || [],
|
||||
nodeKind: node.kind || "statement"
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTrigger(trigger) {
|
||||
return {
|
||||
id: trigger.id,
|
||||
name: trigger.name,
|
||||
kind: "trigger",
|
||||
group: "trigger",
|
||||
categories: ["trigger"],
|
||||
categorySignature: "trigger",
|
||||
description: trigger.description,
|
||||
messageTemplate: "",
|
||||
baseType: "Trigger",
|
||||
functionName: trigger.id,
|
||||
functionCalls: [],
|
||||
version: "",
|
||||
tags: ["触发", trigger.name],
|
||||
platformTag: null,
|
||||
icon: "trigger",
|
||||
risk: "low",
|
||||
fields: trigger.fields.map((f) => normalizeVendorField(f)),
|
||||
output: { label: "", key: "", suggestedName: "", valueTypes: [], fields: [] },
|
||||
slots: []
|
||||
};
|
||||
}
|
||||
|
||||
function main() {
|
||||
const source = loadCatalogSource();
|
||||
const vendorMap = loadComponentsNew();
|
||||
|
||||
const vendorComponents = (source.components || []).map((c) => normalizeVendorComponent(c, vendorMap));
|
||||
const nativeNodes = (source.nativeControlNodes || []).map(normalizeNativeNode);
|
||||
const triggers = TRIGGERS.map(normalizeTrigger);
|
||||
const components = [...triggers, ...nativeNodes, ...vendorComponents];
|
||||
|
||||
const groupMap = new Map(GROUPS.map((g) => [g.id, { ...g, count: 0 }]));
|
||||
for (const c of components) {
|
||||
const g = groupMap.get(c.group) || groupMap.get("data");
|
||||
g.count += 1;
|
||||
}
|
||||
|
||||
const counts = {
|
||||
total: components.length,
|
||||
vendor: vendorComponents.length,
|
||||
native: nativeNodes.length,
|
||||
triggers: triggers.length,
|
||||
fields: components.reduce((sum, c) => sum + c.fields.length, 0)
|
||||
};
|
||||
|
||||
const catalog = {
|
||||
schemaVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
source: {
|
||||
catalog: path.basename(SRC_CATALOG),
|
||||
componentsNew: fs.existsSync(SRC_COMPONENTS_NEW) ? path.basename(SRC_COMPONENTS_NEW) : null
|
||||
},
|
||||
counts,
|
||||
groups: [...groupMap.values()],
|
||||
components
|
||||
};
|
||||
|
||||
fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true });
|
||||
fs.writeFileSync(OUT_FILE, JSON.stringify(catalog), "utf8");
|
||||
const size = fs.statSync(OUT_FILE).size;
|
||||
console.log(`catalog: ${counts.total} components (${counts.vendor} vendor + ${counts.native} native + ${counts.triggers} triggers)`);
|
||||
console.log(`fields: ${counts.fields}; groups: ${catalog.groups.map((g) => `${g.id}:${g.count}`).join(",")}`);
|
||||
console.log(`output: ${OUT_FILE} (${(size / 1024 / 1024).toFixed(2)} MB)`);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in new issue
Block a user