Skip to content

插件工作台卡片功能升级方案 ​

版本:v1.0 | 日期:2026-09-16 | 状态:规划中


一、现状分析 ​

1.1 仪表盘页面架构 ​

核心组件: WorkbenchHome.tsx(macOS 桌面小组件风格)

┌─────────────────────────────────────────────────────────────┐
│  顶部栏:问候语 + 日期 + 场景模式切换(工作/生活/学习)+ 自定义按钮  │
├─────────────────────────────────────────────────────────────┤
│                          卡片墙                              │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐          │
│  │ 操作卡   │ │ 工具卡   │ │ 插件卡   │ │  ...    │          │
│  │ 新建任务 │ │ 待办清单 │ │ 自定义   │ │         │          │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘          │
├─────────────────────────────────────────────────────────────┤
│  添加卡片弹层(CardLibrary)                                  │
└─────────────────────────────────────────────────────────────┘

1.2 卡片类型 ​

类型说明示例
action操作卡 - 固定功能入口新建任务、打开文件夹、场景市场
tool工具卡 - 内置数据摘要待办清单、周报、番茄钟
plugin插件卡 - 插件自定义数据天气卡片、宏录制统计

1.3 现有插件卡片支持 ​

已实现 ✅:

  • PluginWidgetCardContribution 类型定义完整
  • PluginCardPreview 组件(自动刷新、手动刷新、IntersectionObserver)
  • resolveCardMeta() 支持解析插件卡片
  • pluginUiRegistry.getWidgetCards() 获取插件卡片
  • 刷新策略分层(core 15s / enhanced 60s)

缺失 ❌:

  • CardLibrary 中没有显示插件卡片,只显示通用操作卡和当前模式工具卡
  • 用户无法从卡片库添加插件提供的卡片

二、升级方案 ​

2.1 CardLibrary 增强 ​

目标: 在添加卡片弹层中显示插件卡片,按插件分组展示

修改文件: aweeclaw-client/src/renderer/components/workbench/WorkbenchHome.tsx

CardLibrary 组件改动 ​

typescript
// 新增:获取所有可用的插件卡片
const pluginCards = useMemo(() => {
  const result: Array<{
    pluginKey: string
    cardId: string
    label: string
    labelZh: string
    icon: string
    modes?: SceneMode[]
  }> = []

  // 遍历所有已发现的插件
  for (const [pluginKey, contribution] of pluginUiRegistry.discovered) {
    if (!contribution.contributes.widgetCards?.length) continue

    for (const wc of contribution.contributes.widgetCards) {
      // 模式过滤
      if (wc.modes?.length && !wc.modes.includes(mode)) continue

      result.push({
        pluginKey,
        cardId: wc.id,
        label: wc.label,
        labelZh: wc.labelZh,
        icon: wc.icon,
        modes: wc.modes,
      })
    }
  }

  return result
}, [mode])

渲染插件卡片分组 ​

typescript
{/* 插件卡片 */}
{pluginCards.length > 0 && (
  <div className="mb-5">
    <div className="flex items-center gap-2 mb-2.5">
      <Blocks className="w-3.5 h-3.5 text-purple-500/70" strokeWidth={1.8} />
      <span className="text-xs font-semibold text-text-secondary">
        {isZh ? '插件卡片' : 'Plugin Cards'}
      </span>
      <div className="flex-1 h-px bg-border/30" />
    </div>
    <div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
      {pluginCards.map((card) => {
        const Icon = getLucideIcon(card.icon) || Blocks
        const idx = TOOL_COLORS.length > 0 ? Math.abs(hashCode(card.cardId)) % TOOL_COLORS.length : 0
        return renderItem({
          id: card.cardId,
          icon: Icon,
          titleZh: card.labelZh ?? card.label,
          titleEn: card.label,
          color: TOOL_COLORS[idx],
        })
      })}
    </div>
  </div>
)}

2.2 插件发现机制优化 ​

问题: 当前 pluginUiRegistry.discovered 是私有属性,CardLibrary 无法直接访问

方案 A: 添加公开方法 getAllWidgetCards(mode?: string)

typescript
// PluginUiRegistry.ts
getAllWidgetCards(mode?: SceneMode): Array<{
  pluginKey: string
  cardId: string
  contribution: PluginWidgetCardContribution
}> {
  const result = []

  for (const [pluginKey, contribution] of this.discovered) {
    if (!contribution.contributes.widgetCards?.length) continue

    for (const wc of contribution.contributes.widgetCards) {
      if (wc.modes?.length && mode && !wc.modes.includes(mode)) continue

      result.push({
        pluginKey,
        cardId: wc.id,
        contribution: wc,
      })
    }
  }

  return result
}

方案 B: 通过 Hook 订阅(推荐)

typescript
// usePluginExtensions.ts
export function usePluginWidgetCards(mode?: SceneMode) {
  return useSyncExternalStore(
    pluginUiRegistry.subscribe.bind(pluginUiRegistry),
    () => pluginUiRegistry.getAllWidgetCards(mode),
  )
}

2.3 完整交互流程 ​

