Skip to content

侧栏 Tab 自定义渲染

自 v2.1.4-beta.7 起ChatBot / AIBlueking 支持将 FlowAgent 等场景下侧栏自定义 Tab 的内容区标签栏交由业务方渲染,并与主对话区「在对话中定位」联动。

概述

带执行情况侧栏的对话布局中,除默认「执行情况」Tab 外,FlowAgent 等活动消息可通过 addCustomTab 打开节点详情等自定义 Tab。此前需在 ChatContainer 层配置 getSideRenderComponent 等能力;自 v2.1.4-beta.7 起,以下 Props 已透传到 ChatBotAIBlueking,集成方无需再单独包裹 ChatContainer

能力Prop说明
自定义侧栏内容getSideRenderComponent按当前 Tab 的 data.props 返回 VNode,覆盖 addCustomTab 注册的 data.component
自定义 Tab 标签getSideTabRenderComponent自定义标签栏图标、文案、关闭按钮等
切换 Tab 时拉取详情onCustomTabChange异步返回详情数据,写入 data.props(含 loadingdata);不传时 ChatBot 对 Flow 节点 Tab 走内置 getFlowAgentTaskNodeInfo

消息自定义渲染 的区别:

  • 消息自定义渲染:主对话区消息体内的 custom-component
  • 侧栏自定义渲染:右侧(或 placement 指定侧)执行情况面板中的 Tab 内容与标签

底层机制与 @blueking/chat-xChatContainer 一致,详见 chat-x Wiki「自定义侧栏内容 / Tab 标签」。

数据流

mermaid
sequenceDiagram
  participant Flow as FlowAgentContent
  participant Container as ChatContainer
  participant App as 业务层 onCustomTabChange
  participant UI as 侧栏内容组件

  Flow->>Container: addCustomTab({ name, label, data: { component, props, messageUid } })
  Container->>App: onTabChange → onCustomTabChange(tab)
  Note over Container: props.loading=true
  App-->>Container: 详情数据
  Note over Container: props.loading=false, props.data=结果
  Container->>UI: getSideRenderComponent(h, props) ?? data.component

选中自定义 Tab 后,内容区渲染优先级为:

  1. getSideRenderComponent(h, selectedTab.data.props) 返回的 VNode
  2. 否则使用 addCustomTab 时传入的 data.component,并 v-bind="data.props"

快速开始

1. 定义侧栏内容组件

内容组件需根据 loading / data 展示骨架与详情,并在标题区保留 locateButton 插槽(由容器注入「在对话中定位」按钮):

vue
<!-- CustomTabContent.vue -->
<template>
  <div>
    <header>
      <h3>{{ loading ? '加载中…' : titleText }}</h3>
      <slot name="locateButton" />
    </header>
    <div v-if="loading" class="skeleton" />
    <dl v-else>
      <dt>node_name</dt>
      <dd>{{ nodeName }}</dd>
      <!-- 使用 props.data 渲染业务字段 -->
    </dl>
  </div>
</template>

<script setup lang="ts">
defineProps<{
  loading?: boolean;
  nodeId?: string;
  nodeName?: string;
  taskId?: number;
  taskName?: string;
  data?: Record<string, unknown>;
}>();
</script>

2. 封装渲染函数(推荐 composable)

typescript
// use-side-render-handlers.ts
import { h } from 'vue';
import CustomTabContent from './CustomTabContent.vue';
import type { GetSideRenderComponent, GetSideTabRenderComponent } from '@blueking/ai-blueking';

export function useSideRenderHandlers() {
  const getSideRenderComponent: GetSideRenderComponent = (createElement, props) => {
    const raw = props ?? {};
    return createElement(CustomTabContent, {
      loading: Boolean(raw.loading),
      nodeId: typeof raw.node_id === 'string' ? raw.node_id : '',
      nodeName: typeof raw.node_name === 'string' ? raw.node_name : '',
      taskId: typeof raw.task_id === 'number' ? raw.task_id : undefined,
      taskName: typeof raw.task_name === 'string' ? raw.task_name : '',
      data:
        typeof raw.data === 'object' && raw.data !== null && !Array.isArray(raw.data)
          ? (raw.data as Record<string, unknown>)
          : {},
    });
  };

  const getSideTabRenderComponent: GetSideTabRenderComponent = (createElement, tab, { removeCustomTab }) => {
    // 仅自定义 Flow 节点 Tab(name 形如 task_id|node_id|node_name)
    if (!tab.name.includes('|')) return undefined;

    const [, , nodeName = ''] = tab.name.split('|');
    return createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: '6px' } }, [
      createElement('span', {}, nodeName || tab.label),
      createElement('button', {
        type: 'button',
        onClick: (e: Event) => {
          e.stopPropagation();
          removeCustomTab(tab.name);
        },
      }, '×'),
    ]);
  };

  return { getSideRenderComponent, getSideTabRenderComponent };
}

