Skip to content

自定义会话列表

本文介绍如何构建一个带有左侧会话列表 + 右侧 ChatBot 的完整聊天页面,实现外部会话列表与 ChatBot 的联动。

适用场景

当 AIBlueking 内置的会话管理 UI 无法满足需求时(如需要搜索过滤、批量操作、自定义样式等),可以使用本方案构建完全自定义的会话列表。

核心思路

  1. 使用 ChatBot 组件处理核心聊天逻辑
  2. 通过 @agent-info-loaded 事件获取 chatHelper 实例
  3. 监听 chatHelper.session.listchatHelper.session.current 获取响应式会话数据
  4. 通过 chatHelper.session 的方法(chooseSessioncreateSessiondeleteSession 等)操作会话

精简示例

vue
<template>
  <div class="chat-window">
    <!-- 左侧会话列表 -->
    <aside class="session-panel">
      <input v-model="searchQuery" placeholder="搜索会话名称" />
      <button @click="createNewSession">添加会话</button>

      <div class="session-list">
        <div
          v-for="session in filteredSessionList"
          :key="session.sessionCode"
          :class="['session-item', { active: currentSession?.sessionCode === session.sessionCode }]"
          @click="switchToSession(session.sessionCode)"
        >
          <span class="session-name">{{ session.sessionName }}</span>
          <span class="session-actions">
            <button @click.stop="renameByAI(session.sessionCode)">AI 命名</button>
            <button @click.stop="deleteSingle(session.sessionCode)">删除</button>
          </span>
        </div>
      </div>
    </aside>

    <!-- 右侧聊天区域 -->
    <main class="chat-main">
      <ChatBot
        ref="chatBotRef"
        :url="url"
        height="100%"
        @agent-info-loaded="handleAgentInfoLoaded"
      />
    </main>
  </div>
</template>

<script setup lang="ts">
import { ref, shallowRef, computed, watch } from 'vue';
import { ChatBot } from '@blueking/ai-blueking';
import type { ChatBotExpose } from '@blueking/ai-blueking';

type ChatHelper = NonNullable<ReturnType<ChatBotExpose['getChatHelper']>>;

const chatBotRef = ref<ChatBotExpose | null>(null);
// 重点:使用 shallowRef 存储 chatHelper,避免 reactive 自动解包内部 ref
const chatHelperInstance = shallowRef<ChatHelper | null>(null);
const searchQuery = ref('');
const sessionList = ref<any[]>([]);
const currentSession = ref<any | null>(null);
const url = ref('https://your-aidev-url.com/api/');

// 搜索过滤
const filteredSessionList = computed(() => {
  if (!searchQuery.value) return sessionList.value;
  const query = searchQuery.value.toLowerCase();
  return sessionList.value.filter(s => s.sessionName.toLowerCase().includes(query));
});

// 核心:通过 @agent-info-loaded 获取 chatHelper 实例
const handleAgentInfoLoaded = (helper: ChatHelper) => {
  chatHelperInstance.value = helper;

  // 监听会话列表变化
  watch(
    () => helper.session.list.value,
    (list) => {
      sessionList.value = Array.isArray(list) ? [...list] : [];
    },
    { immediate: true, deep: true },
  );

  // 监听当前会话变化
  watch(
    () => helper.session.current?.value,
    (current) => {
      currentSession.value = current ?? null;
    },
    { immediate: true },
  );
};

// 会话操作
const switchToSession = async (sessionCode: string) => {
  if (!chatHelperInstance.value) return;
  await chatHelperInstance.value.session.chooseSession(sessionCode);
};

const createNewSession = async () => {
  if (!chatHelperInstance.value) return;
  await chatHelperInstance.value.session.createSession({
    sessionCode: `new_session_${Date.now()}`,
    sessionName: '新会话',
  });
};

const deleteSingle = async (sessionCode: string) => {
  if (!chatHelperInstance.value) return;
  await chatHelperInstance.value.session.deleteSession(sessionCode);
};

const renameByAI = async (sessionCode: string) => {
  if (!chatHelperInstance.value) return;
  await chatHelperInstance.value.session.renameSession(sessionCode);
};
</script>

<style scoped>
.chat-window {
  display: flex;
  width: 100%;
  height: 100vh;
}
.session-panel {
  width: 300px;
  border-right: 1px solid #dcdee5;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.session-list {
  flex: 1;
  overflow-y: auto;
}
.session-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 12px;
  border-radius: 6px;
  cursor: pointer;
  margin-bottom: 4px;
}
.session-item:hover { background-color: #f0f5ff; }
.session-item.active { background-color: #e1ecff; color: #3a84ff; }
.session-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-main { flex: 1; min-width: 0; }
</style>

关键要点

1. 使用 shallowRef 存储 chatHelper

ts
const chatHelperInstance = shallowRef<ChatHelper | null>(null);

chatHelper 内部包含多个 ref 属性(如 session.listsession.current),使用 shallowRef 可以避免 Vue 的 reactive 自动解包这些内部 ref,保留其原始语义。

2. 通过 @agent-info-loaded 获取实例

ts
const handleAgentInfoLoaded = (helper: ChatHelper) => {
  chatHelperInstance.value = helper;
  // 此时可以访问所有会话管理 API
};

该事件在 ChatBot 完成 Agent 信息加载后触发,此时 chatHelper 已完全初始化。

3. 响应式会话数据

ts
watch(() => helper.session.list.value, (list) => {
  sessionList.value = [...list];
}, { immediate: true, deep: true });

helper.session.listhelper.session.current 是响应式的 Ref,任何会话操作(创建、删除、切换)都会自动更新。

4. 会话操作 API

方法说明
session.chooseSession(code)切换到指定会话
session.createSession(options)创建新会话
session.deleteSession(code)删除指定会话
session.renameSession(code)AI 自动生成会话标题
session.updateSession(session)更新会话信息(如手动重命名)

进阶:批量操作、编辑标题

完整的生产级实现可参考 publish-template/src/views/ChatWindow.vue,该文件包含:

  • 搜索过滤会话
  • 内联编辑会话标题
  • AI 自动生成标题
  • 勾选 + 批量删除
  • 删除确认弹窗
  • Loading 状态管理
  • 错误处理与 403 跳转

实际项目中建议基于 ChatWindow.vue 进行修改,而非从零开始编写。

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