Skip to content

feat:扩展插件前端能力,支持向响应式前端内追加自定义组件 - #293

Open
HarcoChen wants to merge 2 commits into
AUTO-MAS-Project:dev_v2from
HarcoChen:feat/custom-frontend
Open

feat:扩展插件前端能力,支持向响应式前端内追加自定义组件#293
HarcoChen wants to merge 2 commits into
AUTO-MAS-Project:dev_v2from
HarcoChen:feat/custom-frontend

Conversation

@HarcoChen

@HarcoChen HarcoChen commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Motivation

例如MFW、OKWW、MAAEND响应式布局并不令人满意,一个较为明显的例子是“任务列表”这样的一个组件。
当前采取的策略是这些脚本把所需的前端文件硬编码在MAS本体内,这样插件和本体还是耦合的。
PR希望更新契约以允许脚本向SchemaForm内追加自定义的组件。

Overview

  • 为 Script Adapter 增加非持久化 plugin-element Schema 节点,并根据插件上下文校验、解析 frontend manifest 资源。
  • SchemaForm 支持加载插件 custom element、同步响应式属性,并处理单字段更新和深层表单 patch;加载失败时不影响其他配置和保存。
  • 修复现有前端 Prettier lint 错误及 Python 未使用导入。

Test

提交了一个Unit Test因为这是针对本体的修改。

Summary by Sourcery

为脚本/用户 schema 添加对非持久化插件自定义元素的支持,并将其接入响应式的 SchemaForm,使插件可以提供自己的前端组件。

New Features:

  • 允许插件在 schema 定义中声明 plugin-element 字段,通过统一的描述符解析其前端资源,并根据插件 manifest 进行校验。
  • 在 SchemaForm 中通过专用的包裹组件渲染插件自定义元素,该组件负责加载插件资源、同步表单上下文,以及处理字段级变更和表单补丁(patch)。
  • 将脚本和用户上下文(scriptIduserIdscriptConfigmode)暴露给 SchemaForm,使插件组件可以根据使用场景调整行为。

Bug Fixes:

  • 通过跟踪样式表加载的 Promise 并在出错时进行清理,防止插件前端样式处于不一致状态。
  • 修复现有的前端细微 lint/样式问题(Vue 组件中),并移除未使用的 Python 导入。

Enhancements:

  • 提炼插件前端加载逻辑,抽取可复用的 ensurePluginFrontendElement 帮助方法,在页面与 schema 字段之间共享。
  • 确保插件前端元素标签会根据插件 manifest 进行校验,并在预加载其资源时尽早捕获无效路径。
  • plugin-element schema 字段视为仅运行时(非持久化)字段,并在渲染自定义元素时隐藏标准的 label/required 装饰。
  • 添加 deep-mmerge 工具及测试,用于在应用来自插件组件的嵌套表单补丁时避免覆盖同级字段。

Tests:

  • 添加单元测试,用于验证 plugin-element 的 manifest 解析、对未声明元素的拒绝行为,以及 plugin-element schema 字段的非持久化特性及其正确接线。
Original summary in English

Summary by Sourcery

Add support for non-persisted plugin custom elements in script/user schemas and wire them into the reactive SchemaForm so plugins can provide their own frontend components.

New Features:

  • Allow plugins to declare plugin-element fields in schema definitions, resolving their frontend resources via a unified descriptor validated against the plugin manifest.
  • Render plugin custom elements inside SchemaForm with a dedicated wrapper component that loads plugin assets, syncs form context, and handles field-level changes and form patches.
  • Expose script and user context (scriptId, userId, scriptConfig, mode) to SchemaForm so plugin components can adapt behavior based on usage.

Bug Fixes:

  • Prevent plugin frontend styles from being left in an inconsistent state by tracking stylesheet load promises and cleaning up on errors.
  • Fix existing minor frontend lint/style issues in Vue components and remove unused Python imports.

Enhancements:

  • Refine plugin frontend loading by extracting a reusable ensurePluginFrontendElement helper shared between pages and schema fields.
  • Ensure plugin frontend element tags are validated against the plugin manifest and their assets are preloaded to catch invalid paths early.
  • Treat plugin-element schema fields as runtime-only (non-persisted) and hide standard label/required decorations when rendering custom elements.
  • Add a deep-mmerge utility and tests to apply nested form patches from plugin components without clobbering sibling fields.

