一个面向 Agent 场景的 Vue 3 对话输入组件。组件只负责输入与上下文收集,不包含消息列表、模型选择器或思考模式等外围界面。
底栏默认仅提供附件交互按钮,并显示 SHIFT + ENTER 换行 提示;发送按钮及其他操作可通过插槽自由组合。组件能够收集项目目录、@ 文件引用、附件和用户文本,并输出统一的 Agent 输入结构。
截图使用默认浅色主题,展示项目目录输入、无边框消息输入框、附件按钮和换行快捷键提示。
| 技术 | 用途 |
|---|---|
| Vue 3 | Composition API 与组件运行时 |
| TypeScript | 组件、事件及 Agent 内容结构类型 |
| Vite | 开发服务器与生产构建 |
| Tailwind CSS v4 | 布局与主题样式 |
| shadcn-vue | Button、InputGroup、Command 等基础组件 |
| Reka UI | shadcn-vue 无障碍交互基础 |
| ESLint + Prettier | 代码检查与格式化 |
- 消息与项目目录双向绑定。
- 输入
@后动态匹配项目文件。 - 支持静态文件列表和异步文件解析器。
- 支持方向键、Enter、Tab 与 Esc 操作文件候选列表。
- 支持附件选择、拖放和从剪贴板粘贴文件。
- 支持图片附件预览及附件移除。
- Enter 提交,Shift + Enter 换行。
- Textarea 无边框、无轮廓、无聚焦环,保留外层对话框边界。
- 底栏默认仅包含附件交互按钮,并显示可隐藏或替换的换行快捷键提示。
- 同时提供原始提交数据和 Agent 格式数据。
npm install
npm run dev生产构建:
npm run build<script setup lang="ts">
import { ref } from 'vue'
import {
AgentComposer,
type AgentComposerSubmitPayload,
type AgentInputPayload,
type ProjectFile,
} from '@/components/agent-composer'
const message = ref('')
const projectRoot = ref('/workspace/my-project')
const projectFiles: ProjectFile[] = [
{ path: 'src/App.vue' },
{ path: 'src/lib/api.ts' },
{ path: 'src/components', kind: 'directory' },
]
function handleSubmit(payload: AgentComposerSubmitPayload) {
console.log('原始数据', payload)
}
function handleAgentSubmit(payload: AgentInputPayload) {
console.log('Agent 数据', payload)
}
</script>
<template>
<AgentComposer
v-model="message"
v-model:project-root="projectRoot"
:project-files="projectFiles"
@submit="handleSubmit"
@agent-submit="handleAgentSubmit"
/>
</template>隐藏或替换快捷键提示:
<!-- 隐藏提示 -->
<AgentComposer :show-shortcut-hint="false" />
<!-- 替换提示内容 -->
<AgentComposer>
<template #shortcut-hint>
<span>Shift + Enter 新起一行</span>
</template>
</AgentComposer>当 submitOnEnter 为 false 时,Enter 本身即为换行,快捷键提示会自动隐藏。
组件入口位于 src/components/agent-composer/index.ts,全局主题样式由 src/style.css 提供。
浏览器不能根据文本路径直接读取本机目录。Web 项目通常需要从后端 API 获取文件列表;Electron 或 Tauri 项目可以在安全桥接层中读取目录。
通过 resolveFiles 传入同步或异步解析器:
<script setup lang="ts">
import type { ProjectFileResolver } from '@/components/agent-composer'
const resolveFiles: ProjectFileResolver = async (query, projectRoot) => {
const params = new URLSearchParams({ query, projectRoot })
const response = await fetch(`/api/project-files?${params}`)
if (!response.ok) {
throw new Error('项目文件读取失败')
}
return response.json()
}
</script>
<template>
<AgentComposer
v-model="message"
v-model:project-root="projectRoot"
:resolve-files="resolveFiles"
@resolver-error="handleResolverError"
/>
</template>解析器应返回 ProjectFile[]:
interface ProjectFile {
path: string
name?: string
kind?: 'file' | 'directory'
}组件会根据文件名与路径进行二次排序和截断。匹配优先级依次为:文件名前缀、路径前缀、文件名包含、路径包含。
底栏默认不会渲染发送按钮、模型选择器或其他 Agent 操作。附件按钮旁会显示 SHIFT + ENTER 换行,使用 actions 插槽可按需添加操作:
<script setup lang="ts">
import { ArrowUpIcon } from '@lucide/vue'
import { Button } from '@/components/ui/button'
</script>
<template>
<AgentComposer v-model="message" v-model:project-root="projectRoot">
<template #actions="{ submit, canSubmit }">
<Button size="icon" :disabled="!canSubmit" aria-label="发送消息" @click="submit">
<ArrowUpIcon />
</Button>
</template>
</AgentComposer>
</template>submit 事件输出组件内部的完整原始数据:
interface AgentComposerSubmitPayload {
message: string
projectRoot: string
attachments: ComposerAttachment[]
mentionedFiles: ProjectFile[]
}agent-submit 事件将所有有效内容转换为带判别类型的 role + content[] 结构:
{
role: 'user',
content: [
{ type: 'project_directory', path: '/workspace/my-project' },
{
type: 'file_reference',
path: 'src/App.vue',
name: 'App.vue',
kind: 'file',
},
{
type: 'input_file',
id: 'attachment-id',
name: 'design.png',
mediaType: 'image/png',
size: 2048,
file: File,
},
{ type: 'input_text', text: '请检查 @src/App.vue' },
],
}内容块类型:
type |
内容 |
|---|---|
project_directory |
当前项目目录 |
file_reference |
用户通过 @ 选择的项目文件或目录 |
input_file |
用户添加的附件及原始 File 对象 |
input_text |
去除首尾空白后的用户消息 |
input_file.file 是原始浏览器 File,需要在 API 适配层中上传、转换为 Base64,或替换成服务端文件 ID。该结构是通用 Agent 输入协议,不与某一家模型供应商的请求格式强绑定。
也可以在组件外单独转换:
import { formatAgentInput } from '@/components/agent-composer'
const agentInput = formatAgentInput(rawPayload)| 绑定 | 类型 | 默认值 | 说明 |
|---|---|---|---|
v-model |
string |
'' |
当前消息内容 |
v-model:project-root |
string |
'' |
当前项目目录 |
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
projectFiles |
ProjectFile[] |
[] |
用于本地匹配的静态文件列表 |
resolveFiles |
ProjectFileResolver |
undefined |
动态文件解析器;提供后将优先于 projectFiles |
placeholder |
string |
输入消息,使用 @ 引用项目文件… |
消息占位文字 |
directoryPlaceholder |
string |
/path/to/project |
项目目录占位文字 |
accept |
string |
undefined |
原生文件输入的 accept 值 |
multiple |
boolean |
true |
是否允许一次选择多个附件 |
disabled |
boolean |
false |
是否禁用输入与操作 |
autoFocus |
boolean |
false |
是否自动聚焦消息输入框 |
submitOnEnter |
boolean |
true |
是否使用 Enter 提交 |
clearOnSubmit |
boolean |
true |
提交后是否清空消息、引用和附件 |
showShortcutHint |
boolean |
true |
Enter 提交启用时是否显示换行快捷键提示 |
mentionLimit |
number |
8 |
文件候选列表最大数量 |
maxAttachments |
number |
10 |
最大附件数量 |
Vue 模板中使用 kebab-case,例如 submit-on-enter、clear-on-submit 和 max-attachments。
| 事件 | 参数 | 触发时机 |
|---|---|---|
submit |
AgentComposerSubmitPayload |
消息通过 Enter 或插槽中的 submit() 提交时 |
agent-submit |
AgentInputPayload |
与 submit 同时触发,输出 Agent 格式数据 |
files-selected |
ComposerAttachment[] |
新附件成功加入时,仅包含本次新增附件 |
resolver-error |
unknown |
异步文件解析器抛出错误时 |
| 插槽 | 参数 | 说明 |
|---|---|---|
actions |
submit、canSubmit、attachments、mentionedFiles |
自定义底栏右侧操作 |
attachment |
attachment、remove(id) |
自定义单个附件预览 |
mention-item |
file、active |
自定义文件候选项内容 |
directory-prefix |
无 | 自定义项目目录输入框前缀 |
shortcut-hint |
无 | 自定义换行快捷键提示 |
| 按键 | 行为 |
|---|---|
@ |
打开项目文件候选列表 |
ArrowUp / ArrowDown |
切换活动候选项 |
Enter / Tab |
插入当前活动文件引用 |
Esc |
关闭候选列表 |
Enter |
候选列表关闭时提交消息 |
Shift + Enter |
插入换行 |
组件会在输入法组合输入期间忽略提交快捷键,避免中文输入被误提交。
- 点击底栏附件按钮打开系统文件选择器。
- 文件可以直接拖入组件。
- 从剪贴板粘贴图片或其他文件时,会将其转换为附件。
- 图片使用
URL.createObjectURL()生成本地预览,并在移除、提交清空或组件卸载时释放。 - 超过
maxAttachments的文件不会被加入。
组件使用 shadcn-vue 语义化主题变量,例如 background、foreground、muted、border 和 ring。可以在 src/style.css 中修改对应 CSS 变量,而不需要改写组件颜色类。
Textarea 自身不显示边框、outline、focus ring 或阴影;焦点层次由外层对话框容器表达。
src/
├── components/
│ ├── agent-composer/
│ │ ├── AgentComposer.vue
│ │ ├── AttachmentPreview.vue
│ │ ├── formatAgentInput.ts
│ │ ├── index.ts
│ │ └── types.ts
│ └── ui/ # shadcn-vue 基础组件
├── lib/
│ └── utils.ts
├── App.vue # 示例页面
├── main.ts
└── style.css # Tailwind 与主题变量
| 命令 | 说明 |
|---|---|
npm run dev |
启动 Vite 开发服务器 |
npm run build |
执行 Vue 类型检查并构建生产文件 |
npm run preview |
预览生产构建 |
npm run lint |
运行 ESLint |
npm run lint:fix |
自动修复可修复的 ESLint 问题 |
npm run format |
使用 Prettier 格式化项目 |
npm run format:check |
检查代码格式但不修改文件 |
- 浏览器无法只凭项目目录字符串遍历本地文件,必须提供
projectFiles或resolveFiles。 - 当前
@查询以空白字符作为引用边界;包含空格的文件路径需要在数据源中使用可插入的无空格表示,或扩展引用解析规则。 - Agent 内容块不会自动读取项目文件正文,也不会自动上传附件;这些操作应由具有相应权限的宿主应用完成。
crypto.randomUUID()、File和URL.createObjectURL()需要现代浏览器环境。
参见 CHANGELOG.md。
