ChatComposer 输入区改造:基于技能市场实现 Agent 输入框
前言
前面已经把 AI 工作台中间区域拆成了几个组件:
text
ChatWorkspace.vue
ChatHeader.vue
ChatMessageList.vue
ChatComposer.vue
其中 ChatComposer 负责底部输入区。
在普通聊天产品里,输入区通常只需要一个输入框和发送按钮。
但在 Agent 产品里,输入区不只是让用户输入一句话,它还需要告诉系统:
用户这次想让 AI 用什么能力来处理这个任务。
所以这次我对 ChatComposer 做了一个小改造:在输入框里加入"技能市场"。
当前效果大概是:

发送时,前端不再只提交用户输入的文字,而是提交:
ts
{
message: '帮我生成一个科技企业官网',
skillKeys: ['generate-page', 'optimize-style'],
skills: [...]
}
这样它就更接近真实生产环境里的 Agent 输入框。
什么是Skill
这里的"技能"可以理解成 AI Agent 的一种能力。
普通聊天输入框只关心:
text
用户输入了什么
比如:
text
帮我生成一个官网
但 Agent 产品通常还需要关心:
text
用户想用什么Skill处理这个需求
比如:
text
生成网页
优化样式
拆分组件
修复问题
生成文档
同样一句话,在不同技能下,AI 的处理方式是不一样的。
比如用户输入:
text
帮我优化首页
如果选择的是:
text
优化样式
AI 可能更关注页面间距、颜色、布局和视觉层级。
如果选择的是:
text
拆分组件
AI 可能更关注组件结构、文件拆分和代码可维护性。
所以技能的作用是:
给用户输入补充一个任务上下文,让 AI 更清楚这次应该按什么能力处理。
生产环境里的技能不只是一个按钮
一开始做原型时,技能很容易被写成这样:
ts
{
label: '生成网页',
value: 'webpage'
}
这种写法可以展示 UI,但还不太像真实项目。
真实生产环境里,一个技能通常应该是一份结构化配置。
它至少应该包含:
| 字段 | 作用 |
|---|---|
key |
技能唯一标识,后续传给后端或 Agent 调度层 |
name |
技能名称,用于页面展示 |
description |
技能说明,用于下拉菜单解释 |
category |
技能分类,后续方便分组和筛选 |
enabled |
是否启用,可用于权限控制 |
icon |
技能图标 |
prompt |
技能对应的系统提示词 |
placeholder |
选中技能后的输入框提示语 |
tags |
标签,后续可以用于搜索或分类 |
defaultSelected |
是否默认选中 |
所以我把技能从 ChatComposer.vue 里抽了出来,单独放到配置文件里。
对应文件:
text
src/features/workbench/config/skills.ts
Ant Design X React 版是怎么做的
在 Ant Design X 的 React 版中,Sender 已经内置了和技能相关的能力。
官方文档里,Sender 支持:
text
skill
slotConfig
其中:
skill表示当前技能配置;slotConfig表示词槽配置;- 配置后输入框会进入词槽模式;
- 提交时可以拿到用户输入、词槽配置和技能信息。

