Skip to content

Latest commit

 

History

History
448 lines (327 loc) · 12.6 KB

frontend-home-panel-plugin-development-guide.md

File metadata and controls

448 lines (327 loc) · 12.6 KB

Frontend Home Panel Plugin Development Guide

适用范围:

  • apps/web/src/components/home/panels/**
  • apps/web/src/features/home/host/**
  • Agent 控制台首页抽屉中的 overview / files / processes / services / startup / terminal

这份文档是“新增首页抽屉插件怎么做”的实操指南。

如果你是第一次接这个模块,建议先读:

  • docs/frontend-home-panel-plugin-guide.md

然后回到本文,直接按步骤接新插件。

1. 先判断你要加的是哪种插件

当前首页抽屉面板分两类:

1.1 non-managed panel

适合:

  • overview
  • files
  • 或者未来不需要通用插件安装 / 卸载 / 依赖修复 / 配置管理 UI 的面板

特征:

  • 主要是自己的业务 UI
  • 管理区如果有,也通常是自己提供 manageComponent
  • 不复用 shared 的 managed plugin 管理面板

参考实现:

  • apps/web/src/components/home/panels/overview/plugin.ts
  • apps/web/src/components/home/panels/files/plugin.ts

1.2 managed panel

适合:

  • processes
  • services
  • startup
  • terminal
  • 或者未来任何需要“插件安装、卸载、依赖检查、配置、日志”这套通用管理能力的面板

特征:

  • 业务 body 是自己的
  • 管理区走 shared 的 managed helper
  • 插件可用性和头部 meta 默认从自己的 manageBindings 投影

参考实现:

  • apps/web/src/components/home/panels/processes/plugin.ts
  • apps/web/src/components/home/panels/services/plugin.ts
  • apps/web/src/components/home/panels/startup/plugin.ts
  • apps/web/src/components/home/panels/terminal/plugin.ts

2. 当前接入原则

按当前主链,新增一个插件应该尽量只做两件事:

  1. 在自己的插件目录里实现
  2. 在 apps/web/src/features/home/host/agentDrawerRegistry.ts 注册一次

默认不要改这些文件:

  • apps/web/src/composables/useHomePanelModules.ts
  • apps/web/src/composables/useHomeViewDrawerSetup.ts
  • apps/web/src/features/home/host/homePanelRuntimeSetup.ts
  • apps/web/src/features/home/host/homePanelRuntimeLifecycleSetup.ts
  • apps/web/src/features/home/host/homePanelRuntimeBindingSetup.ts
  • apps/web/src/features/home/shared/agentDrawerBindings.ts
  • apps/web/src/features/home/host/AgentDrawerHost.vue
  • apps/web/src/features/home/host/AgentDrawerBodyContent.vue
  • apps/web/src/features/home/host/AgentDrawerManageContent.vue

只有在下面这些情况,才考虑动宿主:

  • 你真的需要新的通用 runtime context 能力
  • 你发现 registry 无法表达该插件的生命周期或日志源
  • 你明确是在做 shared 抽象升级,而不是在补单插件特例

3. 当前推荐目录模板

当前仓库真实落点仍在:

apps/web/src/components/home/panels/<panel>/

新增一个插件,优先按下面的最小模板建:

apps/web/src/components/home/panels/<panel>/
  plugin.ts
  useXxxModule.ts
  XxxPanelEntry.vue
  XxxManagePanel.vue        # 只有 non-managed 且需要自定义管理区时才需要

如果该面板更复杂,再继续拆:

  • components/
  • types.ts
  • bindings.ts

当前不要为了“看起来更规范”先去动整个目录迁移。先接入成功,再谈归位。

4. non-managed panel 接入步骤

4.1 建 module

先有自己的业务模块,负责状态和动作。

典型形式:

export function useXxxModule(
  api: ConsoleApiClient,
  selectedAgent: Readonly<{ value: Agent | null }>,
) {
  return {
    // refs
    // actions
  };
}

4.2 在 plugin.ts 里声明 runtime plugin

核心思路:

  • runtime.createModule 负责创建模块
  • runtime.createBodyBindings 负责把模块投影成 body 需要的数据
  • 如果有管理区,再补 runtime.createManageBindings
  • getBodyProps / getManageProps 只做宿主 bindings 到组件 props 的适配

最小骨架:

import XxxPanelEntry from "./XxxPanelEntry.vue";
import type { HomePanelRuntimePlugin } from "../../../../features/home/host/contracts";
import { useXxxModule } from "./useXxxModule";

type XxxRuntimeModule = {
  xxxModule: ReturnType<typeof useXxxModule>;
};

type XxxBodyBindings = ReturnType<typeof createXxxBodyBindings>;
type XxxManageBindings = ReturnType<typeof createXxxManageBindings>;

export const xxxPlugin: HomePanelRuntimePlugin<
  XxxRuntimeModule,
  XxxBodyBindings,
  XxxManageBindings,
  never,
  XxxHostBindings,
  "xxx"
> = {
  mode: "xxx",
  meta: {
    menuLabel: "你的菜单名",
    titleSuffix: "你的标题后缀",
    manageTitle: "你的管理区标题",
  },
  bodyComponent: XxxPanelEntry,
  manageComponent: XxxManagePanel,
  getBodyProps: (bindings) => ({
    panel: bindings.panelBindings.xxx,
  }),
  getManageProps: (bindings) => ({
    manage: bindings.manageBindings.xxx,
    logs: bindings.logs,
  }),
  runtime: {
    createModule: ({ api, selectedAgent }) => ({
      xxxModule: useXxxModule(api, selectedAgent),
    }),
    createBodyBindings: (module, runtimeContext) =>
      createXxxBodyBindings(module.xxxModule, runtimeContext),
    createManageBindings: (module, runtimeContext) =>
      createXxxManageBindings(module.xxxModule, runtimeContext),
    loadOnOpen: (module) => {
      void module.xxxModule.load();
    },
  },
};

4.3 如果不需要管理区

那就:

  • TManageBindings 传 never
  • 不实现 runtime.createManageBindings
  • getManageProps 可以只返回 logs

参考:

  • apps/web/src/components/home/panels/files/plugin.ts

4.4 如果需要抽屉右上角插件状态

可以实现:

  • runtime.createPluginMeta
  • getPluginMeta

但注意:

  • 这已经不是宿主默认 bindings 主链
  • 只有确实需要抽屉头部状态展示时再加
  • 不要为了“也许以后有用”先把它塞回宿主常驻绑定

参考消费点:

  • apps/web/src/features/home/host/AgentDrawerHost.vue

5. managed panel 接入步骤

5.1 先有自己的业务 module

managed panel 也一样先写业务模块,比如:

  • 列表
  • 详情
  • 动作
  • 本地插件状态刷新

5.2 用 createHomeManagedPluginBindings(...) 产出管理区 bindings

这个 helper 负责把插件安装、依赖、配置等状态整理成统一结构。

参考:

  • apps/web/src/composables/useHomeManagedPluginBindings.ts

典型用法:

export function createXxxManagedBindings(module: ReturnType<typeof useXxxModule>) {
  return createHomeManagedPluginBindings({
    pluginId: "your_plugin_id",
    fallbackName: "your_plugin_id",
    fallbackDescription: "插件说明",
    pluginAvailable: module.pluginAvailable,
    pluginInstalled: module.pluginInstalled,
    pluginVersion: module.pluginVersion,
    pluginAvailableVersion: module.pluginAvailableVersion,
    dependencyStatus: module.dependencyStatus,
    dependencyHealthy: module.dependencyHealthy,
    dependencyError: module.dependencyError,
    declaredDependencies: module.declaredDependencies,
    manifest: module.pluginManifest,
    pluginConfigLoading: module.pluginConfigLoading,
    pluginConfigSaving: module.pluginConfigSaving,
    pluginConfigLoaded: module.pluginConfigLoaded,
    pluginConfig: module.pluginConfig,
    pluginConfigDefaults: module.pluginConfigDefaults,
    pluginConfigSchema: module.pluginConfigSchema,
    pluginRefreshLoading: module.pluginRefreshLoading,
    pluginInstallLoading: module.pluginInstallLoading,
    pluginUninstallLoading: module.pluginUninstallLoading,
    pluginReinstallLoading: module.pluginReinstallLoading,
    dependencyLoading: module.dependencyLoading,
    dependencyInstalling: module.dependencyInstalling,
    dependencyVerifying: module.dependencyVerifying,
    refreshPlugin: module.refreshLocalPlugin,
    installPlugin: module.installPlugin,
    uninstallPlugin: module.uninstallPlugin,
    reinstallPlugin: module.reinstallPlugin,
    verifyDependencies: module.verifyDependencies,
    reinstallDependencies: module.reinstallDependencies,
    loadPluginConfig: module.loadPluginConfig,
    savePluginConfig: module.savePluginConfig,
  });
}

5.3 用 createManagedHomePanelPlugin(...) 声明插件

这是 managed panel 的标准入口。

参考:

  • apps/web/src/features/home/shared/managed-plugin/createManagedHomePanelPlugin.ts
  • apps/web/src/components/home/panels/processes/plugin.ts

最小骨架:

export const xxxPlugin = createManagedHomePanelPlugin<
  "xxx",
  XxxRuntimeModule,
  XxxBodyBindings,
  XxxManageBindings,
  XxxPluginMeta
>({
  mode: "xxx",
  meta: {
    menuLabel: "你的菜单名",
    titleSuffix: "你的标题后缀",
    manageTitle: "你的管理区标题",
  },
  bodyComponent: XxxPanelEntry,
  getBodyProps: (bindings) => ({
    panel: bindings.panelBindings.xxx,
  }),
  getManageBindings: (bindings) => bindings.manageBindings.xxx,
  missingTitle: "插件未安装时的标题",
  missingDescription: "插件未安装时的说明",
  createModule: ({ api, selectedAgent }) => useXxxModule(api, selectedAgent),
  createBodyBindings: (module) => createXxxBodyBindings(module),
  createManagedBindings: (module) => createXxxManagedBindings(module),
  loadOnOpen: (module) => {
    void module.loadXxx();
  },
  refreshManager: (module) => module.refreshLocalPlugin(),
  getLogSource: (module) => ({
    installed: module.pluginInstalled,
    pluginId: "your_plugin_id",
  }),
});

当前 helper 已经帮你处理这些事:

  • 管理区组件默认走 shared 的 AgentManagedPluginManagePanel.vue
  • getAvailability 默认根据 manageBindings[mode].pluginInstalled 投影
  • getPluginMeta 默认根据 manageBindings[mode] 投影

所以不要再把 managed panel 的 plugin meta 塞回宿主常驻 bindings。

6. 注册插件

新增插件后,只需要改一处宿主入口:

  • apps/web/src/features/home/host/agentDrawerRegistry.ts

要做的事只有两步:

  1. import 你的 xxxPlugin
  2. 在 homePanelPluginRegistry 里注册:
export const homePanelPluginRegistry = defineHomePanelPluginRegistry({
  overview: overviewPlugin,
  files: filesPlugin,
  xxx: xxxPlugin,
});

注意:

  • registry key 必须和 plugin.mode 一致
  • 不一致时会直接破坏类型推导

7. 什么时候才需要改 shared / host

默认不要动 shared / host。

只有在下面这些场景才考虑改:

7.1 需要新的 runtimeContext 能力

例如你真的需要:

  • 新的宿主级公共动作
  • 新的跨面板共享状态

这时才评估改:

  • apps/web/src/features/home/host/contracts.ts
  • apps/web/src/features/home/host/homePanelRuntimeLifecycleSetup.ts
  • apps/web/src/features/home/host/homePanelRuntimeBindingSetup.ts

7.2 需要新的 shared 管理抽象

如果两个及以上面板都会复用,再考虑进 shared。

否则,优先留在自己的 panel 目录。

7.3 只是想导出一个命名类型别名

这不是宿主接线必需项。

如果你想继续在 shared 层暴露 XxxPanelBindings 这种命名类型,才顺手改:

  • apps/web/src/features/home/shared/agentDrawerBindings.ts

8. 提交前检查清单

至少确认下面这些点:

  • plugin.mode 和 registry key 一致
  • HomePanelRuntimePlugin 或 createManagedHomePanelPlugin 的泛型没有省掉到退化成 unknown
  • 没有为了单插件需求去改 useHomeViewDrawerSetup.ts
  • 没有把 managed panel 的安装 / 卸载 / 配置 / 日志管理 UI 重造一套
  • 没有把 plugin meta 再塞回宿主常驻 bindings

9. 验证命令

每轮修改后都要跑:

cd apps/web
npm run typecheck
npm run build

10. 当前最可靠的参考文件

non-managed 参考:

  • apps/web/src/components/home/panels/overview/plugin.ts
  • apps/web/src/components/home/panels/files/plugin.ts

managed 参考:

  • apps/web/src/components/home/panels/processes/plugin.ts
  • apps/web/src/components/home/panels/services/plugin.ts
  • apps/web/src/components/home/panels/startup/plugin.ts
  • apps/web/src/components/home/panels/terminal/plugin.ts

shared / host 参考:

  • apps/web/src/features/home/host/contracts.ts
  • apps/web/src/features/home/host/agentDrawerRegistry.ts
  • apps/web/src/features/home/shared/managed-plugin/createManagedHomePanelPlugin.ts
  • apps/web/src/features/home/host/homePanelRuntimeSetup.ts
  • apps/web/src/features/home/host/homePanelRuntimeLifecycleSetup.ts
  • apps/web/src/features/home/host/homePanelRuntimeBindingSetup.ts

11. 当前一句话结论

现在新增首页抽屉插件,默认答案应该是:

  • 先判断是 managed 还是 non-managed
  • 在自己的 panel 目录里实现 plugin.ts + module + body
  • 需要管理区就在自己的 plugin.ts 里补 createManageBindings/manageComponent
  • 最后只在 agentDrawerRegistry.ts 注册一次

如果你发现自己开始想改很多宿主文件,先停一下,通常说明你在绕开现有 plugin/runtime 主链。