feat: remove capability execution status markers

This commit is contained in:
yu committed 2026-11-02 12:00:39 +08:00
1 parent e956ec9055
commit d22468e29f
16 files changed
+25 -73

No files matched your search

+3 -15
View File
@@ -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<string> | 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;
+2 -2
View File
@@ -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;
}
@@ -83,7 +83,6 @@ function testComponent(id: string): CatalogComponent {
fields: [],
output: { label: "", key: "", suggestedName: "", valueTypes: [], fields: [] },
slots: [],
implemented: true
};
}
+1 -1
View File
@@ -265,7 +265,7 @@ async function executeNode(node: FlowNode, ctx: RunContext, states: Map<string,
{
nodeId: node.id,
componentId: node.componentId,
recommendation: "换用已接入组件;该能力已列入后续接入清单"
recommendation: "请检查组件配置或重新添加节点"
}
);
}
+2 -3
View File
@@ -103,12 +103,11 @@ export function App() {
return (
<div className="loading">
<Loader2 className="icon spin" style={{ width: 24, height: 24 }} />
正在加载 1016 项组件目录…
正在加载组件目录…
</div>
);
}
const executable = catalog.components.filter((item) => item.implemented).length;
const refreshFlows = async () => {
try {
@@ -214,7 +213,7 @@ export function App() {
本地执行 · 离线
</span>
<span className="engine-chip">
{catalog.counts.total} 目录 · {executable} 可执行
{catalog.counts.total} 个可编排组件
</span>
</div>
</header>
-1
View File
@@ -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,
+2 -9
View File
@@ -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 (
<div className="settings">
@@ -96,10 +90,9 @@ export function SettingsPage({ catalog }: { catalog: Catalog }) {
{tab === "catalog" && (
<>
<SettingCard icon={Database} title="组件接入概览">
<dl className="kv"><dt>目录总数</dt><dd>{catalog.counts.total} 项</dd><dt>已接入执行器</dt><dd>{executable} 项({((executable / catalog.counts.total) * 100).toFixed(1)}%)</dd><dt>待接入</dt><dd>{catalog.counts.total - executable} 项</dd><dt>注册处理器 ID</dt><dd>{handlers?.count ?? "—"}</dd></dl>
<SettingCard icon={Database} title="组件目录">
<dl className="kv"><dt>可编排组件</dt><dd>{catalog.counts.total} 项</dd><dt>注册处理器 ID</dt><dd>{handlers?.count ?? "—"}</dd><dt>备份清单</dt><dd>resources/excluded.json</dd></dl>
</SettingCard>
<div className="setting-card"><h3><Database className="icon" />分类覆盖</h3><div className="setting-group-table">{groups.map((group) => <div key={group.id}><strong>{group.label}</strong><span>{group.connected} 已接入</span><span>{group.pending} 待接入</span><progress max={group.count} value={group.connected} /></div>)}</div></div>
</>
)}
-1
View File
@@ -148,7 +148,6 @@ function PaletteRow({ component, onAdd }: { component: CatalogComponent; onAdd:
>
<span className="palette-icon"><Icon className="icon" /></span>
<span className="palette-copy"><span className="palette-name">{component.name}</span><span className="palette-id">{component.id}</span></span>
<span className={`status-dot${component.implemented ? "" : " pending"}`} />
</button>
);
}
-2
View File
@@ -866,8 +866,6 @@ function RunTab({ node, component }: { node: FlowNode; component: Catalog["compo
<strong>未运行</strong>
</div>
<div>
<span>执行器</span>
<strong>已接入</strong>
</div>
<div>
<span>超时</span>
-18
View File
@@ -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;
@@ -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: "" }
@@ -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;
-1
View File
@@ -59,7 +59,6 @@ export interface CatalogComponent {
output: CatalogOutput;
slots: string[];
nodeKind?: string;
implemented?: boolean;
}
export interface CatalogGroup {