React 版文档里的 onSubmit 类型大致是:
ts
(message: string, slotConfig: SlotConfigType[], skill: SkillType) => void
也就是说,React 版的 Sender 提交时不是只给一个字符串。
它会同时给出:
text
message 用户输入内容
slotConfig 结构化词槽内容
skill 当前技能
官方 SkillType 大致是:
ts
interface SkillType {
title?: React.ReactNode;
value: string;
toolTip?: TooltipProps;
closable?:
| boolean
| {
closeIcon?: React.ReactNode;
onClose?: React.MouseEventHandler<HTMLDivElement>;
disabled?: boolean;
};
}
它最核心的字段是:
text
title:展示给用户看的技能名称
value:真正提交时使用的技能值
而 slotConfig 更偏结构化输入。
比如官方示例中类似:
text
Writing Assistant × Please write an article about [Please enter a topic]. The requirement is [800] words.
这里的 [Please enter a topic] 和 [800] 就不是普通文本,而是词槽。
所以 React 版的能力更完整,它不是简单画一个技能标签,而是把技能和结构化输入都放进了 Sender 内部。
Vue 版当前的问题
我当前项目使用的是:
text
ant-design-x-vue@1.6.0
我查了本地源码和文档,当前 Vue 版 Sender 暂时没有 React 版那种:
ts
skill
slotConfig
它现在主要支持:
ts
prefix
footer
header
actions
loading
placeholder
onSubmit
onChange
onCancel
onPasteFile
也就是说,不能直接这样写:
vue
<Sender
:skill="skill"
:slot-config="slotConfig"
/>
如果这样写,当前 Vue 版并不会识别这些属性。
所以我的做法是:
UI 上先使用
Sender已有的prefix和footer插槽实现技能市场,数据层再设计完整的 AgentSkill 配置和提交 payload。
这样虽然不是组件库原生 skill API,但业务上已经具备生产环境需要的技能上下文。
当前项目怎么实现
当前输入区对应文件是:
text
src/features/workbench/components/chat/ChatComposer.vue
技能配置对应文件是:
text
src/features/workbench/config/skills.ts
类型定义对应文件是:
text
src/features/workbench/types.ts
整体思路是:
text
skills.ts 维护完整技能配置
ChatComposer.vue 展示技能市场和已选技能
ComposerSubmitPayload 定义发送给业务层的数据结构
WorkbenchPage.vue 接收 payload 并模拟 AI 回复
定义技能类型
先在 types.ts 中定义技能 key:
ts
export type AgentSkillKey =
| 'generate-page'
| 'optimize-style'
| 'split-component'
| 'fix-issue'
| 'write-doc';
这些 key 是给程序识别用的。
比如:
text
generate-page 生成网页
optimize-style 优化样式
split-component 拆分组件
fix-issue 修复问题
write-doc 生成文档
然后定义技能分类:
ts
export type AgentSkillCategory = 'generation' | 'optimization' | 'engineering' | 'document';
最后定义完整的技能结构:
ts
export interface AgentSkill {
key: AgentSkillKey;
name: string;
description: string;
category: AgentSkillCategory;
enabled: boolean;
icon: Component;
prompt: string;
placeholder: string;
tags?: string[];
defaultSelected?: boolean;
}
这样技能就不是一个简单 label,而是一份完整配置。
定义提交数据
为了让发送更接近真实业务,我没有继续只提交字符串。
而是新增了:
ts
export interface ComposerSubmitPayload {
message: string;
skillKeys: AgentSkillKey[];
skills: Pick<AgentSkill, 'key' | 'name' | 'category' | 'prompt'>[];
}
也就是说,用户点击发送后,业务层拿到的是:
ts
{
message: '帮我生成一个科技企业官网',
skillKeys: ['generate-page', 'optimize-style'],
skills: [
{
key: 'generate-page',
name: '生成网页',
category: 'generation',
prompt: '你是一个前端页面生成助手...'
}
]
}
这里最关键的是:
text
message 是用户输入
skillKeys 是技能标识
skills 里带了技能的 prompt 和分类信息
后续如果要接真实后端,就可以把这些信息作为请求参数传过去。
抽离技能配置
技能配置放在:
text
src/features/workbench/config/skills.ts
示例:
ts
export const agentSkills: AgentSkill[] = [
{
key: 'generate-page',
name: '生成网页',
description: '根据自然语言需求生成页面结构和初版布局',
category: 'generation',
enabled: true,
icon: AppstoreOutlined,
prompt: '你是一个前端页面生成助手,请根据用户需求生成清晰的页面结构、模块划分和初版布局方案。',
placeholder: '描述你想生成的网页,例如:帮我生成一个科技企业官网首页',
tags: ['page', 'layout', 'zero-code'],
defaultSelected: true,
},
];
这样做有几个好处:
- 技能配置集中管理;
- UI 组件不再写死技能数据;
- 后续可以从接口加载技能;
- 可以做权限控制;
- 可以根据技能切换 placeholder;
- 可以把 skill key 传给后端做 Agent 调度。
ChatComposer 中怎么使用
在 ChatComposer.vue 中引入技能配置:
ts
import { agentSkills, defaultAgentSkillKeys } from '../../config/skills';
import type { AgentSkillKey, ComposerSubmitPayload } from '../../types';
然后定义当前选中的技能:
ts
const selectedSkillKeys = ref<AgentSkillKey[]>([...defaultAgentSkillKeys]);
这里默认选中配置里带有:
ts
defaultSelected: true
的技能。
当前可用技能这样计算:
ts
const skillOptions = computed(() => agentSkills.filter(skill => skill.enabled));
意思是:
只展示 enabled 为 true 的技能。
当前已选技能这样计算:
ts
const selectedSkills = computed(() => {
return skillOptions.value.filter(skill => selectedSkillKeys.value.includes(skill.key));
});
意思是:
从全部可用技能里筛选出当前已经选中的技能。
技能市场下拉怎么实现
技能市场放在 Sender 的 footer 插槽里。
因为当前 Vue 版 Sender 支持:
vue
<template #footer>
自定义底部内容
</template>
所以我在 footer 里放了:
vue
<a-dropdown :trigger="['click']">
<button type="button" class="composer-tool-button">
<ProjectOutlined />
<span>技能市场</span>
<DownOutlined />
</button>
<template #overlay>
<div class="skill-menu">
...
</div>
</template>
</a-dropdown>
这段逻辑就是:
text
点击"技能市场"
↓
展开技能列表
↓
点击某个技能
↓
选中或取消选中
其中多选逻辑是:
ts
const toggleSkill = (skillKey: AgentSkillKey) => {
if (isSkillSelected(skillKey)) {
selectedSkillKeys.value = selectedSkillKeys.value.filter(key => key !== skillKey);
return;
}
selectedSkillKeys.value = [...selectedSkillKeys.value, skillKey];
};
这段代码的意思是:
text
如果技能已经选中,再点击就取消
如果技能还没选中,点击就加入
已选技能怎么展示
已选技能放在 Sender 的 prefix 插槽里。
代码大致是:
vue
<template #prefix>
<div v-if="selectedSkills.length" class="skill-chip-list">
<button
v-for="skill in selectedSkills"
:key="skill.key"
type="button"
class="skill-chip"
>
<component :is="skill.icon" />
<span>{{ skill.name }}</span>
<CloseOutlined @click.stop="removeSkill(skill.key)" />
</button>
</div>
</template>
这段就是输入框左上角的技能标签。
比如当前选中了:
text
生成网页
优化样式
输入框里就会显示:

