From d22468e29f31d1f98c5b3cb707c5f5810c354dfd Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Mon, 2 Nov 2026 11:59:14 +0800 Subject: [PATCH] feat: remove capability execution status markers --- README.md | 2 +- scripts/app-system-tests.mjs | 10 +++++----- scripts/app-system-tests.test.mjs | 6 +++--- src/main/catalog.ts | 18 +++--------------- src/main/engine/runtime.error-route.test.ts | 4 ++-- src/main/engine/runtime.integration.test.ts | 1 - src/main/engine/runtime.ts | 2 +- src/renderer/src/App.tsx | 5 ++--- src/renderer/src/canvas/FlowCanvas.tsx | 1 - src/renderer/src/pages/SettingsPage.tsx | 11 ++--------- src/renderer/src/panels/Palette.tsx | 1 - src/renderer/src/panels/Properties.tsx | 2 -- src/renderer/src/styles/app.css | 18 ------------------ .../src/validation/flowValidation.test.ts | 13 ++++++------- src/renderer/src/validation/flowValidation.ts | 3 --- src/shared/types.ts | 1 - 16 files changed, 25 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index abc774f..fbe18a2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # RPA Studio -画布式 RPA 桌面端(Electron + React Flow)。把 1016 项能力目录拖到画布上,连线、配置、运行,生成可执行的 RPA 流程。 +画布式 RPA 桌面端(Electron + React Flow)。把 894 项可编排能力拖到画布上,连线、配置、运行,生成可执行的 RPA 流程。 > 当前状态:任务优先的编排工作台。默认进入“编排”任务列表,编辑任务后进入上下流向画布;组件库按分类树展示,设置为独立入口。 > 当前可编排能力:**894 / 894**(不接入项已分离备份;网页自动化、桌面自动化、数据、文件、办公、HTTP 等可直接编排)。 diff --git a/scripts/app-system-tests.mjs b/scripts/app-system-tests.mjs index 24bd32b..bfe9939 100644 --- a/scripts/app-system-tests.mjs +++ b/scripts/app-system-tests.mjs @@ -18,7 +18,7 @@ export function readBundle() { return { manifest, groups: manifest.groups.map(entry => ({ entry, flow: JSON.parse(fs.readFileSync(path.join(bundleRoot, entry.file), "utf8")) })) }; } -// 静态清单供离线校验;真实运行前再与 Electron 返回的 implemented 清单逐项比较。 +// 静态清单供离线校验;真实运行前再与运行时注册清单逐项比较。 export function staticCatalog() { const registered = new Set(); const directory = path.join(projectRoot, "src/main/capabilities"); @@ -35,7 +35,7 @@ export function staticCatalog() { visit(source); } const catalog = JSON.parse(fs.readFileSync(path.join(projectRoot, "resources/catalog.json"), "utf8")); - for (const c of catalog.components) c.implemented = containers.has(c.id) || registered.has(c.id) || registered.has(c.functionName); + for (const c of catalog.components) c.registered = containers.has(c.id) || registered.has(c.id) || registered.has(c.functionName); return catalog; } @@ -47,9 +47,9 @@ export async function validator() { export async function verifyBundle(catalog = staticCatalog()) { const { manifest, groups } = readBundle(); - const expected = catalog.components.filter(c => c.group === "app" && c.implemented).map(c => c.id).sort(); + const expected = catalog.components.filter(c => c.group === "app" && c.registered).map(c => c.id).sort(); const actual = groups.flatMap(g => g.entry.components).sort(); - if (groups.length !== 13 || actual.length !== 50 || new Set(actual).size !== 50 || JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error("流程包与当前 50 项可执行清单不一致,请重新生成并核对覆盖范围。"); + if (groups.length !== 13 || actual.length !== 50 || new Set(actual).size !== 50 || JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error("流程包与当前 50 项验收清单不一致,请重新生成并核对覆盖范围。"); const validateFlow = await validator(); const validation = {}; for (const { flow, entry } of groups) { @@ -323,7 +323,7 @@ export function makeReport(evidence) { const counts = Object.fromEntries(priority.map(s => [s, rows.filter(r => r.status === s).length])); const escape = value => String(value ?? "无执行记录").replace(/\|/g, "\\|").replace(/[\r\n]+/g, " ").slice(0, 1200); const recordedAt = evidence.generatedAt ? new Intl.DateTimeFormat("zh-CN", { timeZone: "Asia/Shanghai", dateStyle: "medium", timeStyle: "medium", hour12: false }).format(new Date(evidence.generatedAt)) + "(北京时间)" : "未执行"; - const lines = ["# 应用与系统组件测试报告", "", `记录时间:${recordedAt}。覆盖 50 项可执行组件,13 组任务。`, "", + const lines = ["# 应用与系统组件测试报告", "", `记录时间:${recordedAt}。覆盖 50 项验收组件,13 组任务。`, "", `按组件统计:通过 ${counts.PASS},失败 ${counts.FAIL},阻塞 ${counts.BLOCKED},待人工确认 ${counts.MANUAL},未运行 ${counts.UNRUN}。`, "", "状态依据是断言、前置校验和人工证据,节点执行成功不直接代表功能通过。未执行手动系统操作。", "", "| 组 | 组件 ID | 名称 | 状态 |", "|---|---|---|---|"]; diff --git a/scripts/app-system-tests.test.mjs b/scripts/app-system-tests.test.mjs index a55e7a0..fa63ab3 100644 --- a/scripts/app-system-tests.test.mjs +++ b/scripts/app-system-tests.test.mjs @@ -11,7 +11,7 @@ import { validateFlow } from "../src/renderer/src/validation/flowValidation.ts"; const containers = new Set(["logic_control_try_catch", "logic_control_if_else"]); const catalog = JSON.parse(fs.readFileSync("resources/catalog.json", "utf8")); -for (const c of catalog.components) c.implemented = containers.has(c.id) || hasCapability(c.id) || hasCapability(c.functionName); +const isRegistered = (component) => containers.has(component.id) || hasCapability(component.id) || hasCapability(component.functionName); const componentMap = new Map(catalog.components.map(c => [c.id, c])); const temporary = []; afterEach(() => { for (const dir of temporary.splice(0)) fs.rmSync(dir, { recursive: true, force: true }); }); @@ -29,11 +29,11 @@ async function execute(number, change) { } describe("应用与系统测试流程包", () => { - it("覆盖真实注册的全部 50 项,13 组无重复归属,不覆盖待接入组件", () => { + it("覆盖真实注册的全部 50 项,13 组无重复归属,不覆盖备份组件", () => { const suite = buildSuite(); expect(suite).toHaveLength(13); const covered = suite.flatMap(group => group.entry.components).sort(); - const executable = catalog.components.filter(c => c.group === "app" && c.implemented).map(c => c.id).sort(); + const executable = catalog.components.filter(c => c.group === "app" && isRegistered(c)).map(c => c.id).sort(); expect(covered).toHaveLength(50); expect(new Set(covered).size).toBe(50); expect(covered).toEqual(executable); diff --git a/src/main/catalog.ts b/src/main/catalog.ts index 931dde9..d6d5053 100644 --- a/src/main/catalog.ts +++ b/src/main/catalog.ts @@ -2,21 +2,10 @@ import { app } from "electron"; import fs from "node:fs"; import path from "node:path"; import type { Catalog, CatalogComponent } from "../shared/types"; -import { hasCapability } from "./engine/registry"; let cached: Catalog | null = null; let excludedIds: Set | null = null; -const RUNTIME_CONTAINERS = new Set([ - "logic_control_if_else", - "logic_control_count_for", - "logic_control_array_for_v3", - "logic_control_dictionary_for_v1", - "logic_control_while", - "infinite_loop", - "logic_control_try_catch" -]); - function resolveCatalogPath(): string { const candidates = [ process.env.RPA_CATALOG_PATH, @@ -64,10 +53,9 @@ export function loadCatalog(): Catalog { const raw = fs.readFileSync(catalogPath(), "utf8"); const catalog = JSON.parse(raw) as Catalog; const excluded = loadExcludedIds(); - catalog.components = catalog.components.filter((component) => !excluded.has(component.id)).map((component: CatalogComponent) => ({ - ...component, - implemented: RUNTIME_CONTAINERS.has(component.id) || hasCapability(component.id) || hasCapability(component.functionName) - })); + catalog.components = catalog.components + .filter((component) => !excluded.has(component.id)) + .map((component: CatalogComponent) => component); catalog.counts.total = catalog.components.length; catalog.counts.vendor = catalog.components.filter((component) => component.kind === "vendor").length; catalog.counts.native = catalog.components.filter((component) => component.kind === "native").length; diff --git a/src/main/engine/runtime.error-route.test.ts b/src/main/engine/runtime.error-route.test.ts index 8b6b71a..f33d4d4 100644 --- a/src/main/engine/runtime.error-route.test.ts +++ b/src/main/engine/runtime.error-route.test.ts @@ -8,8 +8,8 @@ function component(id: string): CatalogComponent { const value = { id, name: id, kind: "vendor", group: "data", categories: [], categorySignature: "", description: "", messageTemplate: "", baseType: id, functionName: id, functionCalls: [], version: "", tags: [], platformTag: null, - icon: "data", risk: "low", fields: [], output: { label: "", key: "", suggestedName: "", valueTypes: [], fields: [] }, slots: [], implemented: true - } as CatalogComponent; + icon: "data", risk: "low", fields: [], output: { label: "", key: "", suggestedName: "", valueTypes: [], fields: [] }, slots: [] + } as unknown as CatalogComponent; components.set(id, value); return value; } diff --git a/src/main/engine/runtime.integration.test.ts b/src/main/engine/runtime.integration.test.ts index 3768d11..d1f4d24 100644 --- a/src/main/engine/runtime.integration.test.ts +++ b/src/main/engine/runtime.integration.test.ts @@ -83,7 +83,6 @@ function testComponent(id: string): CatalogComponent { fields: [], output: { label: "", key: "", suggestedName: "", valueTypes: [], fields: [] }, slots: [], - implemented: true }; } diff --git a/src/main/engine/runtime.ts b/src/main/engine/runtime.ts index d3e8edd..05b54fb 100644 --- a/src/main/engine/runtime.ts +++ b/src/main/engine/runtime.ts @@ -265,7 +265,7 @@ async function executeNode(node: FlowNode, ctx: RunContext, states: Map - 正在加载 1016 项组件目录… + 正在加载组件目录… ); } - const executable = catalog.components.filter((item) => item.implemented).length; const refreshFlows = async () => { try { @@ -214,7 +213,7 @@ export function App() { 本地执行 · 离线 - {catalog.counts.total} 目录 · {executable} 可执行 + {catalog.counts.total} 个可编排组件 diff --git a/src/renderer/src/canvas/FlowCanvas.tsx b/src/renderer/src/canvas/FlowCanvas.tsx index 9e52c4b..438b7db 100644 --- a/src/renderer/src/canvas/FlowCanvas.tsx +++ b/src/renderer/src/canvas/FlowCanvas.tsx @@ -116,7 +116,6 @@ function CanvasInner({ catalog, onAddAt }: { catalog: Catalog; onAddAt: (compone fields: [], output: { label: "", key: "", suggestedName: "", valueTypes: [], fields: [] }, slots: [], - implemented: false }; return { id: node.id, diff --git a/src/renderer/src/pages/SettingsPage.tsx b/src/renderer/src/pages/SettingsPage.tsx index a95c43a..4164b60 100644 --- a/src/renderer/src/pages/SettingsPage.tsx +++ b/src/renderer/src/pages/SettingsPage.tsx @@ -67,12 +67,6 @@ export function SettingsPage({ catalog }: { catalog: Catalog }) { }; - const executable = catalog.components.filter((item) => item.implemented).length; - const groups = useMemo(() => catalog.groups.map((group) => { - const components = catalog.components.filter((component) => component.group === group.id); - const connected = components.filter((component) => component.implemented).length; - return { ...group, connected, pending: components.length - connected }; - }), [catalog]); return (
@@ -96,10 +90,9 @@ export function SettingsPage({ catalog }: { catalog: Catalog }) { {tab === "catalog" && ( <> - -
目录总数
{catalog.counts.total} 项
已接入执行器
{executable} 项({((executable / catalog.counts.total) * 100).toFixed(1)}%)
待接入
{catalog.counts.total - executable} 项
注册处理器 ID
{handlers?.count ?? "—"}
+ +
可编排组件
{catalog.counts.total} 项
注册处理器 ID
{handlers?.count ?? "—"}
备份清单
resources/excluded.json
-

分类覆盖

{groups.map((group) =>
{group.label}{group.connected} 已接入{group.pending} 待接入
)}
)} diff --git a/src/renderer/src/panels/Palette.tsx b/src/renderer/src/panels/Palette.tsx index 802a7d1..f4813c9 100644 --- a/src/renderer/src/panels/Palette.tsx +++ b/src/renderer/src/panels/Palette.tsx @@ -148,7 +148,6 @@ function PaletteRow({ component, onAdd }: { component: CatalogComponent; onAdd: > {component.name}{component.id} - ); } diff --git a/src/renderer/src/panels/Properties.tsx b/src/renderer/src/panels/Properties.tsx index 3403e20..08be64e 100644 --- a/src/renderer/src/panels/Properties.tsx +++ b/src/renderer/src/panels/Properties.tsx @@ -866,8 +866,6 @@ function RunTab({ node, component }: { node: FlowNode; component: Catalog["compo 未运行
- 执行器 -已接入
超时 diff --git a/src/renderer/src/styles/app.css b/src/renderer/src/styles/app.css index 8767277..72543b1 100644 --- a/src/renderer/src/styles/app.css +++ b/src/renderer/src/styles/app.css @@ -546,11 +546,6 @@ textarea:focus-visible { box-shadow: 0 0 0 3px rgba(7, 148, 85, 0.08); } -.status-dot.pending { - background: #b9c2cd; - box-shadow: 0 0 0 3px rgba(120, 132, 148, 0.08); -} - .palette-more { padding: 8px 14px 12px; display: flex; @@ -911,19 +906,6 @@ textarea:focus-visible { white-space: nowrap; } -.pending-badge { - margin-left: auto; - height: 16px; - padding: 0 5px; - border-radius: 5px; - background: #f0f3f7; - color: #8a96a6; - font-size: 9.5px; - font-weight: 650; - line-height: 16px; - white-space: nowrap; -} - .rpa-node-title { color: var(--ink); font-size: 12.5px; diff --git a/src/renderer/src/validation/flowValidation.test.ts b/src/renderer/src/validation/flowValidation.test.ts index ba6d5b9..93d526d 100644 --- a/src/renderer/src/validation/flowValidation.test.ts +++ b/src/renderer/src/validation/flowValidation.test.ts @@ -5,8 +5,8 @@ import { fieldIsActive, validateFlow } from "./flowValidation"; const catalog = { schemaVersion: 1, generatedAt: "", counts: { total: 2, vendor: 1, native: 0, triggers: 1 }, groups: [], components: [ - { id: "Trigger.Manual", name: "人工发起", kind: "trigger", group: "trigger", fields: [], slots: [], implemented: true }, - { id: "Test.Action", name: "测试动作", kind: "vendor", group: "data", fields: [{ key: "value", label: "内容", required: true, widget: "text", defaultValue: "" }], slots: [], implemented: false } + { id: "Trigger.Manual", name: "人工发起", kind: "trigger", group: "trigger", fields: [], slots: [] }, + { id: "Test.Action", name: "测试动作", kind: "vendor", group: "data", fields: [{ key: "value", label: "内容", required: true, widget: "text", defaultValue: "" }], slots: [] } ] } as unknown as Catalog; @@ -38,10 +38,9 @@ describe("flow validation", () => { expect(fieldIsActive("System_ScreenSaverShowV1", "password", { set_password: "1" })).toBe(true); }); - it("reports pending components, required fields, and disconnected nodes", () => { + it("reports required fields and disconnected nodes", () => { const issues = validateFlow(flow(), catalog); expect(issues).toEqual(expect.arrayContaining([ - expect.objectContaining({ code: "not_implemented", nodeId: "action" }), expect.objectContaining({ code: "required", nodeId: "action" }), expect.objectContaining({ code: "disconnected", nodeId: "orphan", severity: "warning" }) ])); @@ -56,10 +55,10 @@ describe("flow validation", () => { it("does not require inactive designated browser fields or optional screenshot output", () => { const conditionalCatalog = structuredClone(catalog); conditionalCatalog.components.push({ - id: "Element.Input", name: "输入", kind: "vendor", group: "web", implemented: true, slots: [], + id: "Element.Input", name: "输入", kind: "vendor", group: "web", slots: [], fields: [{ key: "designated_obj", label: "指定对象", required: true, widget: "text", defaultValue: "" }] } as never, { - id: "Image_Screenshot", name: "截图", kind: "vendor", group: "vision", implemented: true, slots: [], + id: "Image_Screenshot", name: "截图", kind: "vendor", group: "vision", slots: [], fields: [{ key: "image_path", label: "输出路径", required: true, widget: "file", defaultValue: "" }] } as never); const definition = flow(); @@ -91,7 +90,7 @@ describe("flow validation", () => { it("warns when a bare output variable name is used in an object field", () => { const objectCatalog = structuredClone(catalog); objectCatalog.components.push({ - id: "Excel.Write", name: "写入", kind: "vendor", group: "office", implemented: true, slots: [], + id: "Excel.Write", name: "写入", kind: "vendor", group: "office", slots: [], fields: [ { key: "workbook", label: "工作簿对象", required: false, widget: "text", type: "object", defaultValue: "" }, { key: "value", label: "内容", required: true, widget: "text", defaultValue: "" } diff --git a/src/renderer/src/validation/flowValidation.ts b/src/renderer/src/validation/flowValidation.ts index 37a3c83..1073c55 100644 --- a/src/renderer/src/validation/flowValidation.ts +++ b/src/renderer/src/validation/flowValidation.ts @@ -54,9 +54,6 @@ export function validateFlow(flow: FlowDefinition, catalog: Catalog): FlowValida issues.push({ severity: "error", code: "missing_component", nodeId: node.id, message: `组件不存在:${node.componentId}` }); continue; } - if (!component.implemented) { - issues.push({ severity: "error", code: "not_implemented", nodeId: node.id, message: `「${node.label || component.name}」尚未接入执行器` }); - } for (const field of component.fields.filter((item) => item.required && item.widget !== "hidden" && item.widget !== "region")) { if (!requiredInContext(component.id, field.key, node.values)) continue; const current = Object.prototype.hasOwnProperty.call(node.values, field.key) ? node.values[field.key] : field.defaultValue; diff --git a/src/shared/types.ts b/src/shared/types.ts index b41a68b..c4a685c 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -59,7 +59,6 @@ export interface CatalogComponent { output: CatalogOutput; slots: string[]; nodeKind?: string; - implemented?: boolean; } export interface CatalogGroup {