#MarkdownInputField - 输入框
MarkdownInputField 是一个带发送功能的 Markdown 输入字段组件,允许用户编辑 Markdown 内容并通过按钮或快捷键发送。
#功能特点
- 📝 支持 Markdown 输入
- 📎 支持附件上传
- 🔘 支持自定义操作按钮
- 🍵 支持插槽输入
- 🎯 支持技能模式
import { Button, message } from 'antd';
import { AimOutlined, GlobalOutlined, EditOutlined } from '@ant-design/icons';
import { Sparkles, ChevronDown } from '@sofa-design/icons';
import {
ActionItemBox,
ActionItemContainer,
MarkdownInputField,
SuggestionList,
ActionIconBox,
CreateRecognizer,
type MarkdownEditorInstance,
} from '@ant-design/agentic-ui';
const createRecognizer: CreateRecognizer = async ({ onPartial, onError }) => {
let timer: ReturnType<typeof setInterval>;
return {
start: async () => {
// 真实场景应启动麦克风与ASR服务,这里仅用计时器模拟持续的转写片段
let i = 0;
timer = setInterval(() => {
onPartial(`语音片段${i} `);
i += 1;
}, 500);
},
stop: async () => {
clearInterval(timer);
},
};
};
export default () => {
const [value, setValue] = React.useState(
'`${placeholder:目标场景}`今天的拒绝率为什么下降`${placeholder:目标事件}`输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本',
);
const markdownRef = React.useRef<MarkdownEditorInstance>(null);
return (
<div
style={{
boxSizing: 'border-box',
margin: '0 auto',
maxWidth: 920,
padding: '0 12px',
width: '100%',
}}
>
<MarkdownInputField
style={{ maxWidth: '100%' }}
value={value}
inputRef={markdownRef}
voiceRecognizer={createRecognizer}
attachment={{
enable: true,
accept: '.pdf,.doc,.docx,image/*',
maxFileSize: 10 * 1024 * 1024, // 10MB(字节)
upload: async (file, index) => {
if (index == 3) {
throw new Error('上传失败');
}
// 模拟上传文件
await new Promise((resolve) => setTimeout(resolve, 1000));
return URL.createObjectURL(file);
},
onDelete: async (file) => {
console.log('删除文件:', file);
await new Promise((resolve) => setTimeout(resolve, 500));
},
}}
tagInputProps={{
type: 'dropdown',
enable: true,
items: async (props) => {
if (props?.placeholder === '目标场景') {
return [];
}
return ['tag1', 'tag2', 'tag3'].map((item) => {
return {
key: item,
label: props?.placeholder + item,
};
});
},
}}
actionsRender={(props, defaultActions) => {
return [
<ActionIconBox
showTitle={props.collapseSendActions}
title="提示词库"
key="edit"
style={{
padding: 8,
fontSize: 16,
}}
>
<Sparkles />
</ActionIconBox>,
...defaultActions,
];
}}
beforeToolsRender={() => {
return (
<ActionItemContainer showMenu={true}>
{new Array(6).fill(0).map((_, index) => (
<ActionItemBox
onClick={() => message.info('快捷技能' + index)}
icon="https://mdn.alipayobjects.com/huamei_ptjqan/afts/img/A*Bgr8QrMHLvoAAAAAF1AAAAgAekN6AQ/original"
iconSize={16}
size="small"
title={
<span
style={{ display: 'flex', alignItems: 'center', gap: 8 }}
>
{'快捷技能' + index}
</span>
}
disabled={index < 2}
key={'快捷技能' + index}
/>
))}
</ActionItemContainer>
);
}}
toolsRender={() => [
<Button
type="text"
key="bold"
onClick={() => console.log('DeepThink clicked')}
>
DeepThink
<ChevronDown />
</Button>,
<Button
type="text"
key="italic"
icon={<GlobalOutlined />}
onClick={() => console.log('深度思考 clicked')}
>
深度思考
</Button>,
<Button
type="text"
key="link"
icon={<AimOutlined />}
onClick={() => console.log('联网搜索 clicked')}
>
联网搜索
</Button>,
]}
onChange={(newValue) => {
setValue(newValue);
console.log('newValue', newValue);
}}
placeholder="请输入内容..."
onSend={async (text) => {
console.log('发送内容:', text);
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
<SuggestionList
style={{
marginTop: 8,
maxWidth: '100%',
}}
items={[
{
key: 'qwe',
icon: '💸',
text: '关税对消费类基金的影响',
actionIcon: <EditOutlined />,
},
{
key: 'asd',
icon: '📝',
text: '恒生科技指数基金相关新闻',
actionIcon: <EditOutlined />,
},
{
key: 'zxc',
icon: '📊',
text: '数据分析与可视化',
actionIcon: <EditOutlined />,
},
]}
layout={'horizontal'}
onItemClick={() => {
markdownRef?.current?.store?.setMDContent(
'关税对`${placeholder:消费类}`基金的影响',
);
}}
/>
</div>
);
};#快捷键
组件支持以下快捷键操作,可以通过 triggerSendKey 属性配置发送行为。
#发送与换行
| 模式 | 发送快捷键 | 换行快捷键 | 适用场景 |
|---|---|---|---|
'Enter' (默认) | Enter | Shift + Enter | 适合短文本对话,按回车直接发送 |
'Mod+Enter' | Cmd / Ctrl + Enter | Enter | 适合长文本编辑,需要频繁换行 |
特殊说明:
- 移动端适配:在移动设备上,为防止误触,强制使用
'Mod+Enter'模式(即点击键盘回车键仅换行,不发送)。- 输入法兼容:在中文输入法(IME)组字/选词过程中,按 Enter 键不会触发发送。
#输入历史导航
开启 inputHistory.enable 后(对齐桌面 IDE 聊天输入框行为):
| 快捷键 | 行为 |
|---|---|
| ↑ | 光标位于内容开头时,回溯上一条已发送内容 |
| ↓ | 光标位于内容末尾时,前进到下一条(回到栈顶时找回编辑中内容) |
- 发送成功的内容自动进入历史栈(去重、上限默认 100 条,可配
maxLength); - 回溯前当前编辑中的内容会被暂存,按 ↓ 到栈顶可找回;
- 历史仅保存在组件实例内存中,不跨会话持久化。
#拖拽上传
配置 attachment.enable 且提供 upload / uploadWithResponse 后,直接把文件拖入输入框即可上传(含格式、大小、数量校验),拖拽悬停时会显示高亮覆盖层。
<MarkdownInputField
attachment={{
enable: true,
accept: '.pdf,.doc,.docx,image/*',
upload: async (file) => URL.createObjectURL(file),
}}
/>#编辑器通用快捷键
除了发送快捷键外,组件还支持以下 Markdown 编辑常用的快捷键:
| 快捷键 (Mac / Windows) | 功能 |
|---|---|
| Cmd + B / Ctrl + B | 加粗 |
| Cmd + I / Ctrl + I | 斜体 |
| Cmd + Shift + S / Ctrl + Shift + S | 删除线 |
Option + </kbd> / <kbd>Alt</kbd> + <kbd> | 行内代码 |
| Cmd + \ / Ctrl + \ | 清除格式 |
| Cmd + 1 | 标题 H1 ~ H4 |
| Cmd + 0 / Ctrl + 0 | 转换为普通段落 |
| Cmd + ] / Ctrl + ] | 增加标题级别(变小) |
| Cmd + [ / Ctrl + [ | 降低标题级别(变大) |
| Option + Q / Alt + Q | 引用块 |
| Cmd + Opt + O / Ctrl + Alt + O | 有序列表 |
| Cmd + Opt + U / Ctrl + Alt + U | 无序列表 |
| Cmd + Opt + S / Ctrl + Alt + S | 任务列表 |
| Cmd + Opt + C / Ctrl + Alt + C | 代码块 |
| Cmd + Opt + T / Ctrl + Alt + T | 表格 |
| Cmd + Opt + / / Ctrl + Alt + / | 分割线 |
| Cmd + Shift + L / Ctrl + Shift + L | 选择当前行 |
| Cmd + D / Ctrl + D | 选择当前单词/汉字 |
| Cmd + Z / Ctrl + Z | 撤销 |
| Cmd + Shift + Z / Ctrl + Shift + Z | 重做 |
#API
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
value | 当前的 markdown 文本值 | string | - | - |
onChange | 当输入值改变时触发的回调函数 | (value: string) => void | - | - |
placeholder | 输入字段的占位文本 | string | - | - |
style | 应用于输入字段的内联样式 | React.CSSProperties | - | - |
className | 应用于输入字段的 CSS 类名 | string | - | - |
disabled | 是否禁用输入字段 | boolean | - | - |
typing | AI 回复中等场景下为 true,输入区只读并显示提示 | boolean | - | - |
allowEmptySubmit | 是否允许在内容为空时也触发发送 | boolean | false | - |
triggerSendKey | 触发发送操作的键盘快捷键 | 'Enter' | 'Mod+Enter' | 'Enter' | - |
onSend | 当内容发送时触发的异步回调函数 | (value: string) => Promise<void> | - | - |
onStop | 正在输入中时点击发送按钮的回调函数 | () => void | - | - |
onFocus | 当输入字段获得焦点时触发的回调函数 | (value: string, schema: Elements[], e: FocusEvent) => void | - | - |
onBlur | 当输入字段失去焦点时触发的回调函数 | (value: string, schema: Elements[], e: MouseEvent) => void | - | - |
tagInputProps | 标签输入的相关属性 | MarkdownEditorProps['tagInputProps'] | - | - |
suggestionProps | 内部建议下拉框配置;可关闭下拉框或指定弹层容器 | SuggestionProps | - | - |
borderRadius | 边框圆角大小 | number | 12 | - |
attachment | 附件配置 | { enable?: boolean } & AttachmentButtonProps | - | - |
voiceRecognizer | 语音输入配置 | CreateRecognizer | - | - |
actionsRender | 自定义渲染操作按钮的函数 | (props, defaultActions) => React.ReactNode[] | - | - |
toolsRender | 自定义渲染操作按钮前内容的函数 | (props) => React.ReactNode[] | - | - |
beforeToolsRender | 自定义渲染输入框上方的工具栏区域 | (props) => React.ReactNode | - | - |
quickActionRender | 自定义右上操作按钮渲染函数 | (props) => React.ReactNode[] | - | - |
leafRender | 自定义叶子节点渲染函数 | (props, defaultDom) => React.ReactElement | - | - |
inputRef | 输入框引用 | React.MutableRefObject<MarkdownEditorInstance> | - | - |
skillMode | 技能模式配置 | SkillModeConfig | - | - |
onSkillModeOpenChange | 技能模式状态变化回调 | (open: boolean) => void | - | - |
pasteConfig | 粘贴配置 | { enabled?: boolean; allowedTypes?: string[] } | - | - |
refinePrompt | 提示词优化配置 | { enable: boolean; onRefine: (input: string) => Promise<string>; } | - | - |
enlargeable | 放大功能配置 | { enable?: boolean; height?: number; } | - | - |
isShowTopOperatingArea | 是否显示顶部操作区域 | boolean | false | - |
targetRef | 顶部操作区域回到顶部/底部功能的目标元素引用 | React.RefObject<HTMLDivElement> | - | - |
operationBtnRender | 顶部操作区域自定义操作按钮渲染函数 | () => React.ReactNode | - | - |
isShowBackTo | 是否在顶部操作区域显示回到顶部/底部按钮 | boolean | true | - |
maxHeight | 输入框的最大高度 | number | string | - | - |
maxLength | 输入文本的最大字符数限制 | number | - | - |
onMaxLengthExceeded | 当输入达到最大长度限制时的回调函数 | (value: string) => void | - | - |
sendButtonProps | 发送按钮配置 | SendButtonCustomizationProps | - | - |
disableHoverAnimation | 是否禁用 hover 动画 | boolean | false | - |
disableFocusAnimation | 是否禁用聚焦边框光束动画 | boolean | false | - |
inputHistory | 输入历史导航配置(↑/↓ 翻阅已发送内容) | { enable?: boolean; maxLength?: number } | - | - |
followups | 建议问题配置(输入框下方可点击发送/回填) | { items?: { text: string; fillOnly?: boolean; icon?: ReactNode; title?: string }[] } | - | - |
bgColorList | 背景颜色列表 | string[] | - | - |
composerChips | 内联原子 chip 体系(slash 命令 / @file / @symbol) | { enable?: boolean; onSlashChipGate?; onSlashChipClick? } | - | 2.33.0 |
longTextFold | 长文本粘贴折叠(≥1200 字符或 ≥12 行折叠为 chip) | { enable?: boolean } | { enable: false } | 2.33.0 |
draft | fragment 级草稿恢复(idle/blur/send/switch 提交) | { draftKey?; storage?; idleDelay?; onDraftCommit?; onDraftRestore? } | - | 2.33.0 |
contextUsage | 上下文用量环形指示器 | { usedTokens: number; contextWindow: number; ... } | - | 2.33.0 |
branch | 分支选择触发器(分支 pill + 搜索菜单) | { branchName?; branches?; onSelectBranch?; ... } | - | 2.33.0 |
#类型定义
#AttachmentButtonProps
附件按钮配置属性。
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
upload | 文件上传处理函数,返回文件 URL | (file: AttachmentFile, index: number) => Promise<string> | - | - |
uploadWithResponse | 文件上传处理函数(返回完整响应),优先级高于 upload | (file: AttachmentFile, index: number) => Promise<UploadResponse> | - | - |
fileMap | 文件映射表,用于存储已上传的文件 | Map<string, AttachmentFile> | - | - |
onFileMapChange | 文件映射表变更时的回调 | (files?: Map<string, AttachmentFile>) => void | - | - |
supportedFormat | 支持的文件格式配置 | SupportedFileFormats | - | - |
disabled | 是否禁用按钮 | boolean | - | - |
maxFileSize | 单个文件最大大小(字节) | number | - | - |
maxFileCount | 最大文件数量 | number | - | - |
allowMultiple | 是否允许一次选择多个文件(默认:true) | boolean | - | - |
onExceedMaxCount | 文件数量超出 maxFileCount 限制时的回调 | (info: { maxCount: number; currentCount: number; selectedCount: number }) => void | - | - |
onPreview | 自定义文件预览处理函数,不传时图片默认弹窗预览、其他文件新窗口打开 | (file: AttachmentFile) => void | Promise<void> | - | - |
#SkillModeConfig
技能模式配置接口。
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
enable | 是否启用技能模式组件,当为 false 时组件完全不渲染 | boolean | true | - |
open | 是否打开技能模式 | boolean | false | - |
title | 技能模式标题 | React.ReactNode | - | - |
rightContent | 右侧自定义内容,支持单个节点或数组 | React.ReactNode | React.ReactNode[] | - | - |
closable | 是否显示默认关闭按钮 | boolean | true | - |
style | 技能模式容器样式 | React.CSSProperties | - | - |
className | 技能模式容器类名 | string | - | - |
#SendButtonCustomizationProps
发送按钮自定义配置。
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
compact | 是否使用紧凑模式显示按钮 | boolean | - | - |
colors | 自定义按钮颜色配置 | SendButtonColors | - | - |
disabled | 是否禁用发送按钮。显式传入时优先级高于内部上传状态判断;未传入时,文件上传中会自动禁用 | boolean | - | - |
#SendButtonColors
发送按钮颜色配置。
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
icon | 默认状态下的图标颜色 | string | - | - |
iconHover | Hover 状态下的图标颜色 | string | - | - |
background | 默认状态下的背景颜色 | string | - | - |
backgroundHover | Hover 状态下的背景颜色 | string | - | - |
#示例
#基础使用
import { MarkdownInputField } from '@ant-design/agentic-ui';
import { Button } from 'antd';
import { ChevronDown } from '@sofa-design/icons';
const App = () => {
const [value, setValue] = React.useState('');
return (
<div
style={{
padding: 12,
display: 'flex',
flexDirection: 'column',
gap: 12,
}}
>
<MarkdownInputField
value={value}
toolsRender={() => [
<Button
type="text"
key="bold"
onClick={() => console.log('DeepThink clicked')}
>
DeepThink
<ChevronDown />
</Button>,
<Button
type="text"
key="italic"
onClick={() => console.log('深度思考 clicked')}
>
深度思考
</Button>,
<Button
type="text"
key="link"
onClick={() => console.log('联网搜索 clicked')}
>
联网搜索
</Button>,
]}
onChange={(newValue) => setValue(newValue)}
placeholder="请输入内容..."
onSend={async (text) => {
console.log('发送内容:', text);
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
<MarkdownInputField
value={value}
onChange={(newValue) => setValue(newValue)}
placeholder="请输入内容..."
onSend={async (text) => {
console.log('发送内容:', text);
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
<div>
<h4>Props 说明</h4>
<ul>
<li>
<code>value</code> - 当前的 markdown 文本值
</li>
<li>
<code>onChange</code> - 当输入值改变时触发的回调函数
</li>
<li>
<code>placeholder</code> - 输入字段的占位文本
</li>
<li>
<code>onSend</code> - 当内容发送时触发的异步回调函数
</li>
</ul>
</div>
</div>
);
};
export default App;#AI 回复中的输入指引
当 typing 为 true(例如 AI 正在流式输出)或发送中的 onSend 尚未 resolve,且当前输入为空时,输入区左侧会显示带动画的提示文案(国际化键 input.typing.hint),比仅依赖发送按钮 loading 更明显。typing 为 true 时输入区为只读,无法编辑或语音输入,附件按钮亦不可用。
#小屏幕
import { Button, message } from 'antd';
import { AimOutlined, GlobalOutlined, EditOutlined } from '@ant-design/icons';
import { Sparkles, ChevronDown } from '@sofa-design/icons';
import {
ActionItemBox,
ActionItemContainer,
MarkdownInputField,
SuggestionList,
ActionIconBox,
CreateRecognizer,
type MarkdownEditorInstance,
} from '@ant-design/agentic-ui';
const createRecognizer: CreateRecognizer = async ({ onPartial, onError }) => {
let timer: ReturnType<typeof setInterval>;
return {
start: async () => {
// 真实场景应启动麦克风与ASR服务,这里仅用计时器模拟持续的转写片段
let i = 0;
timer = setInterval(() => {
onPartial(`语音片段${i} `);
i += 1;
}, 500);
},
stop: async () => {
clearInterval(timer);
},
};
};
export default () => {
const [value, setValue] = React.useState(
'`${placeholder:目标场景}`今天的拒绝率为什么下降`${placeholder:目标事件}`输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本效果,输入多行文本',
);
const markdownRef = React.useRef<MarkdownEditorInstance>(null);
return (
<div
style={{
boxSizing: 'border-box',
border: '1px solid #eee',
margin: 'auto',
maxWidth: 460,
overflow: 'hidden',
padding: 16,
width: '100%',
}}
>
<MarkdownInputField
style={{ maxWidth: '100%' }}
value={value}
inputRef={markdownRef}
voiceRecognizer={createRecognizer}
attachment={{
enable: true,
accept: '.pdf,.doc,.docx,image/*',
maxFileSize: 10 * 1024 * 1024, // 10MB(字节)
upload: async (file, index) => {
if (index == 3) {
throw new Error('上传失败');
}
// 模拟上传文件
await new Promise((resolve) => setTimeout(resolve, 1000));
return URL.createObjectURL(file);
},
onDelete: async (file) => {
console.log('删除文件:', file);
await new Promise((resolve) => setTimeout(resolve, 500));
},
}}
tagInputProps={{
type: 'dropdown',
enable: true,
items: async (props) => {
if (props?.placeholder === '目标场景') {
return [];
}
return ['tag1', 'tag2', 'tag3'].map((item) => {
return {
key: item,
label: props?.placeholder + item,
};
});
},
}}
actionsRender={(props, defaultActions) => {
return [
<ActionIconBox
showTitle={props.collapseSendActions}
title="提示词库"
key="edit"
style={{
padding: 8,
fontSize: 16,
}}
>
<Sparkles />
</ActionIconBox>,
...defaultActions,
];
}}
beforeToolsRender={() => {
return (
<ActionItemContainer showMenu={true}>
{new Array(6).fill(0).map((_, index) => (
<ActionItemBox
onClick={() => message.info('快捷技能' + index)}
icon="https://mdn.alipayobjects.com/huamei_ptjqan/afts/img/A*Bgr8QrMHLvoAAAAAF1AAAAgAekN6AQ/original"
iconSize={16}
size="small"
title={
<span
style={{ display: 'flex', alignItems: 'center', gap: 8 }}
>
{'快捷技能' + index}
</span>
}
disabled={index < 2}
key={'快捷技能' + index}
/>
))}
</ActionItemContainer>
);
}}
toolsRender={() => [
<Button
type="text"
key="bold"
onClick={() => console.log('DeepThink clicked')}
>
DeepThink
<ChevronDown />
</Button>,
<Button
type="text"
key="italic"
icon={<GlobalOutlined />}
onClick={() => console.log('深度思考 clicked')}
>
深度思考
</Button>,
]}
onChange={(newValue) => {
setValue(newValue);
console.log('newValue', newValue);
}}
placeholder="请输入内容..."
onSend={async (text) => {
console.log('发送内容:', text);
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
<SuggestionList
style={{
marginTop: 8,
maxWidth: '100%',
}}
items={[
{
key: 'qwe',
icon: '💸',
text: '关税对消费类基金的影响',
actionIcon: <EditOutlined />,
},
{
key: 'asd',
icon: '📝',
text: '恒生科技指数基金相关新闻',
actionIcon: <EditOutlined />,
},
{
key: 'zxc',
icon: '📊',
text: '数据分析与可视化',
actionIcon: <EditOutlined />,
},
]}
layout={'horizontal'}
onItemClick={() => {
markdownRef?.current?.store?.setMDContent(
'关税对`${placeholder:消费类}`基金的影响',
);
}}
/>
</div>
);
};#启用语音输入按钮(支持句级回调)
import { Button } from 'antd';
import {
MarkdownInputField,
type CreateRecognizer,
} from '@ant-design/agentic-ui';
import { ChevronDown } from '@sofa-design/icons';
export default () => {
const createRecognizer: CreateRecognizer = async ({
onSentenceBegin,
onPartial,
onSentenceEnd,
onError,
}) => {
let timer: ReturnType<typeof setInterval>;
let i = 0;
return {
start: async () => {
// 真实场景应启动麦克风与ASR服务,这里用计时器模拟:句子开始 -> 多次增量 -> 句子结束
onSentenceBegin();
timer = setInterval(() => {
if (i < 3) {
onPartial(`片段${i}`);
i += 1;
} else {
clearInterval(timer);
onSentenceEnd('完整句子');
}
}, 500);
},
stop: async () => {
clearInterval(timer);
},
};
};
return (
<MarkdownInputField
placeholder="请开始讲话..."
toolsRender={() => [
<Button
type="text"
key="bold"
onClick={() => console.log('DeepThink clicked')}
>
DeepThink
<ChevronDown />
</Button>,
<Button
type="text"
key="italic"
onClick={() => console.log('深度思考 clicked')}
>
深度思考
</Button>,
<Button
type="text"
key="link"
onClick={() => console.log('联网搜索 clicked')}
>
联网搜索
</Button>,
]}
voiceRecognizer={createRecognizer}
onChange={(a) => console.log(a)}
onSend={async (text) => {
console.log('发送内容:', text);
}}
/>
);
};交互说明:
- 第一次点击语音按钮开始录音,实时将转写文本写入输入框。
- 再次点击语音按钮结束录音。
- 录音过程中点击发送按钮将先停止录音,再发送当前输入内容。
#自定义触发键和样式
import { MarkdownInputField } from '@ant-design/agentic-ui';
export default () => {
const [value, setValue] = React.useState('');
return (
<div>
<MarkdownInputField
value={value}
onChange={setValue}
placeholder="按Enter发送消息,Shift+Enter换行..."
triggerSendKey="Enter"
style={{ minHeight: '200px' }}
borderRadius={8}
/>
<div>
<h4>Props 说明</h4>
<ul>
<li>
<code>triggerSendKey</code> - 触发发送操作的键盘快捷键
</li>
<li>
<code>style</code> - 应用于输入字段的内联样式
</li>
<li>
<code>borderRadius</code> - 边框圆角大小
</li>
</ul>
</div>
</div>
);
};#启用提示词优化
import { MarkdownInputField } from '@ant-design/agentic-ui';
export default () => {
const [value, setValue] = React.useState(
'请将这段提示语优化为更清晰的英文表达,并保留关键术语。',
);
return (
<div>
<div
style={{
padding: 12,
}}
>
<MarkdownInputField
value={value}
onChange={setValue}
refinePrompt={{
enable: true,
onRefine: async (input) => {
// 模拟异步优化(真实项目可调用后端/模型服务)
await new Promise((r) => setTimeout(r, 2000));
return `你好呀,哈哈哈哈 ${input}`;
},
}}
/>
</div>
<div>
<h4>说明</h4>
<ul>
<li>
<code>refinePrompt.enable</code> 为 true
时,右上“快速操作”区域会显示“优化提示词/撤销”按钮
</li>
<li>
<code>refinePrompt.onRefine</code> 接收当前输入文本,返回
Promise<string> 作为优化后的文本
</li>
<li>优化完成后按钮恢复为“优化提示词”;</li>
</ul>
</div>
</div>
);
};#放大
MarkdownInputField 支持放大功能,用户可以通过点击放大图标将输入框扩展到指定的容器内,提供更大的编辑空间。此功能特别适用于需要编写长文本或复杂内容的场景。
#相关属性
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
enlargeable | 是否启用放大功能 | boolean | false | - |
enlargeTargetRef | 放大时的目标容器引用,必须是一个具有定位属性的元素 | React.RefObject<HTMLElement> | - | - |
#使用注意事项
- 容器定位:目标容器必须具有相对定位(
position: relative)或其他非静态定位 - 容器尺寸:确保目标容器有足够的空间容纳放大后的输入框
- z-index:放大后的输入框具有较高的 z-index 值,确保不被其他元素遮盖
- 响应式设计:在移动端或小屏幕设备上使用时,建议调整目标容器尺寸
#基本示例
import { MarkdownInputField } from '@ant-design/agentic-ui';
export default () => {
const [value, setValue] = React.useState(
'输入文本效果,输入文本效果,输入文本效果,输入文本效果,输入文本效果,输入文本效果,输入文本效果,输入文本效果,输入文本效果,输入文本效果,输入文本效果,输入文本效果',
);
const containerRef = React.useRef<HTMLDivElement>(null);
return (
<div>
<div
style={{
height: 500,
padding: 32,
position: 'relative',
}}
ref={containerRef}
>
<MarkdownInputField
value={value}
onChange={setValue}
enlargeable={{ enable: true, height: 600 }}
style={{ marginTop: 128, height: 190 }}
refinePrompt={{
enable: true,
onRefine: async (input) => {
// 模拟异步优化(真实项目可调用后端/模型服务)
await new Promise((r) => setTimeout(r, 2000));
return `你好呀,哈哈哈哈 ${input}`;
},
}}
/>
</div>
</div>
);
};#便捷操作区域
便捷操作区提供了在输入框上方自定义操作按钮的功能。
Props 说明:
isShowTopOperatingArea- 是否显示便捷操作区域,默认为 falseisShowBackTo- 是否显示到顶到底operationBtnRender- 自定义操作按钮渲染函数,用于在便捷操作区中添加自定义按钮
import { Button } from 'antd';
import {
MarkdownInputField,
CreateRecognizer,
type MarkdownEditorInstance,
} from '@ant-design/agentic-ui';
const createRecognizer: CreateRecognizer = async ({ onPartial, onError }) => {
let timer: ReturnType<typeof setInterval>;
return {
start: async () => {
// 真实场景应启动麦克风与ASR服务,这里仅用计时器模拟持续的转写片段
let i = 0;
timer = setInterval(() => {
onPartial(`语音片段${i} `);
i += 1;
}, 500);
},
stop: async () => {
clearInterval(timer);
},
};
};
export default () => {
const [value, setValue] = React.useState(
[
'帮我创建一个定时任务。请根据我的描述: `${placeholder:任务名称}` 、 `${placeholder:执行频率}` ,内容如下:',
'```markdown',
'任务内容',
'```',
'帮我生成合适的定时任务配置。',
].join('\n'),
);
const markdownRef = React.useRef<MarkdownEditorInstance>(null);
const targetRef = React.useRef<HTMLDivElement>(null);
return (
<div
style={{
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'column',
gap: 12,
height: 450,
margin: '0 auto',
maxWidth: 720,
width: '100%',
}}
>
<div
style={{
flex: 1,
border: '1px solid #e8e8e8',
borderRadius: '8px',
overflow: 'auto',
padding: '16px',
}}
ref={targetRef}
>
<h1>长内容页面</h1>
<div
style={{
height: '600px',
background: 'linear-gradient(to bottom, #f0f0f0, #ffffff)',
}}
/>
<p>内容结束</p>
</div>
<div
style={{
borderRadius: '8px',
}}
>
<MarkdownInputField
value={value}
targetRef={targetRef}
inputRef={markdownRef}
voiceRecognizer={createRecognizer}
isShowTopOperatingArea={true}
operationBtnRender={() => (
<>
<Button>次按钮</Button>
<Button type="primary">主按钮</Button>
</>
)}
style={{
maxHeight: 120,
maxWidth: '100%',
}}
attachment={{
enable: true,
accept: '.pdf,.doc,.docx,image/*',
maxFileSize: 2 * 1024 * 1024, // 2MB,超出将拦截并提示
maxFileCount: 3,
upload: async (file) => {
await new Promise((r) => setTimeout(r, 800));
return URL.createObjectURL(file);
},
onDelete: async () => {},
}}
onChange={(newValue) => {
setValue(newValue);
console.log('newValue', newValue);
}}
placeholder="请输入内容..."
onSend={async (text) => {
console.log('发送内容:', text);
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
</div>
</div>
);
};#启用附件功能
import { Button } from 'antd';
import { MarkdownInputField } from '@ant-design/agentic-ui';
import { ChevronDown } from '@sofa-design/icons';
export default () => {
const [value, setValue] = React.useState('');
return (
<>
<MarkdownInputField
value={value}
onChange={setValue}
toolsRender={() => [
<Button
type="text"
key="bold"
onClick={() => console.log('DeepThink clicked')}
>
DeepThink
<ChevronDown />
</Button>,
<Button
type="text"
key="italic"
onClick={() => console.log('深度思考 clicked')}
>
深度思考
</Button>,
<Button
type="text"
key="link"
onClick={() => console.log('联网搜索 clicked')}
>
联网搜索
</Button>,
]}
attachment={{
enable: true,
accept: '.pdf,.doc,.docx,image/*',
maxFileSize: 10 * 1024 * 1024, // 10MB(字节)
upload: async (file) => {
// 模拟上传文件
await new Promise((resolve) => setTimeout(resolve, 10000));
return URL.createObjectURL(file);
},
onDelete: async (file) => {
console.log('删除文件:', file);
await new Promise((resolve) => setTimeout(resolve, 500));
},
}}
/>
<div>
<h4>Props 说明</h4>
<ul>
<li>
<code>attachment</code> - 附件配置
<ul>
<li>
<code>enable</code> - 是否启用附件功能
</li>
<li>
<code>accept</code> - 接受的文件类型
</li>
<li>
<code>maxFileSize</code> - 文件最大大小限制(字节)
</li>
<li>
<code>upload</code> - 文件上传回调函数
</li>
<li>
<code>onDelete</code> - 文件删除回调函数
</li>
</ul>
</li>
</ul>
</div>
</>
);
};#最大文件限制与错误展示
通过 maxFileSize(单文件最大字节数)和 maxFileCount(最大文件数量)可限制附件选择。当文件超过大小限制时,该文件会以错误状态出现在附件列表中,并展示「超过 xxx KB」等错误文案;超过数量限制时,多选会被拦截。
#文件超过最大值报错
下方 Demo 中单文件限制为 100KB,选择超过该大小的文件时,该文件会以错误状态出现在附件列表并展示「超过 xxx KB」的报错提示。
#文件数量超限回调
通过 onExceedMaxCount 回调,可以在文件数量超过 maxFileCount 限制时收到通知,由消费者决定如何展示提示信息,而不是静默失败。
#uploadWithResponse - 获取完整上传响应
使用 uploadWithResponse 接口可以返回完整的上传响应对象,包含文件ID、URL、状态等详细信息。响应数据会自动存储在 file.uploadResponse 中。
#uploadWithResponse 接口定义
uploadWithResponse?: (
file: AttachmentFile,
index: number
) => Promise<UploadResponse>;#UploadResponse 类型
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| fileId | 文件ID(必填) | string | - | - |
| fileName | 文件名(必填) | string | - | - |
| fileType | 文件类型(必填) | string | - | - |
| fileUrl | 文件URL(必填) | string | - | - |
| uploadStatus | 上传状态(必填) | 'SUCCESS' | 'FAIL' | string | - | - |
| contentId | 内容ID | string | null | - | - |
| errorMessage | 错误消息 | string | null | - | - |
| fileSize | 文件大小(字节) | number | null | - | - |
#特性
- ✅ 返回完整的响应对象,包含更多元信息
- ✅ 响应数据自动存储在
file.uploadResponse中 - ✅ 支持自定义错误消息(errorMessage)
- ✅ 优先级高于旧的
upload接口 - ✅ 向后兼容,可与
upload接口共存
#使用示例
<MarkdownInputField
attachment={{
enable: true,
uploadWithResponse: async (file, index) => {
const response = await api.uploadFile(file);
return {
contentId: response.contentId,
errorMessage: null,
fileId: response.fileId,
fileName: file.name,
fileSize: file.size,
fileType: file.type,
fileUrl: response.fileUrl,
uploadStatus: 'SUCCESS'
};
},
onFileMapChange: (fileMap) => {
// 访问完整的上传响应数据
fileMap?.forEach(file => {
console.log('文件响应:', file);
});
}
}}
/>#自定义附件按钮渲染
通过 attachment.render 属性,您可以完全替换默认的 AttachmentButtonPopover 组件,实现自定义的附件按钮交互体验。
#render 属性
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| render | 自定义渲染组件函数 | (props: RenderProps) => React.ReactElement | - | - |
#RenderProps
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| children | 需要包装的子元素,通常是 Paperclip 图标 | React.ReactNode | - | - |
| supportedFormat | 支持的文件格式配置 | AttachmentButtonPopoverProps['supportedFormat'] | - | - |
#supportedFormat
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| type | 文件类型名称 | string | - | - |
| extensions | 支持的文件扩展名数组 | string[] | - | - |
| icon | 文件类型图标 | React.ReactNode | - | - |
#兼容性
- 完全向后兼容,不使用
render时保持原有行为 - 支持所有现有的
AttachmentButton属性 - 可与其他附件配置选项(如
supportedFormat、maxFileSize等)配合使用
#自定义操作按钮
普通操作按钮可直接使用 Ant Design 的 Button,通过 toolsRender、actionsRender 和 quickActionRender 接入输入框。
import { Button } from 'antd';
import { MarkdownInputField } from '@ant-design/agentic-ui';
import { ChevronDown } from '@sofa-design/icons';
export default () => {
const [value, setValue] = React.useState('');
return (
<>
<div
style={{
padding: 12,
}}
>
<MarkdownInputField
value={value}
toolsRender={() => [
<Button
type="text"
key="custom"
onClick={() => console.log('自定义工具栏按钮')}
>
自定义
</Button>,
<Button
type="text"
key="bold"
onClick={() => console.log('DeepThink clicked')}
>
DeepThink
<ChevronDown />
</Button>,
<Button
type="text"
key="italic"
onClick={() => console.log('深度思考 clicked')}
>
深度思考
</Button>,
<Button
type="text"
key="link"
onClick={() => console.log('联网搜索 clicked')}
>
联网搜索
</Button>,
]}
onChange={setValue}
actionsRender={(props) => [
<button key="custom" onClick={() => console.log('自定义按钮')}>
自定义
</button>,
]}
quickActionRender={(props) => [
<button key="top-right" onClick={() => console.log('右上按钮')}>
右上
</button>,
]}
/>
</div>
<div>
<h4>Props 说明</h4>
<ul>
<li>
<code>toolsRender</code> - 自定义渲染操作按钮前内容的函数
</li>
<li>
<code>actionsRender</code> - 自定义渲染操作按钮的函数
<ul>
<li>
<code>props</code> - 组件属性
</li>
<li>
<code>defaultActions</code> - 默认的操作按钮数组
</li>
</ul>
</li>
<li>
<code>quickActionRender</code> -
在编辑区域右上、贴右侧渲染按钮组;组件会根据其宽度自动为文本区域预留右侧内边距,避免遮挡。
</li>
</ul>
</div>
</>
);
};#获取编辑器实例
import { Button } from 'antd';
import { MarkdownInputField } from '@ant-design/agentic-ui';
import { ChevronDown } from '@sofa-design/icons';
const App = () => {
const editorRef = React.useRef();
const [value, setValue] = React.useState('');
return (
<>
<MarkdownInputField
inputRef={editorRef}
value={value}
onChange={setValue}
toolsRender={() => [
<Button
type="text"
key="bold"
onClick={() => console.log('DeepThink clicked')}
>
DeepThink
<ChevronDown />
</Button>,
<Button
type="text"
key="italic"
onClick={() => console.log('深度思考 clicked')}
>
深度思考
</Button>,
<Button
type="text"
key="link"
onClick={() => console.log('联网搜索 clicked')}
>
联网搜索
</Button>,
]}
/>
<button
onClick={() => {
// 获取编辑器内容
console.log(editorRef.current?.store?.getMDContent());
document.getElementById('test').innerHTML =
editorRef.current?.store?.getMDContent();
}}
>
获取内容
</button>
<div id="test" />
</>
);
};
export default App;#焦点事件处理
import { Button } from 'antd';
import { MarkdownInputField } from '@ant-design/agentic-ui';
import { ChevronDown } from '@sofa-design/icons';
export default () => {
const [value, setValue] = React.useState('');
const [isFocused, setIsFocused] = React.useState(false);
return (
<>
<MarkdownInputField
value={value}
onChange={setValue}
toolsRender={() => [
<Button
type="text"
key="bold"
onClick={() => console.log('DeepThink clicked')}
>
DeepThink
<ChevronDown />
</Button>,
<Button
type="text"
key="italic"
onClick={() => console.log('深度思考 clicked')}
>
深度思考
</Button>,
<Button
type="text"
key="link"
onClick={() => console.log('联网搜索 clicked')}
>
联网搜索
</Button>,
]}
placeholder="点击输入框获得焦点..."
onFocus={(value, schema) => {
console.log('输入框获得焦点:', { value, schema });
setIsFocused(true);
}}
onSend={async (text) => {
console.log('发送内容:', text);
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
<div style={{ marginTop: 16 }}>
<p>当前焦点状态: {isFocused ? '已获得焦点' : '未获得焦点'}</p>
</div>
<div>
<h4>Props 说明</h4>
<ul>
<li>
<code>onFocus</code> - 当输入字段获得焦点时触发的回调函数
<ul>
<li>
<code>value</code> - 当前的 markdown 文本值
</li>
<li>
<code>schema</code> - 当前的编辑器 schema
</li>
</ul>
</li>
</ul>
</div>
</>
);
};#自定义叶子节点渲染
import { MarkdownInputField } from '@ant-design/agentic-ui';
export default () => {
const [value, setValue] = React.useState('**粗体文本** *斜体文本* `代码`');
return (
<MarkdownInputField
value={value}
onChange={setValue}
placeholder="尝试输入 **粗体**、*斜体* 或 `代码`..."
leafRender={(props, defaultDom) => {
const { leaf, children } = props;
// 自定义粗体样式
if (leaf.bold) {
return (
<strong
style={{
color: '#1890ff',
backgroundColor: '#e6f7ff',
padding: '2px 4px',
borderRadius: '4px',
}}
>
{children}
</strong>
);
}
// 自定义斜体样式
if (leaf.italic) {
return (
<em
style={{
color: '#722ed1',
backgroundColor: '#f9f0ff',
padding: '2px 4px',
borderRadius: '4px',
}}
>
{children}
</em>
);
}
// 自定义代码样式
if (leaf.code) {
return (
<code
style={{
color: '#d83931',
backgroundColor: '#fff2f0',
padding: '2px 6px',
borderRadius: '6px',
border: '1px solid #ffccc7',
fontFamily: 'Monaco, Consolas, monospace',
}}
>
{children}
</code>
);
}
// 返回默认渲染
return defaultDom;
}}
onSend={async (text) => {
console.log('发送内容:', text);
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
);
};#技能模式
import { Tag, Button, Space, Switch, Divider } from 'antd';
import { ExperimentOutlined, SettingOutlined } from '@ant-design/icons';
import { MarkdownInputField } from '@ant-design/agentic-ui';
export default () => {
const [skillModeEnabled, setSkillModeEnabled] = React.useState(true);
const [enableFeature, setEnableFeature] = React.useState(true);
const [changeLog, setChangeLog] = React.useState([]);
return (
<>
{/* 控制面板 */}
<div
style={{
marginBottom: 16,
padding: 16,
background: '#f6f8fa',
borderRadius: 6,
}}
>
<Space split={<Divider type="vertical" />}>
<label>
功能开关:
<Switch
checked={enableFeature}
onChange={setEnableFeature}
style={{ marginLeft: 8 }}
/>
</label>
<label>
显示控制:
<Switch
checked={skillModeEnabled}
onChange={setSkillModeEnabled}
disabled={!enableFeature}
style={{ marginLeft: 8 }}
/>
</label>
<Button size="small" onClick={() => setChangeLog([])}>
清空日志
</Button>
</Space>
<div style={{ marginTop: 8, fontSize: 12, color: '#666' }}>
<p style={{ margin: '4px 0' }}>
<strong>enable={enableFeature ? 'true' : 'false'}</strong> -{' '}
{enableFeature
? '功能启用时,组件正常渲染和工作'
: '功能禁用时,组件完全不渲染,不执行任何逻辑'}
</p>
<p style={{ margin: '4px 0' }}>
<strong>open={skillModeEnabled ? 'true' : 'false'}</strong> -{' '}
控制技能模式的显示与隐藏
</p>
</div>
</div>
<MarkdownInputField
placeholder="请输入内容..."
skillMode={{
enable: enableFeature, // 控制整个功能是否启用
open: skillModeEnabled,
title: (
<Space>
<ExperimentOutlined />
AI助手模式
</Space>
),
rightContent: [
<Tag key="version" color="blue">
v2.0
</Tag>,
<Tag
key="status"
color={enableFeature ? 'green' : 'red'}
style={{ fontSize: 11 }}
>
{enableFeature ? '已启用' : '已禁用'}
</Tag>,
<Button
key="settings"
type="text"
size="small"
icon={<SettingOutlined />}
onClick={() => console.log('设置点击')}
>
设置
</Button>,
],
closable: true,
}}
onSkillModeOpenChange={(open) => {
const timestamp = new Date().toLocaleTimeString();
const actionText = open ? '打开' : '关闭';
const logEntry = `[${timestamp}] ${actionText}`;
setChangeLog((prev) => [logEntry, ...prev.slice(0, 4)]);
setSkillModeEnabled(open);
}}
onSend={async (text) => {
console.log('发送内容:', text);
}}
/>
{/* 状态变化日志 */}
{changeLog.length > 0 && (
<div
style={{
marginTop: 16,
padding: 12,
background: '#f6f8fa',
borderRadius: 6,
maxHeight: 120,
overflow: 'auto',
}}
>
<strong>状态变化日志:</strong>
{changeLog.map((log, index) => (
<div
key={index}
style={{ fontSize: 12, color: '#666', margin: '4px 0' }}
>
{log}
</div>
))}
</div>
)}
<div style={{ marginTop: 16 }}>
<h4>Props 说明</h4>
<ul>
<li>
<code>skillMode</code> - 技能模式配置
<ul>
<li>
<code>enable</code> - 是否启用技能模式组件,默认为 true
</li>
<li>
<code>open</code> - 是否打开技能模式
</li>
<li>
<code>title</code> - 技能模式标题,支持React节点
</li>
<li>
<code>rightContent</code> - 右侧自定义内容数组
</li>
<li>
<code>closable</code> - 是否显示默认关闭按钮
</li>
<li>
<code>style</code> - 容器样式
</li>
<li>
<code>className</code> - 容器类名
</li>
</ul>
</li>
<li>
<code>onSkillModeOpenChange</code> -
技能模式状态变化时触发的回调函数
<ul>
<li>
<code>open</code> - 新的开关状态
</li>
</ul>
</li>
</ul>
</div>
</>
);
};#内联原子 Chip(slash 命令 / @file / @symbol)
开启 composerChips.enable 后,编辑器支持 composer-chip 内联原子节点(对齐桌面 IDE 聊天输入框的 slash 命令与 mention 体验):
- slash 命令 chip:
/name结构化技能节点。chip 整体删除(Backspace),点击触发onSlashChipClick打开宿主配置浮层;未配置参数(无payload)的 chip 在发送前被 gate 拦截(onSlashChipGate)。 - @file / @folder chip:文件 / 目录引用,发送时序列化为
@path(路径含空格自动加尖括号@<path>)。 - @symbol chip:符号引用(函数 / 类),展示
symbolKind副标题与容器名。
import {
MarkdownInputField,
createFileChipNode,
createSlashChipNode,
createSymbolChipNode,
insertComposerChip,
} from '@ant-design/agentic-ui';
// 宿主侧插入 chip(如 slash 命令面板选中、@补全选中)
insertComposerChip(editor, {
kind: 'slash',
name: 'plan',
section: 'skill',
extensionId: 'my-ext',
});
insertComposerChip(editor, {
kind: 'file',
path: 'src/MarkdownInputField/index.tsx',
name: 'index.tsx',
});
insertComposerChip(editor, {
kind: 'symbol',
name: 'useComposerDraft',
symbolKind: 'function',
containerName: 'hooks',
});发送前 gate(存在未配置 slash chip 时阻止发送并提示):
<MarkdownInputField
composerChips={{
enable: true,
onSlashChipClick: (chip) => openSkillConfigOverlay(chip),
onSlashChipGate: (chips) => {
message.warning(`请先配置 /${chips[0].name} 的参数`);
return true; // 阻止发送;返回 false 放行
},
}}
/>#长文本粘贴折叠
开启 longTextFold.enable 后,纯文本粘贴超过阈值(≥ 1200 字符或 ≥ 12 行)时自动折叠为单个「长文本」chip,展示字符数与行数统计,避免一次粘贴撑爆输入框;发送时自动还原完整原文(多行原文以 fenced code 形式序列化)。
点击长文本 chip 可弹出原文预览浮层(对齐 IDE ComposerLongTextPreview),在不展开编辑器的情况下核对折叠内容。
<MarkdownInputField longTextFold={{ enable: true }} />#草稿恢复
传入 draft.draftKey 后自动持久化输入内容(fragment 级,对齐 IDE 会话切换体验):
- 提交时机:输入停顿(idle,默认 180ms)、失焦(blur)、发送成功(send)、
draftKey切换(switch)、组件卸载(unmount); - 恢复时机:
draftKey变化且当前编辑器为空时自动回填; - 默认写入
localStorage(key 为draftKey派生),可通过storage替换为内存 / IndexedDB 等自定义存储。
<MarkdownInputField
draft={{
draftKey: sessionId,
idleDelay: 200,
onDraftCommit: (value, reason) => {
// 返回 false 表示宿主自行持久化,hook 跳过默认写入
return persistDraft(sessionId, value);
},
}}
/>#上下文用量指示
配置 contextUsage 后在工具栏渲染环形用量指示器:16px 圆环 + 分档颜色(<50% 灰、≥50% 黄、≥80% 高用量深黄、溢出红),悬浮展示 used / window (percent%)。
内置面板:未传 onClick 且提供 categories 时,点击指示器展开内置 ContextUsagePanel——占用百分比 + token 摘要、分类分段条形图(分类合计超出/不足总占用时自动缩放/补残差)、分类明细行(可展开 items)、高用量警告行、compact 压缩操作(onCompact / compacting / compactDisabled)。传入 onClick 则由宿主自管面板。
<MarkdownInputField
contextUsage={{
usedTokens: 42000,
contextWindow: 128000,
highUsageThreshold: 80,
categories: [
{ key: 'system', name: '系统', tokenCount: 12000 },
{
key: 'skills',
name: '技能',
tokenCount: 8000,
items: [{ label: 'pdf-reader', estimatedTokens: 5000 }],
},
{ key: 'messages', name: '会话', tokenCount: 22000 },
],
onCompact: () => compactSession(),
compacting: false,
}}
/>也可直接使用独立导出的 ContextUsageIndicator / ContextUsagePanel 组件与 computeBarSegments 工具函数自定义布局。
#分支选择
配置 branch 后在工具栏渲染分支触发器:分支图标 + 当前分支名 pill,点击展开可搜索的分支菜单。不传 branches 时仅展示当前分支不可切换。
菜单能力(对齐 IDE ComposerBranchMenuView):
- 搜索过滤(本地过滤或
onSearch远程); - 当前分支概览:
currentBranchOverview展示未提交文件数与+新增/-删除行数; - 逐分支切换 spinner:
switching传分支名时仅对该分支行显示 loading(全局切换传true); - 新建分支入口:传入
onCreateBranch时显示; - 文案定制:
labels覆盖搜索占位、分组标题、remote 标记等。
<MarkdownInputField
branch={{
branchName: 'feat/composer',
tooltipTitle: '切换工作分支',
branches: [
{ name: 'main', displayName: 'main', isRemote: false, isCurrent: false },
{
name: 'feat/composer',
displayName: 'composer',
isRemote: false,
isCurrent: true,
},
{
name: 'origin/main',
displayName: 'origin/main',
isRemote: true,
isCurrent: false,
},
],
currentBranchOverview: { fileCount: 3, insertions: 120, deletions: 8 },
switching: false,
onSelectBranch: (name) => switchBranch(name),
onCreateBranch: () => openCreateDialog(),
}}
/>也可直接使用独立导出的 ComposerBranchTrigger 组件。
#粘贴配置
import { MarkdownInputField } from '@ant-design/agentic-ui';
export default () => {
const [value, setValue] = React.useState('');
return (
<>
<MarkdownInputField
value={value}
onChange={setValue}
placeholder="只能粘贴纯文本内容..."
pasteConfig={{
enabled: true,
allowedTypes: ['text/plain'],
}}
onSend={async (text) => {
console.log('发送内容:', text);
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
<div style={{ marginTop: 16 }}>
<h4>Props 说明</h4>
<ul>
<li>
<code>pasteConfig</code> - 粘贴配置
<ul>
<li>
<code>enabled</code> - 是否启用粘贴功能,默认为 true
</li>
<li>
<code>allowedTypes</code> - 允许的粘贴内容类型
<ul>
<li>
<code>application/x-slate-md-fragment</code> - Slate
Markdown 片段
</li>
<li>
<code>text/html</code> - HTML 内容
</li>
<li>
<code>Files</code> - 文件
</li>
<li>
<code>text/markdown</code> - Markdown 文本
</li>
<li>
<code>text/plain</code> - 纯文本
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</>
);
};