Tests:

  • Add unit tests to validate plugin-element manifest resolution, rejection of undeclared elements, and that plugin-element schema fields are non-persisted and correctly wired.

@HarcoChen
HarcoChen requested a review from Alirea10 July 14, 2026 00:35
@sourcery-ai

sourcery-ai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Reviewer's Guide

扩展插件前端契约,使脚本可以在 schema 中声明非持久化的自定义元素;将后端的 manifest 解析和资源校验接入一个新的前端 PluginSchemaElement 渲染器,该渲染器负责加载插件自定义元素、同步响应式 props、处理细粒度表单更新,同时重构共享加载逻辑并添加相应测试和工具。

SchemaForm 插件自定义元素生命周期时序图

sequenceDiagram
  participant SchemaForm
  participant PluginSchemaElement
  participant PluginFrontendLoader as pluginFrontendLoader
  participant CustomElement

  SchemaForm->>PluginSchemaElement: loadElement()
  PluginSchemaElement->>pluginFrontendLoader: ensurePluginFrontendElement(descriptor)
  pluginFrontendLoader->>pluginFrontendLoader: ensureStyle(url)
  pluginFrontendLoader->>pluginFrontendLoader: loadEntryScript(entry_asset_url, cacheKey)
  pluginFrontendLoader-->>PluginSchemaElement: waitForElement(element_tag)
  PluginSchemaElement-->>SchemaForm: attachListeners()

  CustomElement-->>PluginSchemaElement: handleFieldChange(event)
  PluginSchemaElement->>SchemaForm: handlePluginFieldChange(detail)
  SchemaForm->>SchemaForm: updateFieldValue(path, value)

  CustomElement-->>PluginSchemaElement: handleFormPatch(event)
  PluginSchemaElement->>SchemaForm: handlePluginFormPatch(detail)
  SchemaForm->>SchemaForm: deepMergeRecord(modelValue, patch)
Loading

解析后端 plugin-element schema 字段的流程图

flowchart TD
  A[_build_schema_artifacts] --> B[_resolve_schema_frontend_elements]
  B -->|iterate groups/fields| C{field_schema.type == plugin-element}
  C -->|no| B
  C -->|yes| D{frontend_plugin provided?}
  D -->|no| E[raise ValueError]
  D -->|yes| F[resolve_plugin_frontend_element]
  F --> G[_load_frontend_dev_manifest]
  G --> H{dev manifest found?}
  H -->|yes| I[PluginFrontendDevManifest -> PluginFrontendElementDescriptor]
  H -->|no| J[load_frontend_manifest]
  J --> K[load_frontend_asset for entry/style]
  K --> L[PluginFrontendElementDescriptor]
  I --> M[set field_schema.persisted = False]
  L --> M
  M --> N[set field_schema.frontend_extension = descriptor.model_dump]
Loading

File-Level Changes

Change Details Files
引入经过验证的描述符模型和解析器,用于插件自定义元素,并将其集成进脚本适配器的 schema 构建流程,使 plugin-element 字段能获得来源于 manifest 的、非持久化前端元数据。
  • 新增 PluginFrontendElementDescriptor Pydantic 模型,用于表示可加载的自定义元素资源及其元数据。
  • 实现 resolve_plugin_frontend_element,用于校验插件名称/标签,在 dev 与 release manifest 之间进行选择,确保资源存在,并构建带版本的资源 URL。
  • script_adapter 中新增 _resolve_schema_frontend_elements,扫描 schema 分组中的 plugin-element 字段,确保它们仅用于插件适配器,校验 frontend_element,标记其为非持久化,并附加解析得到的 frontend_extension 描述符。
  • _resolve_schema_frontend_elements 接入 ScriptAdapterDefinition.build_provider,同时作用于 script_schemauser_schema
  • script_adapter_schema._is_runtime_field 中将 plugin-element 标记为非运行时/非持久化字段,使其从配置持久化中排除。
