leatop-ai
v1.5.7
Published
Leatop AI 组件库:AI 悬浮助手(Agent)、AI 对话页(AiChat)、知识库(KnowledgeBase)、常见问题(Question)
Readme
AI 组件库使用文档
本包将 AI 悬浮助手(Agent,右下角小人 + 对话弹窗)、AI 对话页面(AiChat)、知识库(KnowledgeBase)、常见问题(Question)以 Vue3 组件形式导出, 供宿主系统直接引入。不含路由、不含登录:路由跳转由宿主控制,token 由宿主注入。
效果示例
Agent 悬浮助手:右下角知识 IP 小人问候,点击后以圆弧扩散动画弹出对话窗口:

一、安装与前置要求
pnpm add leatop-ai --registry https://registry.npmjs.org/宿主必须已安装以下依赖(peerDependencies,组件库不重复打包,均为宿主项目常见依赖):
| 依赖 | 版本要求 | | ----------------------- | ----- | | vue | ^3.3 | | element-plus | >=2.4 | | @element-plus/icons-vue | ^2.0 | | pinia | ^2.0 | | axios | ^1.0 | | @vueuse/core | ^10.0 | | dayjs | ^1.11 | | echarts | ^5.4 |
其余依赖(docx-preview、@vue-office/excel、@vue-office/pptx、pdfjs-dist、markdown-it、marked、x-markdown-vue、@pureadmin/table、sortablejs、screenfull、js-cookie、qs)为组件库功能性依赖,声明在 dependencies 中,安装本包时自动装好,宿主无需手动安装。
组件库内部使用独立的 Pinia 实例,与宿主的 Pinia 完全隔离:宿主无需(也不应)注册组件库的 store, 即使宿主存在同名 store(如登录的
user)也互不冲突。
二、全局配置(宿主必须调用)
在宿主入口(如 main.ts)调用一次 configureAi,组件库所有请求统一从该配置读取:
import { configureAi } from 'leatop-ai';
configureAi({
// 接口基础地址:两种写法均支持
// 1. 完整地址:'https://xxx.com/api/'(跨域时由后端开 CORS 或宿主网关转发)
// 2. 相对路径:'/api/'(由宿主的 dev proxy / nginx 转发到后端)
baseURL: '/api/',
// token 获取函数:从宿主自己的登录态读取(必配,否则请求不带 token、
// 也不拉取用户信息;组件库不做任何 cookie/localStorage 兜底)
getToken: () => authStore.token,
// 可选:请求携带 token 的 header key,默认 'cdp-token',可按后端约定自定义
// tokenKey: 'Authorization',
// 可选:自定义用户信息获取(异步函数,resolve 组件库所需字段即可)。
// 组件库主要使用 account(历史列表/收藏的 userId)与 userName(侧栏欢迎语),
// 其余字段按需返回。不传则默认由组件库根据 token 调用 /system/user/curUser
// getUserInfo: async () => {
// const res = await myApi.curUser();
// return { account: res.account, userName: res.userName, avatar: res.avatar };
// },
// 可选:Agent 界面开关(默认都为 true)
uiConfig: {
showSettingButton: true, // 侧边栏"设置"入口
showFullscreenButton: true, // 全屏按钮
showUploadButton: true, // 输入区上传文件按钮(+),传 false 隐藏
showVoiceButton: true, // 输入区语音(麦克风)按钮,传 false 隐藏
assistantAvatar: '', // AI 消息头像外部 URL(不传/空则用包内默认头像)
inputPlaceholder: '发消息或者按住空格说话...', // 未选中技能时的输入框占位文案
},
// 可选:接口 401 时的回调(如跳转宿主登录页)
onUnauthorized: () => router.push('/login'),
// 可选:技能列表(不传 = 内置两个技能,详见下方「技能与请求参数自定义」)
// skills: ['1', '2'],
// 可选:发送前按当前技能注入额外请求参数
// getExtraParams: ({ skill }) => ({ bizId: myStore.bizId }),
// 可选:发送前校验(返回 false 静默阻断、字符串阻断并提示、true/undefined 放行,支持 Promise)
// validateSend: (payload, context) => payload.text ? undefined : '请输入内容',
});用户信息默认不需要配置传递:Agent 组件挂载时会自动根据注入的 token 调用
/system/user/curUser获取昵称、头像、组织等信息(无缓存,每次挂载拉取一次)。 若后端接口路径/结构不一致,可配置getUserInfo(异步函数)由宿主自行获取并返回 组件库所需字段,配置后组件库不再调用该接口。
技能与请求参数自定义(可选)
输入区技能条默认展示知识问答与智能问数两个内置技能。type 是与后端的契约值,发送消息时作为 type 参数随 /ai/stream/chat 提交,内置写死的对应关系如下:
| type(写死) | 名称 | 图标(包内雪碧图) | 是否有知识库下拉 |
| -------- | ------ | ----------- | -------- |
| 1 | 知识问答 | knowledge-qa | 有 |
| 2 | 智能问数 | smart-query | 无 |
knowledgeBase不再作为宿主配置项:内置「知识问答(type 1)」的知识库下拉为包内内部行为,改名/改图标不影响;自定义技能需要选择类交互(下拉、级联、日期等)时,用render自行实现(见下文「自定义技能渲染组件」)。 使用1/2以外的type时,需后端支持该技能编号,否则消息无法正确路由。
三种用法(数组顺序即技能条的展示顺序):
// 1) 不传:使用内置两个技能
configureAi({ baseURL, getToken });
// 2) 字符串数组:只保留命中的内置技能(下例隐藏「知识问答」,只留「智能问数」)
configureAi({ skills: ['2'] });
// 3) 对象数组:覆盖内置技能字段,或新增自定义技能
configureAi({
skills: [
{ type: '1', name: '业务问答' }, // 命中内置 type 1:只改名称,其余字段沿用内置
{ type: '2', iconUrl: '/icons/query.png' }, // 命中内置 type 2:换成宿主自己的图标图片
{ type: '9', name: '智能报告', iconUrl: 'https://xxx.com/report.png',
placeholder: '描述你要生成的报告', finishMessage: '报告已生成' }, // 未命中内置:全新技能
],
});| 字段 | 类型 | 必填 | 说明 |
| --------------- | ------- | -- | ----------------------------------------------- |
| type | String | 是 | 技能标识,随 /ai/stream/chat 提交后端 |
| name | String | 否 | 展示名称;仅当 type 未命中内置技能时必填 |
| icon | String | 否 | 包内雪碧图图标名(不含 .svg,如 knowledge-qa) |
| iconUrl | String | 否 | 自定义图标图片地址(http / 相对路径 / dataURL),优先于 icon |
| placeholder | String | 否 | 选中该技能后的输入框占位文案 |
| finishMessage | String | 否 | 该技能对话结束时的提示语 |
| render | Component / Function | 否 | 技能激活后渲染在技能标识后面的自定义组件,支持组件定义或工厂函数,详见「自定义技能渲染组件(render)」 |
输入框占位文案优先级:选中技能的
placeholder→uiConfig.inputPlaceholder→ 包内默认「发消息或者按住空格说话...」。技能未配置placeholder时会自动落到全局配置,两者都不用改代码。
注入额外请求参数:自定义技能通常需要宿主自己的参数(如数据源 id、指标编码),用 getExtraParams 在发送前按当前技能注入,同名参数以宿主返回值为准:
configureAi({
getExtraParams: ({ skill }) =>
skill?.type === '9' ? { dataSourceId: myStore.dataSourceId } : {},
});自定义技能渲染组件(render)
技能激活后,在技能标识后面渲染宿主传入的自定义组件(选择器、表单面板等)。组件通过 props 拿到一个可直接赋值的参数对象,写入的值在发送消息时自动并入请求参数:
import { defineAsyncComponent } from 'vue';
import ReportPanel from './ReportPanel.vue';
configureAi({
skills: [
'1',
{
type: '9',
name: '智能报告',
iconUrl: 'https://xxx.com/report.png',
// 写法一:直接传组件定义(最常用)
render: ReportPanel,
// 写法二:工厂函数,接收上下文 { skill, params },返回组件。
// 适合按技能动态挑选组件,或在组件挂载前给双向参数设初始值:
// render: ({ skill, params }) => {
// params.reportType = 'weekly'; // 直接赋值,发送时随消息携带
// params.source = skill.type; // 也可读取上下文里的技能信息
// return ReportPanel;
// },
// 写法三:工厂函数 + 异步组件(组件体量较大时按需加载)
// render: () => defineAsyncComponent(() => import('./ReportPanel.vue')),
},
],
});<!-- ReportPanel.vue:params 直接赋值即"双向",发送时自动携带 -->
<script setup>
const props = defineProps({
params: { type: Object, required: true }, // 双向参数对象
skill: { type: Object, default: null }, // 当前激活的技能
});
</script>
<template>
<el-select v-model="params.reportType" placeholder="报告类型" style="width: 120px">
<el-option label="日报" value="daily" />
<el-option label="周报" value="weekly" />
</el-select>
</template>规则说明:
- 组件也可写成工厂函数
render: ({ skill, params }) => Component(支持返回defineAsyncComponent异步组件); - 发送时参数合并优先级:
knowledgeIds(内置知识库)→render.params→getExtraParams(宿主函数最高,同名覆盖); - 切换技能 / 清空输入时
params由组件库重置,发送后保留(支持连续追问同条件); - 小窗(500px)与主输入区(1000px)切换时
params随草稿自动互通,组件实例销毁不丢值。
技能区整体接管(#skills 插槽)
需要完全自定义技能条交互(自带 UI 组件库、复杂选中逻辑等)时,在 Agent / AiChat 上使用 #skills 插槽,技能区整块由宿主实现(上传「+」按钮与分隔线保留,可通过 uiConfig.showUploadButton 隐藏):
<template>
<Agent>
<template #skills="{ skills, selectedSkill, selectSkill, params }">
<MySkillBar
:skills="skills"
:selected="selectedSkill"
@select="selectSkill"
/>
</template>
</Agent>
</template>插槽作用域:
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| skills | AiSkill[] | 技能列表(响应式,来自 configureAi({ skills })) |
| selectedSkill | AiSkill / null | 当前激活的技能 |
| selectSkill | (skill) => void | 选中技能;同时自动重置草稿中的知识与自定义参数 |
| params | Object | 发送参数对象,直接赋值即随消息提交(与 render.params 同一对象) |
技能列表取自响应式全局配置:组件挂载后再调用一次
configureAi({ skills })(如按登录用户权限切换技能)可即时生效,无需重新挂载组件。
自定义欢迎页标题、问候语与侧边栏头部(#welcome-header / #welcome-greeting / #sidebar-header 插槽)
三块品牌区域均开放给宿主自定义,Agent / AiChat 上使用(组件内部逐层透传,仅桌面布局生效):
#welcome-header:欢迎页(无会话空态)标题区内容。组件内置标题已移除,不传该插槽时标题区为空白(仅保留原有间距布局),宿主自行渲染品牌标题、横幅图等#welcome-greeting:欢迎页推荐卡片顶部的问候语区(原「路安安」头像 + 文案)。组件内置内容已移除,不传该插槽时整块不渲染(不会留出空白条)#sidebar-header:侧边栏顶部区域(默认为空),可放自定义 logo、标题栏等;小窗模式(500px / 1000px 双屏)侧边栏整体隐藏,插槽不渲染
<template>
<Agent>
<!-- 欢迎页标题:替换内置「数字赋能监控」标题图 -->
<template #welcome-header>
<img src="./my-banner.png" alt="产品标题" />
</template>
<!-- 推荐卡片顶部问候语:容器(高度 64px、相对定位)由组件提供,内部内容自行编写 -->
<template #welcome-greeting>
<img
src="./my-bot.png"
alt="bot"
class="w-[84px] h-[84px] absolute top-[-12px] left-[18px]"
/>
<span class="text-[18px] text-[#333333] font-bold pl-[94px]">
Hi!我是路安安。我可以为您解答高速公路行业的政策及业务规范!
</span>
</template>
<!-- 侧边栏头部:渲染在「新对话 / 收藏夹」菜单上方 -->
<template #sidebar-header>
<div class="my-sidebar-logo">AI 助手</div>
</template>
</Agent>
</template>三个插槽均为无作用域插槽,直接传模板内容即可;
AiChat(全屏嵌入形态)上用法相同。#welcome-greeting的外层容器由组件提供(px-6 py-4 flex items-center relative h-[64px] overflow-visible),头像若需要悬浮出卡片上沿,按示例加absolute top-[-12px] left-[18px]即可。
三、组件使用
1. Agent(AI 悬浮助手:右下角小人 + 对话弹窗)
组件渲染常驻右下角的"知识 IP 小人",点击小人弹出对话窗口(整组件 Teleport 到 body,宿主任意祖先元素的 transform/filter 不影响定位):
- 弹窗锚定视口右下角,以圆弧扩散动画展开,初始为视口的 85% × 85%
- 弹窗左缘 / 顶缘 / 左上角可拖拽调整大小(右、下边缘固定)
- AI 页面随组件直接挂载(挂载时自动拉取用户信息);收起仅隐藏不销毁,会话上下文保留,后台流式回答继续且滚动位置不丢
- AI 回复结束后收起弹窗时,小人带完成提示气泡跳出展示,约 6.5 秒后自动消失
- 小人本体可上下拖动:拖到左/右自动贴边,松手带纵向惯性;位置写入 localStorage(贴边方向 + 纵向相对比例,换分辨率后仍合理),双击复位;位移小于 4px 仍按点击处理,不会误触打开弹窗
- 小人聚焦后支持键盘操作:上下方向键微调、左右方向键切换贴边、
Home复位
<script setup>
import { Agent } from 'leatop-ai';
</script>
<template>
<Agent />
</template>| Prop | 类型 | 默认 | 说明 |
| ------------- | ---------------- | ------------------ | ------------------------------------------------------------------------------- |
| width | String | 85vw | 弹窗初始宽度(css 值) |
| height | String | 85vh | 弹窗初始高度(css 值) |
| min-width | Number | 850 | 拖拽允许的最小宽度(px) |
| min-height | Number | 500 | 拖拽允许的最小高度(px) |
| right | String | 20px | 弹窗距视口右侧的距离(css 值) |
| bottom | String | 20px | 弹窗距视口底部的距离(css 值) |
| name | String | Hi!我是路安安 | 小人首次问候气泡文案 |
| finish-text | String | 对话已完成,需要我时随时点我哦~ | 对话完成提示语,传空字符串则不提示 |
| messages | BubbleMessage[] | [] | 小人气泡消息列表,元素结构 { id, text, type? },type 可选 speaking / listening / none |
| draggable | Boolean | true | 小人是否可上下拖动;传 false 时位置固定,点击小人仍可打开弹窗 |
| drag-limit | Object | {} | 小人可拖动范围:距视口四边的内边距(px),如 { left: 16, right: 16, bottom: 16 };负值可让形象压出边缘 |
| default-open | Boolean | false | 是否初始自动打开对话弹窗(挂载后以圆弧扩散动画展开,无需点击小人) |
| actions | Array / Function | null | 自定义头部动作项,详见「2.1 头部操作区自定义(AiChat / Agent 通用)」;内置收起按钮为 key: "close" 的动作,可覆盖/删除 |
| validate-send | Function | null | 发送前校验(透传给内部 AiChat),优先级高于全局 configureAi({ validateSend }),详见「2.2 发送前校验(validateSend)」 |
| 插槽 | 作用域 | 说明 |
| --- | --- | --- |
| #skills | { skills, selectedSkill, selectSkill, params } | 接管技能区(上传按钮保留),详见「技能区整体接管(#skills 插槽)」 |
| #actions | { actions, defaults, context, trigger } | 接管头部动作区,详见「4)示例:#actions 插槽整块接管」 |
| #welcome-header | 无 | 欢迎页标题区,不传则为空白,详见「自定义欢迎页标题、问候语与侧边栏头部」 |
| #welcome-greeting | 无 | 欢迎页推荐卡片顶部问候语,不传则整块不渲染,详见同上 |
| #sidebar-header | 无 | 侧边栏顶部区域,小窗模式不渲染,详见同上 |
| 事件 | 参数 | 说明 |
| --------------- | -- | ----------------------------------------- |
| setting-click | 无 | 用户点击侧边栏"设置"按钮时触发,由宿主自行处理(跳知识库页、打开抽屉等) |
实例方法(通过组件 ref 调用):
| 方法 | 说明 |
| --------- | -------------------------------------- |
| open() | 打开对话弹窗 |
| close() | 收起对话弹窗;若存在待展示的完成提示,会跳出小人并显示提示气泡 |
| refreshQuestions() | 重新请求接口刷新欢迎页「常见问题」列表(不是从已拉取数据里「换一批」),无返回值 |
<script setup>
import { ref } from 'vue';
import { Agent } from 'leatop-ai';
const agentRef = ref();
// 手动控制弹窗显隐(如由宿主自己的按钮触发)
const openAgent = () => agentRef.value?.open();
const closeAgent = () => agentRef.value?.close();
</script>
<template>
<!-- 传 default-open 则挂载后自动展开对话;不传则保持右下角小人入口 -->
<Agent
ref="agentRef"
default-open
name="Hi!我是小智"
finish-text="本轮回完啦~"
width="60vw"
height="70vh"
@setting-click="onSettingClick"
/>
</template>1.1.0:Agent 已从全屏页面升级为悬浮入口,原
is-compact/is-double-screen/initial-tab/initial-event-idprops 不再在 Agent 上暴露;如需独立的全屏对话页形态,请使用下方AiChat组件。
2. AiChat(AI 对话页面)
内部完整 AI 对话页(历史会话、收藏、知识库引用、全屏等能力齐全),宿主可将其嵌入自己的容器(全屏页、抽屉、自定义弹窗等):
<script setup>
import { AiChat } from 'leatop-ai';
</script>
<template>
<AiChat @setting-click="onSettingClick" @chat-finish="onChatFinish" />
</template>| 事件 | 参数 | 说明 |
| --------------- | -- | ----------------------------------------- |
| setting-click | 无 | 用户点击侧边栏"设置"按钮时触发,由宿主自行处理(跳知识库页、打开抽屉等) |
| chat-finish | 无 | 任一会话的 AI 回复流结束时触发(可据此播提示音、发通知等) |
| Prop | 类型 | 默认 | 说明 |
| --------- | ---------------- | ------ | --------------------------------- |
| actions | Array / Function | null | 自定义头部动作项,详见「2.1 头部操作区自定义(AiChat / Agent 通用)」 |
| validate-send | Function | null | 发送前校验,优先级高于全局 configureAi({ validateSend }),详见「2.2 发送前校验(validateSend)」 |
| 插槽 | 作用域 | 说明 |
| --- | --- | --- |
| #skills | { skills, selectedSkill, selectSkill, params } | 接管技能区(上传按钮保留),详见「技能区整体接管(#skills 插槽)」 |
| #actions | { actions, defaults, context, trigger } | 接管头部动作区,详见「4)示例:#actions 插槽整块接管」 |
| #welcome-header | 无 | 欢迎页标题区,不传则为空白,详见「自定义欢迎页标题、问候语与侧边栏头部」 |
| #welcome-greeting | 无 | 欢迎页推荐卡片顶部问候语,不传则整块不渲染,详见同上 |
| #sidebar-header | 无 | 侧边栏顶部区域,小窗模式不渲染,详见同上 |
实例方法(通过组件 ref 调用):
| 方法 | 说明 |
| --- | --- |
| refreshQuestions() | 重新请求接口刷新欢迎页「常见问题」列表(不是从已拉取数据里「换一批」),无返回值 |
<script setup>
import { ref } from 'vue';
import { AiChat } from 'leatop-ai';
const chatRef = ref();
// 后台常见问题配置更新后,主动拉取最新列表
const reloadQuestions = () => chatRef.value?.refreshQuestions();
</script>
<template>
<AiChat ref="chatRef" />
</template>2.1 头部操作区自定义(AiChat / Agent 通用)
头部右侧的动作区(新对话 / 收藏 / 全屏)由配置驱动,宿主可整体替换、按状态改写,或直接接管渲染。
默认动作:new-chat(紧凑态显示)、collect / uncollect(收藏与取消收藏互斥)、toggle-fullscreen(受 uiConfig.showFullscreenButton 控制)。
1)actions 的两种形态
<!-- 形态一:数组 = 完整列表(只渲染这些,缺省 visible 为 true) -->
<AiChat :actions="[{ key: 'toggle-fullscreen', icon: 'ui-fullscreen', title: '全屏' }]" />
<!-- 形态二:函数 = 拿到「默认动作 + 上下文」,返回什么就渲染什么(不自动合并) -->
<AiChat :actions="resolveActions" />函数第二个参数 context(只读):
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| tab | String | 当前视图:newchat(新对话)/ history-detail(会话详情)/ history(历史列表)/ favorites(收藏) |
| isSession | Boolean | 当前会话是否已有消息(chatStore.hasChat);新对话空态为 false |
| isCollected | Boolean | 当前会话是否已收藏 |
| compact / minimal | Boolean | 是否紧凑态 / 极简态 |
| isFullscreen | Boolean | 是否浏览器全屏 |
| isSmallWindow | Boolean | 是否小窗模式 |
| showFullscreenButton | Boolean | 是否启用全屏按钮 |
2)动作项字段
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| key | String | 是 | 唯一标识,用于渲染 key、去重与事件 type 兜底 |
| title | String | 否 | 原生 title + aria-label |
| icon | String | 否 | 包内雪碧图名称(如 newchat、star、ui-fullscreen) |
| iconSize | String | 否 | 图标尺寸,如 24px / 32px |
| iconColor | String | 否 | 图标填充色,缺省跟随文字色 |
| component | Component | 否 | 自定义渲染组件,优先级高于 icon |
| componentProps | Object | 否 | 透传给 component 的 props |
| visible | Boolean | 否 | 是否渲染,缺省 true;传 false 即隐藏/删除该项 |
| disabled | Boolean | 否 | 禁用态,不触发点击 |
| active | Boolean | 否 | 激活态高亮 |
| badge | String / Number | 否 | 右上角角标,0 与空串不显示 |
| order | Number | 否 | 排序权重,数字小的在前;不传则排在已设 order 的项之后 |
| onClick | Function | 否 | 点击回调,入参 { key, action, context, event } |
| event | String | 否 | 兼容字段:组件内部动作分发使用的 type,缺省取 key;宿主自定义动作直接用 onClick 即可 |
3)示例:追加按钮 / 按会话状态改写
import type { HeaderActionsResolver } from 'leatop-ai';
const resolveActions: HeaderActionsResolver = (defaults, context) => [
// 保留全部默认动作,仅改写全屏按钮文案
...defaults.map((item) =>
item.key === 'toggle-fullscreen' ? { ...item, title: '全屏模式' } : item,
),
// 有会话时才出现「分享」按钮
...(context.isSession
? [{
key: 'share',
icon: 'share',
iconSize: '24px',
title: '分享对话',
onClick: () => openShareDialog(),
}]
: []),
];<AiChat :actions="resolveActions" />
<Agent :actions="resolveActions" />4)示例:#actions 插槽整块接管
插槽作用于 AiChat / Agent,提供后默认渲染整块由宿主接管(容器间距、ml-auto 仍由组件提供):
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| actions | HeaderAction[] | 归一化后的最终动作列表(与默认渲染同源) |
| defaults | HeaderAction[] | 内置默认动作,便于插槽内再判断 |
| context | Object | 同上文上下文 |
| trigger | (action, event?) => void | 统一点击入口:执行 onClick,并交给组件内部动作分发(宿主不必自己转发) |
<script setup>
import { HeaderActionButton } from 'leatop-ai';
const onExport = () => exportConversation();
</script>
<template>
<AiChat :actions="resolveActions">
<template #actions="{ actions, trigger }">
<!-- 用 HeaderActionButton 复用内置按钮样式 -->
<HeaderActionButton
v-for="item in actions"
:key="item.key"
:action="item"
@click="trigger(item, $event)"
/>
<!-- 插槽内可放任意内容,样式需宿主自己写 -->
<button type="button" class="my-export-btn" @click="onExport">导出</button>
</template>
</AiChat>
</template>插槽内容在宿主作用域编译,组件库的 scoped 样式不会作用到槽内元素;需要与内置按钮一致的外观时直接使用
HeaderActionButton。
5)示例:Agent 的「收起」按钮也可控
Agent 悬浮窗的内置收起按钮是 key 为 "close" 的普通动作项,随 actions 一并交给宿主:
// 删除收起按钮(函数形态直接过滤)
const noClose: HeaderActionsResolver = (defaults) =>
defaults.filter((item) => item.key !== 'close');
// 只改图标/文案,保留默认的收起回调(未覆盖的字段会继承默认项)
const renameClose: HeaderActionsResolver = (defaults) =>
defaults.map((item) => (item.key === 'close' ? { ...item, icon: 'minus', title: '最小化' } : item));
// 数组形态下同 key 覆盖默认项,visible: false 即删除
const onlyNewChat = [
{ key: 'new-chat', icon: 'newchat', iconSize: '24px', title: '新对话' },
{ key: 'close', visible: false },
];数组形态是「完整列表」,只写需要的项;
close仅Agent有。宿主传入actions后,AiChat双屏模式下「只显示新对话」的内部规则让位。
2.2 发送前校验(validateSend)(AiChat / Agent 通用)
宿主可插入业务校验(知识库必选、字数上限、敏感词、权限/额度、二次确认)。校验在参数组装完成、真正发送之前执行;失败时不发送,输入内容与附件都保留。
1)两种使用位置
// 全局:所有 AiChat / Agent 实例生效
configureAi({
validateSend: (payload, context) => {
// 校验逻辑
},
});<!-- 组件级:只对当前实例生效,优先级高于全局配置 -->
<AiChat :validate-send="validateFn" />
<Agent :validate-send="validateFn" />2)入参
payload:与 @send 事件拿到的内容完全一致(不含技能对象,技能见 context.skill)
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| text | String | 用户输入(已 trim) |
| type | String | 当前技能 type,未选技能时为空串 |
| extraParams | Object | 最终请求参数(知识库 + 技能 params + getExtraParams 合并结果) |
| attachments | AiSendAttachment[] | 已上传成功的附件(含 name / format / size 等) |
context:payload 之外的附加信息(技能对象、知识库)
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| skill | AiSkill / null | 当前选中的技能 |
| knowledgeBaseIds | String[] | 选中的知识库 identifier;未选/全部知识库时为空数组 |
3)返回值
| 返回 | 行为 |
| --- | --- |
| true / undefined | 放行,继续发送 |
| false / '' | 静默阻断(宿主自己弹窗时用) |
| '提示文案' | 阻断并 ElMessage.warning(文案) |
| Promise<...> | 异步等待;期间发送按钮进入等待态,输入与附件保留 |
| 抛异常 | 仅 console.log,静默阻断 |
4)示例:同步校验(知识库必选 + 字数上限)
configureAi({
validateSend: (payload, context) => {
// 知识问答必须先选具体知识库
if (payload.type === '1' && !context.knowledgeBaseIds.length) {
return '请先选择知识库';
}
// 字数上限
if (payload.text.length > 2000) {
return '内容过长,请精简到 2000 字以内';
}
// 既没文字也没附件(内部已拦,这里演示兜底写法)
if (!payload.text && !payload.attachments.length) {
return '请输入内容或上传附件';
}
// 不返回 = 放行
},
});5)示例:异步校验(权限 / 额度)
configureAi({
validateSend: async (payload) => {
const { allowed, message } = await checkQuota(payload.type);
if (!allowed) return message || '今日额度已用完';
// await 期间发送按钮进入等待态,用户无法重复提交
},
});6)示例:组件级局部规则
<script setup>
// 只对当前页面生效,优先级高于全局配置
const validateForPage = (payload) =>
payload.text.length <= 500 || '单条消息不超过 500 字';
</script>
<template>
<AiChat :validate-send="validateForPage" />
<Agent :validate-send="validateForPage" />
</template>7)示例:静默阻断(宿主自己弹确认框)
configureAi({
validateSend: (payload) => {
if (payload.extraParams.needConfirm) {
openConfirmDialog(payload); // 宿主自己的弹窗
return false; // 静默阻断,组件不再 toast
}
},
});8)规则说明
- 只做「放行 / 阻断」,不支持改写 payload;需要追加参数请继续用
getExtraParams; - 优先级:
AiChat/Agent的validate-sendprop >configureAi({ validateSend }); - 附件在校验前已经上传完成,
validateSend里拦附件只能阻止发送,不会撤销已上传文件; - 不配置时行为与之前完全一致;空内容、上传中/失败、生成中、20MB/50MB 限制等内部约束仍由组件负责,不下放给宿主。
3. KnowledgeBase(知识库,原 /confluence 页面)
<template>
<KnowledgeBase />
</template>无需 props,接口配置走全局 configureAi。
4. Question(常见问题,原 /question 页面)
<template>
<Question />
</template>5. 报表文件下载卡片(AiChat / Agent 内部行为)
后端 /ai/stream/chat 返回 excel-ready 事件(字段 attachmentId / filename / downloadUrl)时,助手消息会自动展示文件下载卡片,点击优先走 downloadUrl 直链,无直链时回退 attachmentId 下载接口。宿主管辖范围内无需额外配置。
历史回显依赖后端历史消息带回
file字段(含attachmentId)且消息type为3/4;缺失时刷新页面后卡片不显示,需后端补齐落库字段。
四、样式引入
// 宿主 main.ts
import 'leatop-ai/dist/leatop-ai.css';包含组件样式、tailwind 工具类、SvgIcon 雪碧图,以及 Element Plus 命令式组件
(ElMessage / ElMessageBox / ElNotification)的结构样式——宿主无需再单独引入 EP 样式,
但 element-plus 包本身仍需安装(peer 依赖)。若宿主已有全局样式冲突
(preflight 重置),可在接入评审时协商加样式前缀隔离。
Element Plus 中文文案:Question / KnowledgeBase 内部已用 ElConfigProvider(zh-cn)包裹,
分页「共 x 条」、表格空态「暂无数据」等内置文案为中文,与宿主是否配置 locale 无关。
Agent / AiChat 不含表格与分页,宿主若希望整站统一中文,按常规在应用入口
app.use(ElementPlus, { locale: zhCn }) 配置即可。
五、版本更新记录
1.5.7(2026-10-23)
修复生成中「停止生成」按钮被置灰(视觉修复,无接口变更):
- 触发条件:宿主配置
uiConfig.showVoiceButton: false(隐藏麦克风)时,生成中输入框已清空、无附件,按钮误命中「空内容禁用发送」规则进入disabled,被grayscale(1)+opacity: 0.5滤镜变成灰色 - 修复方式:
loading(生成中)优先于所有禁用条件,停止按钮始终为蓝色可点击态;非生成时仍按原规则处理空内容、附件上传中/失败、语音识别中 - 影响范围:
AiChat/Agent/ 小屏FixedScreen三处输入区共用同一组件,行为一致
1.5.6(2026-10-23)
新增刷新常见问题实例方法(无接口变更,宿主按需调用):
AiChat/Agent新增实例方法refreshQuestions():重新请求getQuestionList刷新欢迎页「常见问题」列表,与卡片右上角「换一批」的本地翻页不同,调用后重置到第一页并拿到后台最新配置;无返回值- 调用链为
Agent→AiChat→MainContent→WelcomeScreen,宿主通过组件ref调用即可,无需改模板或配置 - 仅在新对话/会话详情视图(欢迎页所在视图)下生效:其它视图下调用会静默跳过,切回欢迎页不会自动补刷
1.5.5(2026-10-22)
数字人拖拽与会话标识透传(无接口变更,宿主无需改动):
- 新增数字人(Agent 右下角小人)上下拖拽:拖到左/右自动贴边、松手带纵向惯性;位置写入 localStorage(保存贴边方向 + 纵向相对比例,换分辨率后位置依旧合理),双击复位;位移小于 4px 仍视为点击,不会误触打开弹窗
- 拖拽细节:横向位置锁死在当前贴边(拖拽只改纵坐标)、位置用
translate3d驱动不触发布局、拖拽期间铺满视口的透明遮罩避免指针划过面板 iframe 丢事件、拖拽吞掉尾随 click;失焦/焦点在小人上时支持上下方向键微调、左右方向键切换贴边、Home复位 Agent新增 propsdraggable(默认true)与drag-limit(距视口四边的内边距,如{ left: 16, right: 16, bottom: 16 },负值可压出边缘):关闭拖拽后小人位置固定、点击行为不变;drag-limit运行时可响应式调整,越界会自动夹回安全区- 流式会话标识透传:SSE 回传
chatRecordId时组件库自动记录并回填会话,下一轮/ai/stream/chat请求同时携带sessionId与chatRecordId(两者独立取值、互不替代,缺失时传空串),便于后端按历史记录续聊
1.5.4(2026-10-21)
报表下载卡片与 Element Plus 中文文案(无接口变更,宿主无需改动):
- 报表生成结束(后端
excel-ready事件)后自动展示文件下载卡片:事件携带attachmentId/filename/downloadUrl,组件库写入消息并渲染预览卡片;点击卡片优先用downloadUrl直链下载,无直链时回退附件下载接口 - 报表卡片历史回显兼容
filename/downloadUrl字段命名(此前仅识别fileName/cdpUrl,导致刷新后卡片标题为空、直链丢失),消息type同时兼容字符串形态("3"/"4") Question/KnowledgeBase内部用ElConfigProvider(zh-cn) 包裹:表格空态、分页「共 x 条」等 Element Plus 内置文案由英文改为中文,不再受宿主全局 locale 影响
1.5.1(2026-10-20)
样式修复(视觉变更,宿主需做一次回归):
- 修复收藏按钮「已收藏」态图标不变蓝:
iconColor改为通过 CSScolor生效(雪碧图 path 自带fill="currentColor",原<use fill>被覆盖) - 修复组件内
border/border-[#xxx]与hover:边框全部失效:reset 改用:where()降低特异性,不再覆盖 tailwind 边框工具类;边框恢复显示后请回归相关页面
1.5.0(2026-10-20)
头部操作区改为配置驱动,宿主可自定义(含破坏性变更,宿主需同步调整):
- 新增
actionsprop(Agent/AiChat):支持数组(完整列表)与函数(defaults, context) => actions(返回值即最终结果,不与默认合并);context提供tab/isSession/isCollected/compact/minimal/isFullscreen/isSmallWindow/showFullscreenButton——tab为当前视图(newchat/history-detail/history/favorites),isSession表示当前会话已有消息(新对话空态为false) - 动作项新增
onClick/disabled/active/badge/order/componentProps:点击统一走onClick;内置动作仍通过event字段走组件内部事件分发(兼容原有行为) - 新增
#actions作用域插槽(Agent/AiChat):载荷{ actions, defaults, context, trigger },可直接接管整块动作区渲染 - 新增包入口导出
HeaderActionButton及类型HeaderAction/HeaderActionContext/HeaderActionClickPayload/HeaderActions/HeaderActionsResolver/HeaderActionsSlotProps:插槽内渲染动作时可直接复用内置按钮样式(插槽内容不带组件的 scoped 样式) Agent内置收起按钮改为key: "close"的普通动作项,随actions交给宿主——可改图标/文案/回调,也可过滤删除- 删除
#append-actions与AiChat的header-append插槽(破坏性变更):原先「在动作区末尾追加按钮」的能力统一由#actions承接;宿主迁移方式为在#actions中渲染actions+ 自定义按钮(见「2.1 头部操作区自定义」) - 头部动作统一渲染为
<button>(收藏/取消收藏由div改为button),按钮仅清除浏览器默认样式、尺寸随图标,视觉与原先一致;补充键盘焦点环、禁用态,支持disabled/badge - 动作区尺寸收敛:容器去掉固定
80px高度与重复色值,图标统一 24 / 32 两档(原小窗 34px 取消),紧凑态按钮不再强制36×36 AiChat双屏仍默认「只显示新对话」;宿主显式传入actions时以宿主配置为准- 新增
configureAi({ validateSend })与Agent/AiChat的validate-sendprop:发送前自定义校验,在参数组装完成、真正发送之前执行;返回false静默阻断、返回字符串阻断并提示、返回true/undefined放行,支持Promise(校验期间按钮进入等待态、输入与附件保留);异常仅console.log静默阻断。组件级 prop 优先于全局配置 - 新增包入口导出类型:
AiSendPayload/AiSendAttachment/AiSendContext/AiValidateSendResult/AiValidateSend - 说明:空内容、附件上传中/失败、生成中、文件大小限制等内部约束不交给宿主校验,
validateSend只承接业务规则(知识库必选、长度、敏感词、权限额度等)
1.4.2(2026-10-19)
输入框占位文案与欢迎页修复:
- 新增
uiConfig.inputPlaceholder:自定义未选中技能时的输入框默认占位文案;优先级为「技能placeholder→uiConfig.inputPlaceholder→ 包内默认『发消息或者按住空格说话...』」 - 修复
#welcome-header插槽不渲染:1.4.1 中欢迎页标题区是一个空的占位 div,<slot name="welcome-header">未被输出,宿主传入的标题内容实际不显示;现已接上插槽 - 欢迎页间距调整:标题区上边距 130px,标题区与推荐卡片之间保留弹性占位(206px),推荐卡片间距 40px
1.4.1(2026-10-19)
问候语插槽与弹窗样式内置(含视觉变更,宿主需确认):
- 新增
#welcome-greeting插槽(Agent / AiChat,逐层透传):欢迎页推荐卡片顶部的问候语区(原「路安安」头像 + 文案)交由宿主渲染;外层容器(64px 高 /relative/px-6 py-4)仍由组件提供,宿主只写内部内容 - 移除欢迎页内置问候语(视觉变更):不传
#welcome-greeting时整块不渲染(不留空白条),推荐卡片顶部直接接常见问题区;需要原有问候语请按 README 插槽示例自行渲染 - 弹窗/提示样式随包内置:补入 Element Plus 命令式组件样式(
ElMessage/ElMessageBox/ElNotification)。三者均为 JS 调用,unplugin-vue-components的按需样式注入覆盖不到——此前宿主自身若未引入 message-box 等样式,确认框会退化为无遮罩、无居中、无动画的普通 div。现由dist/leatop-ai.css直接提供 - 接入调整:宿主如曾为确认框手动
import 'element-plus/es/components/message-box/style/css',可删除(重复引入无害);element-plus包本身仍是 peer 依赖,必须安装 - 构建调整:样式类模块(
*.css与element-plus/.../style/css)不再 external,统一打进产物 css;@pureadmin/table样式随之进入产物,宿主无需额外引入 - 产物体积:
leatop-ai.css约 666KB → 1135KB(gzip 约 696KB),增量来自 EP base 变量表、三个命令式组件结构样式、pureadmin table 样式与内联背景图 - 新增
uiConfig.showVoiceButton(默认true):传false隐藏输入区语音输入(麦克风)按钮,空输入时该位置显示发送按钮并呈现禁用态(0.5 透明度 + 禁止光标);读响应式全局配置,挂载后再调configureAi即时生效 - 新增
uiConfig.assistantAvatar:AI 消息头像支持外部 URL(http(s) 链接 / base64),传了用外部图片,不传或为空沿用包内默认头像;响应式配置,可运行时切换
1.3.0(2026-10-16)
技能自定义渲染能力(含破坏性变更,宿主需同步调整):
- 新增技能字段
render:技能激活后在技能标识后面渲染宿主传入的自定义组件,支持组件定义(render: A)与工厂函数(render: ({ skill, params }) => A,可返回defineAsyncComponent)两种写法 - render 组件双向参数:组件通过 props 接收
{ skill, params },params为草稿级响应式对象,组件内直接赋值即随消息发送(自动并入/ai/stream/chat请求体);切换技能/清空输入时由组件库重置,发送后保留,小窗与主输入区切换自动互通 - 新增
#skills插槽(Agent / AiChat):技能区整块交由宿主实现(上传按钮保留),插槽作用域提供skills/selectedSkill/selectSkill/params - 新增包入口导出:
useChatDraft(宿主#skills自定义时读写输入草稿)、类型AiSkillRenderContext - 移除技能配置项
knowledgeBase(破坏性变更):知识库选择下拉不再作为宿主配置暴露,内置「知识问答(type 1)」的下拉保留为包内内部行为不受影响;自定义技能需要选择类交互请用render实现 - 发送参数合并优先级固定为:
knowledgeIds(内置知识库)→render.params→getExtraParams(宿主函数最高,同名覆盖) - 新增
uiConfig.showUploadButton(默认true):传false隐藏输入区上传文件按钮(+);与#skills插槽接管、响应式配置即时生效机制兼容 - 新增
#welcome-header/#sidebar-header插槽(Agent / AiChat,逐层透传):自定义欢迎页标题区与侧边栏顶部区域;小窗模式侧边栏隐藏,#sidebar-header不渲染 - 移除欢迎页内置标题(破坏性变更):原「数字赋能监控,智慧保障畅行」标题图不再内置,不传
#welcome-header时欢迎页标题区为空白(保留间距布局),宿主通过插槽自行渲染品牌内容
1.2.5(2026-10-16)
token 头与用户信息获取可自定义:
- 新增
configureAi({ tokenKey }):请求携带 token 的 header key 可配置,默认cdp-token,宿主可按后端约定改为其他 key(如Authorization);axios 请求、附件上传、SSE 流式请求三处统一生效 - 新增
configureAi({ getUserInfo }):宿主传入异步(Promise)函数自行获取用户信息,resolve 组件库所需字段即可(如{ account, userName, avatar }),返回值直接合并进组件库 user store;配置后组件库不再请求/system/user/curUser,且不强制要求先有 token(登录态由宿主函数自行负责)。不传保持原行为:组件挂载时按 token 自动调用/system/user/curUser
1.2.4(2026-10-15)
Agent 弹窗显隐控制:
- 新增 prop
default-open(默认false):传true时组件挂载后自动打开对话弹窗(带圆弧扩散动画),无需点击右下角小人 - 新增实例方法
open()/close():宿主通过ref主动控制弹窗显隐;close()与界面收起按钮行为一致——存在待展示的完成提示时,跳出小人并显示提示气泡 - 自动打开在
onMounted中经nextTick触发:先以隐藏态提交首帧、再切到打开态,保证clip-path扩散动画有起始帧(若直接就绪于打开态会直接闪出、无动画)
1.2.3(2026-10-15)
技能与请求参数自定义:
- 新增
configureAi({ skills }):宿主可裁剪或自定义输入区技能列表——不传为内置「知识问答(1) + 智能问数(2)」;字符串数组按内置技能筛选(如['2']只留智能问数,即隐藏知识问答);对象数组可覆盖内置技能字段或新增自定义技能(type 命中内置时未填字段沿用内置值) - 技能字段扩展:新增
iconUrl(自定义图标图片,渲染优先于包内雪碧图icon)、knowledgeBase(是否展示知识库选择下拉,内置仅 type1开启) - 新增
configureAi({ getExtraParams }):发送消息前按当前技能注入额外请求参数(自定义技能所需的业务参数),同名参数以宿主返回值为准 - 全局配置改用
reactive包装:组件挂载后再次调用configureAi修改技能列表可即时生效 - 修复技能识别硬编码:知识库下拉的判断由技能名(
name === '知识问答')改为技能声明(knowledgeBase),宿主重命名内置技能后不再丢失知识库选择
1.2.1(2026-10-15)
宿主接入 1.2.0 后的三项修复:
- 修复宿主 TS 报 7016"找不到模块 leatop-ai 的声明文件":
types入口原指向不存在的dist/types/index.d.ts,已修正为实际产物dist/index.d.ts,宿主更新后即可获得组件 props/事件类型提示 - 修复 Teleport 弹层样式退化:下拉/气泡/MessageBox/图片预览等弹层渲染到 body 后脱离
.leatop-ai-root作用域,弹层内边框、滚动条样式异常;已通过统一作用域清单覆盖各弹层 - 修复悬浮弹窗内下拉面板点不开:Agent 悬浮窗口层级(原 2100)高于 Element Plus 弹层基础层级(2000),弹窗内的下拉/tooltip/确认框等被窗口盖住;窗口层级下调至 2000
1.2.0(2026-10-15)
AiChat 组件导出与类型声明自动化:
- 新增
AiChat组件导出:内部完整 AI 对话页(历史会话/收藏/知识库引用/全屏),宿主可将其嵌入自有容器(全屏页/抽屉/自定义弹窗);事件setting-click(设置按钮点击)/chat-finish(AI 回复流结束) - 类型声明改为构建时自动生成(vite-plugin-dts + vue-tsc):
dist/types/index.d.ts由各组件defineProps/defineEmits直接转换产出,组件 props/事件变更时类型自动同步,不再手写维护types/leatop-ai.d.ts - 依赖调整:vue-tsc 升级 1.8 → 2.2,新增 devDependency vite-plugin-dts
全局样式防污染重构(修复组件库 css 污染宿主整站样式):
- 移除原管理后台残留的全局样式:删除 sidebar/dark/cdp/transition/variables/mixin 等死样式文件,theme.scss 从 2370 行裁剪至约 110 行,仅保留
leatop-ai-dialog/leatop-ai-messagebox/pure-table三块已作用域限定的主题规则——清除.theme-dark/.theme-blue换肤规则对宿主主题的覆盖,以及大量无作用域.el-button/.el-input/.el-table等规则对宿主 element-plus 外观的影响 - reset 与滚动条样式命名空间化:全局重置(
*border/box-sizing)与:root滚动条规则统一收敛到.leatop-ai-root根类,Agent/AiChat/KnowledgeBase/Question 组件根节点自动挂载,不再修改宿主 html/body/#app 与整站滚动条外观 - 收紧裸选择器:知识库下拉高亮
.el-dropdown-menu__item.is-active-knowledge限定到.custom-plus-dropdown弹层作用域,避免命中宿主下拉菜单项
1.1.1(2026-10-14)
修复生产构建小人裂图:
- 修复打包后知识 IP 小人动效图全部不显示:APNG 重播机制由"src 追加
?t=时间戳"改为:key重建 img 元素——生产构建图片被 base64 内联为 dataURL,追加 query 会破坏数据格式导致裂图(dev 模式为普通 URL 故无此问题)
1.1.0(2026-10-14)
Agent 悬浮化重构:
- Agent 升级为悬浮入口:默认渲染右下角知识 IP 小人,点击弹出对话窗口(整体 Teleport 到 body);内部 AI 全屏页不再单独导出,原
is-compact/is-double-screen/initial-tab/initial-event-idprops 移除 - 对话弹窗锚定视口右下角,初始 85vw × 85vh,左缘/顶缘/左上角可拖拽调整大小(右、下边缘固定);出现/收起为右下角圆弧扩散动画
- 弹窗显隐采用
visibility而非display:none:收起期间后台流式回答的滚动位置计算不失效,重新打开时消息区自动位于最新底部 - 新增 props:
width/height(弹窗初始尺寸)、min-width/min-height(拖拽下限)、right/bottom(锚定位置)、name(小人问候文案)、finish-text(对话完成提示语,空串关闭)、messages(小人气泡消息列表) - AI 回复流结束后收起弹窗,小人带完成提示气泡跳出展示,约 6.5s 自动消失;对话中收起时小人隐藏,避免与弹窗重叠
1.0.7(2026-10-14)
弹窗主题补全:
- ElMessageBox 确认框(删除历史/重命名/删除收藏/批量删除等 8 处)统一挂
leatop-ai-messageboxclass,主题规则同步作用域化并提权——此前 JS 调用的确认框未走组件库主题,被宿主全局样式接管 - dialog 与 message-box 的 header 显式清除
border-bottom(!important),修复宿主全局分隔线规则导致的标题栏下边框残留
1.0.6(2026-10-14)
弹窗与图标宿主兼容修复:
- 修复宿主端 SVG 图标丢失:组件库雪碧图改用独立 DOM 容器 id(
__leatop_ai_svg_icons_dom__),避免与宿主(同用 vite-plugin-svg-icons)的默认 id 冲突——同 id 时插件会整体跳过注入,导致后注册方图标全部 404 - 弹窗样式作用域化:组件库全部 el-dialog 统一挂
leatop-ai-dialogclass,弹窗主题规则限定该作用域并提权——弹窗append-to-body后不再被宿主全局弹窗样式覆盖(修复 header 背景丢失、背景图未覆盖到标题栏的问题),同时不污染宿主整站弹窗外观 - 弹窗统一
append-to-body:遮罩渲染到 body 层,修复宿主内容区存在 transform/filter 祖先时遮罩降级定位、无法全屏覆盖的问题 - 移除
/ai/stream/tongwen技能判断接口:发送消息不再单独预判技能类型,外部有 type 时随/ai/stream/chat请求携带,没有则不传
1.0.5(2026-10-14)
Pinia 实例隔离(修复与宿主同名 store 冲突):
- 组件库所有 store(user / aiChatSession / small-window)统一通过
useXxxStoreHook()显式绑定组件库独立的createPinia()实例,不再依赖activePinia——宿主即使存在同名 store(如登录的defineStore("user"))也互不冲突、互不污染 - 此前组件内 9 处无参
useUserStore()会命中宿主 activePinia,若宿主也有 id 为 "user" 的登录 store 会拿到错误结构导致历史列表/收藏失效 - 删除零引用的 app store
1.0.4(2026-10-14)
token 与用户信息链路重构(含破坏性变更,宿主需同步调整):
- token 获取统一为
configureAi({ getToken })唯一来源:移除sessionCookie配置项及 cookie/localStorage 兜底逻辑(utils/auth.ts 整体移除),未注入 getToken 时请求不带 token、不拉取用户信息 - 用户信息不再 localStorage 落盘缓存:Agent 挂载时按需拉取
/system/user/curUser,刷新/重挂载重新拉取,账号切换天然正确 - 修复嵌入宿主后不请求
/system/user/curUser导致历史列表、收藏等接口参数(account)为空的问题(原路由守卫拉取逻辑随路由裁剪丢失)