点击 × 会执行:
ts
const removeSkill = (skillKey: AgentSkillKey) => {
selectedSkillKeys.value = selectedSkillKeys.value.filter(key => key !== skillKey);
};
也就是把这个技能从已选列表里移除。
placeholder 怎么跟着技能变化
为了让输入框更像真实 Agent 产品,我让 placeholder 根据当前技能变化。
ts
const placeholder = computed(() => {
if (selectedSkills.value.length === 0) {
return '先选择技能,再描述你想让 AI 完成的任务';
}
if (selectedSkills.value.length === 1) {
return selectedSkills.value[0].placeholder;
}
return `描述需求,当前技能:${selectedSkills.value.map(skill => skill.name).join('、')}`;
});
逻辑是:
text
没有选择技能:提示先选择技能
只选择一个技能:使用该技能自己的 placeholder
选择多个技能:展示当前选择了哪些技能
这样用户输入前就能知道:
text
当前 AI 会按哪些技能处理我的需求
发送时如何带上技能
这是这次改造里最重要的一步。
发送函数是:
ts
const handleSubmit = (value: string) => {
const text = value.trim();
if (!text || props.running) return;
emit('submit', {
message: text,
skillKeys: [...selectedSkillKeys.value],
skills: selectedSkills.value.map(skill => ({
key: skill.key,
name: skill.name,
category: skill.category,
prompt: skill.prompt,
})),
});
inputValue.value = '';
};
这里先做了两个判断:
ts
if (!text || props.running) return;
意思是:
text
如果输入为空,不发送
如果正在生成中,不重复发送
然后提交:
ts
message
skillKeys
skills
这一步让技能真正参与了业务流程。
如果只是显示技能标签,但发送时不带技能,那它只是 UI。
现在发送时带上技能,后续就可以根据技能做:
- 不同 prompt 拼接;
- 不同任务流程;
- 不同执行过程展示;
- 不同结果格式处理。
父组件怎么接收
父组件的接受会在后续流程中介绍(TOdolist)
当前实现的价值
这次 ChatComposer 改造主要完成了几件事:
- 使用
Sender搭建 Agent 输入框; - 通过
footer插槽实现技能市场入口; - 通过
prefix插槽展示已选技能; - 支持多个技能同时选择;
- 支持移除已选技能;
- 技能配置从组件中抽离;
- 定义完整的
AgentSkill类型; - 定义
ComposerSubmitPayload; - 发送消息时携带技能信息;
- 为后续接入真实 Agent 服务预留数据结构。
所以它不是单纯做了一个下拉菜单。
更准确地说,它完成的是:
基于现有 Sender 能力,在业务层实现一个可用于生产扩展的 Agent 技能输入模型。
总结
这次改造的核心目标是让 ChatComposer 更像真实 AI Agent 产品里的输入区。
在 AI 时代,输入框不只是输入文本的地方,它还需要承载任务意图、技能选择、附件、上下文等信息。
React 版 Ant Design X 的 Sender 已经提供了 skill 和 slotConfig,可以直接支持技能和词槽模式。
但当前 ant-design-x-vue@1.6.0 还没有这套 API。
所以我在业务项目里选择了一种更稳的方式:
text
UI 上使用 Sender 的 prefix/footer 插槽
数据上设计完整 AgentSkill 配置
提交上使用 ComposerSubmitPayload 传递 message + skills
这样既能保持现有组件库不动,又能让业务输入框具备生产环境所需要的技能上下文。
后续如果 Vue 版组件库补齐原生 skill 能力,也可以把现在这套业务数据模型平滑迁移过去。