app/plugins/frontend_extensions.py
app/plugins/script_adapter.py
app/plugins/script_adapter_schema.py
为适配器暴露一个高级的 plugin_element 字段辅助方法,用于声明自定义元素挂载点,并通过单元测试验证此类字段能被正确解析且保持非持久化。
  • 新增 PluginField.plugin_element 辅助方法,定义一个不可配置、非持久化的 plugin-element 字段,带有 frontend_element 标签、可选 props 和布局尺寸。
  • 新增测试,确保 resolve_plugin_frontend_element 会验证 manifest、加载资源、拒绝未声明标签,并确保 plugin_element 字段能获得解析后的 frontend_extension 元数据,同时不会新增持久化的配置属性。
app/plugins/fields.py
tests/plugins/test_schema_plugin_element.py
创建可复用的前端插件元素加载器和 Vue 组件,用于从已验证的描述符加载插件自定义元素,管理样式和脚本加载(带错误处理),并在插件与宿主 SchemaForm 之间桥接事件和 props。
  • 重构 pluginFrontendLoader,新增 ensurePluginFrontendElement,接收 PluginFrontendElementDescriptor,通过具备加载/错误感知的 ensureStyle 加载 CSS,使用包含插件和 manifest 版本的缓存 key 加载入口脚本,并等待自定义元素标签就绪;保留 ensurePluginFrontendPage 作为使用描述符的薄封装。
  • loadedStylesSet 改为 URL 到加载 PromiseMap,使 ensureStyle 变为异步,并具备正确的加载/错误清理语义。
  • 引入 PluginSchemaElement Vue 组件,调用 ensurePluginFrontendElement,处理加载与错误 UI(含重试),在自定义元素上添加/移除 field-change 和 form-patch 事件监听器,并安全地将宿主提供的属性(scriptIduserIdscriptConfigmodelValuefieldPathmodeextensionProps)同步到元素上。
  • 在新的 pluginFrontend.ts 模块中定义共享的 TypeScript 类型,用于插件前端描述符、schema 元素输入以及事件详情负载。
frontend/src/plugin/pluginFrontendLoader.ts
frontend/src/components/PluginSchemaElement.vue
frontend/src/types/pluginFrontend.ts
扩展 SchemaForm 及编辑视图,以支持渲染 PluginSchemaElement 的 plugin-element 字段,透传必要上下文(脚本/用户 ID 和配置),并在不破坏其他字段的情况下处理由插件驱动的单字段变化和深度表单补丁。
  • 更新 SchemaForm.vue,对 plugin-element 字段进行特殊处理:隐藏 label/header/必填标记,当存在 frontend_extension 时渲染 PluginSchemaElement,否则回退到非阻塞的警告提示;新增用于 PluginSchemaElement 的 props(scriptIduserIdscriptConfigmode)以及 field-change 和 form-patch 事件处理方法。
  • 使用 deepMergeRecord 工具,将表单 patch 负载深度合并进当前 model,保留不相关的兄弟字段,并为此行为添加针对性的单元测试。
  • 扩展 SchemaFieldDefinition,新增 plugin-element 特定属性(frontend_elementfrontend_extensionpropspersisted 标记),并相应更新辅助谓词(例如 isPluginElementField)。
  • 从通用/插件的 UserEditScriptEdit 视图向 SchemaForm 透传 scriptIduserIdscriptConfigmode;在用户编辑中从 scriptRecord.config 初始化 scriptConfig,在脚本编辑中从 formModel 初始化。
frontend/src/components/SchemaForm.vue
frontend/src/views/EditView/User/GenericUserEdit.vue
frontend/src/views/EditView/User/PluginUserEdit.vue
frontend/src/views/EditView/Script/GenericScriptEdit.vue
frontend/src/views/EditView/Script/PluginScriptEdit.vue
frontend/src/types/schemaForm.ts
frontend/src/utils/deepMerge.ts
frontend/src/utils/deepMerge.test.ts
对现有前端代码进行少量 Lint 和样式修复。
  • 修复 MaaEndUserEditTaskConfigSection 以及现有部分 Vue 模板的 Prettier 格式问题,以满足 lint 要求。
frontend/src/views/MaaEndUserEdit/TaskConfigSection.vue

Tips and commands