Flow 协议侧 data.props 常用 snake_case(如 task_idnode_name),在 getSideRenderComponent 内映射为组件 camelCase props 即可。

3. 挂到 ChatBot / AIBlueking

vue
<template>
  <ChatBot
    url="/api/ai"
    :get-side-render-component="getSideRenderComponent"
    :get-side-tab-render-component="getSideTabRenderComponent"
    @execution-panel-change="onExecutionPanelChange"
  />
</template>

<script setup lang="ts">
import { ChatBot } from '@blueking/ai-blueking';
import '@blueking/ai-blueking/style.css';
import { useSideRenderHandlers } from './use-side-render-handlers';

const { getSideRenderComponent, getSideTabRenderComponent } = useSideRenderHandlers();

function onExecutionPanelChange(isCollapse: boolean) {
  // 侧栏展开/收起时,若外层有 draggable 容器,需同步布局(见注意事项)
}
</script>

AIBlueking 用法相同,将上述三个 Prop 传给内层 ChatBot 即可。

onCustomTabChange(可选)

类型:

typescript
import type { OnCustomTabChange } from '@blueking/ai-blueking';

const onCustomTabChange: OnCustomTabChange = async (tab) => {
  const { task_id, node_id } = tab.data?.props ?? {};
  if (task_id == null || !node_id) return {};
  const detail = await fetchMyNodeDetail(task_id, node_id);
  return detail; // 合并进 tab.data.props.data,并置 loading=false
};
场景行为
未传 onCustomTabChangeChatBot 对含 task_idnode_id 的 Flow 节点 Tab 自动调用 chatHelper.message.getFlowAgentTaskNodeInfo
已传完全由业务方返回详情,内置拉取不再执行

切换 Tab 时容器会先将 props.loading 设为 true,再 await onCustomTabChange(tab),最后合并 { loading: false, data: 返回值 }

类型导出

@blueking/ai-blueking 引入:

typescript
import type {
  GetSideRenderComponent,
  GetSideTabRenderComponent,
  OnCustomTabChange,
} from '@blueking/ai-blueking';

与 FlowAgent / addCustomTab 的关系

  • 打开 Tab:仍由 FlowAgentContent 等内部调用 useCustomTabConsumer().addCustomTab(),业务方通常无需手动调用
  • 覆盖展示:通过 getSideRenderComponent / getSideTabRenderComponent 统一替换默认的 BkFlowNodeDetail 与默认标签样式
  • 定位联动addCustomTabdata.messageUid 须与活动消息 message.uid 一致;内容组件保留 #locateButton 插槽

直接使用 ChatContainer 时,Prop 名称与行为相同,见 ChatBot API

版本要求

本能力自 v2.1.4-beta.7 起在 ChatBot / AIBlueking 上提供;更低版本请直接使用 @blueking/chat-xChatContainer 同名 Props。

注意事项

外层 draggable 与侧栏展开

ChatBot 包在可拖拽面板内,监听 execution-panel-change,在侧栏因 addCustomTab 自动展开时同步外层布局(与 isCollapse 联动)。

执行情况 Tab 清空自定义 Tab

executionGroups 为空且搜索关键词为空时,ChatContainerresetCustomTab,清空自定义 Tab 并折叠侧栏。

props 命名与 remount

侧栏内容使用 :key="selectedTab.name",切换 Tab 会重建子树。data.props 建议与后端 Flow 字段对齐(snake_case),在 getSideRenderComponent 中再映射为组件 props。

参考示例

仓库内 Playground(packages/ai-blueking/playground)提供完整示例:

  • 页面:views/SideRenderView.vue(路由 /examples/side-render
  • 组件:components/side-render/CustomTabContent.vueFlowAgentSideRenderDemo.vue
  • 逻辑:components/side-render/use-side-render-handlers.ts

本地调试:在 ai-blueking 包目录执行 pnpm dev:ai,访问侧栏渲染示例页。

Standalone 宿主注意

v2.1.4-beta.8 起,非 Vue 宿主通过 @blueking/ai-blueking/standalone 集成时,getSideRenderComponentcreateElement 必须使用本入口导出的 h

typescript
import { mountChatBot, h } from '@blueking/ai-blueking/standalone';

mountChatBot('#root', {
  props: {
    url: '/api/',
    getSideRenderComponent: (createElement, props) => {
      // createElement 与 standalone 的 h 同源;也可直接写 h(...)
      return h(MySidePanel, props);
    },
  },
});

勿从 npm 的 vue 包单独 import { h },否则 VNode 与组件内部 runtime 不一致会导致渲染异常。

相关文档

All Rights Reserved. 腾讯蓝鲸 版权所有