Agent Composer

August 27, 2026 · View on GitHub

一个面向 Agent 场景的 Vue 3 对话输入组件。组件只负责输入与上下文收集,不包含消息列表、模型选择器或思考模式等外围界面。

底栏默认仅提供附件交互按钮,并显示 SHIFT + ENTER 换行 提示;发送按钮及其他操作可通过插槽自由组合。组件能够收集项目目录、@ 文件引用、附件和用户文本,并输出统一的 Agent 输入结构。

页面效果

Agent Composer 页面效果

截图使用默认浅色主题,展示项目目录输入、无边框消息输入框、附件按钮和换行快捷键提示。

技术栈

技术用途
Vue 3Composition API 与组件运行时
TypeScript组件、事件及 Agent 内容结构类型
Vite开发服务器与生产构建
Tailwind CSS v4布局与主题样式
shadcn-vueButton、InputGroup、Command 等基础组件
Reka UIshadcn-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>

submitOnEnterfalse 时,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 数据

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)

组件 API

双向绑定

绑定类型默认值说明
v-modelstring''当前消息内容
v-model:project-rootstring''当前项目目录

Props

属性类型默认值说明
projectFilesProjectFile[][]用于本地匹配的静态文件列表
resolveFilesProjectFileResolverundefined动态文件解析器;提供后将优先于 projectFiles
placeholderstring输入消息,使用 @ 引用项目文件…消息占位文字
directoryPlaceholderstring/path/to/project项目目录占位文字
acceptstringundefined原生文件输入的 accept
multiplebooleantrue是否允许一次选择多个附件
disabledbooleanfalse是否禁用输入与操作
autoFocusbooleanfalse是否自动聚焦消息输入框
submitOnEnterbooleantrue是否使用 Enter 提交
clearOnSubmitbooleantrue提交后是否清空消息、引用和附件
showShortcutHintbooleantrueEnter 提交启用时是否显示换行快捷键提示
mentionLimitnumber8文件候选列表最大数量
maxAttachmentsnumber10最大附件数量

Vue 模板中使用 kebab-case,例如 submit-on-enterclear-on-submitmax-attachments

Events

事件参数触发时机
submitAgentComposerSubmitPayload消息通过 Enter 或插槽中的 submit() 提交时
agent-submitAgentInputPayloadsubmit 同时触发,输出 Agent 格式数据
files-selectedComposerAttachment[]新附件成功加入时,仅包含本次新增附件
resolver-errorunknown异步文件解析器抛出错误时

Slots

插槽参数说明
actionssubmitcanSubmitattachmentsmentionedFiles自定义底栏右侧操作
attachmentattachmentremove(id)自定义单个附件预览
mention-itemfileactive自定义文件候选项内容
directory-prefix自定义项目目录输入框前缀
shortcut-hint自定义换行快捷键提示

键盘交互

按键行为
@打开项目文件候选列表
ArrowUp / ArrowDown切换活动候选项
Enter / Tab插入当前活动文件引用
Esc关闭候选列表
Enter候选列表关闭时提交消息
Shift + Enter插入换行

组件会在输入法组合输入期间忽略提交快捷键,避免中文输入被误提交。

附件行为

  • 点击底栏附件按钮打开系统文件选择器。
  • 文件可以直接拖入组件。
  • 从剪贴板粘贴图片或其他文件时,会将其转换为附件。
  • 图片使用 URL.createObjectURL() 生成本地预览,并在移除、提交清空或组件卸载时释放。
  • 超过 maxAttachments 的文件不会被加入。

样式与主题

组件使用 shadcn-vue 语义化主题变量,例如 backgroundforegroundmutedborderring。可以在 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检查代码格式但不修改文件

使用限制

  • 浏览器无法只凭项目目录字符串遍历本地文件,必须提供 projectFilesresolveFiles
  • 当前 @ 查询以空白字符作为引用边界;包含空格的文件路径需要在数据源中使用可插入的无空格表示,或扩展引用解析规则。
  • Agent 内容块不会自动读取项目文件正文,也不会自动上传附件;这些操作应由具有相应权限的宿主应用完成。
  • crypto.randomUUID()FileURL.createObjectURL() 需要现代浏览器环境。

变更记录

参见 CHANGELOG.md