Interacting with Sourcery

  • 触发新一次代码审查: 在 pull request 上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 在某条审查评论下回复,请 Sourcery 从该评论创建一个 issue。你也可以回复 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题中任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 pull request 上评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 描述正文任意位置写上 @sourcery-ai summary,即可在对应位置生成 PR 摘要。你也可以在 pull request 上评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成审查者指南: 在 pull request 上评论 @sourcery-ai guide,即可随时(重新)生成审查者指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 上评论 @sourcery-ai resolve,将所有 Sourcery 评论标记为已解决。适用于你已处理完所有评论且不再希望看到它们的情况。
  • 一次性关闭所有 Sourcery 审查: 在 pull request 上评论 @sourcery-ai dismiss,将所有现有 Sourcery 审查关闭。如果你想从一次全新的审查开始,这会特别有用——别忘了再评论 @sourcery-ai review 触发新的审查!

Customizing Your Experience

访问你的 dashboard 以:

  • 启用或关闭审查功能,例如 Sourcery 自动生成的 PR 摘要、审查者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查设置。

Getting Help

Original review guide in English

Reviewer's Guide

Extends the plugin frontend contract so scripts can declare non-persistent custom elements in schemas, wiring backend manifest resolution and asset validation to a new frontend PluginSchemaElement renderer that loads plugin custom elements, syncs reactive props, and handles granular form updates, while also refactoring shared loader logic and adding tests/utilities.

Sequence diagram for SchemaForm plugin custom element lifecycle

sequenceDiagram
  participant SchemaForm
  participant PluginSchemaElement
  participant PluginFrontendLoader as pluginFrontendLoader
  participant CustomElement

  SchemaForm->>PluginSchemaElement: loadElement()
  PluginSchemaElement->>pluginFrontendLoader: ensurePluginFrontendElement(descriptor)
  pluginFrontendLoader->>pluginFrontendLoader: ensureStyle(url)
  pluginFrontendLoader->>pluginFrontendLoader: loadEntryScript(entry_asset_url, cacheKey)
  pluginFrontendLoader-->>PluginSchemaElement: waitForElement(element_tag)
  PluginSchemaElement-->>SchemaForm: attachListeners()

  CustomElement-->>PluginSchemaElement: handleFieldChange(event)
  PluginSchemaElement->>SchemaForm: handlePluginFieldChange(detail)
  SchemaForm->>SchemaForm: updateFieldValue(path, value)

  CustomElement-->>PluginSchemaElement: handleFormPatch(event)
  PluginSchemaElement->>SchemaForm: handlePluginFormPatch(detail)
  SchemaForm->>SchemaForm: deepMergeRecord(modelValue, patch)
Loading

Flow diagram for resolving backend plugin-element schema fields

flowchart TD
  A[_build_schema_artifacts] --> B[_resolve_schema_frontend_elements]
  B -->|iterate groups/fields| C{field_schema.type == plugin-element}
  C -->|no| B
  C -->|yes| D{frontend_plugin provided?}
  D -->|no| E[raise ValueError]
  D -->|yes| F[resolve_plugin_frontend_element]
  F --> G[_load_frontend_dev_manifest]
  G --> H{dev manifest found?}
  H -->|yes| I[PluginFrontendDevManifest -> PluginFrontendElementDescriptor]
  H -->|no| J[load_frontend_manifest]
  J --> K[load_frontend_asset for entry/style]
  K --> L[PluginFrontendElementDescriptor]
  I --> M[set field_schema.persisted = False]
  L --> M
  M --> N[set field_schema.frontend_extension = descriptor.model_dump]
Loading

File-Level Changes

Change Details Files
Introduce a validated descriptor model and resolver for plugin custom elements, and integrate it into the script adapter schema building pipeline so plugin-element fields get non-persistent, manifest-derived frontend metadata.
  • Add PluginFrontendElementDescriptor Pydantic model representing loadable custom element assets and metadata.
  • Implement resolve_plugin_frontend_element to validate plugin name/tag, pick dev vs release manifest, ensure assets exist, and construct versioned asset URLs.
  • Add _resolve_schema_frontend_elements in script_adapter to scan schema groups for plugin-element fields, ensure they are only used in plugin adapters, validate frontend_element, mark them non-persistent, and attach resolved frontend_extension descriptors.
  • Wire _resolve_schema_frontend_elements into ScriptAdapterDefinition.build_provider for both script_schema and user_schema.
  • Mark plugin-element as non-runtime/persisted in script_adapter_schema._is_runtime_field so it is excluded from config persistence.