用户点击 "+" 添加卡片
        ↓
    打开 CardLibrary
        ↓
    ┌─────────────────────────────────────┐
    │  通用操作卡(ACTION_CARDS)           │
    │  ├─ 新建任务                         │
    │  ├─ 打开文件夹                       │
    │  └─ ...                             │
    ├─────────────────────────────────────┤
    │  工作工具(当前模式)                 │
    │  ├─ 待办清单                         │
    │  ├─ 工作周报                         │
    │  └─ ...                             │
    ├─────────────────────────────────────┤
    │  插件卡片(新增)                     │
    │  ├─ 天气卡片 (weather-widget)        │
    │  ├─ 宏统计 (ai-macro-recorder)       │
    │  └─ ...                             │
    └─────────────────────────────────────┘
        ↓
    用户选择插件卡片
        ↓
    添加到 workbenchWidgets
        ↓
    卡片墙显示 PluginCardPreview
        ↓
    自动调用 getCardData 获取数据

三、实现清单 ​

3.1 PluginUiRegistry 改动 ​

改动文件说明
添加 getAllWidgetCards() 方法PluginUiRegistry.ts公开获取所有可用插件卡片
优化 ensureWidgetCardsLoaded()PluginUiRegistry.ts确保 CardLibrary 打开时卡片已加载

3.2 WorkbenchHome 改动 ​

改动文件说明
CardLibrary 添加插件卡片分组WorkbenchHome.tsx渲染插件卡片列表
使用 usePluginWidgetCards HookWorkbenchHome.tsx响应式获取插件卡片

3.3 usePluginExtensions 改动 ​

改动文件说明
添加 usePluginWidgetCards HookusePluginExtensions.ts订阅插件卡片变化

四、用户体验优化 ​

4.1 空状态提示 ​

当没有插件卡片时,显示引导:

typescript
{pluginCards.length === 0 && (
  <div className="text-center py-4 text-[11px] text-text-muted">
    <Blocks className="w-6 h-6 mx-auto mb-2 opacity-30" />
    <p>{isZh ? '暂无插件卡片' : 'No plugin cards available'}</p>
    <p className="mt-1">
      {isZh ? '安装支持卡片的插件后将在此显示' : 'Install plugins with card support to see them here'}
    </p>
  </div>
)}

4.2 卡片来源标识 ​

在 CardLibrary 中显示插件名称,便于用户识别来源:

typescript
<div className="text-[10px] text-text-muted mt-0.5">
  {pluginKey}
</div>

4.3 模式过滤提示 ​

当插件卡片被模式过滤时,显示提示:

typescript
{filteredCount > 0 && (
  <p className="text-[10px] text-text-muted mt-2">
    {isZh 
      ? `还有 ${filteredCount} 个卡片在其他模式下可用`
      : `${filteredCount} more cards available in other modes`
    }
  </p>
)}

五、兼容性考虑 ​

5.1 向后兼容 ​

  • 未声明 widgetCards 的插件不受影响
  • 已添加的插件卡片继续正常工作
  • CardLibrary 新增分组不影响现有布局

5.2 性能优化 ​

  • 插件卡片列表通过 useMemo 缓存
  • 避免在 CardLibrary 打开时重复加载 ui.js
  • 使用 IntersectionObserver 暂停不可见卡片刷新

六、测试用例 ​

6.1 功能测试 ​

测试项预期结果
无插件时打开 CardLibrary不显示插件卡片分组
安装有 widgetCards 的插件CardLibrary 显示对应卡片
切换场景模式只显示当前模式的插件卡片
添加插件卡片卡片墙正确显示 PluginCardPreview
移除插件卡片卡片从卡片墙消失

6.2 边界测试 ​

测试项预期结果
插件卸载后已添加的卡片自动移除或显示错误状态
插件更新后卡片自动刷新
网络断开卡片显示离线状态,不报错

七、后续扩展 ​

7.1 卡片配置 ​

支持插件卡片的用户自定义配置:

typescript
interface WidgetCardConfig {
  /** 刷新间隔覆盖 */
  refreshInterval?: number
  /** 显示/隐藏标题 */
  showTitle?: boolean
  /** 自定义参数传递给 getCardData */
  customParams?: Record<string, unknown>
}

7.2 卡片事件 ​

支持卡片与宿主的事件通信:

typescript
interface WidgetCardEvent {
  type: 'click' | 'hover' | 'action'
  cardId: string
  payload?: unknown
}

// 插件可通过 host 发送事件
host.emitEvent({ type: 'click', cardId: 'weather-widget:current' })

7.3 卡片联动 ​

支持卡片之间的数据联动:

typescript
// 插件可以订阅其他卡片的数据变化
host.subscribeCard('todo-list:count', (data) => {
  // 根据待办数量调整显示
})

附录:相关文件 ​

文件说明
aweeclaw-client/src/renderer/components/workbench/WorkbenchHome.tsx仪表盘主组件
aweeclaw-client/src/renderer/plugins/PluginUiRegistry.ts插件 UI 注册表
aweeclaw-client/src/renderer/plugins/usePluginExtensions.ts插件扩展 Hook
aweeclaw-client/src/renderer/plugins/types.ts插件类型定义
aweeclaw-client/src/shared/plugin-sdk/types.ts插件 SDK 类型

AweeClaw AI 应用构建平台