app/plugins/frontend_extensions.py
app/plugins/script_adapter.py
app/plugins/script_adapter_schema.py
Expose a high-level plugin_element field helper for adapters to declare custom element mount points and verify via unit tests that such fields resolve correctly and remain non-persisted.
  • Add PluginField.plugin_element helper that defines a non-configurable, non-persisted plugin-element field with frontend_element tag, optional props, and layout size.
  • Create tests ensuring resolve_plugin_frontend_element validates manifest, loads assets, rejects undeclared tags, and that plugin_element fields get resolved frontend_extension metadata without adding persisted config attributes.
app/plugins/fields.py
tests/plugins/test_schema_plugin_element.py
Create a reusable frontend plugin element loader and Vue component that load plugin custom elements from validated descriptors, manage styles and script loading with error handling, and bridge events/props between plugins and the host SchemaForm.
  • Refactor pluginFrontendLoader to add ensurePluginFrontendElement that accepts a PluginFrontendElementDescriptor, loads CSS with load/error-aware ensureStyle, loads the entry script with cache keys incorporating plugin and manifest version, and waits for the custom element tag; keep ensurePluginFrontendPage as a thin wrapper using the descriptor.
  • Change loadedStyles from Set to Map of URL to loading Promise and make ensureStyle async with proper load/error cleanup semantics.
  • Introduce PluginSchemaElement Vue component that calls ensurePluginFrontendElement, handles loading and error UI with retry, attaches/removes field-change and form-patch event listeners on the custom element, and syncs host-provided properties (scriptId, userId, scriptConfig, modelValue, fieldPath, mode, extensionProps) onto the element safely.
  • Define shared TypeScript types for plugin frontend descriptors, schema element inputs, and event detail payloads in a new pluginFrontend.ts module.
frontend/src/plugin/pluginFrontendLoader.ts
frontend/src/components/PluginSchemaElement.vue
frontend/src/types/pluginFrontend.ts
Extend SchemaForm and edit views to support plugin-element fields that render PluginSchemaElement, pass through necessary context (script/user ids and configs), and handle plugin-driven single-field changes and deep form patches without breaking other fields.
  • Update SchemaForm.vue to treat plugin-element fields specially: hide label/header/required markers, render PluginSchemaElement when frontend_extension is present, fall back to a non-blocking warning alert when not; wire up new props (scriptId, userId, scriptConfig, mode) and handlers for field-change and form-patch emitted from PluginSchemaElement.
  • Use deepMergeRecord utility to merge form patch payloads deeply into the current model, preserving unrelated sibling fields, and add a focused unit test for this behavior.
  • Extend SchemaFieldDefinition with plugin-element-specific attributes (frontend_element, frontend_extension, props, persisted flag) for typing, and update helper predicates (e.g., isPluginElementField) accordingly.
  • Propagate scriptId, userId, scriptConfig, and mode into SchemaForm from Generic/Plugin UserEdit and ScriptEdit views; initialize scriptConfig from scriptRecord.config for user edits and from formModel for script edits.
frontend/src/components/SchemaForm.vue
frontend/src/views/EditView/User/GenericUserEdit.vue
frontend/src/views/EditView/User/PluginUserEdit.vue
frontend/src/views/EditView/Script/GenericScriptEdit.vue
frontend/src/views/EditView/Script/PluginScriptEdit.vue
frontend/src/types/schemaForm.ts
frontend/src/utils/deepMerge.ts
frontend/src/utils/deepMerge.test.ts
Minor lint and style fixes in existing frontend code.
  • Fix Prettier formatting issues in MaaEndUserEdit TaskConfigSection and existing Vue templates to satisfy linting.
frontend/src/views/MaaEndUserEdit/TaskConfigSection.vue

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我已经审查了你的修改,看起来非常棒!


Sourcery 对开源项目是免费的 —— 如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用吧!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的代码评审。
Original comment in English

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant